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