use object::write::{
Object as Writer, Relocation, StandardSection, Symbol, SymbolId, SymbolSection,
};
use object::{
Architecture, BinaryFormat, Endianness, RelocationFlags, SectionKind, SymbolFlags, SymbolKind,
SymbolScope, elf,
};
use rucc_target::{ObjectFormat, TargetInfo};
use rucc_tuple::Arch;
use crate::section::{
Alias, Binding, Data, Object, Output, Place, Property, Reference, Reloc, Sections, Text,
Visibility,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
Format {
triple: String,
},
Refused {
why: String,
},
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Format { triple } => {
write!(f, "there is no object writer for {triple} in this compiler yet")
}
Error::Refused { why } => {
write!(f, "the object writer refused what it was given: {why}")
}
}
}
}
impl std::error::Error for Error {}
pub fn write(
text: &Text,
data: &Data,
aliases: &[Alias],
target: &TargetInfo,
output: Output,
) -> Result<Vec<u8>, Error> {
let Output { sections, property } = output;
if target.tuple.arch() != Arch::X86_64 || target.object_format != ObjectFormat::Elf {
return Err(Error::Format { triple: target.tuple.to_string() });
}
let mut obj = Writer::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little);
let whole = obj.section_id(StandardSection::Text);
if !sections.functions {
obj.append_section_data(whole, &text.bytes, u64::from(text.align));
}
let mut symbols = std::collections::BTreeMap::new();
let mut split = Vec::with_capacity(text.funcs.len());
for func in &text.funcs {
let (section, at) = if sections.functions {
let name = format!(".text.{}", func.name).into_bytes();
let id = obj.add_section(Vec::new(), name, SectionKind::Text);
let bytes = &text.bytes[func.start..func.start + func.len];
obj.append_section_data(id, bytes, u64::from(func.align.max(1)));
(id, 0)
} else {
(whole, func.start as u64)
};
let id = obj.add_symbol(Symbol {
name: func.name.clone().into_bytes(),
value: at,
size: func.len as u64,
kind: SymbolKind::Text,
scope: scope_of(func.binding),
weak: func.binding == Binding::Weak,
section: SymbolSection::Section(section),
flags: SymbolFlags::None,
});
see(&mut obj, id, func.binding, func.visibility);
symbols.insert(func.name.clone(), id);
split.push(section);
}
let mut placed = Vec::with_capacity(data.objects.len());
let mut local = None;
for object in &data.objects {
let (section, offset) = put(&mut obj, object, &mut local, sections);
let id = obj.add_symbol(Symbol {
name: object.name.clone().into_bytes(),
value: if object.place == Place::Merged { object.align } else { offset },
size: object.size,
kind: SymbolKind::Data,
scope: scope_of(object.binding),
weak: object.binding == Binding::Weak,
section,
flags: SymbolFlags::None,
});
see(&mut obj, id, object.binding, object.visibility);
symbols.insert(object.name.clone(), id);
placed.push((section.id(), offset));
}
for alias in aliases {
let Some(&id) = symbols.get(&alias.target) else {
let why =
format!("'{}' is aliased to '{}', which is not here", alias.name, alias.target);
return Err(Error::Refused { why });
};
let (value, size) = (obj.symbol(id).value, obj.symbol(id).size);
let (kind, section) = (obj.symbol(id).kind, obj.symbol(id).section);
let id = obj.add_symbol(Symbol {
name: alias.name.clone().into_bytes(),
value,
size,
kind,
scope: scope_of(alias.binding),
weak: alias.binding == Binding::Weak,
section,
flags: SymbolFlags::None,
});
see(&mut obj, id, alias.binding, alias.visibility);
symbols.insert(alias.name.clone(), id);
}
let wanted = text
.relocs
.iter()
.chain(text.unwind.relocs.iter())
.chain(data.objects.iter().flat_map(|object| &object.relocs));
for reloc in wanted {
if symbols.contains_key(&reloc.symbol) {
continue;
}
let id = obj.add_symbol(Symbol {
name: reloc.symbol.clone().into_bytes(),
value: 0,
size: 0,
kind: SymbolKind::Unknown,
scope: SymbolScope::Dynamic,
weak: false,
section: SymbolSection::Undefined,
flags: SymbolFlags::None,
});
symbols.insert(reloc.symbol.clone(), id);
}
for reloc in &text.relocs {
let (section, at) = if sections.functions {
let after = text.funcs.partition_point(|func| func.start <= reloc.at);
let Some(func) = after.checked_sub(1).map(|i| &text.funcs[i]) else {
let why = format!("a relocation at {} is in front of every function", reloc.at);
return Err(Error::Refused { why });
};
(split[after - 1], (reloc.at - func.start) as u64)
} else {
(whole, reloc.at as u64)
};
add(&mut obj, section, at, reloc, &symbols)?;
}
if !text.unwind.bytes.is_empty() {
let frames = obj.add_section(Vec::new(), b".eh_frame".to_vec(), SectionKind::ReadOnlyData);
obj.append_section_data(frames, &text.unwind.bytes, 8);
for reloc in &text.unwind.relocs {
add(&mut obj, frames, reloc.at as u64, reloc, &symbols)?;
}
}
for (object, &(section, offset)) in data.objects.iter().zip(&placed) {
let Some(section) = section else { continue };
for reloc in &object.relocs {
add(&mut obj, section, offset + reloc.at as u64, reloc, &symbols)?;
}
}
if property.any() {
let note = obj.section_id(StandardSection::GnuProperty);
obj.append_section_data(note, &record(property), 8);
}
obj.add_section(Vec::new(), b".note.GNU-stack".to_vec(), SectionKind::Metadata);
obj.write().map_err(|why| Error::Refused { why: why.to_string() })
}
fn record(property: Property) -> Vec<u8> {
let head = [4, 16, elf::NT_GNU_PROPERTY_TYPE_0.0];
let desc = [Property::X86_FEATURES, 4, property.features, 0];
let mut out = Vec::with_capacity(32);
for word in head {
out.extend_from_slice(&word.to_le_bytes());
}
out.extend_from_slice(b"GNU\0");
for word in desc {
out.extend_from_slice(&word.to_le_bytes());
}
out
}
fn put(
obj: &mut Writer<'_>,
object: &Object,
local: &mut Option<object::write::SectionId>,
sections: Sections,
) -> (SymbolSection, u64) {
if sections.data {
if let Some(name) = object.place.split(&object.name) {
let section = obj.add_section(Vec::new(), name.into_bytes(), kind_of(&object.place));
let offset = if object.place == Place::Zero {
obj.append_section_bss(section, object.size, object.align)
} else {
obj.append_section_data(section, &object.bytes, object.align)
};
return (SymbolSection::Section(section), offset);
}
}
let section = match &object.place {
Place::Written => obj.section_id(StandardSection::Data),
Place::ReadOnly => obj.section_id(StandardSection::ReadOnlyData),
Place::RelocReadOnly { local: false } => {
obj.section_id(StandardSection::ReadOnlyDataWithRel)
}
Place::RelocReadOnly { local: true } => *local.get_or_insert_with(|| {
obj.add_section(
Vec::new(),
b".data.rel.ro.local".to_vec(),
SectionKind::ReadOnlyDataWithRel,
)
}),
Place::Zero => obj.section_id(StandardSection::UninitializedData),
Place::Merged => return (SymbolSection::Common, 0),
Place::Named(name) => {
obj.add_section(Vec::new(), name.clone().into_bytes(), SectionKind::Data)
}
};
let offset = if object.place == Place::Zero {
obj.append_section_bss(section, object.size, object.align)
} else {
obj.append_section_data(section, &object.bytes, object.align)
};
(SymbolSection::Section(section), offset)
}
fn kind_of(place: &Place) -> SectionKind {
match place {
Place::ReadOnly => SectionKind::ReadOnlyData,
Place::RelocReadOnly { .. } => SectionKind::ReadOnlyDataWithRel,
Place::Zero => SectionKind::UninitializedData,
Place::Written | Place::Merged | Place::Named(_) => SectionKind::Data,
}
}
fn add(
obj: &mut Writer<'_>,
section: object::write::SectionId,
at: u64,
reloc: &Reloc,
symbols: &std::collections::BTreeMap<String, SymbolId>,
) -> Result<(), Error> {
let r_type = r_type(reloc.kind)
.ok_or_else(|| Error::Refused { why: format!("no relocation is {:?}", reloc.kind) })?;
obj.add_relocation(
section,
Relocation {
offset: at,
symbol: symbols[&reloc.symbol],
addend: reloc.addend,
flags: RelocationFlags::Elf { r_type },
},
)
.map_err(|why| Error::Refused { why: why.to_string() })
}
fn scope_of(binding: Binding) -> SymbolScope {
match binding {
Binding::Local => SymbolScope::Compilation,
Binding::Global | Binding::Weak => SymbolScope::Dynamic,
}
}
fn see(obj: &mut Writer<'_>, id: SymbolId, binding: Binding, visibility: Visibility) {
if binding == Binding::Local {
return;
}
let wanted = match visibility {
Visibility::Default => elf::STV_DEFAULT,
Visibility::Hidden => elf::STV_HIDDEN,
Visibility::Protected => elf::STV_PROTECTED,
};
if let SymbolFlags::Elf { st_other, .. } = obj.symbol_flags_mut(id) {
*st_other = st_other.with_visibility(wanted);
}
}
fn r_type(reference: Reference) -> Option<elf::RelocationType> {
Some(match reference {
Reference::Call => elf::R_X86_64_PLT32,
Reference::Data => elf::R_X86_64_PC32,
Reference::Got => elf::R_X86_64_REX_GOTPCRELX,
Reference::Address { bytes: 8 } => elf::R_X86_64_64,
Reference::Address { bytes: 4 } => elf::R_X86_64_32,
Reference::Address { .. } => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
use object::read::elf::Sym as _;
use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
use rucc_target::{Arch, Env, Os, Triple};
use crate::section::{Extent, Reloc};
fn target() -> TargetInfo {
TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
}
fn extent(name: String, start: usize, len: usize, binding: Binding) -> Extent {
Extent {
name,
start,
len,
align: crate::FUNC_ALIGN,
binding,
visibility: Visibility::Default,
}
}
fn calling(name: &str) -> Text {
Text {
bytes: vec![0xe8, 0, 0, 0, 0, 0xc3],
funcs: vec![extent("f".to_owned(), 0, 6, Binding::Global)],
relocs: vec![Reloc {
at: 1,
symbol: name.to_owned(),
kind: Reference::Call,
addend: -4,
}],
..Text::default()
}
}
#[test]
fn the_bytes_come_back_out_of_the_section_they_went_into() {
let text = calling("puts");
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let section = file.section_by_name(".text").expect("a text section");
assert_eq!(section.data().expect("the bytes"), &text.bytes[..]);
}
#[test]
fn a_function_is_a_symbol_that_says_where_it_is_and_how_long_it_is() {
let mut text = calling("puts");
text.funcs.push(extent("g".to_owned(), 16, 1, Binding::Global));
text.bytes.resize(17, 0x90);
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let g = file.symbols().find(|s| s.name() == Ok("g")).expect("the second function");
assert_eq!(g.address(), 16);
assert_eq!(g.size(), 1);
assert_eq!(g.kind(), SymbolKind::Text);
assert!(g.is_global(), "nothing said otherwise about this one");
}
#[test]
fn a_function_no_other_file_can_see_is_a_local_symbol() {
let mut text = calling("puts");
text.funcs.push(extent("hidden".to_owned(), 16, 1, Binding::Local));
text.funcs.push(extent("shared".to_owned(), 32, 1, Binding::Weak));
text.bytes.resize(33, 0x90);
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let hidden = file.symbols().find(|s| s.name() == Ok("hidden")).expect("the static one");
assert!(hidden.is_local(), "a static function must not be offered to the linker");
assert!(!hidden.is_weak());
let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the weak one");
assert!(shared.is_weak(), "a weak function has to be able to lose");
assert!(shared.is_global());
}
#[test]
fn a_global_is_visible_to_the_dynamic_linker_and_a_static_one_is_not_a_symbol_at_all() {
let mut text = calling("puts");
text.funcs.push(extent("g".to_owned(), 16, 1, Binding::Global));
text.funcs.push(extent("w".to_owned(), 32, 1, Binding::Weak));
text.funcs.push(extent("s".to_owned(), 48, 1, Binding::Local));
text.bytes.resize(49, 0x90);
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
let visibility = |name: &str| {
file.symbols()
.find(|s| s.name() == Ok(name))
.expect("the function")
.elf_symbol()
.st_visibility()
};
assert_eq!(visibility("g"), elf::STV_DEFAULT);
assert_eq!(visibility("w"), elf::STV_DEFAULT, "a weak one is still a name others may use");
assert_eq!(visibility("s"), elf::STV_DEFAULT);
}
#[test]
fn a_name_that_asked_to_be_hidden_is_hidden_and_a_protected_one_is_protected() {
let mut text = calling("puts");
for (index, (name, seen)) in
[("h", Visibility::Hidden), ("p", Visibility::Protected)].into_iter().enumerate()
{
let mut func = extent(name.to_owned(), 16 + index * 16, 1, Binding::Global);
func.visibility = seen;
text.funcs.push(func);
}
text.bytes.resize(49, 0x90);
let mut data = Data::default();
for (name, seen) in [("vh", Visibility::Hidden), ("vp", Visibility::Protected)] {
let mut object = variable(name, Place::Written);
object.visibility = seen;
data.objects.push(object);
}
let bytes = write(&text, &data, &[], &target(), Output::default()).expect("an object");
let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
let visibility = |name: &str| {
file.symbols()
.find(|s| s.name() == Ok(name))
.expect("the symbol")
.elf_symbol()
.st_visibility()
};
assert_eq!(visibility("h"), elf::STV_HIDDEN);
assert_eq!(visibility("p"), elf::STV_PROTECTED);
assert_eq!(visibility("vh"), elf::STV_HIDDEN, "a variable goes through a second loop");
assert_eq!(visibility("vp"), elf::STV_PROTECTED);
let h = file.symbols().find(|s| s.name() == Ok("h")).expect("the function");
assert!(h.is_global(), "hidden is about the dynamic linker and not about the binding");
assert_eq!(h.size(), 1, "and it is still a function of the length it was");
}
#[test]
fn a_name_this_file_does_not_define_is_left_for_the_linker_to_find() {
let bytes = write(&calling("puts"), &Data::default(), &[], &target(), Output::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let puts = file.symbols().find(|s| s.name() == Ok("puts")).expect("the callee");
assert!(puts.is_undefined(), "the file does not define it and must not claim to");
}
#[test]
fn a_call_asks_for_the_relocation_a_stub_may_answer_and_a_load_asks_for_the_one_that_may_not() {
for (reference, wanted) in [
(Reference::Call, elf::R_X86_64_PLT32),
(Reference::Data, elf::R_X86_64_PC32),
(Reference::Got, elf::R_X86_64_REX_GOTPCRELX),
] {
let mut text = calling("puts");
text.relocs[0].kind = reference;
let bytes = write(&text, &Data::default(), &[], &target(), Output::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let section = file.section_by_name(".text").expect("a text section");
let (offset, reloc) = section.relocations().next().expect("one relocation");
assert_eq!(offset, 1);
assert_eq!(reloc.addend(), -4);
assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: wanted });
}
}
#[test]
fn a_name_wanted_twice_is_one_symbol_rather_than_two() {
let mut text = calling("puts");
text.relocs.push(Reloc {
at: 1,
symbol: "puts".to_owned(),
kind: Reference::Call,
addend: -4,
});
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
assert_eq!(file.symbols().filter(|s| s.name() == Ok("puts")).count(), 1);
}
#[test]
fn a_function_that_is_also_called_is_not_a_second_symbol() {
let text = calling("f");
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let mut found = file.symbols().filter(|s| s.name() == Ok("f"));
let f = found.next().expect("the function");
assert!(!f.is_undefined(), "the file defines it");
assert!(found.next().is_none(), "and defines it once");
}
#[test]
fn the_marker_that_says_the_stack_is_not_executable_is_written() {
let bytes = write(&calling("puts"), &Data::default(), &[], &target(), Output::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let note = file.section_by_name(".note.GNU-stack").expect("the marker");
assert!(note.data().expect("no bytes").is_empty());
}
#[test]
fn the_note_that_says_what_the_file_was_built_to_have_checked_is_written() {
let property = Property { features: Property::IBT | Property::SHSTK };
let output = Output { property, ..Output::default() };
let bytes =
write(&calling("puts"), &Data::default(), &[], &target(), output).expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let note = file.section_by_name(".note.gnu.property").expect("the note");
assert_eq!(note.align(), 8, "a note in a sixty four bit object is read a word at a time");
let want: Vec<u8> = [
4u32,
16,
5,
u32::from_le_bytes(*b"GNU\0"),
Property::X86_FEATURES,
4,
Property::IBT | Property::SHSTK,
0,
]
.iter()
.flat_map(|word| word.to_le_bytes())
.collect();
assert_eq!(note.data().expect("the bytes"), &want[..]);
}
#[test]
fn a_file_built_to_have_nothing_checked_says_nothing() {
let bytes = write(&calling("puts"), &Data::default(), &[], &target(), Output::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
assert!(file.section_by_name(".note.gnu.property").is_none());
}
#[test]
fn an_unwind_record_names_the_function_it_is_about_and_not_the_first_one() {
let mut text = calling("puts");
text.funcs.push(extent("g".to_owned(), 16, 1, Binding::Global));
text.bytes.resize(17, 0x90);
text.unwind.bytes = vec![0; 64];
for (at, name) in [(32usize, "f"), (48usize, "g")] {
text.unwind.relocs.push(Reloc {
at,
symbol: name.to_owned(),
kind: Reference::Address { bytes: 8 },
addend: 0,
});
}
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let frames = file.section_by_name(".eh_frame").expect("the table");
let mut at = frames.relocations().map(|(offset, _)| offset).collect::<Vec<_>>();
at.sort_unstable();
assert_eq!(at, [32, 48]);
}
fn lives_in<'a>(file: &'a object::File<'a>, name: &str) -> String {
let symbol = file.symbols().find(|s| s.name() == Ok(name)).expect("the symbol");
let index = symbol.section_index().expect("a section to be defined in");
let section = file.section_by_index(index).expect("a readable section");
section.name().expect("a named section").to_owned()
}
fn two() -> Text {
let mut text = calling("puts");
text.bytes.resize(16, 0x90);
text.bytes.extend_from_slice(&[0xe8, 0, 0, 0, 0, 0xc3]);
text.funcs.push(extent("g".to_owned(), 16, 6, Binding::Global));
text.relocs.push(Reloc {
at: 17,
symbol: "puts".to_owned(),
kind: Reference::Call,
addend: -4,
});
text
}
#[test]
fn every_function_gets_a_section_of_its_own_when_that_is_what_was_asked_for() {
let sections =
Output { sections: Sections { functions: true, data: false }, ..Output::default() };
let bytes = write(&two(), &Data::default(), &[], &target(), sections).expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
assert_eq!(lives_in(&file, "f"), ".text.f");
assert_eq!(lives_in(&file, "g"), ".text.g");
assert!(file.section_by_name(".text").expect("the empty one").size() == 0);
for name in ["f", "g"] {
let symbol = file.symbols().find(|s| s.name() == Ok(name)).expect("the function");
assert_eq!(symbol.address(), 0, "{name}");
assert_eq!(symbol.size(), 6, "{name}");
}
let section = file.section_by_name(".text.g").expect("the second function");
assert_eq!(section.data().expect("the bytes"), &[0xe8, 0, 0, 0, 0, 0xc3]);
assert_eq!(section.align(), u64::from(crate::FUNC_ALIGN));
}
#[test]
fn a_relocation_moves_with_the_function_whose_bytes_it_is_in() {
let sections =
Output { sections: Sections { functions: true, data: false }, ..Output::default() };
let bytes = write(&two(), &Data::default(), &[], &target(), sections).expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
for name in [".text.f", ".text.g"] {
let section = file.section_by_name(name).expect("a function");
let (offset, _) = section.relocations().next().expect("the call in it");
assert_eq!(offset, 1, "{name}");
assert_eq!(section.relocations().count(), 1, "{name}");
}
}
fn variable(name: &str, place: Place) -> Object {
Object {
name: name.to_owned(),
bytes: if place == Place::Zero { Vec::new() } else { vec![1, 0, 0, 0] },
size: 4,
align: 4,
place,
binding: Binding::Global,
visibility: Visibility::Default,
relocs: Vec::new(),
}
}
fn holding(object: Object) -> Vec<u8> {
let data = Data { objects: vec![object] };
write(&Text::default(), &data, &[], &target(), Output::default()).expect("an object")
}
#[test]
fn what_a_variable_is_decides_which_section_it_goes_in() {
for (place, wanted) in [
(Place::Written, ".data"),
(Place::ReadOnly, ".rodata"),
(Place::RelocReadOnly { local: false }, ".data.rel.ro"),
(Place::RelocReadOnly { local: true }, ".data.rel.ro.local"),
(Place::Zero, ".bss"),
(Place::Named(".init_array".to_owned()), ".init_array"),
] {
let bytes = holding(variable("x", place.clone()));
let file = object::File::parse(&bytes[..]).expect("a readable object");
let section = file.section_by_name(wanted).unwrap_or_else(|| panic!("{place:?}"));
assert_eq!(section.size(), 4, "{place:?}");
let carried = section.data().expect("the bytes").len();
assert_eq!(carried, if place == Place::Zero { 0 } else { 4 }, "{place:?}");
}
}
#[test]
fn every_variable_gets_a_section_of_its_own_when_that_is_what_was_asked_for() {
let sections =
Output { sections: Sections { functions: false, data: true }, ..Output::default() };
for (place, wanted) in [
(Place::Written, ".data.x"),
(Place::ReadOnly, ".rodata.x"),
(Place::RelocReadOnly { local: false }, ".data.rel.ro.x"),
(Place::RelocReadOnly { local: true }, ".data.rel.ro.local.x"),
(Place::Zero, ".bss.x"),
] {
let data = Data { objects: vec![variable("x", place.clone())] };
let bytes = write(&Text::default(), &data, &[], &target(), sections).expect("object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
assert_eq!(lives_in(&file, "x"), wanted, "{place:?}");
let section = file.section_by_name(wanted).expect("the section it named");
assert_eq!(section.size(), 4, "{place:?}");
let carried = section.data().expect("the bytes").len();
assert_eq!(carried, if place == Place::Zero { 0 } else { 4 }, "{place:?}");
}
}
#[test]
fn a_variable_that_has_no_section_of_its_own_to_be_given_is_left_where_it_was() {
let sections =
Output { sections: Sections { functions: false, data: true }, ..Output::default() };
let named = Place::Named(".init_array".to_owned());
let objects = vec![variable("m", Place::Merged), variable("n", named)];
let bytes =
write(&Text::default(), &Data { objects }, &[], &target(), sections).expect("object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let m = file.symbols().find(|s| s.name() == Ok("m")).expect("the tentative one");
assert!(m.is_common(), "still the linker's to merge and not in a section at all");
assert_eq!(lives_in(&file, "n"), ".init_array");
assert!(file.section_by_name(".init_array.n").is_none(), "the source already answered");
}
#[test]
fn a_relocation_in_an_image_moves_with_the_variable_whose_image_it_is_in() {
let sections =
Output { sections: Sections { functions: false, data: true }, ..Output::default() };
let pointer = Object {
bytes: vec![0; 8],
size: 8,
align: 8,
relocs: vec![Reloc {
at: 0,
symbol: "y".to_owned(),
kind: Reference::Address { bytes: 8 },
addend: 0,
}],
..variable("p", Place::Written)
};
let objects = vec![variable("first", Place::Written), pointer];
let bytes =
write(&Text::default(), &Data { objects }, &[], &target(), sections).expect("object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let section = file.section_by_name(".data.p").expect("the pointer's own section");
let (offset, reloc) = section.relocations().next().expect("one relocation");
assert_eq!(offset, 0);
assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_X86_64_64 });
}
#[test]
fn every_variable_that_wants_the_local_relocated_section_shares_one() {
let place = Place::RelocReadOnly { local: true };
let data =
Data { objects: vec![variable("first", place.clone()), variable("second", place)] };
let bytes =
write(&Text::default(), &data, &[], &target(), Output::default()).expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let named = file.sections().filter(|s| s.name() == Ok(".data.rel.ro.local")).count();
assert_eq!(named, 1, "one section holding both, not one each");
}
#[test]
fn a_variable_is_a_symbol_that_says_where_it_is_and_how_long_it_is() {
let mut data = Data { objects: vec![variable("first", Place::Written)] };
data.objects.push(Object { align: 16, ..variable("second", Place::Written) });
let bytes =
write(&Text::default(), &data, &[], &target(), Output::default()).expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let second = file.symbols().find(|s| s.name() == Ok("second")).expect("the second one");
assert_eq!(second.kind(), SymbolKind::Data);
assert_eq!(second.size(), 4);
assert_eq!(second.address(), 16);
}
#[test]
fn the_linkage_a_variable_had_is_the_binding_the_symbol_gets() {
for (binding, global, weak) in [
(Binding::Global, true, false),
(Binding::Local, false, false),
(Binding::Weak, true, true),
] {
let bytes = holding(Object { binding, ..variable("x", Place::Written) });
let file = object::File::parse(&bytes[..]).expect("a readable object");
let x = file.symbols().find(|s| s.name() == Ok("x")).expect("the variable");
assert_eq!(x.is_global(), global, "{binding:?}");
assert_eq!(x.is_weak(), weak, "{binding:?}");
}
}
#[test]
fn a_tentative_definition_asks_the_linker_for_space_rather_than_naming_any() {
let bytes = holding(Object { align: 8, ..variable("x", Place::Merged) });
let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
let x = file.symbols().find(|s| s.name() == Ok("x")).expect("the variable");
assert!(x.is_common(), "the linker merges every definition of this name into one");
assert_eq!(x.size(), 4);
assert_eq!(x.address(), 0);
assert_eq!(x.elf_symbol().st_value(Endianness::Little), 8);
}
#[test]
fn an_address_in_an_image_is_the_address_and_not_a_distance_to_it() {
let object = Object {
bytes: vec![0; 8],
size: 8,
align: 8,
relocs: vec![Reloc {
at: 0,
symbol: "y".to_owned(),
kind: Reference::Address { bytes: 8 },
addend: 16,
}],
..variable("p", Place::Written)
};
let bytes = holding(object);
let file = object::File::parse(&bytes[..]).expect("a readable object");
let section = file.section_by_name(".data").expect("a data section");
let (offset, reloc) = section.relocations().next().expect("one relocation");
assert_eq!(offset, 0);
assert_eq!(reloc.addend(), 16);
assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_X86_64_64 });
let y = file.symbols().find(|s| s.name() == Ok("y")).expect("what it points at");
assert!(y.is_undefined(), "nothing here defines it and the linker is being asked for it");
}
#[test]
fn a_relocation_counts_from_the_start_of_the_section_and_not_of_the_image_it_is_in() {
let mut data = Data { objects: vec![variable("first", Place::Written)] };
data.objects.push(Object {
bytes: vec![0; 16],
size: 16,
align: 8,
relocs: vec![Reloc {
at: 8,
symbol: "y".to_owned(),
kind: Reference::Address { bytes: 8 },
addend: 0,
}],
..variable("second", Place::Written)
});
let bytes =
write(&Text::default(), &data, &[], &target(), Output::default()).expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let section = file.section_by_name(".data").expect("a data section");
let (offset, _) = section.relocations().next().expect("one relocation");
assert_eq!(offset, 16);
}
#[test]
fn a_second_name_is_a_second_symbol_at_the_first_one_s_address_and_no_second_image() {
let data = Data {
objects: vec![Object { binding: Binding::Local, ..variable("a", Place::Written) }],
};
let aliases = [Alias {
name: "b".to_owned(),
target: "a".to_owned(),
binding: Binding::Global,
visibility: Visibility::Default,
}];
let bytes = write(&Text::default(), &data, &aliases, &target(), Output::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let a = file.symbols().find(|s| s.name() == Ok("a")).expect("the variable");
let b = file.symbols().find(|s| s.name() == Ok("b")).expect("the second name");
assert_eq!(b.address(), a.address(), "the same place");
assert_eq!(b.size(), a.size());
assert_eq!(b.section_index(), a.section_index());
assert!(a.is_local(), "the target was written `static`");
assert!(b.is_global(), "and the name given to it was not");
assert_eq!(file.section_by_name(".data").expect("a data section").size(), 4);
}
#[test]
fn a_function_can_be_given_a_second_name_the_same_way_a_variable_can() {
let text = calling("puts");
let aliases = [Alias {
name: "g".to_owned(),
target: "f".to_owned(),
binding: Binding::Weak,
visibility: Visibility::Default,
}];
let bytes = write(&text, &Data::default(), &aliases, &target(), Output::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let f = file.symbols().find(|s| s.name() == Ok("f")).expect("the function");
let g = file.symbols().find(|s| s.name() == Ok("g")).expect("the second name");
assert_eq!(g.address(), f.address());
assert_eq!(g.size(), f.size());
assert_eq!(g.kind(), f.kind(), "a second name for a function is a function");
assert!(g.is_weak(), "so that a program may define the name itself instead");
}
#[test]
fn a_second_name_for_something_this_file_does_not_define_is_refused() {
let aliases = [Alias {
name: "b".to_owned(),
target: "a".to_owned(),
binding: Binding::Global,
visibility: Visibility::Default,
}];
let error =
write(&Text::default(), &Data::default(), &aliases, &target(), Output::default())
.expect_err("nothing to point at");
assert!(matches!(error, Error::Refused { .. }), "{error:?}");
}
#[test]
fn a_platform_this_does_not_write_is_said_so_rather_than_written_as_elf() {
let text = calling("puts");
for triple in [
Triple::new(Arch::Aarch64, Os::Linux, Env::Gnu),
Triple::new(Arch::X86_64, Os::Darwin, Env::Gnu),
] {
let error =
write(&text, &Data::default(), &[], &TargetInfo::new(triple), Output::default())
.expect_err("no writer");
assert!(matches!(error, Error::Format { .. }), "{error:?}");
}
}
}