Skip to main content

rucc_asm/
format.rs

1//! What an assembler is told about a function or a variable, which is the object format's answer
2//! rather than the machine's.
3//!
4//! Design: `spec/11-asm-objects-debug.md` section 11.3, which is about the object files
5//! themselves. The directives here are the same facts said in text: which section code and data go
6//! in, how a symbol is spelled, which symbols leave the file, and where each one ends.
7//!
8//! They are not the same on the three formats and the differences are not cosmetic. A Mach-O
9//! symbol carries an underscore in front of the C name and an ELF one does not, so a listing that
10//! got that wrong would fail to link against every library on the machine. A local label is
11//! spelled `.L` on ELF and COFF and `L` on Mach-O, and a label that is not spelled the local way
12//! ends up in the symbol table, where it is a name a debugger and a backtrace will show. And ELF
13//! wants a marker saying the stack is not executable, whose absence makes it executable, which
14//! section 11.3 calls out as a real and recurring security bug.
15
16use std::fmt::Write as _;
17
18use rucc_mir as mir;
19use rucc_object::{Alias, Array, Binding, Place, Property, Sections, Visibility};
20use rucc_target::ObjectFormat;
21
22use crate::data::Variable;
23
24/// The directives one object format wraps a function in.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum Directives {
27    /// ELF, which is Linux and the freestanding targets.
28    Elf,
29    /// Mach-O, which is Apple's.
30    MachO,
31    /// COFF, which is Windows.
32    Coff,
33}
34
35impl Directives {
36    /// The directives that go with that object format.
37    #[must_use]
38    pub const fn of(format: ObjectFormat) -> Directives {
39        match format {
40            ObjectFormat::Elf => Directives::Elf,
41            ObjectFormat::MachO => Directives::MachO,
42            ObjectFormat::Coff => Directives::Coff,
43            // No assembler in this crate writes wasm, and the caller that asked has a target it
44            // cannot emit for. ELF's directives are the ones nothing here depends on being right
45            // for a target it will not reach.
46            ObjectFormat::Wasm => Directives::Elf,
47        }
48    }
49
50    /// What goes in front of a C name to make the name the linker sees.
51    ///
52    /// Mach-O keeps the underscore that every Unix linker once had, so `main` in C is `_main` in
53    /// the object, and a listing that leaves it off refers to a symbol nothing defines.
54    #[must_use]
55    pub const fn symbol(self) -> &'static str {
56        match self {
57            Directives::Elf | Directives::Coff => "",
58            Directives::MachO => "_",
59        }
60    }
61
62    /// What goes in front of a label that belongs to one function and leaves no symbol behind.
63    #[must_use]
64    pub const fn local(self) -> &'static str {
65        match self {
66            Directives::Elf | Directives::Coff => ".L",
67            Directives::MachO => "L",
68        }
69    }
70
71    /// The directive that opens the section code goes in.
72    #[must_use]
73    pub const fn text(self) -> &'static str {
74        match self {
75            Directives::Elf | Directives::Coff => "\t.text",
76            Directives::MachO => "\t.section\t__TEXT,__text,regular,pure_instructions",
77        }
78    }
79
80    /// The directive that opens the section one function goes in, and nothing at all when they
81    /// are all going in the same one.
82    ///
83    /// Nothing on Mach-O either, whatever was asked for. Every Mach-O object ends with
84    /// `.subsections_via_symbols`, which tells the linker it may split a section at each symbol in
85    /// it and drop the parts nothing reaches, so the format does by default what the flag asks a
86    /// linker to be able to do and there is nothing left for it to change. Clang takes both flags
87    /// on an Apple target and writes one text section, which is the same answer.
88    ///
89    /// ELF names the section after the function and COFF gives one name to several sections and
90    /// tells the linker which symbol each belongs to. The COFF form is a COMDAT, which is more
91    /// than the ELF one says: a linker keeps one section out of every group that names the same
92    /// symbol. That is what a Windows toolchain does with `/Gy`, and it is what clang writes for
93    /// `-ffunction-sections` on a Windows target, so it is what a Windows linker is expecting.
94    pub fn code(self, out: &mut String, name: &str, sections: Sections) {
95        if !sections.functions {
96            return;
97        }
98        match self {
99            Directives::Elf => {
100                let _ = writeln!(out, "\t.section\t.text.{name},\"ax\",@progbits");
101            }
102            Directives::Coff => {
103                let _ = writeln!(out, "\t.section\t.text,\"xr\",one_only,{name}");
104            }
105            Directives::MachO => {}
106        }
107    }
108
109    /// What is said about a function before its first instruction.
110    ///
111    /// The binding is written the way it is written for a variable, and a local one gets no
112    /// directive at all: a name no directive mentions is still in the symbol table, as a local,
113    /// which is what `static` is. Windows says the same thing as a storage class, where three is
114    /// the local one and two the rest.
115    ///
116    /// `align` is in bytes and is a power of two. `fill` is the byte the padding is made of, which
117    /// on x86-64 is `0x90` because the space in front of a function is reached by falling off the
118    /// end of the one before it and that byte is a `nop`. A machine with no one byte `nop` gives
119    /// `None` and the assembler pads with whatever `nop` the machine has.
120    #[allow(
121        clippy::too_many_arguments,
122        reason = "each is a separate thing a function is opened with"
123    )]
124    pub fn open(
125        self,
126        out: &mut String,
127        name: &str,
128        align: u32,
129        fill: Option<u8>,
130        binding: Binding,
131        visibility: Visibility,
132        ahead: &str,
133    ) {
134        let symbol = self.symbol();
135        let power = align.max(1).trailing_zeros();
136        let _ = match fill {
137            Some(byte) => writeln!(out, "\t.p2align\t{power}, {byte:#x}"),
138            None => writeln!(out, "\t.p2align\t{power}"),
139        };
140        self.bind(out, name, binding);
141        self.seen(out, name, binding, visibility);
142        match self {
143            Directives::Elf => {
144                let _ = writeln!(out, "\t.type\t{name}, @function");
145            }
146            // Windows says the storage class and the type code, and thirty two is a function.
147            Directives::Coff => {
148                let scl = if binding == Binding::Local { 3 } else { 2 };
149                let _ = writeln!(out, "\t.def\t{name}\n\t.scl\t{scl}\n\t.type\t32\n\t.endef");
150            }
151            Directives::MachO => {}
152        }
153        out.push_str(ahead);
154        let _ = writeln!(out, "{symbol}{name}:");
155    }
156
157    /// Where the room a patcher was promised at the top of this function is, as the record a
158    /// tracer reads to find every one of them.
159    ///
160    /// Eight bytes in a section of its own holding the address of the room, and the section is the
161    /// point of it: a tracer that wants to patch every function in a kernel has to be able to find
162    /// them without reading the symbol table, which a stripped image does not have. `o` is the flag
163    /// that ties this section to the text the label is in, so that a linker throwing that text away
164    /// throws the record away with it and never leaves an address pointing at nothing.
165    ///
166    /// `back` is what returns to the text section, which is passed in because which one that is
167    /// depends on whether every function got a section of its own and this does not otherwise care.
168    ///
169    /// Nothing on the other two formats. Neither has a section that works this way and neither has
170    /// a tracer looking for one, and the driver refuses the flag on a target that is not ELF rather
171    /// than letting a build come out looking patchable and not being.
172    pub fn patchable(self, out: &mut String, label: &str, back: &str) {
173        if self != Directives::Elf {
174            return;
175        }
176        let _ = writeln!(out, "\t.section\t__patchable_function_entries,\"awo\",@progbits,{label}");
177        let _ = writeln!(out, "\t.align\t8");
178        let _ = writeln!(out, "\t.quad\t{label}");
179        let _ = writeln!(out, "{back}");
180    }
181
182    /// What is said about how far a name reaches outside a shared library, which is nothing at
183    /// all in the ordinary case.
184    ///
185    /// A local name gets no directive whatever was asked for. `static` is already invisible to
186    /// everything outside the file, so there is no dynamic symbol table for it to be in or out of,
187    /// and gcc writes no visibility directive for one either.
188    ///
189    /// ELF says both of the other two and says them the same way an assembler expects. Mach-O has
190    /// one of them: `.private_extern` is a symbol that leaves this object and does not leave the
191    /// library, which is what hidden means, and there is no Mach-O spelling of protected because
192    /// the format has no way to say a symbol is exported and cannot be interposed. COFF has
193    /// neither, since what leaves a Windows DLL is decided by an export table the linker is given
194    /// rather than by a bit on each symbol.
195    pub fn seen(self, out: &mut String, name: &str, binding: Binding, visibility: Visibility) {
196        if binding == Binding::Local || visibility == Visibility::Default {
197            return;
198        }
199        let symbol = self.symbol();
200        match (self, visibility) {
201            (Directives::Elf, Visibility::Hidden) => {
202                let _ = writeln!(out, "\t.hidden\t{name}");
203            }
204            (Directives::Elf, Visibility::Protected) => {
205                let _ = writeln!(out, "\t.protected\t{name}");
206            }
207            (Directives::MachO, Visibility::Hidden) => {
208                let _ = writeln!(out, "\t.private_extern\t{symbol}{name}");
209            }
210            (Directives::MachO, Visibility::Protected) | (Directives::Coff, _) => {}
211            (_, Visibility::Default) => unreachable!("returned above"),
212        }
213    }
214
215    /// The directive that opens the section a variable goes in when it is being given one of its
216    /// own, and nothing at all when it is not.
217    ///
218    /// The name is worked out once, in [`Place::split`], so that the listing and the object file
219    /// cannot come to disagree about it. What is left here is the flags, which are the flags the
220    /// section it was split off from carries: splitting changes which section header a symbol
221    /// points at and must not quietly change whether the page it lands in is writable.
222    ///
223    /// Nothing on Mach-O, for the reason [`Directives::code`] gives.
224    fn split(self, out: &mut String, place: &Place, name: &str) -> bool {
225        let Some(named) = place.split(name) else { return false };
226        match self {
227            Directives::Elf => {
228                // `@nobits` for the zero filled one, because a section that says nothing about it
229                // is one the assembler writes the bytes of into the file, and the point of that
230                // section is that the file carries none of them. The rest of the flags are what
231                // gcc 16 writes, which is a shorter spelling than the one it uses elsewhere: no
232                // `@progbits`, since that is what a section is when nothing says otherwise.
233                let flags = match place {
234                    Place::Zero => "\"aw\",@nobits",
235                    Place::ReadOnly => "\"a\"",
236                    _ => "\"aw\"",
237                };
238                let _ = writeln!(out, "\t.section\t{named},{flags}");
239            }
240            // COFF gives every one of them the name of the section it came out of and tells the
241            // linker which symbol the group is about, which is the same COMDAT the code above is.
242            Directives::Coff => {
243                let (named, flags) = match place {
244                    Place::Zero => (".bss", "\"bw\""),
245                    Place::ReadOnly | Place::RelocReadOnly { .. } => (".rdata", "\"dr\""),
246                    _ => (".data", "\"dw\""),
247                };
248                let _ = writeln!(out, "\t.section\t{named},{flags},one_only,{name}");
249            }
250            Directives::MachO => return false,
251        }
252        true
253    }
254
255    /// The directive that opens the section a variable goes in.
256    ///
257    /// The three formats disagree about the names and about how much has to be said. ELF and COFF
258    /// have a directive per section that every assembler knows, and both want the flags spelled
259    /// out for a section the program named, since nothing else says whether it may be written to.
260    /// Mach-O has one directive and a segment in front of every section name.
261    ///
262    /// `name` is the variable's, which matters only when it is being given a section of its own.
263    pub fn section(self, out: &mut String, place: &Place, name: &str, sections: Sections) {
264        if sections.data && self.split(out, place, name) {
265            return;
266        }
267        match (self, place) {
268            // A tentative definition is not in a section at all, and the caller is what decides
269            // that. It is answered here as the section it would otherwise have gone in, so that
270            // the match stays about sections and nothing has to be said twice.
271            (Directives::Elf | Directives::Coff, Place::Written | Place::Merged) => {
272                out.push_str("\t.data\n");
273            }
274            (Directives::Elf | Directives::Coff, Place::Zero) => out.push_str("\t.bss\n"),
275            // The flags are spelled out because no assembler has a one word directive for either
276            // of these, and `T` is the one that matters: it is `SHF_TLS`, and it is what tells the
277            // linker the section is the template every thread gets a copy of rather than storage
278            // the program has one of. The other two are the `a` and `w` that `.data` has already.
279            (Directives::Elf, Place::Thread { zero: false }) => {
280                out.push_str("\t.section\t.tdata,\"awT\",@progbits\n");
281            }
282            (Directives::Elf, Place::Thread { zero: true }) => {
283                out.push_str("\t.section\t.tbss,\"awT\",@nobits\n");
284            }
285            // COFF and Mach-O spell thread-local storage in ways that are not a section with a
286            // flag on it, and [`crate::globals`] refuses a thread-local variable on both of them
287            // before anything reaches here. This arm is here because the match is over every pair
288            // of a format and a place, not because it is one that can be taken.
289            (Directives::Coff, Place::Thread { .. }) => out.push_str("\t.data\n"),
290            (Directives::Elf, Place::ReadOnly) => out.push_str("\t.section\t.rodata\n"),
291            (Directives::Elf, Place::RelocReadOnly { local }) => {
292                let name = if *local { ".data.rel.ro.local" } else { ".data.rel.ro" };
293                let _ = writeln!(out, "\t.section\t{name},\"aw\",@progbits");
294            }
295            // COFF has no section of this kind and needs none. A Windows image is relocated as a
296            // whole rather than a symbol at a time, and the loader makes whatever pages it has to
297            // write writable for as long as it is writing them and puts them back afterwards, so
298            // an address in a read only section costs a base relocation and nothing else.
299            (Directives::Coff, Place::ReadOnly | Place::RelocReadOnly { .. }) => {
300                out.push_str("\t.section\t.rdata,\"dr\"\n");
301            }
302            // The type is `@progbits` for almost every name a program writes, and the three it is
303            // not for are the ones the startup code calls what it finds in. A section of the wrong
304            // type under the right name is gathered by the linker all the same and then called by
305            // nobody, which is a program whose constructors silently do not run.
306            (Directives::Elf, Place::Named(name)) => {
307                let kind = Array::of(name).map_or("@progbits", Array::asm);
308                let _ = writeln!(out, "\t.section\t{name},\"aw\",{kind}");
309            }
310            (Directives::Coff, Place::Named(name)) => {
311                let _ = writeln!(out, "\t.section\t{name},\"dw\"");
312            }
313            (Directives::MachO, Place::ReadOnly) => out.push_str("\t.section\t__TEXT,__const\n"),
314            // Mach-O has the same problem and the same answer under a different name. A section in
315            // `__TEXT` is never writable, so a constant holding an address goes in `__DATA,__const`
316            // instead, which `dyld` writes and then protects. There is no `.local` half: the layout
317            // hint is an ELF linker's, and this one has nothing to do with it.
318            (Directives::MachO, Place::RelocReadOnly { .. }) => {
319                out.push_str("\t.section\t__DATA,__const\n");
320            }
321            // A Mach-O section name carries the segment it is in, so a program that named one
322            // named both halves and there is nothing to add to it.
323            (Directives::MachO, Place::Named(name)) => {
324                let _ = writeln!(out, "\t.section\t{name}");
325            }
326            (Directives::MachO, Place::Thread { .. }) => {
327                out.push_str("\t.section\t__DATA,__thread_data,thread_local_regular\n");
328            }
329            (Directives::MachO, _) => out.push_str("\t.section\t__DATA,__data\n"),
330        }
331    }
332
333    /// What is said about a variable before its image, and whether an image follows.
334    ///
335    /// Two kinds of variable are one directive rather than a section, a label and bytes. A
336    /// tentative definition is a request to the linker for that much zeroed space on every format,
337    /// and on Mach-O so is a variable whose image is all zeros, because the section that would
338    /// hold it is one nothing may write bytes into.
339    pub fn variable(self, out: &mut String, var: &Variable, sections: Sections) -> bool {
340        let symbol = self.symbol();
341        let align = var.align.max(1).trailing_zeros();
342        match (self, &var.place) {
343            (_, Place::Merged) => {
344                let comm = if var.binding == Binding::Local { ".lcomm" } else { ".comm" };
345                let name = &var.name;
346                // Apple's assembler takes the alignment as a power of two and gas as a count of
347                // bytes, which are the same number only for one byte.
348                let boundary = if self == Directives::MachO { u64::from(align) } else { var.align };
349                let _ = writeln!(out, "\t{comm}\t{symbol}{name},{},{boundary}", var.size);
350                return false;
351            }
352            // Apple's `.tbss` is `.zerofill` for the per thread image, and the name a program uses is
353            // the descriptor written after it, so that is the one that gets the binding.
354            (Directives::MachO, Place::Thread { zero: true }) => {
355                let name = &var.name;
356                let _ = writeln!(out, "\t.tbss\t{symbol}{name}$tlv$init,{},{align}", var.size);
357                self.descriptor(out, var);
358                return false;
359            }
360            (Directives::MachO, Place::Thread { zero: false }) => {
361                self.section(out, &var.place, &var.name, sections);
362                let _ = writeln!(out, "\t.p2align\t{align}");
363                let _ = writeln!(out, "{symbol}{}$tlv$init:", var.name);
364                return true;
365            }
366            // The directive defines the symbol and says nothing about who may see it, so the
367            // binding is written first, the same as it is above a label.
368            (Directives::MachO, Place::Zero) => {
369                let name = &var.name;
370                self.bind(out, name, var.binding);
371                self.seen(out, name, var.binding, var.visibility);
372                let _ =
373                    writeln!(out, "\t.zerofill\t__DATA,__bss,{symbol}{name},{},{align}", var.size);
374                return false;
375            }
376            _ => {}
377        }
378        self.section(out, &var.place, &var.name, sections);
379        self.bind(out, &var.name, var.binding);
380        self.seen(out, &var.name, var.binding, var.visibility);
381        let _ = writeln!(out, "\t.p2align\t{align}");
382        if self == Directives::Elf {
383            // The type a linker checks a relocation against. A reference to a thread-local is not
384            // the distance to an address, because it has a different address in every thread, so
385            // saying which kind of object this is is what lets the linker refuse a reference that
386            // asked for the wrong thing rather than resolve it to a number that means nothing.
387            let kind = match var.place {
388                Place::Thread { .. } => "@tls_object",
389                _ => "@object",
390            };
391            let _ = writeln!(out, "\t.type\t{}, {kind}", var.name);
392        }
393        let _ = writeln!(out, "{symbol}{}:", var.name);
394        true
395    }
396
397    /// What a thread-local variable on Mach-O is known by, which is not its image.
398    ///
399    /// The image above is only what each thread's copy starts out as. The name the program uses is
400    /// a descriptor of three words: the function that finds this thread's copy, a key it fills in,
401    /// and where the image is. Code reaches the variable by loading the descriptor's address and
402    /// calling the first word with it. `dyld` points the first word at its own function the first
403    /// time the image is loaded, and `__tlv_bootstrap` is only what it starts as. Nothing on any
404    /// other format or for any other variable.
405    pub fn descriptor(self, out: &mut String, var: &Variable) {
406        if self != Directives::MachO || !matches!(var.place, Place::Thread { .. }) {
407            return;
408        }
409        let symbol = self.symbol();
410        let name = &var.name;
411        out.push_str("\t.section\t__DATA,__thread_vars,thread_local_variables\n");
412        self.bind(out, name, var.binding);
413        self.seen(out, name, var.binding, var.visibility);
414        out.push_str("\t.p2align\t3\n");
415        let _ = writeln!(out, "{symbol}{name}:");
416        let _ = writeln!(out, "\t.quad\t{symbol}_tlv_bootstrap");
417        out.push_str("\t.quad\t0\n");
418        let _ = writeln!(out, "\t.quad\t{symbol}{name}$tlv$init");
419    }
420
421    /// The directive that makes a variable visible outside the file, if it is.
422    fn bind(self, out: &mut String, name: &str, binding: Binding) {
423        let symbol = self.symbol();
424        match binding {
425            Binding::Global => {
426                let _ = writeln!(out, "\t.globl\t{symbol}{name}");
427            }
428            // Apple's assembler reads `.weak` as nothing it knows. A weak definition there is an
429            // external name with a second directive saying another object may beat it.
430            Binding::Weak if self == Directives::MachO => {
431                let _ = writeln!(out, "\t.globl\t{symbol}{name}");
432                let _ = writeln!(out, "\t.weak_definition\t{symbol}{name}");
433            }
434            Binding::Weak => {
435                let _ = writeln!(out, "\t.weak\t{symbol}{name}");
436            }
437            // Nothing, which is what makes it invisible outside the file. A name no directive
438            // mentions is still in the symbol table as a local one, which is what `static` is.
439            Binding::Local => {}
440        }
441    }
442
443    /// What is said about a function after its last instruction.
444    ///
445    /// The size, on the format that has one. It is written as the distance from the label to here
446    /// rather than as a number, because the assembler is the one that knows how long an
447    /// instruction turned out to be and this file is what it is about to find out from.
448    pub fn close(self, out: &mut String, name: &str) {
449        if self == Directives::Elf {
450            let _ = writeln!(out, "\t.size\t{name}, .-{name}");
451        }
452    }
453
454    /// A second name for something the file already wrote down.
455    ///
456    /// The binding and then `.set`, which is all gcc writes and all an assembler needs: the type
457    /// and the size of the new symbol are taken from the old one, so writing them again would
458    /// only be a second chance to disagree. Nothing opens a section first, because the symbol is
459    /// an entry in a table rather than a byte of anything, and no `.size` closes it for the same
460    /// reason.
461    ///
462    /// A name with an `@` in it is a symbol version, which gas will not take in `.set` or
463    /// `.globl`. It is written back as the `.symver` it came from, which binds it the way the
464    /// name it stands for is bound.
465    pub fn alias(self, out: &mut String, alias: &Alias) {
466        let symbol = self.symbol();
467        if alias.name.contains('@') {
468            let _ = writeln!(out, "\t.symver\t{symbol}{},{symbol}{}", alias.target, alias.name);
469            return;
470        }
471        self.bind(out, &alias.name, alias.binding);
472        self.seen(out, &alias.name, alias.binding, alias.visibility);
473        let _ = writeln!(out, "\t.set\t{symbol}{},{symbol}{}", alias.name, alias.target);
474    }
475
476    /// A name this file uses and does not define, which the link may leave undefined.
477    ///
478    /// The same directive a weak definition gets and nothing else, because the difference between
479    /// the two is whether a label follows it: `.weak f` with a body under it is a definition
480    /// another object may beat, and `.weak f` with nothing under it is a reference that may come
481    /// to nothing and whose address is then zero. That is how gas reads it and it is what gcc
482    /// writes, which was measured rather than read off the manual.
483    ///
484    /// After everything else, which is also where gcc writes it. Nothing turns on the position,
485    /// since a directive about a name is not a byte of any section, but a listing somebody
486    /// compares against gcc's is easier to compare when the two put things in the same order.
487    ///
488    /// Apple's assembler has a directive of its own for the reference, `.weak_reference`.
489    pub fn absent(self, out: &mut String, name: &str) {
490        let weak = if self == Directives::MachO { ".weak_reference" } else { ".weak" };
491        let _ = writeln!(out, "\t{weak}\t{}{name}", self.symbol());
492    }
493
494    /// What is said once, after every function.
495    ///
496    /// `property` is what the file says it was built to have checked, which is written on the one
497    /// format that has somewhere to put it and is nothing on the other two.
498    pub fn end(self, out: &mut String, property: Property) {
499        match self {
500            Directives::Elf => {
501                if property.any() {
502                    self.property(out, property);
503                }
504                // Without this the stack is executable, which is not a default anybody chose.
505                out.push_str("\t.section\t.note.GNU-stack,\"\",@progbits\n");
506            }
507            // What lets the linker throw away a function nothing calls, which it cannot do
508            // without being told that the boundaries between them are real.
509            Directives::MachO => out.push_str("\t.subsections_via_symbols\n"),
510            Directives::Coff => {}
511        }
512    }
513
514    /// The note that says what the file was built to have checked.
515    ///
516    /// A note is how long its name is, how long its description is, which kind it is, the name and
517    /// then the description, and this kind's description is a list of properties. The one written
518    /// here is the feature word, whose bits are what `-fcf-protection=` asked for.
519    ///
520    /// The lengths count the padding that follows what they measure, which is why the description
521    /// is sixteen bytes for a property of twelve. Nothing between the name and the description,
522    /// because twelve bytes of header and four of name is already a multiple of eight, and the four
523    /// zero bytes at the end are what carries it to the next one. Written as numbers rather than as
524    /// distances between labels, which is what gcc writes, because the numbers are fixed by there
525    /// being exactly one property in it and a label in a listing is another name that can collide.
526    fn property(self, out: &mut String, property: Property) {
527        out.push_str("\t.section\t.note.gnu.property,\"a\",@note\n");
528        out.push_str("\t.p2align\t3\n");
529        let _ = writeln!(out, "\t.long\t4");
530        let _ = writeln!(out, "\t.long\t16");
531        let _ = writeln!(out, "\t.long\t5");
532        let _ = writeln!(out, "\t.asciz\t\"GNU\"");
533        let _ = writeln!(out, "\t.long\t{:#x}", Property::X86_FEATURES);
534        let _ = writeln!(out, "\t.long\t4");
535        let _ = writeln!(out, "\t.long\t{:#x}", property.features);
536        let _ = writeln!(out, "\t.long\t0");
537    }
538}
539
540/// What the object file is told about a function's name, from what the machine function carries.
541///
542/// Two names for one set of three, because the machine IR is not allowed to know what an object
543/// file is and the object writer is not allowed to know what a machine function is. This crate is
544/// where they meet, which is where the two spellings are put side by side.
545#[must_use]
546pub(crate) fn binding(binding: mir::Binding) -> Binding {
547    match binding {
548        mir::Binding::Global => Binding::Global,
549        mir::Binding::Local => Binding::Local,
550        mir::Binding::Weak => Binding::Weak,
551    }
552}
553
554/// What the object file is told about how far a name reaches outside a shared library, from what
555/// the machine function carries.
556///
557/// Two spellings of one set of three, for the reason [`binding`] above has two.
558#[must_use]
559pub(crate) fn visibility(visibility: mir::Visibility) -> Visibility {
560    match visibility {
561        mir::Visibility::Default => Visibility::Default,
562        mir::Visibility::Hidden => Visibility::Hidden,
563        mir::Visibility::Protected => Visibility::Protected,
564    }
565}
566
567#[cfg(test)]
568mod tests {
569    use rucc_object::FUNC_ALIGN;
570
571    use super::*;
572
573    #[test]
574    fn a_mach_o_symbol_is_the_c_name_with_an_underscore_in_front_of_it() {
575        let mut out = String::new();
576        Directives::MachO.open(
577            &mut out,
578            "main",
579            16,
580            Some(0x90),
581            Binding::Global,
582            Visibility::Default,
583            "",
584        );
585        assert!(out.contains("\t.globl\t_main\n"), "{out}");
586        assert!(out.contains("\n_main:\n"), "{out}");
587        // No type and no size, neither of which Mach-O has.
588        assert!(!out.contains(".type"), "{out}");
589        let mut close = String::new();
590        Directives::MachO.close(&mut close, "main");
591        assert_eq!(close, "");
592    }
593
594    /// What clang writes for the same declarations with `-target arm64-apple-macos`, and what
595    /// Apple's assembler takes: it has no `.weak`, and a byte count in `.comm` would be read as
596    /// a power of two and ask for a boundary of 2^8.
597    #[test]
598    fn a_mach_o_listing_says_weak_and_common_the_way_apple_does() {
599        let mut out = String::new();
600        Directives::MachO.open(&mut out, "f", 4, None, Binding::Weak, Visibility::Default, "");
601        assert!(out.contains("\t.globl\t_f\n\t.weak_definition\t_f\n"), "{out}");
602        let mut absent = String::new();
603        Directives::MachO.absent(&mut absent, "g");
604        assert_eq!(absent, "\t.weak_reference\t_g\n");
605        let var = Variable {
606            name: "shared".to_owned(),
607            size: 16,
608            align: 8,
609            place: Place::Merged,
610            binding: Binding::Global,
611            visibility: Visibility::Default,
612            pieces: Vec::new(),
613        };
614        let mut apple = String::new();
615        Directives::MachO.variable(&mut apple, &var, Sections::default());
616        assert_eq!(apple, "\t.comm\t_shared,16,3\n");
617        let mut elf = String::new();
618        Directives::Elf.variable(&mut elf, &var, Sections::default());
619        assert_eq!(elf, "\t.comm\tshared,16,8\n");
620    }
621
622    #[test]
623    fn an_elf_function_says_what_it_is_and_how_long_it_is() {
624        let mut out = String::new();
625        Directives::Elf.open(
626            &mut out,
627            "main",
628            16,
629            Some(0x90),
630            Binding::Global,
631            Visibility::Default,
632            "",
633        );
634        Directives::Elf.close(&mut out, "main");
635        assert!(out.contains("\t.type\tmain, @function\n"), "{out}");
636        assert!(out.contains("\t.size\tmain, .-main\n"), "{out}");
637    }
638
639    #[test]
640    fn a_function_that_asked_to_be_more_aligned_is_written_at_that_alignment() {
641        let mut out = String::new();
642        Directives::Elf.open(
643            &mut out,
644            "f",
645            256,
646            Some(0x90),
647            Binding::Global,
648            Visibility::Default,
649            "",
650        );
651        // The directive counts in powers of two and the attribute counts in bytes, and two
652        // hundred and fifty six bytes is eight of them.
653        assert!(out.contains("\t.p2align\t8, 0x90\n"), "{out}");
654        let mut plain = String::new();
655        Directives::Elf.open(
656            &mut plain,
657            "f",
658            FUNC_ALIGN,
659            Some(0x90),
660            Binding::Global,
661            Visibility::Default,
662            "",
663        );
664        assert!(plain.contains("\t.p2align\t4, 0x90\n"), "{plain}");
665    }
666
667    /// The two directives that say a name does not leave the shared library, or leaves it and
668    /// cannot be replaced.
669    ///
670    /// The listing half of tamnd/rucc#733. It matters that this is written in the listing and not
671    /// only in the object writer, because the two are the same compiler taking two roads out and a
672    /// program built through `-S` and an assembler has to come out the same as one built straight
673    /// to an object.
674    #[test]
675    fn a_name_that_does_not_leave_the_library_says_so_in_the_listing() {
676        let mut out = String::new();
677        Directives::Elf.open(
678            &mut out,
679            "f",
680            16,
681            Some(0x90),
682            Binding::Global,
683            Visibility::Hidden,
684            "",
685        );
686        assert!(out.contains("\t.globl\tf\n"), "still global to the static linker: {out}");
687        assert!(out.contains("\t.hidden\tf\n"), "{out}");
688        let mut protected = String::new();
689        Directives::Elf.open(
690            &mut protected,
691            "f",
692            16,
693            Some(0x90),
694            Binding::Global,
695            Visibility::Protected,
696            "",
697        );
698        assert!(protected.contains("\t.protected\tf\n"), "{protected}");
699        // Mach-O's one spelling of the one of these it has, and it carries the underscore every
700        // other Apple symbol does.
701        let mut apple = String::new();
702        Directives::MachO.open(
703            &mut apple,
704            "f",
705            16,
706            Some(0x90),
707            Binding::Global,
708            Visibility::Hidden,
709            "",
710        );
711        assert!(apple.contains("\t.private_extern\t_f\n"), "{apple}");
712    }
713
714    /// A `static` name gets no visibility directive whatever it asked for.
715    ///
716    /// gcc writes none for one either, and an assembler that is handed `.hidden` for a name that
717    /// was never `.globl` has been told something about a symbol that is not in anybody's dynamic
718    /// table to begin with.
719    #[test]
720    fn a_static_name_is_told_nothing_about_a_dynamic_linker_it_will_never_meet() {
721        for seen in [Visibility::Default, Visibility::Hidden, Visibility::Protected] {
722            let mut out = String::new();
723            Directives::Elf.open(&mut out, "f", 16, Some(0x90), Binding::Local, seen, "");
724            assert!(!out.contains(".hidden"), "{seen:?}: {out}");
725            assert!(!out.contains(".protected"), "{seen:?}: {out}");
726        }
727    }
728
729    /// The names are what gcc 16 writes for the same declarations, checked against it on a Linux
730    /// host, and the leading `.text.` is the part that has to be right rather than decoration:
731    /// `--gc-sections` and the linker scripts a kernel is linked with both match on it.
732    #[test]
733    fn a_function_given_a_section_of_its_own_opens_one_named_after_it() {
734        let split = Sections { functions: true, data: false };
735        let mut out = String::new();
736        Directives::Elf.code(&mut out, "f", split);
737        assert_eq!(out, "\t.section\t.text.f,\"ax\",@progbits\n");
738        // Windows says it as a COMDAT, which is one name for several sections and a symbol saying
739        // which of them is which. That is what clang writes for the same flag on a Windows target.
740        let mut windows = String::new();
741        Directives::Coff.code(&mut windows, "f", split);
742        assert_eq!(windows, "\t.section\t.text,\"xr\",one_only,f\n");
743        // Nothing on Mach-O, whose objects end with `.subsections_via_symbols` and so already let
744        // the linker drop a function nothing reaches.
745        let mut apple = String::new();
746        Directives::MachO.code(&mut apple, "f", split);
747        assert_eq!(apple, "");
748        // And nothing anywhere when nothing asked, which is the default and is what leaves every
749        // function in the one `.text` the file opens with.
750        for directives in [Directives::Elf, Directives::Coff, Directives::MachO] {
751            let mut plain = String::new();
752            directives.code(&mut plain, "f", Sections::default());
753            assert_eq!(plain, "", "{directives:?}");
754        }
755    }
756
757    /// Splitting must change which section header a symbol points at and nothing else, so each of
758    /// these carries the flags of the section it came out of. The spellings are gcc 16's, which is
759    /// shorter than what it writes for the unsplit sections: no `@progbits`, since that is what a
760    /// section is when nothing says otherwise.
761    #[test]
762    fn a_variable_given_a_section_of_its_own_keeps_the_flags_it_would_have_had() {
763        let split = Sections { functions: false, data: true };
764        let cases = [
765            (Place::Written, "\t.section\t.data.x,\"aw\"\n"),
766            (Place::Zero, "\t.section\t.bss.x,\"aw\",@nobits\n"),
767            (Place::ReadOnly, "\t.section\t.rodata.x,\"a\"\n"),
768            (Place::RelocReadOnly { local: false }, "\t.section\t.data.rel.ro.x,\"aw\"\n"),
769            (Place::RelocReadOnly { local: true }, "\t.section\t.data.rel.ro.local.x,\"aw\"\n"),
770        ];
771        for (place, want) in cases {
772            let mut out = String::new();
773            Directives::Elf.section(&mut out, &place, "x", split);
774            assert_eq!(out, want, "{place:?}");
775        }
776    }
777
778    /// The two kinds of variable the flag leaves alone, and the format that ignores it.
779    ///
780    /// A tentative definition is a request to the linker for that much zeroed space rather than an
781    /// image, so there is no section to split off, and a variable the program put a section name on
782    /// has the answer the source gave, which a flag must not overrule.
783    #[test]
784    fn a_variable_that_has_no_section_of_its_own_to_be_given_is_left_where_it_was() {
785        let split = Sections { functions: false, data: true };
786        let mut merged = String::new();
787        Directives::Elf.section(&mut merged, &Place::Merged, "x", split);
788        assert_eq!(merged, "\t.data\n");
789        let named = Place::Named(".init_array".to_owned());
790        let mut asked = String::new();
791        Directives::Elf.section(&mut asked, &named, "x", split);
792        assert_eq!(asked, "\t.section\t.init_array,\"aw\",@init_array\n");
793        let mut apple = String::new();
794        Directives::MachO.section(&mut apple, &Place::Written, "x", split);
795        assert_eq!(apple, "\t.section\t__DATA,__data\n");
796    }
797
798    /// The three names the startup code calls what it finds in, and one that merely begins like
799    /// one.
800    ///
801    /// A numbered priority is written as a suffix on the name and is the same kind of section, so
802    /// the type has to survive the number. `.init_arrays` is an ordinary section whose name happens
803    /// to start with one of theirs, and writing the type on it would tell the linker to gather it
804    /// with them.
805    #[test]
806    fn a_section_of_function_addresses_says_which_kind_it_is() {
807        let cases = [
808            (".init_array", "\t.section\t.init_array,\"aw\",@init_array\n"),
809            (".init_array.00101", "\t.section\t.init_array.00101,\"aw\",@init_array\n"),
810            (".fini_array", "\t.section\t.fini_array,\"aw\",@fini_array\n"),
811            (".preinit_array", "\t.section\t.preinit_array,\"aw\",@preinit_array\n"),
812            (".init_arrays", "\t.section\t.init_arrays,\"aw\",@progbits\n"),
813        ];
814        for (name, want) in cases {
815            let mut out = String::new();
816            let place = Place::Named(name.to_owned());
817            Directives::Elf.section(&mut out, &place, "x", Sections::default());
818            assert_eq!(out, want, "{name}");
819        }
820    }
821
822    #[test]
823    fn an_elf_file_says_the_stack_is_not_executable() {
824        // The absence of this is what makes it executable, so the test is that it is there
825        // rather than that it is spelled a particular way.
826        let mut out = String::new();
827        Directives::Elf.end(&mut out, Property::default());
828        assert!(out.contains(".note.GNU-stack"), "{out}");
829        assert!(!out.contains(".note.gnu.property"), "nothing was asked to be checked");
830    }
831
832    /// What the file says it was built to have checked, as the assembler reads it.
833    ///
834    /// The two lengths are the part worth a test. They count the padding after what they measure,
835    /// so a note that gets them right for its own contents and wrong for the alignment is one the
836    /// linker drops without a word, and what comes of that is a program the loader leaves the check
837    /// turned off for.
838    #[test]
839    fn an_elf_file_says_what_it_was_built_to_have_checked() {
840        let mut out = String::new();
841        Directives::Elf.end(&mut out, Property { features: Property::IBT });
842        let lines: Vec<&str> = out.lines().collect();
843        assert_eq!(
844            lines,
845            [
846                "\t.section\t.note.gnu.property,\"a\",@note",
847                "\t.p2align\t3",
848                "\t.long\t4",
849                "\t.long\t16",
850                "\t.long\t5",
851                "\t.asciz\t\"GNU\"",
852                "\t.long\t0xc0000002",
853                "\t.long\t4",
854                "\t.long\t0x1",
855                "\t.long\t0",
856                "\t.section\t.note.GNU-stack,\"\",@progbits",
857            ]
858        );
859    }
860
861    #[test]
862    fn every_object_format_has_directives() {
863        for format in [ObjectFormat::Elf, ObjectFormat::MachO, ObjectFormat::Coff] {
864            let directives = Directives::of(format);
865            assert!(directives.text().starts_with('\t'));
866            let mut out = String::new();
867            directives.open(
868                &mut out,
869                "f",
870                16,
871                Some(0x90),
872                Binding::Global,
873                Visibility::Default,
874                "",
875            );
876            directives.close(&mut out, "f");
877            directives.end(&mut out, Property::default());
878            assert!(out.ends_with('\n'), "{format:?} left a line unfinished");
879        }
880    }
881}