Skip to main content

rucc_debug/
line.rs

1//! The line table, as the bytes of the sections it goes in.
2//!
3//! Design: `spec/11-asm-objects-debug.md` section 11.4.
4//!
5//! A line table answers one question: given an address in the program, which line of which file was
6//! the compiler writing code for when it produced the instruction there. Everything else DWARF
7//! describes is about what the program means, and this is about where it came from, which is why it
8//! is a table of its own rather than an attribute on something.
9//!
10//! # Why this is the first part written
11//!
12//! Because of what a safety report is. The monitor's descriptor carries a judgement, a class, an
13//! access size and a program counter, and `spec/safe-memory/06-instrumentation.md` section 6.5
14//! deliberately keeps the source location out of it so that a compiler does not ship two line
15//! tables that can come to disagree. That is the right design and it only pays when there is one
16//! line table, and until now there was none, so every report from a corpus run had to be read
17//! backwards out of a disassembly. On a quarter of a million lines of SQLite that is the difference
18//! between a minute and an afternoon per shape.
19//!
20//! # What is here
21//!
22//! The line number program, in `.debug_line`, with the file and directory tables DWARF 5 puts in
23//! its header, and the strings those tables name, in `.debug_line_str`. Beside them the smallest
24//! compilation unit that makes them findable: one `DW_TAG_compile_unit` in `.debug_info` with the
25//! producer, the name of the file, the directory the compiler ran in, a `DW_AT_stmt_list` pointing
26//! at the program and a `DW_AT_ranges` saying which addresses this unit covers, and the
27//! abbreviation it is written against in `.debug_abbrev`. A reader handed an address walks the
28//! units, and a unit with no entry in `.debug_info` is a unit nothing walks, so the table alone
29//! would have been a section no tool reads.
30//!
31//! The ranges are a list with one entry per function rather than a low and a high address over the
32//! whole unit. Under `-ffunction-sections` each function is a section of its own and the linker may
33//! place them anywhere and drop the ones nothing reaches, so there is no single span that covers
34//! them, and writing one would be writing down something that is true of the object and false of
35//! the program.
36//!
37//! # What is not here
38//!
39//! What the program means is in `tree.rs` and goes in the same unit: the types, the functions and
40//! the variables the unit defines at file scope, which is what a debugger reads a value through.
41//! Half the locals are there too, which is the ones lowering gave a frame slot, each a
42//! `DW_OP_fbreg` at an offset the frame layout worked out. The other half are held in SSA values
43//! and need a location list built over the register allocator's output, which is the rest of
44//! tamnd/rucc#9. What makes a location a different piece of work rather than more of this one is
45//! that it is checked differently: a line table is right or wrong against `addr2line` and a local's
46//! location is right or wrong against a debugger that stops in the middle of a function and prints
47//! it.
48//!
49//! One sequence per function, each beginning at that function's own symbol. A sequence is the unit
50//! of address ordering in a line program and its rows have to run forwards, so a table with one
51//! sequence over a section would be a table that breaks the moment two functions are laid out in an
52//! order the source did not have. One per function costs a `DW_LNE_set_address` and a relocation
53//! each and is correct under every combination of flags there is.
54
55use crate::shape::{Global, Local, Place, Scope, Shape, Sig};
56use crate::tree;
57
58use rucc_object::{Chunk, Info, Reference, Reloc};
59
60/// One compilation unit's worth of debug information.
61#[derive(Debug, Clone, Default, PartialEq, Eq)]
62pub struct Unit {
63    /// The file being compiled, as the command line spelled it, already prefix mapped.
64    pub name: String,
65    /// The directory the compiler was run in, already prefix mapped.
66    ///
67    /// This is `DW_AT_comp_dir`, and what it is for is that every relative name in the tables below
68    /// is relative to it. A build that cannot say where it ran writes a single dot, which is what
69    /// the tables are already relative to and is therefore the one answer that changes nothing.
70    pub dir: String,
71    /// What produced this, which is this compiler and its version.
72    pub producer: String,
73    /// Every file any row names, in the order the rows refer to them by.
74    pub files: Vec<String>,
75    /// Every type anything in the unit names, in the order they refer to them by.
76    ///
77    /// A table of indices rather than a tree, so that a type naming itself is an ordinary entry.
78    /// See [`Shape`] for what is in one and what is deliberately left out of one.
79    pub types: Vec<Shape>,
80    /// The functions, in the order the text section holds them.
81    pub funcs: Vec<Function>,
82    /// The file-scope variables this unit defines, in the order the object file holds them.
83    ///
84    /// Only the ones it defines. A name this unit declares and another one defines is a name the
85    /// linker resolves, so an entry for it here would be an entry whose address is somebody
86    /// else's, and a reader wanting the type of one reads the unit that has it.
87    pub globals: Vec<Global>,
88    /// How many bytes an address is on this target.
89    pub pointer: u8,
90    /// Whether this build writes a call frame table, which is what a frame base is resolved
91    /// through.
92    ///
93    /// A function's `DW_AT_frame_base` is `DW_OP_call_frame_cfa`, and what answers that operation
94    /// is the unwind table the build already writes for every function, or `.debug_frame` in a
95    /// build that turned the unwind table off, which is a kernel or a freestanding image. The
96    /// caller writes that section and says here whether it did. A build with neither leaves a
97    /// reader with nothing to evaluate the operation against, so the attribute is left off there
98    /// rather than written as something no debugger can follow. The locations that would be
99    /// measured from it are left off with it.
100    pub frames: bool,
101    /// Whether the unit goes in a Mach-O object.
102    ///
103    /// ld64 reads the unit's name and directory to write the map a debugger finds the object
104    /// through, and it reads them only from `.debug_str`. Given a reference into `.debug_line_str`
105    /// it leaves the object out of the map, or crashes, so on a Mac those three strings go where
106    /// clang puts them and the line program's own strings stay where they are.
107    pub mach_o: bool,
108    /// Which version of DWARF to write.
109    pub version: Version,
110}
111
112/// A version of DWARF this writes.
113///
114/// Five unless the build asked for four. A kernel built with `CONFIG_DEBUG_INFO_DWARF4` passes
115/// `-gdwarf-4` because the tools it will be read with are older than DWARF 5, and what changes is
116/// the layout rather than what is said: ranges and location lists go in `.debug_ranges` and
117/// `.debug_loc`, the line table names its files inline, and the unit's own strings go in
118/// `.debug_str`.
119#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
120pub enum Version {
121    /// DWARF 4.
122    Four,
123    /// DWARF 5.
124    #[default]
125    Five,
126}
127
128/// One function: where each of its instructions came from, and what it is.
129#[derive(Debug, Clone, Default, PartialEq, Eq)]
130pub struct Function {
131    /// Its name, as the C program spelled it, which is what a relocation here asks the linker for.
132    pub name: String,
133    /// How many bytes of instructions it is.
134    pub len: u64,
135    /// The rows, in increasing order of address.
136    pub rows: Vec<Row>,
137    /// Where it was declared, and nothing when that is not known.
138    pub decl: Option<Place>,
139    /// What it takes and gives back, and [`None`] when this compiler cannot yet say.
140    ///
141    /// A function with nothing here gets no entry in `.debug_info` at all, for the reason in the
142    /// `tree.rs` module documentation: an entry with no return type is an entry saying `void`, so
143    /// half an answer here is a wrong one rather than a partial one.
144    pub sig: Option<Sig>,
145    /// Whether anything outside this unit can see it, which is the opposite of `static`.
146    pub external: bool,
147    /// The locals lowering gave a frame slot, in the order the slots were asked for, which is the
148    /// order they were declared in.
149    ///
150    /// Parameters are not among them, whether or not they have a slot. See [`Local`].
151    pub locals: Vec<Local>,
152    /// The inner scopes of the function, each after the scope it is written inside.
153    ///
154    /// The function's own body is not one of them, for the reason [`Scope`] gives. A scope nothing
155    /// above names is written down anyway and costs nothing: an entry is only made for one that has
156    /// a local of its own or holds a scope that does.
157    pub scopes: Vec<Scope>,
158}
159
160/// One row of the table: an address, and where the code at it came from.
161#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
162pub struct Row {
163    /// How far into its function the instruction is.
164    pub at: u64,
165    /// Which of [`Unit::files`] it is in.
166    pub file: usize,
167    /// Which line of that file, counting from one, or zero for code no line of any file asked for.
168    ///
169    /// Zero is DWARF's own spelling of that and is worth more than a guess: a debugger stepping
170    /// over a row with no line knows not to stop, where one handed the nearest line it could find
171    /// would stop somewhere the program never was.
172    pub line: u32,
173    /// Which column of that line, counting from one, or zero for the left edge.
174    pub column: u32,
175}
176
177/// What went wrong while the sections were being built.
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub enum Error {
180    /// The DWARF writer refused something, which is a bug here rather than in a program.
181    Refused {
182        /// What it said, already formatted.
183        why: String,
184    },
185}
186
187impl std::fmt::Display for Error {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189        match self {
190            Error::Refused { why } => {
191                write!(f, "the debug writer refused what it was given: {why}")
192            }
193        }
194    }
195}
196
197impl std::error::Error for Error {}
198
199/// One section of DWARF being written, and the relocations found while writing it.
200///
201/// The writer underneath hands a relocation over the moment it writes the hole rather than at the
202/// end, because the hole's offset is the length of what it has written so far, so this has to be
203/// one type rather than bytes now and relocations later.
204#[derive(Debug, Clone)]
205struct Section {
206    bytes: gimli::write::EndianVec<gimli::LittleEndian>,
207    relocs: Vec<gimli::write::Relocation>,
208}
209
210impl Default for Section {
211    fn default() -> Self {
212        Self { bytes: gimli::write::EndianVec::new(gimli::LittleEndian), relocs: Vec::new() }
213    }
214}
215
216impl gimli::write::RelocateWriter for Section {
217    type Writer = gimli::write::EndianVec<gimli::LittleEndian>;
218
219    fn writer(&self) -> &Self::Writer {
220        &self.bytes
221    }
222
223    fn writer_mut(&mut self) -> &mut Self::Writer {
224        &mut self.bytes
225    }
226
227    fn relocate(&mut self, relocation: gimli::write::Relocation) {
228        self.relocs.push(relocation);
229    }
230}
231
232/// The sections a unit's debug information goes in.
233///
234/// The result is empty when nothing in the unit has a row, which is a file of declarations and a
235/// file whose every function was dropped. An empty `.debug_line` is worse than no section at all,
236/// since a reader would find a unit covering no addresses and have to decide what that meant.
237///
238/// # Errors
239///
240/// [`Error::Refused`] for anything the DWARF writer objected to. Every value it is handed here came
241/// out of this compiler, so that is a bug here rather than a program's mistake.
242pub fn write(unit: &Unit) -> Result<Info, Error> {
243    if unit.funcs.iter().all(|func| func.rows.is_empty()) {
244        return Ok(Info::default());
245    }
246    let version = match unit.version {
247        Version::Four => 4,
248        Version::Five => 5,
249    };
250    let encoding =
251        gimli::Encoding { format: gimli::Format::Dwarf32, version, address_size: unit.pointer };
252    let mut dwarf = gimli::write::DwarfUnit::new(encoding);
253    let dir = text(&unit.dir, encoding, &mut dwarf.line_strings);
254    let name = text(&unit.name, encoding, &mut dwarf.line_strings);
255    let mut program =
256        gimli::write::LineProgram::new(encoding, gimli::LineEncoding::default(), dir, name, None);
257    // Every file the rows name, under the directory above. The name is written whole rather than
258    // split into a directory and a base, which is legal and is not what gcc does: a reader joins
259    // the two only when the file is relative, so a whole name is one the reader takes as it stands.
260    // Splitting would buy a shorter table on a project whose files share directories and would put
261    // a second place in here where a path is taken apart.
262    let under = program.default_directory();
263    let files: Vec<gimli::write::FileId> = unit
264        .files
265        .iter()
266        .map(|file| {
267            let file = text(file, encoding, &mut dwarf.line_strings);
268            program.add_file(file, under, None)
269        })
270        .collect();
271    for (index, func) in unit.funcs.iter().enumerate() {
272        if func.rows.is_empty() {
273            continue;
274        }
275        program.begin_sequence(Some(gimli::write::Address::Symbol { symbol: index, addend: 0 }));
276        // A row that says what the row before it said is a row a reader would read and discard, so
277        // it is left out. That is most of them: a line of C is several instructions and every one
278        // of them carries the same span.
279        let mut said: Option<(usize, u32, u32)> = None;
280        for row in &func.rows {
281            let now = (row.file, row.line, row.column);
282            if said == Some(now) {
283                continue;
284            }
285            said = Some(now);
286            let Some(&file) = files.get(row.file) else {
287                let why = format!("row at {} names file {}, which is not one", row.at, row.file);
288                return Err(Error::Refused { why });
289            };
290            let state = program.row();
291            state.address_offset = row.at;
292            state.file = file;
293            state.line = u64::from(row.line);
294            state.column = u64::from(row.column);
295            // Every row is somewhere a breakpoint may attach, because at this optimization level
296            // every row is the start of a statement or is code with no statement to be the start
297            // of, and the second kind carries line zero and is not a place a debugger stops.
298            state.is_statement = true;
299            program.generate_row();
300        }
301        program.end_sequence(func.len);
302    }
303    let ranges = unit
304        .funcs
305        .iter()
306        .enumerate()
307        .filter(|(_, func)| !func.rows.is_empty())
308        .map(|(index, func)| gimli::write::Range::StartLength {
309            begin: gimli::write::Address::Symbol { symbol: index, addend: 0 },
310            length: func.len,
311        })
312        .collect();
313    dwarf.unit.line_program = program;
314    let covers = dwarf.unit.ranges.add(gimli::write::RangeList(ranges));
315    let root = dwarf.unit.root();
316    let mut said = Vec::with_capacity(3);
317    for (attr, val) in [
318        (gimli::DW_AT_producer, &unit.producer),
319        (gimli::DW_AT_name, &unit.name),
320        (gimli::DW_AT_comp_dir, &unit.dir),
321    ] {
322        // DWARF 4 has no `.debug_line_str` for the attributes to point into, so under it the three
323        // strings go in `.debug_str`, which is also where gcc puts them.
324        let val = if unit.mach_o || unit.version == Version::Four {
325            let bytes: Vec<u8> = val.bytes().filter(|&byte| byte != 0).collect();
326            gimli::write::AttributeValue::StringRef(dwarf.strings.add(bytes))
327        } else {
328            let string = text(val, encoding, &mut dwarf.line_strings);
329            gimli::write::AttributeValue::LineStringRef(held(string)?)
330        };
331        said.push((attr, val));
332    }
333    let root = dwarf.unit.get_mut(root);
334    let mut said = said.into_iter();
335    if let Some((attr, val)) = said.next() {
336        root.set(attr, val);
337    }
338    // `DW_LANG_C11` is a DWARF 5 code, and gcc says C99 for any later C under DWARF 4.
339    let lang = match unit.version {
340        Version::Four => gimli::DW_LANG_C99,
341        Version::Five => gimli::DW_LANG_C11,
342    };
343    root.set(gimli::DW_AT_language, gimli::write::AttributeValue::Language(lang));
344    for (attr, val) in said {
345        root.set(attr, val);
346    }
347    root.set(gimli::DW_AT_stmt_list, gimli::write::AttributeValue::LineProgramRef);
348    root.set(gimli::DW_AT_ranges, gimli::write::AttributeValue::RangeListRef(covers));
349    // A DWARF 4 range or location list is measured from the unit's base address, which is its low
350    // PC. Every address in the lists here is a relocation to where a function went, so the base is
351    // zero, and writing it says so to a reader that would otherwise have no base at all.
352    if unit.version == Version::Four {
353        let zero = gimli::write::Address::Constant(0);
354        root.set(gimli::DW_AT_low_pc, gimli::write::AttributeValue::Address(zero));
355    }
356    tree::describe(&mut dwarf, &unit.types, &files, &unit.funcs, &unit.globals, unit.frames)?;
357    let mut sections = gimli::write::Sections::new(Section::default());
358    dwarf.write(&mut sections).map_err(refused)?;
359    let mut info = Info::default();
360    // One index space over both lists, the functions first. `gimli` calls a relocation target a
361    // symbol number and leaves it to the caller to say what a number means, and what one means here
362    // is a position in this: the line table and a subprogram's low PC ask for a function, and a
363    // variable's location asks for a variable.
364    let named = |target: gimli::write::RelocationTarget| match target {
365        gimli::write::RelocationTarget::Symbol(index) => match unit.funcs.get(index) {
366            Some(func) => func.name.clone(),
367            None => unit.globals[index - unit.funcs.len()].name.clone(),
368        },
369        gimli::write::RelocationTarget::Section(id) => id.name().to_owned(),
370    };
371    sections.for_each(|id, section| {
372        if section.bytes.slice().is_empty() {
373            return Ok(());
374        }
375        let relocs = section
376            .relocs
377            .iter()
378            .map(|reloc| Reloc {
379                at: reloc.offset,
380                symbol: named(reloc.target),
381                kind: Reference::Address { bytes: reloc.size },
382                addend: reloc.addend,
383                after: 0,
384            })
385            .collect();
386        info.chunks.push(Chunk {
387            name: id.name().to_owned(),
388            bytes: section.bytes.slice().to_vec(),
389            relocs,
390        });
391        Ok::<(), Error>(())
392    })?;
393    Ok(info)
394}
395
396/// A string as the line program writes one, which is a reference into `.debug_line_str`.
397///
398/// Every string here goes in that section rather than in `.debug_str` or inline, because the file
399/// and directory tables of a DWARF 5 line program can reach it and the unit's own attributes can
400/// too, so one section holds all of them and a name that appears in both is written once.
401fn text(
402    val: &str,
403    encoding: gimli::Encoding,
404    strings: &mut gimli::write::LineStringTable,
405) -> gimli::write::LineString {
406    // A null byte in a path is not something a file system hands back and is something the writer
407    // underneath panics on, so it is taken out rather than passed through.
408    let val: Vec<u8> = val.bytes().filter(|&byte| byte != 0).collect();
409    gimli::write::LineString::new(val, encoding, strings)
410}
411
412/// The identifier behind a string that went into `.debug_line_str`.
413///
414/// [`text`] answers with whichever form of string the encoding wanted, and for DWARF 5 that is
415/// always a reference into that section. An attribute has to name the reference rather than repeat
416/// the bytes, so this is where the one shape the encoding can produce is taken apart, and anything
417/// else is a disagreement between this function and that one rather than anything a caller did.
418fn held(string: gimli::write::LineString) -> Result<gimli::write::LineStringId, Error> {
419    match string {
420        gimli::write::LineString::LineStringRef(id) => Ok(id),
421        _ => Err(Error::Refused {
422            why: "a string meant for the line string section was written another way".to_owned(),
423        }),
424    }
425}
426
427/// What the DWARF writer said, as the one kind of news it can be here.
428fn refused(why: gimli::write::Error) -> Error {
429    Error::Refused { why: why.to_string() }
430}
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435
436    /// A unit with one function and two lines in it.
437    fn one() -> Unit {
438        Unit {
439            name: "a.c".to_owned(),
440            dir: "/tmp".to_owned(),
441            producer: "rucc".to_owned(),
442            files: vec!["a.c".to_owned()],
443            types: Vec::new(),
444            funcs: vec![Function {
445                name: "f".to_owned(),
446                len: 16,
447                rows: vec![
448                    Row { at: 0, file: 0, line: 3, column: 1 },
449                    Row { at: 8, file: 0, line: 4, column: 5 },
450                ],
451                ..Function::default()
452            }],
453            globals: Vec::new(),
454            pointer: 8,
455            frames: true,
456            mach_o: false,
457            version: Version::Five,
458        }
459    }
460
461    /// The sections that come out, and that each of them has something in it.
462    ///
463    /// Four rather than two, because a line table nothing can find is a section no reader opens.
464    /// The unit in `.debug_info` is what a reader walks to reach the program, the abbreviation in
465    /// `.debug_abbrev` is what that unit is written against, and the strings are in
466    /// `.debug_line_str` because both the unit and the program's own tables name them.
467    #[test]
468    fn a_unit_with_rows_writes_the_four_sections_a_reader_needs() {
469        let info = write(&one()).expect("sections");
470        let names: Vec<&str> = info.chunks.iter().map(|chunk| chunk.name.as_str()).collect();
471        assert_eq!(
472            names,
473            [".debug_abbrev", ".debug_line_str", ".debug_line", ".debug_rnglists", ".debug_info"]
474        );
475        assert!(info.chunks.iter().all(|chunk| !chunk.bytes.is_empty()));
476    }
477
478    /// Where a function is is the one number no compilation knows, so every sequence asks for it.
479    ///
480    /// The relocation names the function rather than the section it is in, because under
481    /// `-ffunction-sections` the section is the function's own and under anything else the object
482    /// writer is the one that knows where in the text it landed. The others in the same section are
483    /// the header naming its own strings, which is the other thing only a linker can resolve.
484    #[test]
485    fn a_sequence_asks_the_linker_where_its_function_went() {
486        let info = write(&one()).expect("sections");
487        let line = info.chunks.iter().find(|chunk| chunk.name == ".debug_line").expect("a table");
488        let address = line.relocs.iter().find(|reloc| reloc.symbol == "f").expect("an address");
489        assert_eq!(address.kind, Reference::Address { bytes: 8 });
490        assert_eq!(address.addend, 0);
491        // The rest are the header's own, and they are section offsets rather than addresses: a
492        // directory and a file name in DWARF 5 are written as a place in `.debug_line_str`.
493        let rest = line.relocs.iter().filter(|reloc| reloc.symbol != "f");
494        assert!(rest.clone().count() > 0);
495        assert!(rest.clone().all(|reloc| reloc.symbol == ".debug_line_str"));
496        assert!(rest.clone().all(|reloc| reloc.kind == Reference::Address { bytes: 4 }));
497    }
498
499    /// On a Mac the unit's own name, directory and producer are read from `.debug_str`, which is
500    /// the only place ld64 looks for them when it writes the map `dsymutil` follows to the object.
501    #[test]
502    fn a_mach_o_unit_names_itself_in_the_strings_ld64_reads() {
503        let unit = Unit { mach_o: true, ..one() };
504        let info = write(&unit).expect("sections");
505        let strings = info.chunks.iter().find(|chunk| chunk.name == ".debug_str").expect("strings");
506        for name in ["rucc", "a.c"] {
507            let held = format!("{name}\0");
508            assert!(strings.bytes.windows(held.len()).any(|at| at == held.as_bytes()), "{name}");
509        }
510        let unit = info.chunks.iter().find(|chunk| chunk.name == ".debug_info").expect("a unit");
511        assert!(unit.relocs.iter().all(|reloc| reloc.symbol != ".debug_line_str"));
512        assert!(unit.relocs.iter().any(|reloc| reloc.symbol == ".debug_str"));
513    }
514
515    /// DWARF 4 is the same unit laid out the older way: the ranges in `.debug_ranges`, the unit's
516    /// strings in `.debug_str`, and no `.debug_line_str` at all, because the line table of that
517    /// version writes its names inline.
518    #[test]
519    fn a_dwarf_4_unit_uses_the_sections_dwarf_4_has() {
520        let info = write(&Unit { version: Version::Four, ..one() }).expect("sections");
521        let mut names: Vec<&str> = info.chunks.iter().map(|chunk| chunk.name.as_str()).collect();
522        names.sort_unstable();
523        assert_eq!(
524            names,
525            [".debug_abbrev", ".debug_info", ".debug_line", ".debug_ranges", ".debug_str"]
526        );
527        // The version is the two bytes after the 32 bit length, in both headers.
528        for name in [".debug_info", ".debug_line"] {
529            let chunk = info.chunks.iter().find(|chunk| chunk.name == name).expect(name);
530            assert_eq!(chunk.bytes[4..6], [4, 0], "{name}");
531        }
532        let unit = info.chunks.iter().find(|chunk| chunk.name == ".debug_info").expect("a unit");
533        assert!(unit.relocs.iter().any(|reloc| reloc.symbol == ".debug_ranges"));
534        assert!(unit.relocs.iter().all(|reloc| reloc.symbol != ".debug_line_str"));
535    }
536
537    /// DWARF 4 has no code for C11, so the unit says C99 there the way gcc does.
538    #[test]
539    fn a_dwarf_4_unit_says_it_is_c99() {
540        let four = write(&Unit { version: Version::Four, ..one() }).expect("sections");
541        let five = write(&one()).expect("sections");
542        let unit = |info: &Info| {
543            info.chunks.iter().find(|chunk| chunk.name == ".debug_info").expect("a unit").clone()
544        };
545        let c99 = u8::try_from(gimli::DW_LANG_C99.0).expect("one byte");
546        let c11 = u8::try_from(gimli::DW_LANG_C11.0).expect("one byte");
547        assert!(unit(&four).bytes.contains(&c99));
548        assert!(!unit(&four).bytes.contains(&c11));
549        assert!(unit(&five).bytes.contains(&c11));
550    }
551
552    /// A file with nothing to say writes no sections rather than empty ones.
553    #[test]
554    fn a_unit_with_no_rows_writes_nothing() {
555        let mut unit = one();
556        unit.funcs[0].rows.clear();
557        assert_eq!(write(&unit).expect("sections"), Info::default());
558    }
559
560    /// A row naming a file the unit does not have is refused rather than written as something else.
561    #[test]
562    fn a_row_naming_a_file_that_is_not_there_is_refused() {
563        let mut unit = one();
564        unit.funcs[0].rows[1].file = 7;
565        assert!(write(&unit).is_err());
566    }
567}