rucc_object/source.rs
1//! An object file written from what a file of assembly says, rather than from a compilation.
2//!
3//! Design: `spec/11-asm-objects-debug.md` section 11.1, the paragraph that says we also accept
4//! assembly as input.
5//!
6//! # Why this is not [`crate::Text`] and [`crate::Data`]
7//!
8//! Those two are the compiler's view of a file and they are the right view of one. A function is a
9//! run of bytes with a name and a length, a variable is an image with a name and a place worked out
10//! from what the variable is, and neither carries a section name because where a thing goes is an
11//! answer rather than a question. That is exactly what makes them the wrong shape for assembly.
12//!
13//! A file of assembly says the section, so the place is a question again, and it may say a section
14//! this compiler would never have chosen and flags that go with it. It puts names at offsets rather
15//! than around images, so `.long 0` followed by `foo:` is four bytes belonging to nothing with a
16//! name after them, which no list of named variables can hold. It defines names that are not at any
17//! offset at all, which is what `.set` and `.equ` produce. And it may name a symbol in the middle of
18//! a section, with a size the program stated rather than one worked out from the bytes.
19//!
20//! So this is the assembler's view: a list of sections that each know their own name, flags and
21//! bytes, and a list of names that point into them. Bending one into the other would mean deciding
22//! here what a program already said, and a wrong answer about which section something is in is not
23//! visible until a link or a load.
24//!
25//! The two views meet at the [`object`] crate's writer, which is what both call, so there is one
26//! place that knows how an ELF file is laid out.
27
28use object::write::{Object as Writer, Relocation, Symbol, SymbolSection};
29use object::{
30 Architecture, BinaryFormat, Endianness, RelocationFlags, SectionFlags, SectionKind,
31 SymbolFlags, SymbolKind, elf,
32};
33use rucc_target::{ObjectFormat, TargetInfo};
34use rucc_tuple::Arch;
35
36use crate::file::Error;
37use crate::section::{Array, Binding, Reloc, Visibility};
38
39/// One section, as a file of assembly describes one.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct Part {
42 /// What it is called, with the leading dot the source wrote.
43 pub name: String,
44 /// Its bytes, which are empty for a section that says how big it is and holds none of them.
45 pub bytes: Vec<u8>,
46 /// How long it is. The same as the length of the bytes for every section that has any, and the
47 /// whole of what a `@nobits` section says about itself.
48 pub size: u64,
49 /// The boundary it starts on, which is the largest any directive in it asked for.
50 pub align: u64,
51 /// The flags and the type, which the source states and this does not work out.
52 pub shape: Shape,
53 /// Every place in it that names something, counted from the start of the section.
54 pub relocs: Vec<Reloc>,
55}
56
57/// What a section is, which on ELF is a handful of flag letters and a type.
58///
59/// Held as the separate facts rather than as one of a fixed list of kinds, because the list is not
60/// fixed: a program may write `.section .init.text,"ax",@progbits` and mean a section this compiler
61/// has no name for, and the letters are the whole of what it said about it. The writer underneath
62/// takes a [`SectionKind`], so `Shape::kind` is the one place that turns these back into one, and
63/// the cases it cannot say are written as flags directly.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
65pub struct Shape {
66 /// `a`: the section takes space in the loaded image. A section without this is for a debugger
67 /// or a linker to read and is not in the program at run time.
68 pub alloc: bool,
69 /// `w`: the program may write to it.
70 pub write: bool,
71 /// `x`: the processor may execute it.
72 pub exec: bool,
73 /// `T`: one copy per thread rather than one copy per program.
74 pub thread: bool,
75 /// Whether the file carries the bytes. False is `@nobits`, which is what `.bss` is.
76 pub bits: bool,
77 /// Which kind of table of function addresses this is, for the three ELF has a type for.
78 pub array: Option<Array>,
79}
80
81impl Shape {
82 /// What a section of this name is when the source named it and said nothing else.
83 ///
84 /// `.text`, `.data` and the rest are names an assembler already knows the flags of, which is
85 /// why a program may write `.data` on its own and why `.section .data` without letters is the
86 /// same section rather than an unallocated one. A name nothing here knows gets the flags of an
87 /// ordinary allocated writable section, which is what gas does with one.
88 #[must_use]
89 pub fn of(name: &str) -> Shape {
90 let base = Shape { alloc: true, bits: true, ..Shape::default() };
91 let head = name.split_once('.').map_or(name, |(_, rest)| rest);
92 let head = head.split_once('.').map_or(head, |(first, _)| first);
93 match head {
94 "text" | "init" | "fini" => Shape { exec: true, ..base },
95 "rodata" | "eh_frame_hdr" => base,
96 "bss" => Shape { write: true, bits: false, ..base },
97 "tbss" => Shape { write: true, thread: true, bits: false, ..base },
98 "tdata" => Shape { write: true, thread: true, ..base },
99 // The three the linker gathers and the startup code walks. The type is what makes one
100 // of them that, rather than the name: a section of the ordinary type under the same
101 // name is gathered into the same run and called by nobody.
102 _ if Array::of(name).is_some() => Shape { write: true, array: Array::of(name), ..base },
103 // Not allocated, because nothing in the running program reads it. A debugger reads it
104 // out of the file, and a section marked allocated would take space in every process.
105 "debug_info" | "debug_abbrev" | "debug_line" | "debug_str" | "comment" => {
106 Shape { alloc: false, bits: true, ..Shape::default() }
107 }
108 _ => Shape { write: true, ..base },
109 }
110 }
111
112 /// The flag word ELF holds these in.
113 ///
114 /// Not public, and neither are the two below it. The fields above are the whole of what a
115 /// caller says about a section, and how ELF spells them is this crate's business: a reader that
116 /// had to name an ELF constant to describe an executable section would be one that could not
117 /// describe one for any other format.
118 pub(crate) fn sh_flags(self) -> elf::SectionFlags {
119 let mut flags = 0;
120 if self.alloc {
121 flags |= elf::SHF_ALLOC.0;
122 }
123 if self.write {
124 flags |= elf::SHF_WRITE.0;
125 }
126 if self.exec {
127 flags |= elf::SHF_EXECINSTR.0;
128 }
129 if self.thread {
130 flags |= elf::SHF_TLS.0;
131 }
132 elf::SectionFlags(flags)
133 }
134
135 /// The type ELF holds in the header beside those flags.
136 pub(crate) fn sh_type(self) -> elf::SectionType {
137 match self.array {
138 _ if !self.bits => elf::SHT_NOBITS,
139 Some(Array::Init) => elf::SHT_INIT_ARRAY,
140 Some(Array::Fini) => elf::SHT_FINI_ARRAY,
141 Some(Array::Preinit) => elf::SHT_PREINIT_ARRAY,
142 None => elf::SHT_PROGBITS,
143 }
144 }
145
146 /// What the writer underneath calls the nearest thing to this.
147 ///
148 /// It is told the flags in full afterwards, so this only has to be close enough that nothing
149 /// else the writer decides from the kind comes out wrong, which is the default alignment and
150 /// whether it appends bytes or counts them.
151 pub(crate) const fn kind(self) -> SectionKind {
152 match self {
153 Shape { bits: false, thread: true, .. } => SectionKind::UninitializedTls,
154 Shape { bits: false, .. } => SectionKind::UninitializedData,
155 Shape { thread: true, .. } => SectionKind::Tls,
156 Shape { exec: true, .. } => SectionKind::Text,
157 Shape { alloc: false, .. } => SectionKind::Other,
158 Shape { write: false, .. } => SectionKind::ReadOnlyData,
159 Shape { .. } => SectionKind::Data,
160 }
161 }
162}
163
164/// One name in the symbol table, as a file of assembly defines one.
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct Name {
167 /// The name, spelled as the source spelled it.
168 pub name: String,
169 /// Where it is.
170 pub at: Held,
171 /// How long the thing it names is, which is what `.size` said and is zero when nothing did.
172 pub size: u64,
173 /// What kind of thing it names, which is what `.type` said.
174 pub sort: Sort,
175 /// Who can see it.
176 pub binding: Binding,
177 /// How far outside a shared library it reaches.
178 pub visibility: Visibility,
179}
180
181/// Where a name is, which is four different things and not an offset with special cases.
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum Held {
184 /// At an offset into one of the sections, which is what a label is.
185 In {
186 /// Which section, as an index into the list given alongside.
187 part: usize,
188 /// How far into it.
189 offset: u64,
190 },
191 /// A number rather than a place, which is what `.set` and `.equ` produce. The linker resolves
192 /// a reference to one to the number itself and there is nothing for it to be relative to.
193 Absolute(u64),
194 /// That much zeroed space asked of the linker under this name, which is `.comm` and `.lcomm`.
195 /// Every definition of the name across every object is merged into one.
196 Common {
197 /// How much space.
198 size: u64,
199 /// What boundary it has to start on. ELF records this where an ordinary symbol records its
200 /// address, which is why the two cannot both be said.
201 align: u64,
202 },
203 /// Named and not defined here, which the linker has to find somewhere else.
204 Undefined,
205}
206
207/// What kind of thing a name names, which is what `.type` says.
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
209pub enum Sort {
210 /// `@function`. A call through the procedure linkage table may be made to it.
211 Func,
212 /// `@object`. Data.
213 Object,
214 /// `@tls_object`. A thread-local variable, which a linker checks relocations against.
215 Thread,
216 /// `.file`, which names the source this was assembled from rather than anything in it.
217 ///
218 /// Not a thing `.type` can say, and here because it is a symbol and there is nowhere else for
219 /// it. A debugger reads it and so does `nm`, and gas writes one for every file that says its
220 /// own name, which is every file gcc produces.
221 File,
222 /// Nothing was said, which is what a plain label gets and is a real answer rather than a
223 /// missing one: gas writes `STT_NOTYPE` for a label nobody stated a type for.
224 #[default]
225 Untyped,
226}
227
228/// Everything an assembled file holds: its sections, and the names that point into them.
229#[derive(Debug, Clone, Default, PartialEq, Eq)]
230pub struct Assembled {
231 /// The sections, in the order the file first mentioned each of them.
232 pub parts: Vec<Part>,
233 /// The names, in the order the file defined or first referred to each of them.
234 pub names: Vec<Name>,
235}
236
237/// That, as a relocatable ELF object.
238///
239/// ELF only, where the rest of this crate writes COFF as well. What a [`Part`] carries is the
240/// section type and flags the source wrote in as many words, which are ELF's and which a COFF
241/// section header has no field for, so a file of assembly for a Windows target is refused here
242/// rather than written with the flags guessed back from the name.
243///
244/// # Errors
245///
246/// [`Error::Format`] for a machine or a platform this does not write, and [`Error::Refused`] for a
247/// relocation against a name the list does not hold or one this machine has no relocation for.
248pub fn assembled(input: &Assembled, target: &TargetInfo) -> Result<Vec<u8>, Error> {
249 if target.tuple.arch() != Arch::X86_64 || target.object_format != ObjectFormat::Elf {
250 return Err(Error::Format { triple: target.tuple.to_string() });
251 }
252 let mut obj = Writer::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little);
253
254 // Every section first, because a symbol says which one it is in and a relocation says which one
255 // it is written into, so both need the whole list before either can be added.
256 let mut made = Vec::with_capacity(input.parts.len());
257 for part in &input.parts {
258 let id = obj.add_section(Vec::new(), part.name.clone().into_bytes(), part.shape.kind());
259 // The flags in full rather than whatever the kind implied, because the kind is a summary of
260 // them and the source said them exactly. A section the program wrote `"ax"` on is executable
261 // whether or not its name is one this compiler would have made executable.
262 obj.section_mut(id).flags =
263 SectionFlags::Elf { sh_type: part.shape.sh_type(), sh_flags: part.shape.sh_flags() };
264 let align = part.align.max(1);
265 if part.shape.bits {
266 obj.append_section_data(id, &part.bytes, align);
267 } else {
268 obj.append_section_bss(id, part.size, align);
269 }
270 made.push(id);
271 }
272
273 // Then every name. A relocation names one, and the writer wants the symbol before the
274 // relocation that points at it, so this whole pass is in front of the one below.
275 let mut symbols = std::collections::BTreeMap::new();
276 for name in &input.names {
277 let (section, value, size) = match name.at {
278 Held::In { part, offset } => {
279 let Some(id) = made.get(part) else {
280 let why = format!(
281 "'{}' is in section {part} and there is no such section",
282 name.name
283 );
284 return Err(Error::Refused { why });
285 };
286 (SymbolSection::Section(*id), offset, name.size)
287 }
288 Held::Absolute(value) => (SymbolSection::Absolute, value, name.size),
289 // A common symbol says what it wants rather than where it is, and ELF records the
290 // boundary it wants where an ordinary symbol records its address.
291 Held::Common { size, align } => (SymbolSection::Common, align, size),
292 Held::Undefined => (SymbolSection::Undefined, 0, 0),
293 };
294 let id = obj.add_symbol(Symbol {
295 name: name.name.clone().into_bytes(),
296 value,
297 size,
298 kind: sort_of(name.sort),
299 scope: crate::file::scope_of(name.binding),
300 weak: name.binding == Binding::Weak,
301 section,
302 flags: SymbolFlags::None,
303 });
304 crate::elf::see(&mut obj, id, name.binding, name.visibility);
305 // The writer underneath records a common symbol as `STT_COMMON` and gas records the same
306 // symbol as `STT_OBJECT`. Both are a request for storage and a linker reads either, and the
307 // one gas writes is written here, because an object that says the same thing a different
308 // way is the kind of difference that turns up years later in a tool that only ever saw the
309 // other one. A common symbol is global by definition, so there is no binding to preserve.
310 if matches!(name.at, Held::Common { .. }) {
311 if let SymbolFlags::Elf { st_info, .. } = obj.symbol_flags_mut(id) {
312 *st_info = elf::STB_GLOBAL | elf::STT_OBJECT;
313 }
314 }
315 symbols.insert(name.name.clone(), id);
316 }
317
318 for (part, id) in input.parts.iter().zip(&made) {
319 for reloc in &part.relocs {
320 let Some(symbol) = symbols.get(&reloc.symbol) else {
321 let why =
322 format!("'{}' is named by a relocation and by nothing else", reloc.symbol);
323 return Err(Error::Refused { why });
324 };
325 let r_type = crate::elf::r_type(reloc.kind).ok_or_else(|| Error::Refused {
326 why: format!("no relocation is {:?}", reloc.kind),
327 })?;
328 obj.add_relocation(
329 *id,
330 Relocation {
331 offset: reloc.at as u64,
332 symbol: *symbol,
333 addend: reloc.addend,
334 flags: RelocationFlags::Elf { r_type },
335 },
336 )
337 .map_err(|why| Error::Refused { why: why.to_string() })?;
338 }
339 }
340
341 // The same marker every other object this compiler writes gets, and for the same reason: a
342 // linker that does not find it in every input marks the stack executable. Not a second one if
343 // the file already said it, which a file written by hand for a linker that cares often does.
344 if !input.parts.iter().any(|part| part.name == ".note.GNU-stack") {
345 obj.add_section(Vec::new(), b".note.GNU-stack".to_vec(), SectionKind::Metadata);
346 }
347
348 obj.write().map_err(|why| Error::Refused { why: why.to_string() })
349}
350
351/// Every name in it a linker can find, which is what an archive's symbol index is built from.
352///
353/// The same rule as [`crate::defines`]: a local is left out, because a name the static link has
354/// already finished with is not one an archive may offer, and an undefined one is left out because
355/// this file does not have it.
356#[must_use]
357pub fn assembled_defines(input: &Assembled) -> Vec<String> {
358 input
359 .names
360 .iter()
361 .filter(|name| name.binding != Binding::Local && name.at != Held::Undefined)
362 .map(|name| name.name.clone())
363 .collect()
364}
365
366/// What the writer underneath calls one of these.
367///
368/// `Label` is the one that is not obvious from its name. It is what that writer turns into
369/// `STT_NOTYPE`, which is what gas records for a label nobody stated a type for, and it says
370/// nothing about whether the name is local: `Unknown` would have been the reading of the name, and
371/// that writer refuses a defined one of those outright.
372fn sort_of(sort: Sort) -> SymbolKind {
373 match sort {
374 Sort::Func => SymbolKind::Text,
375 Sort::Object => SymbolKind::Data,
376 Sort::Thread => SymbolKind::Tls,
377 Sort::File => SymbolKind::File,
378 Sort::Untyped => SymbolKind::Label,
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385
386 use object::read::elf::{FileHeader as _, Sym as _};
387 use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
388 use rucc_target::{Arch as TargetArch, Env, Os, Triple};
389
390 use crate::section::Reference;
391
392 /// A linux x86-64 target, which is the only one this writes.
393 fn target() -> TargetInfo {
394 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Linux, Env::Gnu))
395 }
396
397 /// One section of that name holding those bytes, with the flags the name implies.
398 fn part(name: &str, bytes: Vec<u8>) -> Part {
399 Part {
400 name: name.to_owned(),
401 size: bytes.len() as u64,
402 bytes,
403 align: 1,
404 shape: Shape::of(name),
405 relocs: Vec::new(),
406 }
407 }
408
409 /// One name at an offset into the first section.
410 fn at(name: &str, offset: u64, sort: Sort, binding: Binding) -> Name {
411 Name {
412 name: name.to_owned(),
413 at: Held::In { part: 0, offset },
414 size: 0,
415 sort,
416 binding,
417 visibility: Visibility::Default,
418 }
419 }
420
421 /// The raw `st_info` and `st_value` of a symbol, as the file holds them.
422 ///
423 /// The reader's own `kind()`, `is_global()` and `address()` are a translation of these, and a
424 /// translation is what several of the cases below are about, so they ask the file rather than
425 /// the reading. A common symbol is the clearest of them: `address()` gives zero for one because
426 /// it has no address, and the field an ordinary symbol keeps its address in is where a common
427 /// one states the boundary it has to start on.
428 fn raw(bytes: &[u8], want: &str) -> (u8, u64) {
429 let header = elf::FileHeader64::<Endianness>::parse(bytes).expect("a header");
430 let endian = header.endian().expect("an endianness");
431 let table = header.sections(endian, bytes).expect("the sections");
432 let symbols = table.symbols(endian, bytes, elf::SHT_SYMTAB).expect("a symbol table");
433 for symbol in symbols.iter() {
434 if symbols.symbol_name(endian, symbol).expect("a name") == want.as_bytes() {
435 return (symbol.st_info().0, symbol.st_value(endian));
436 }
437 }
438 panic!("there is no symbol called '{want}'");
439 }
440
441 /// The first half of that.
442 fn st_info(bytes: &[u8], want: &str) -> u8 {
443 raw(bytes, want).0
444 }
445
446 #[test]
447 fn a_section_carries_the_flags_the_source_said_and_not_the_ones_its_name_suggests() {
448 // The whole reason a shape is separate facts rather than a kind. A program may write
449 // `.section .init.text,"ax"` and mean a section with a name this compiler has never heard
450 // of, and what it said about it is the letters.
451 let mut odd = part(".init.text", vec![0x90]);
452 odd.shape = Shape { alloc: true, exec: true, bits: true, ..Shape::default() };
453 let input = Assembled { parts: vec![odd], names: Vec::new() };
454 let bytes = assembled(&input, &target()).expect("an object");
455 let file = object::File::parse(&bytes[..]).expect("a readable object");
456 let section = file.section_by_name(".init.text").expect("the section");
457 assert_eq!(section.data().expect("the bytes"), &[0x90]);
458 let SectionFlags::Elf { sh_flags, sh_type } = section.flags() else {
459 panic!("this is an ELF file");
460 };
461 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_EXECINSTR.0);
462 assert_eq!(sh_flags.0 & elf::SHF_WRITE.0, 0, "nothing said it was writable");
463 assert_eq!(sh_type, elf::SHT_PROGBITS);
464 }
465
466 #[test]
467 fn a_section_that_holds_no_bytes_still_says_how_long_it_is() {
468 // `.bss` is a length and no bytes, and a writer that appended its data would produce a file
469 // with that much zero in it, which is the difference between an object and a big object.
470 let mut room = part(".bss", Vec::new());
471 room.size = 4096;
472 room.align = 16;
473 let input = Assembled { parts: vec![room], names: Vec::new() };
474 let bytes = assembled(&input, &target()).expect("an object");
475 assert!(bytes.len() < 4096, "the empty space was written out: {} bytes", bytes.len());
476 let file = object::File::parse(&bytes[..]).expect("a readable object");
477 let section = file.section_by_name(".bss").expect("the section");
478 assert_eq!(section.size(), 4096);
479 assert_eq!(section.align(), 16);
480 let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("an ELF file") };
481 assert_eq!(sh_type, elf::SHT_NOBITS);
482 }
483
484 #[test]
485 fn a_label_nobody_stated_a_type_for_is_a_symbol_with_no_type() {
486 // `STT_NOTYPE` is what gas writes for one, and it is a real answer rather than a missing
487 // one. The writer underneath refuses a defined symbol whose kind is `Unknown` outright, so
488 // this is also the case that says the mapping went to `Label` and not there.
489 let input = Assembled {
490 parts: vec![part(".text", vec![0; 8])],
491 names: vec![at("plain", 4, Sort::Untyped, Binding::Global)],
492 };
493 let bytes = assembled(&input, &target()).expect("an object");
494 let file = object::File::parse(&bytes[..]).expect("a readable object");
495 let plain = file.symbols().find(|s| s.name() == Ok("plain")).expect("the label");
496 assert_eq!(plain.address(), 4);
497 assert_eq!(st_info(&bytes, "plain") & 0xf, elf::STT_NOTYPE.0);
498 }
499
500 #[test]
501 fn what_type_said_is_what_the_symbol_gets() {
502 let input = Assembled {
503 parts: vec![part(".text", vec![0; 8])],
504 names: vec![
505 at("run", 0, Sort::Func, Binding::Global),
506 at("held", 4, Sort::Object, Binding::Local),
507 ],
508 };
509 let bytes = assembled(&input, &target()).expect("an object");
510 assert_eq!(st_info(&bytes, "run") & 0xf, elf::STT_FUNC.0);
511 assert_eq!(st_info(&bytes, "held") & 0xf, elf::STT_OBJECT.0);
512 assert_eq!(st_info(&bytes, "run") >> 4, elf::STB_GLOBAL.0);
513 assert_eq!(st_info(&bytes, "held") >> 4, elf::STB_LOCAL.0);
514 }
515
516 #[test]
517 fn a_common_symbol_is_written_the_way_gas_writes_one() {
518 // The writer underneath records `STT_COMMON` and gas records `STT_OBJECT` for the same
519 // `.comm`. Both are a request for storage and a linker takes either, and the one gas writes
520 // is the one written here, so an object of ours and an object of theirs do not differ in a
521 // field somebody's tool reads years from now.
522 let input = Assembled {
523 parts: Vec::new(),
524 names: vec![Name {
525 name: "shared".to_owned(),
526 at: Held::Common { size: 8, align: 8 },
527 size: 0,
528 sort: Sort::Object,
529 binding: Binding::Global,
530 visibility: Visibility::Default,
531 }],
532 };
533 let bytes = assembled(&input, &target()).expect("an object");
534 assert_eq!(st_info(&bytes, "shared"), elf::STB_GLOBAL.0 << 4 | elf::STT_OBJECT.0);
535 let file = object::File::parse(&bytes[..]).expect("a readable object");
536 let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the symbol");
537 assert!(shared.is_common(), "the linker has to be asked for the space");
538 assert_eq!(shared.size(), 8);
539 // Where an ordinary symbol keeps its address, which is why the two cannot both be said.
540 assert_eq!(raw(&bytes, "shared").1, 8, "the boundary it has to start on");
541 }
542
543 #[test]
544 fn a_set_is_a_number_rather_than_a_place() {
545 let input = Assembled {
546 parts: vec![part(".text", vec![0; 8])],
547 names: vec![Name {
548 name: "size_of_it".to_owned(),
549 at: Held::Absolute(25),
550 size: 0,
551 sort: Sort::Untyped,
552 binding: Binding::Global,
553 visibility: Visibility::Default,
554 }],
555 };
556 let bytes = assembled(&input, &target()).expect("an object");
557 let file = object::File::parse(&bytes[..]).expect("a readable object");
558 let sym = file.symbols().find(|s| s.name() == Ok("size_of_it")).expect("the symbol");
559 assert_eq!(sym.address(), 25);
560 assert_eq!(sym.section(), object::SymbolSection::Absolute, "it is not in any section");
561 }
562
563 #[test]
564 fn a_relocation_names_a_symbol_and_lands_where_the_bytes_are() {
565 let mut data = part(".data", vec![0; 8]);
566 data.relocs.push(Reloc {
567 at: 0,
568 symbol: "message".to_owned(),
569 kind: Reference::Address { bytes: 8 },
570 addend: 0,
571 after: 0,
572 });
573 let input = Assembled {
574 parts: vec![data],
575 names: vec![Name {
576 name: "message".to_owned(),
577 at: Held::Undefined,
578 size: 0,
579 sort: Sort::Untyped,
580 binding: Binding::Global,
581 visibility: Visibility::Default,
582 }],
583 };
584 let bytes = assembled(&input, &target()).expect("an object");
585 let file = object::File::parse(&bytes[..]).expect("a readable object");
586 let section = file.section_by_name(".data").expect("the section");
587 let (at, reloc) = section.relocations().next().expect("one relocation");
588 assert_eq!(at, 0);
589 assert_eq!(reloc.addend(), 0);
590 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("an ELF file") };
591 assert_eq!(r_type, elf::R_X86_64_64);
592 }
593
594 #[test]
595 fn a_relocation_against_a_name_the_file_never_mentions_is_refused() {
596 // Rather than written against symbol zero, which is a file that links and resolves the
597 // reference to address zero. The list of names is the whole of what the reader found, so a
598 // relocation naming something outside it is a mistake in this compiler.
599 let mut data = part(".data", vec![0; 8]);
600 data.relocs.push(Reloc {
601 at: 0,
602 symbol: "nowhere".to_owned(),
603 kind: Reference::Address { bytes: 8 },
604 addend: 0,
605 after: 0,
606 });
607 let input = Assembled { parts: vec![data], names: Vec::new() };
608 let why = assembled(&input, &target()).expect_err("this cannot be written");
609 assert!(format!("{why}").contains("nowhere"), "{why}");
610 }
611
612 #[test]
613 fn the_stack_is_marked_once_whoever_asked_for_it() {
614 // A linker that does not find this marker in every input marks the stack executable, and a
615 // file written by hand for one that cares often says it itself.
616 let bare = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
617 let bytes = assembled(&bare, &target()).expect("an object");
618 let file = object::File::parse(&bytes[..]).expect("a readable object");
619 assert!(file.section_by_name(".note.GNU-stack").is_some(), "the marker was left out");
620
621 let said = Assembled {
622 parts: vec![part(".text", vec![0x90]), part(".note.GNU-stack", Vec::new())],
623 names: Vec::new(),
624 };
625 let bytes = assembled(&said, &target()).expect("an object");
626 let file = object::File::parse(&bytes[..]).expect("a readable object");
627 let marks = file.sections().filter(|s| s.name() == Ok(".note.GNU-stack")).count();
628 assert_eq!(marks, 1, "the file said it and it was said again");
629 }
630
631 #[test]
632 fn only_the_names_a_linker_could_find_are_offered_to_an_archive() {
633 let input = Assembled {
634 parts: vec![part(".text", vec![0; 8])],
635 names: vec![
636 at("reachable", 0, Sort::Func, Binding::Global),
637 at("mine", 4, Sort::Func, Binding::Local),
638 Name {
639 name: "elsewhere".to_owned(),
640 at: Held::Undefined,
641 size: 0,
642 sort: Sort::Untyped,
643 binding: Binding::Global,
644 visibility: Visibility::Default,
645 },
646 ],
647 };
648 assert_eq!(assembled_defines(&input), vec!["reachable".to_owned()]);
649 }
650
651 #[test]
652 fn a_machine_this_does_not_write_is_refused_rather_than_written_wrong() {
653 let input = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
654 let elsewhere = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Linux, Env::Gnu));
655 let why = assembled(&input, &elsewhere).expect_err("this cannot be written");
656 assert!(format!("{why}").contains("aarch64"), "{why}");
657 }
658}