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, 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, and the padding is `0x90` because the space in
117 /// front of a function is reached by falling off the end of the one before it.
118 pub fn open(
119 self,
120 out: &mut String,
121 name: &str,
122 align: u32,
123 binding: Binding,
124 visibility: Visibility,
125 ) {
126 let symbol = self.symbol();
127 let _ = writeln!(out, "\t.p2align\t{}, 0x90", align.max(1).trailing_zeros());
128 match binding {
129 Binding::Global => {
130 let _ = writeln!(out, "\t.globl\t{symbol}{name}");
131 }
132 Binding::Weak => {
133 let _ = writeln!(out, "\t.weak\t{symbol}{name}");
134 }
135 Binding::Local => {}
136 }
137 self.seen(out, name, binding, visibility);
138 match self {
139 Directives::Elf => {
140 let _ = writeln!(out, "\t.type\t{name}, @function");
141 }
142 // Windows says the storage class and the type code, and thirty two is a function.
143 Directives::Coff => {
144 let scl = if binding == Binding::Local { 3 } else { 2 };
145 let _ = writeln!(out, "\t.def\t{name}\n\t.scl\t{scl}\n\t.type\t32\n\t.endef");
146 }
147 Directives::MachO => {}
148 }
149 let _ = writeln!(out, "{symbol}{name}:");
150 }
151
152 /// What is said about how far a name reaches outside a shared library, which is nothing at
153 /// all in the ordinary case.
154 ///
155 /// A local name gets no directive whatever was asked for. `static` is already invisible to
156 /// everything outside the file, so there is no dynamic symbol table for it to be in or out of,
157 /// and gcc writes no visibility directive for one either.
158 ///
159 /// ELF says both of the other two and says them the same way an assembler expects. Mach-O has
160 /// one of them: `.private_extern` is a symbol that leaves this object and does not leave the
161 /// library, which is what hidden means, and there is no Mach-O spelling of protected because
162 /// the format has no way to say a symbol is exported and cannot be interposed. COFF has
163 /// neither, since what leaves a Windows DLL is decided by an export table the linker is given
164 /// rather than by a bit on each symbol.
165 pub fn seen(self, out: &mut String, name: &str, binding: Binding, visibility: Visibility) {
166 if binding == Binding::Local || visibility == Visibility::Default {
167 return;
168 }
169 let symbol = self.symbol();
170 match (self, visibility) {
171 (Directives::Elf, Visibility::Hidden) => {
172 let _ = writeln!(out, "\t.hidden\t{name}");
173 }
174 (Directives::Elf, Visibility::Protected) => {
175 let _ = writeln!(out, "\t.protected\t{name}");
176 }
177 (Directives::MachO, Visibility::Hidden) => {
178 let _ = writeln!(out, "\t.private_extern\t{symbol}{name}");
179 }
180 (Directives::MachO, Visibility::Protected) | (Directives::Coff, _) => {}
181 (_, Visibility::Default) => unreachable!("returned above"),
182 }
183 }
184
185 /// The directive that opens the section a variable goes in when it is being given one of its
186 /// own, and nothing at all when it is not.
187 ///
188 /// The name is worked out once, in [`Place::split`], so that the listing and the object file
189 /// cannot come to disagree about it. What is left here is the flags, which are the flags the
190 /// section it was split off from carries: splitting changes which section header a symbol
191 /// points at and must not quietly change whether the page it lands in is writable.
192 ///
193 /// Nothing on Mach-O, for the reason [`Directives::code`] gives.
194 fn split(self, out: &mut String, place: &Place, name: &str) -> bool {
195 let Some(named) = place.split(name) else { return false };
196 match self {
197 Directives::Elf => {
198 // `@nobits` for the zero filled one, because a section that says nothing about it
199 // is one the assembler writes the bytes of into the file, and the point of that
200 // section is that the file carries none of them. The rest of the flags are what
201 // gcc 16 writes, which is a shorter spelling than the one it uses elsewhere: no
202 // `@progbits`, since that is what a section is when nothing says otherwise.
203 let flags = match place {
204 Place::Zero => "\"aw\",@nobits",
205 Place::ReadOnly => "\"a\"",
206 _ => "\"aw\"",
207 };
208 let _ = writeln!(out, "\t.section\t{named},{flags}");
209 }
210 // COFF gives every one of them the name of the section it came out of and tells the
211 // linker which symbol the group is about, which is the same COMDAT the code above is.
212 Directives::Coff => {
213 let (named, flags) = match place {
214 Place::Zero => (".bss", "\"bw\""),
215 Place::ReadOnly | Place::RelocReadOnly { .. } => (".rdata", "\"dr\""),
216 _ => (".data", "\"dw\""),
217 };
218 let _ = writeln!(out, "\t.section\t{named},{flags},one_only,{name}");
219 }
220 Directives::MachO => return false,
221 }
222 true
223 }
224
225 /// The directive that opens the section a variable goes in.
226 ///
227 /// The three formats disagree about the names and about how much has to be said. ELF and COFF
228 /// have a directive per section that every assembler knows, and both want the flags spelled
229 /// out for a section the program named, since nothing else says whether it may be written to.
230 /// Mach-O has one directive and a segment in front of every section name.
231 ///
232 /// `name` is the variable's, which matters only when it is being given a section of its own.
233 pub fn section(self, out: &mut String, place: &Place, name: &str, sections: Sections) {
234 if sections.data && self.split(out, place, name) {
235 return;
236 }
237 match (self, place) {
238 // A tentative definition is not in a section at all, and the caller is what decides
239 // that. It is answered here as the section it would otherwise have gone in, so that
240 // the match stays about sections and nothing has to be said twice.
241 (Directives::Elf | Directives::Coff, Place::Written | Place::Merged) => {
242 out.push_str("\t.data\n");
243 }
244 (Directives::Elf | Directives::Coff, Place::Zero) => out.push_str("\t.bss\n"),
245 (Directives::Elf, Place::ReadOnly) => out.push_str("\t.section\t.rodata\n"),
246 (Directives::Elf, Place::RelocReadOnly { local }) => {
247 let name = if *local { ".data.rel.ro.local" } else { ".data.rel.ro" };
248 let _ = writeln!(out, "\t.section\t{name},\"aw\",@progbits");
249 }
250 // COFF has no section of this kind and needs none. A Windows image is relocated as a
251 // whole rather than a symbol at a time, and the loader makes whatever pages it has to
252 // write writable for as long as it is writing them and puts them back afterwards, so
253 // an address in a read only section costs a base relocation and nothing else.
254 (Directives::Coff, Place::ReadOnly | Place::RelocReadOnly { .. }) => {
255 out.push_str("\t.section\t.rdata,\"dr\"\n");
256 }
257 (Directives::Elf, Place::Named(name)) => {
258 let _ = writeln!(out, "\t.section\t{name},\"aw\",@progbits");
259 }
260 (Directives::Coff, Place::Named(name)) => {
261 let _ = writeln!(out, "\t.section\t{name},\"dw\"");
262 }
263 (Directives::MachO, Place::ReadOnly) => out.push_str("\t.section\t__TEXT,__const\n"),
264 // Mach-O has the same problem and the same answer under a different name. A section in
265 // `__TEXT` is never writable, so a constant holding an address goes in `__DATA,__const`
266 // instead, which `dyld` writes and then protects. There is no `.local` half: the layout
267 // hint is an ELF linker's, and this one has nothing to do with it.
268 (Directives::MachO, Place::RelocReadOnly { .. }) => {
269 out.push_str("\t.section\t__DATA,__const\n");
270 }
271 // A Mach-O section name carries the segment it is in, so a program that named one
272 // named both halves and there is nothing to add to it.
273 (Directives::MachO, Place::Named(name)) => {
274 let _ = writeln!(out, "\t.section\t{name}");
275 }
276 (Directives::MachO, _) => out.push_str("\t.section\t__DATA,__data\n"),
277 }
278 }
279
280 /// What is said about a variable before its image, and whether an image follows.
281 ///
282 /// Two kinds of variable are one directive rather than a section, a label and bytes. A
283 /// tentative definition is a request to the linker for that much zeroed space on every format,
284 /// and on Mach-O so is a variable whose image is all zeros, because the section that would
285 /// hold it is one nothing may write bytes into.
286 pub fn variable(self, out: &mut String, var: &Variable, sections: Sections) -> bool {
287 let symbol = self.symbol();
288 let align = var.align.max(1).trailing_zeros();
289 match (self, &var.place) {
290 (_, Place::Merged) => {
291 let comm = if var.binding == Binding::Local { ".lcomm" } else { ".comm" };
292 let name = &var.name;
293 let _ = writeln!(out, "\t{comm}\t{symbol}{name},{},{}", var.size, var.align);
294 return false;
295 }
296 (Directives::MachO, Place::Zero) => {
297 let name = &var.name;
298 let _ =
299 writeln!(out, "\t.zerofill\t__DATA,__bss,{symbol}{name},{},{align}", var.size);
300 return false;
301 }
302 _ => {}
303 }
304 self.section(out, &var.place, &var.name, sections);
305 match var.binding {
306 Binding::Global => {
307 let _ = writeln!(out, "\t.globl\t{symbol}{}", var.name);
308 }
309 Binding::Weak => {
310 let _ = writeln!(out, "\t.weak\t{symbol}{}", var.name);
311 }
312 // Nothing, which is what makes it invisible outside the file. A name no directive
313 // mentions is still in the symbol table as a local one, which is what `static` is.
314 Binding::Local => {}
315 }
316 self.seen(out, &var.name, var.binding, var.visibility);
317 let _ = writeln!(out, "\t.p2align\t{align}");
318 if self == Directives::Elf {
319 let _ = writeln!(out, "\t.type\t{}, @object", var.name);
320 }
321 let _ = writeln!(out, "{symbol}{}:", var.name);
322 true
323 }
324
325 /// What is said about a function after its last instruction.
326 ///
327 /// The size, on the format that has one. It is written as the distance from the label to here
328 /// rather than as a number, because the assembler is the one that knows how long an
329 /// instruction turned out to be and this file is what it is about to find out from.
330 pub fn close(self, out: &mut String, name: &str) {
331 if self == Directives::Elf {
332 let _ = writeln!(out, "\t.size\t{name}, .-{name}");
333 }
334 }
335
336 /// A second name for something the file already wrote down.
337 ///
338 /// The binding and then `.set`, which is all gcc writes and all an assembler needs: the type
339 /// and the size of the new symbol are taken from the old one, so writing them again would
340 /// only be a second chance to disagree. Nothing opens a section first, because the symbol is
341 /// an entry in a table rather than a byte of anything, and no `.size` closes it for the same
342 /// reason.
343 pub fn alias(self, out: &mut String, alias: &Alias) {
344 let symbol = self.symbol();
345 match alias.binding {
346 Binding::Global => {
347 let _ = writeln!(out, "\t.globl\t{symbol}{}", alias.name);
348 }
349 Binding::Weak => {
350 let _ = writeln!(out, "\t.weak\t{symbol}{}", alias.name);
351 }
352 Binding::Local => {}
353 }
354 self.seen(out, &alias.name, alias.binding, alias.visibility);
355 let _ = writeln!(out, "\t.set\t{symbol}{},{symbol}{}", alias.name, alias.target);
356 }
357
358 /// What is said once, after every function.
359 pub fn end(self, out: &mut String) {
360 match self {
361 // Without this the stack is executable, which is not a default anybody chose.
362 Directives::Elf => out.push_str("\t.section\t.note.GNU-stack,\"\",@progbits\n"),
363 // What lets the linker throw away a function nothing calls, which it cannot do
364 // without being told that the boundaries between them are real.
365 Directives::MachO => out.push_str("\t.subsections_via_symbols\n"),
366 Directives::Coff => {}
367 }
368 }
369}
370
371/// What the object file is told about a function's name, from what the machine function carries.
372///
373/// Two names for one set of three, because the machine IR is not allowed to know what an object
374/// file is and the object writer is not allowed to know what a machine function is. This crate is
375/// where they meet, which is where the two spellings are put side by side.
376#[must_use]
377pub(crate) fn binding(binding: mir::Binding) -> Binding {
378 match binding {
379 mir::Binding::Global => Binding::Global,
380 mir::Binding::Local => Binding::Local,
381 mir::Binding::Weak => Binding::Weak,
382 }
383}
384
385/// What the object file is told about how far a name reaches outside a shared library, from what
386/// the machine function carries.
387///
388/// Two spellings of one set of three, for the reason [`binding`] above has two.
389#[must_use]
390pub(crate) fn visibility(visibility: mir::Visibility) -> Visibility {
391 match visibility {
392 mir::Visibility::Default => Visibility::Default,
393 mir::Visibility::Hidden => Visibility::Hidden,
394 mir::Visibility::Protected => Visibility::Protected,
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use rucc_object::FUNC_ALIGN;
401
402 use super::*;
403
404 #[test]
405 fn a_mach_o_symbol_is_the_c_name_with_an_underscore_in_front_of_it() {
406 let mut out = String::new();
407 Directives::MachO.open(&mut out, "main", 16, Binding::Global, Visibility::Default);
408 assert!(out.contains("\t.globl\t_main\n"), "{out}");
409 assert!(out.contains("\n_main:\n"), "{out}");
410 // No type and no size, neither of which Mach-O has.
411 assert!(!out.contains(".type"), "{out}");
412 let mut close = String::new();
413 Directives::MachO.close(&mut close, "main");
414 assert_eq!(close, "");
415 }
416
417 #[test]
418 fn an_elf_function_says_what_it_is_and_how_long_it_is() {
419 let mut out = String::new();
420 Directives::Elf.open(&mut out, "main", 16, Binding::Global, Visibility::Default);
421 Directives::Elf.close(&mut out, "main");
422 assert!(out.contains("\t.type\tmain, @function\n"), "{out}");
423 assert!(out.contains("\t.size\tmain, .-main\n"), "{out}");
424 }
425
426 #[test]
427 fn a_function_that_asked_to_be_more_aligned_is_written_at_that_alignment() {
428 let mut out = String::new();
429 Directives::Elf.open(&mut out, "f", 256, Binding::Global, Visibility::Default);
430 // The directive counts in powers of two and the attribute counts in bytes, and two
431 // hundred and fifty six bytes is eight of them.
432 assert!(out.contains("\t.p2align\t8, 0x90\n"), "{out}");
433 let mut plain = String::new();
434 Directives::Elf.open(&mut plain, "f", FUNC_ALIGN, Binding::Global, Visibility::Default);
435 assert!(plain.contains("\t.p2align\t4, 0x90\n"), "{plain}");
436 }
437
438 /// The two directives that say a name does not leave the shared library, or leaves it and
439 /// cannot be replaced.
440 ///
441 /// The listing half of tamnd/rucc#733. It matters that this is written in the listing and not
442 /// only in the object writer, because the two are the same compiler taking two roads out and a
443 /// program built through `-S` and an assembler has to come out the same as one built straight
444 /// to an object.
445 #[test]
446 fn a_name_that_does_not_leave_the_library_says_so_in_the_listing() {
447 let mut out = String::new();
448 Directives::Elf.open(&mut out, "f", 16, Binding::Global, Visibility::Hidden);
449 assert!(out.contains("\t.globl\tf\n"), "still global to the static linker: {out}");
450 assert!(out.contains("\t.hidden\tf\n"), "{out}");
451 let mut protected = String::new();
452 Directives::Elf.open(&mut protected, "f", 16, Binding::Global, Visibility::Protected);
453 assert!(protected.contains("\t.protected\tf\n"), "{protected}");
454 // Mach-O's one spelling of the one of these it has, and it carries the underscore every
455 // other Apple symbol does.
456 let mut apple = String::new();
457 Directives::MachO.open(&mut apple, "f", 16, Binding::Global, Visibility::Hidden);
458 assert!(apple.contains("\t.private_extern\t_f\n"), "{apple}");
459 }
460
461 /// A `static` name gets no visibility directive whatever it asked for.
462 ///
463 /// gcc writes none for one either, and an assembler that is handed `.hidden` for a name that
464 /// was never `.globl` has been told something about a symbol that is not in anybody's dynamic
465 /// table to begin with.
466 #[test]
467 fn a_static_name_is_told_nothing_about_a_dynamic_linker_it_will_never_meet() {
468 for seen in [Visibility::Default, Visibility::Hidden, Visibility::Protected] {
469 let mut out = String::new();
470 Directives::Elf.open(&mut out, "f", 16, Binding::Local, seen);
471 assert!(!out.contains(".hidden"), "{seen:?}: {out}");
472 assert!(!out.contains(".protected"), "{seen:?}: {out}");
473 }
474 }
475
476 /// The names are what gcc 16 writes for the same declarations, checked against it on a Linux
477 /// host, and the leading `.text.` is the part that has to be right rather than decoration:
478 /// `--gc-sections` and the linker scripts a kernel is linked with both match on it.
479 #[test]
480 fn a_function_given_a_section_of_its_own_opens_one_named_after_it() {
481 let split = Sections { functions: true, data: false };
482 let mut out = String::new();
483 Directives::Elf.code(&mut out, "f", split);
484 assert_eq!(out, "\t.section\t.text.f,\"ax\",@progbits\n");
485 // Windows says it as a COMDAT, which is one name for several sections and a symbol saying
486 // which of them is which. That is what clang writes for the same flag on a Windows target.
487 let mut windows = String::new();
488 Directives::Coff.code(&mut windows, "f", split);
489 assert_eq!(windows, "\t.section\t.text,\"xr\",one_only,f\n");
490 // Nothing on Mach-O, whose objects end with `.subsections_via_symbols` and so already let
491 // the linker drop a function nothing reaches.
492 let mut apple = String::new();
493 Directives::MachO.code(&mut apple, "f", split);
494 assert_eq!(apple, "");
495 // And nothing anywhere when nothing asked, which is the default and is what leaves every
496 // function in the one `.text` the file opens with.
497 for directives in [Directives::Elf, Directives::Coff, Directives::MachO] {
498 let mut plain = String::new();
499 directives.code(&mut plain, "f", Sections::default());
500 assert_eq!(plain, "", "{directives:?}");
501 }
502 }
503
504 /// Splitting must change which section header a symbol points at and nothing else, so each of
505 /// these carries the flags of the section it came out of. The spellings are gcc 16's, which is
506 /// shorter than what it writes for the unsplit sections: no `@progbits`, since that is what a
507 /// section is when nothing says otherwise.
508 #[test]
509 fn a_variable_given_a_section_of_its_own_keeps_the_flags_it_would_have_had() {
510 let split = Sections { functions: false, data: true };
511 let cases = [
512 (Place::Written, "\t.section\t.data.x,\"aw\"\n"),
513 (Place::Zero, "\t.section\t.bss.x,\"aw\",@nobits\n"),
514 (Place::ReadOnly, "\t.section\t.rodata.x,\"a\"\n"),
515 (Place::RelocReadOnly { local: false }, "\t.section\t.data.rel.ro.x,\"aw\"\n"),
516 (Place::RelocReadOnly { local: true }, "\t.section\t.data.rel.ro.local.x,\"aw\"\n"),
517 ];
518 for (place, want) in cases {
519 let mut out = String::new();
520 Directives::Elf.section(&mut out, &place, "x", split);
521 assert_eq!(out, want, "{place:?}");
522 }
523 }
524
525 /// The two kinds of variable the flag leaves alone, and the format that ignores it.
526 ///
527 /// A tentative definition is a request to the linker for that much zeroed space rather than an
528 /// image, so there is no section to split off, and a variable the program put a section name on
529 /// has the answer the source gave, which a flag must not overrule.
530 #[test]
531 fn a_variable_that_has_no_section_of_its_own_to_be_given_is_left_where_it_was() {
532 let split = Sections { functions: false, data: true };
533 let mut merged = String::new();
534 Directives::Elf.section(&mut merged, &Place::Merged, "x", split);
535 assert_eq!(merged, "\t.data\n");
536 let named = Place::Named(".init_array".to_owned());
537 let mut asked = String::new();
538 Directives::Elf.section(&mut asked, &named, "x", split);
539 assert_eq!(asked, "\t.section\t.init_array,\"aw\",@progbits\n");
540 let mut apple = String::new();
541 Directives::MachO.section(&mut apple, &Place::Written, "x", split);
542 assert_eq!(apple, "\t.section\t__DATA,__data\n");
543 }
544
545 #[test]
546 fn an_elf_file_says_the_stack_is_not_executable() {
547 // The absence of this is what makes it executable, so the test is that it is there
548 // rather than that it is spelled a particular way.
549 let mut out = String::new();
550 Directives::Elf.end(&mut out);
551 assert!(out.contains(".note.GNU-stack"), "{out}");
552 }
553
554 #[test]
555 fn every_object_format_has_directives() {
556 for format in [ObjectFormat::Elf, ObjectFormat::MachO, ObjectFormat::Coff] {
557 let directives = Directives::of(format);
558 assert!(directives.text().starts_with('\t'));
559 let mut out = String::new();
560 directives.open(&mut out, "f", 16, Binding::Global, Visibility::Default);
561 directives.close(&mut out, "f");
562 directives.end(&mut out);
563 assert!(out.ends_with('\n'), "{format:?} left a line unfinished");
564 }
565 }
566}