Skip to main content

rucc_asm/
format.rs

1//! What an assembler is told about a function, which is the object format's answer rather than
2//! 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 goes in,
6//! 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_target::ObjectFormat;
19
20/// The directives one object format wraps a function in.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Directives {
23    /// ELF, which is Linux and the freestanding targets.
24    Elf,
25    /// Mach-O, which is Apple's.
26    MachO,
27    /// COFF, which is Windows.
28    Coff,
29}
30
31impl Directives {
32    /// The directives that go with that object format.
33    #[must_use]
34    pub const fn of(format: ObjectFormat) -> Directives {
35        match format {
36            ObjectFormat::Elf => Directives::Elf,
37            ObjectFormat::MachO => Directives::MachO,
38            ObjectFormat::Coff => Directives::Coff,
39        }
40    }
41
42    /// What goes in front of a C name to make the name the linker sees.
43    ///
44    /// Mach-O keeps the underscore that every Unix linker once had, so `main` in C is `_main` in
45    /// the object, and a listing that leaves it off refers to a symbol nothing defines.
46    #[must_use]
47    pub const fn symbol(self) -> &'static str {
48        match self {
49            Directives::Elf | Directives::Coff => "",
50            Directives::MachO => "_",
51        }
52    }
53
54    /// What goes in front of a label that belongs to one function and leaves no symbol behind.
55    #[must_use]
56    pub const fn local(self) -> &'static str {
57        match self {
58            Directives::Elf | Directives::Coff => ".L",
59            Directives::MachO => "L",
60        }
61    }
62
63    /// The directive that opens the section code goes in.
64    #[must_use]
65    pub const fn text(self) -> &'static str {
66        match self {
67            Directives::Elf | Directives::Coff => "\t.text",
68            Directives::MachO => "\t.section\t__TEXT,__text,regular,pure_instructions",
69        }
70    }
71
72    /// What is said about a function before its first instruction.
73    ///
74    /// Every function is global, because a machine function does not carry the linkage the C did
75    /// and nothing below the driver could ask. That is wrong for a `static` function and is the
76    /// reason `-S` output is a thing to read rather than a thing to link, until the object writer
77    /// gives the machine IR somewhere to keep it.
78    pub fn open(self, out: &mut String, name: &str) {
79        let symbol = self.symbol();
80        out.push_str("\t.p2align\t4, 0x90\n");
81        let _ = writeln!(out, "\t.globl\t{symbol}{name}");
82        match self {
83            Directives::Elf => {
84                let _ = writeln!(out, "\t.type\t{name}, @function");
85            }
86            // Windows says the same thing as a storage class and a type code: two is external and
87            // thirty two is a function, and the two numbers together are what ELF's one word says.
88            Directives::Coff => {
89                let _ = writeln!(out, "\t.def\t{name}\n\t.scl\t2\n\t.type\t32\n\t.endef");
90            }
91            Directives::MachO => {}
92        }
93        let _ = writeln!(out, "{symbol}{name}:");
94    }
95
96    /// What is said about a function after its last instruction.
97    ///
98    /// The size, on the format that has one. It is written as the distance from the label to here
99    /// rather than as a number, because the assembler is the one that knows how long an
100    /// instruction turned out to be and this file is what it is about to find out from.
101    pub fn close(self, out: &mut String, name: &str) {
102        if self == Directives::Elf {
103            let _ = writeln!(out, "\t.size\t{name}, .-{name}");
104        }
105    }
106
107    /// What is said once, after every function.
108    pub fn end(self, out: &mut String) {
109        match self {
110            // Without this the stack is executable, which is not a default anybody chose.
111            Directives::Elf => out.push_str("\t.section\t.note.GNU-stack,\"\",@progbits\n"),
112            // What lets the linker throw away a function nothing calls, which it cannot do
113            // without being told that the boundaries between them are real.
114            Directives::MachO => out.push_str("\t.subsections_via_symbols\n"),
115            Directives::Coff => {}
116        }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn a_mach_o_symbol_is_the_c_name_with_an_underscore_in_front_of_it() {
126        let mut out = String::new();
127        Directives::MachO.open(&mut out, "main");
128        assert!(out.contains("\t.globl\t_main\n"), "{out}");
129        assert!(out.contains("\n_main:\n"), "{out}");
130        // No type and no size, neither of which Mach-O has.
131        assert!(!out.contains(".type"), "{out}");
132        let mut close = String::new();
133        Directives::MachO.close(&mut close, "main");
134        assert_eq!(close, "");
135    }
136
137    #[test]
138    fn an_elf_function_says_what_it_is_and_how_long_it_is() {
139        let mut out = String::new();
140        Directives::Elf.open(&mut out, "main");
141        Directives::Elf.close(&mut out, "main");
142        assert!(out.contains("\t.type\tmain, @function\n"), "{out}");
143        assert!(out.contains("\t.size\tmain, .-main\n"), "{out}");
144    }
145
146    #[test]
147    fn an_elf_file_says_the_stack_is_not_executable() {
148        // The absence of this is what makes it executable, so the test is that it is there
149        // rather than that it is spelled a particular way.
150        let mut out = String::new();
151        Directives::Elf.end(&mut out);
152        assert!(out.contains(".note.GNU-stack"), "{out}");
153    }
154
155    #[test]
156    fn every_object_format_has_directives() {
157        for format in [ObjectFormat::Elf, ObjectFormat::MachO, ObjectFormat::Coff] {
158            let directives = Directives::of(format);
159            assert!(directives.text().starts_with('\t'));
160            let mut out = String::new();
161            directives.open(&mut out, "f");
162            directives.close(&mut out, "f");
163            directives.end(&mut out);
164            assert!(out.ends_with('\n'), "{format:?} left a line unfinished");
165        }
166    }
167}