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