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, Binding, Place};
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 /// What is said about a function before its first instruction.
81 ///
82 /// The binding is written the way it is written for a variable, and a local one gets no
83 /// directive at all: a name no directive mentions is still in the symbol table, as a local,
84 /// which is what `static` is. Windows says the same thing as a storage class, where three is
85 /// the local one and two the rest.
86 ///
87 /// `align` is in bytes and is a power of two, and the padding is `0x90` because the space in
88 /// front of a function is reached by falling off the end of the one before it.
89 pub fn open(self, out: &mut String, name: &str, align: u32, binding: Binding) {
90 let symbol = self.symbol();
91 let _ = writeln!(out, "\t.p2align\t{}, 0x90", align.max(1).trailing_zeros());
92 match binding {
93 Binding::Global => {
94 let _ = writeln!(out, "\t.globl\t{symbol}{name}");
95 }
96 Binding::Weak => {
97 let _ = writeln!(out, "\t.weak\t{symbol}{name}");
98 }
99 Binding::Local => {}
100 }
101 match self {
102 Directives::Elf => {
103 let _ = writeln!(out, "\t.type\t{name}, @function");
104 }
105 // Windows says the storage class and the type code, and thirty two is a function.
106 Directives::Coff => {
107 let scl = if binding == Binding::Local { 3 } else { 2 };
108 let _ = writeln!(out, "\t.def\t{name}\n\t.scl\t{scl}\n\t.type\t32\n\t.endef");
109 }
110 Directives::MachO => {}
111 }
112 let _ = writeln!(out, "{symbol}{name}:");
113 }
114
115 /// The directive that opens the section a variable goes in.
116 ///
117 /// The three formats disagree about the names and about how much has to be said. ELF and COFF
118 /// have a directive per section that every assembler knows, and both want the flags spelled
119 /// out for a section the program named, since nothing else says whether it may be written to.
120 /// Mach-O has one directive and a segment in front of every section name.
121 pub fn section(self, out: &mut String, place: &Place) {
122 match (self, place) {
123 // A tentative definition is not in a section at all, and the caller is what decides
124 // that. It is answered here as the section it would otherwise have gone in, so that
125 // the match stays about sections and nothing has to be said twice.
126 (Directives::Elf | Directives::Coff, Place::Written | Place::Merged) => {
127 out.push_str("\t.data\n");
128 }
129 (Directives::Elf | Directives::Coff, Place::Zero) => out.push_str("\t.bss\n"),
130 (Directives::Elf, Place::ReadOnly) => out.push_str("\t.section\t.rodata\n"),
131 (Directives::Elf, Place::RelocReadOnly { local }) => {
132 let name = if *local { ".data.rel.ro.local" } else { ".data.rel.ro" };
133 let _ = writeln!(out, "\t.section\t{name},\"aw\",@progbits");
134 }
135 // COFF has no section of this kind and needs none. A Windows image is relocated as a
136 // whole rather than a symbol at a time, and the loader makes whatever pages it has to
137 // write writable for as long as it is writing them and puts them back afterwards, so
138 // an address in a read only section costs a base relocation and nothing else.
139 (Directives::Coff, Place::ReadOnly | Place::RelocReadOnly { .. }) => {
140 out.push_str("\t.section\t.rdata,\"dr\"\n");
141 }
142 (Directives::Elf, Place::Named(name)) => {
143 let _ = writeln!(out, "\t.section\t{name},\"aw\",@progbits");
144 }
145 (Directives::Coff, Place::Named(name)) => {
146 let _ = writeln!(out, "\t.section\t{name},\"dw\"");
147 }
148 (Directives::MachO, Place::ReadOnly) => out.push_str("\t.section\t__TEXT,__const\n"),
149 // Mach-O has the same problem and the same answer under a different name. A section in
150 // `__TEXT` is never writable, so a constant holding an address goes in `__DATA,__const`
151 // instead, which `dyld` writes and then protects. There is no `.local` half: the layout
152 // hint is an ELF linker's, and this one has nothing to do with it.
153 (Directives::MachO, Place::RelocReadOnly { .. }) => {
154 out.push_str("\t.section\t__DATA,__const\n");
155 }
156 // A Mach-O section name carries the segment it is in, so a program that named one
157 // named both halves and there is nothing to add to it.
158 (Directives::MachO, Place::Named(name)) => {
159 let _ = writeln!(out, "\t.section\t{name}");
160 }
161 (Directives::MachO, _) => out.push_str("\t.section\t__DATA,__data\n"),
162 }
163 }
164
165 /// What is said about a variable before its image, and whether an image follows.
166 ///
167 /// Two kinds of variable are one directive rather than a section, a label and bytes. A
168 /// tentative definition is a request to the linker for that much zeroed space on every format,
169 /// and on Mach-O so is a variable whose image is all zeros, because the section that would
170 /// hold it is one nothing may write bytes into.
171 pub fn variable(self, out: &mut String, var: &Variable) -> bool {
172 let symbol = self.symbol();
173 let align = var.align.max(1).trailing_zeros();
174 match (self, &var.place) {
175 (_, Place::Merged) => {
176 let comm = if var.binding == Binding::Local { ".lcomm" } else { ".comm" };
177 let name = &var.name;
178 let _ = writeln!(out, "\t{comm}\t{symbol}{name},{},{}", var.size, var.align);
179 return false;
180 }
181 (Directives::MachO, Place::Zero) => {
182 let name = &var.name;
183 let _ =
184 writeln!(out, "\t.zerofill\t__DATA,__bss,{symbol}{name},{},{align}", var.size);
185 return false;
186 }
187 _ => {}
188 }
189 self.section(out, &var.place);
190 match var.binding {
191 Binding::Global => {
192 let _ = writeln!(out, "\t.globl\t{symbol}{}", var.name);
193 }
194 Binding::Weak => {
195 let _ = writeln!(out, "\t.weak\t{symbol}{}", var.name);
196 }
197 // Nothing, which is what makes it invisible outside the file. A name no directive
198 // mentions is still in the symbol table as a local one, which is what `static` is.
199 Binding::Local => {}
200 }
201 let _ = writeln!(out, "\t.p2align\t{align}");
202 if self == Directives::Elf {
203 let _ = writeln!(out, "\t.type\t{}, @object", var.name);
204 }
205 let _ = writeln!(out, "{symbol}{}:", var.name);
206 true
207 }
208
209 /// What is said about a function after its last instruction.
210 ///
211 /// The size, on the format that has one. It is written as the distance from the label to here
212 /// rather than as a number, because the assembler is the one that knows how long an
213 /// instruction turned out to be and this file is what it is about to find out from.
214 pub fn close(self, out: &mut String, name: &str) {
215 if self == Directives::Elf {
216 let _ = writeln!(out, "\t.size\t{name}, .-{name}");
217 }
218 }
219
220 /// A second name for something the file already wrote down.
221 ///
222 /// The binding and then `.set`, which is all gcc writes and all an assembler needs: the type
223 /// and the size of the new symbol are taken from the old one, so writing them again would
224 /// only be a second chance to disagree. Nothing opens a section first, because the symbol is
225 /// an entry in a table rather than a byte of anything, and no `.size` closes it for the same
226 /// reason.
227 pub fn alias(self, out: &mut String, alias: &Alias) {
228 let symbol = self.symbol();
229 match alias.binding {
230 Binding::Global => {
231 let _ = writeln!(out, "\t.globl\t{symbol}{}", alias.name);
232 }
233 Binding::Weak => {
234 let _ = writeln!(out, "\t.weak\t{symbol}{}", alias.name);
235 }
236 Binding::Local => {}
237 }
238 let _ = writeln!(out, "\t.set\t{symbol}{},{symbol}{}", alias.name, alias.target);
239 }
240
241 /// What is said once, after every function.
242 pub fn end(self, out: &mut String) {
243 match self {
244 // Without this the stack is executable, which is not a default anybody chose.
245 Directives::Elf => out.push_str("\t.section\t.note.GNU-stack,\"\",@progbits\n"),
246 // What lets the linker throw away a function nothing calls, which it cannot do
247 // without being told that the boundaries between them are real.
248 Directives::MachO => out.push_str("\t.subsections_via_symbols\n"),
249 Directives::Coff => {}
250 }
251 }
252}
253
254/// What the object file is told about a function's name, from what the machine function carries.
255///
256/// Two names for one set of three, because the machine IR is not allowed to know what an object
257/// file is and the object writer is not allowed to know what a machine function is. This crate is
258/// where they meet, which is where the two spellings are put side by side.
259#[must_use]
260pub(crate) fn binding(binding: mir::Binding) -> Binding {
261 match binding {
262 mir::Binding::Global => Binding::Global,
263 mir::Binding::Local => Binding::Local,
264 mir::Binding::Weak => Binding::Weak,
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use rucc_object::FUNC_ALIGN;
271
272 use super::*;
273
274 #[test]
275 fn a_mach_o_symbol_is_the_c_name_with_an_underscore_in_front_of_it() {
276 let mut out = String::new();
277 Directives::MachO.open(&mut out, "main", 16, Binding::Global);
278 assert!(out.contains("\t.globl\t_main\n"), "{out}");
279 assert!(out.contains("\n_main:\n"), "{out}");
280 // No type and no size, neither of which Mach-O has.
281 assert!(!out.contains(".type"), "{out}");
282 let mut close = String::new();
283 Directives::MachO.close(&mut close, "main");
284 assert_eq!(close, "");
285 }
286
287 #[test]
288 fn an_elf_function_says_what_it_is_and_how_long_it_is() {
289 let mut out = String::new();
290 Directives::Elf.open(&mut out, "main", 16, Binding::Global);
291 Directives::Elf.close(&mut out, "main");
292 assert!(out.contains("\t.type\tmain, @function\n"), "{out}");
293 assert!(out.contains("\t.size\tmain, .-main\n"), "{out}");
294 }
295
296 #[test]
297 fn a_function_that_asked_to_be_more_aligned_is_written_at_that_alignment() {
298 let mut out = String::new();
299 Directives::Elf.open(&mut out, "f", 256, Binding::Global);
300 // The directive counts in powers of two and the attribute counts in bytes, and two
301 // hundred and fifty six bytes is eight of them.
302 assert!(out.contains("\t.p2align\t8, 0x90\n"), "{out}");
303 let mut plain = String::new();
304 Directives::Elf.open(&mut plain, "f", FUNC_ALIGN, Binding::Global);
305 assert!(plain.contains("\t.p2align\t4, 0x90\n"), "{plain}");
306 }
307
308 #[test]
309 fn an_elf_file_says_the_stack_is_not_executable() {
310 // The absence of this is what makes it executable, so the test is that it is there
311 // rather than that it is spelled a particular way.
312 let mut out = String::new();
313 Directives::Elf.end(&mut out);
314 assert!(out.contains(".note.GNU-stack"), "{out}");
315 }
316
317 #[test]
318 fn every_object_format_has_directives() {
319 for format in [ObjectFormat::Elf, ObjectFormat::MachO, ObjectFormat::Coff] {
320 let directives = Directives::of(format);
321 assert!(directives.text().starts_with('\t'));
322 let mut out = String::new();
323 directives.open(&mut out, "f", 16, Binding::Global);
324 directives.close(&mut out, "f");
325 directives.end(&mut out);
326 assert!(out.ends_with('\n'), "{format:?} left a line unfinished");
327 }
328 }
329}