use std::collections::BTreeMap;
use object::write::{
Comdat, Mangling, Object as Writer, Relocation, StandardSection, Symbol, SymbolId,
SymbolSection,
};
use object::{
Architecture, BinaryFormat, ComdatKind, Endianness, RelocationFlags, SectionFlags, SectionKind,
SymbolFlags, SymbolKind, SymbolScope,
};
use rucc_base::hash::{Map, Set};
use rucc_target::{ObjectFormat, TargetInfo};
use rucc_tuple::Arch;
use crate::section::{
Alias, Apart, Array, Binding, Compress, Data, EXCEPT_TABLE, Export, Holds, Info, Object,
Output, Place, Property, Reference, Reloc, Sections, Text, Visibility,
};
use crate::{coff, elf};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Flavour {
Elf,
Coff,
MachO,
}
impl Flavour {
pub(crate) fn of(target: &TargetInfo) -> Option<Flavour> {
match target.object_format {
ObjectFormat::Elf => Some(Flavour::Elf),
ObjectFormat::Coff => Some(Flavour::Coff),
ObjectFormat::MachO | ObjectFormat::Wasm => None,
}
}
pub(crate) fn binary(self) -> BinaryFormat {
match self {
Flavour::Elf => BinaryFormat::Elf,
Flavour::Coff => BinaryFormat::Coff,
Flavour::MachO => BinaryFormat::MachO,
}
}
pub(crate) fn reloc(
self,
machine: Architecture,
reference: Reference,
after: u8,
) -> Option<RelocationFlags> {
let flags = |r_type| RelocationFlags::Elf { r_type };
match (self, machine) {
(Flavour::Elf, Architecture::I386) => elf::r_type_i386(reference).map(flags),
(Flavour::Elf, Architecture::Aarch64) => elf::r_type_aarch64(reference).map(flags),
(Flavour::Elf, _) => elf::r_type(reference).map(flags),
(Flavour::Coff, Architecture::Aarch64) => {
coff::arm64(reference).map(|typ| RelocationFlags::Coff { typ })
}
(Flavour::Coff, Architecture::I386) => {
coff::i386(reference).map(|typ| RelocationFlags::Coff { typ })
}
(Flavour::Coff, _) => coff::reloc(reference, after),
(Flavour::MachO, _) => crate::macho::reloc(reference, 0).ok(),
}
}
pub(crate) fn machine(self, arch: Arch) -> Option<Architecture> {
match (self, arch) {
(Flavour::Elf | Flavour::Coff, Arch::X86_64) => Some(Architecture::X86_64),
(Flavour::Elf | Flavour::Coff, Arch::Aarch64) => Some(Architecture::Aarch64),
(Flavour::Elf | Flavour::Coff, Arch::X86) => Some(Architecture::I386),
_ => None,
}
}
pub(crate) fn spell(self, machine: Architecture, name: &str) -> String {
match (self, machine) {
(Flavour::Coff, Architecture::I386) => coff::decorate(name),
_ => name.to_owned(),
}
}
pub(crate) fn see(
self,
obj: &mut Writer<'_>,
id: SymbolId,
binding: Binding,
visibility: Visibility,
) {
match self {
Flavour::Elf => elf::see(obj, id, binding, visibility),
Flavour::Coff => {}
Flavour::MachO => {
if binding != Binding::Local && visibility == Visibility::Hidden {
obj.symbol_mut(id).scope = SymbolScope::Linkage;
}
}
}
}
fn rel_ro_local(self) -> Option<&'static str> {
match self {
Flavour::Elf => elf::REL_RO_LOCAL,
Flavour::Coff => coff::REL_RO_LOCAL,
Flavour::MachO => None,
}
}
pub(crate) fn debug(self, kind: Reference, into_debug: bool) -> Reference {
match kind {
Reference::Address { bytes: 4 } if self == Flavour::Coff && into_debug => {
Reference::Section
}
kind => kind,
}
}
fn gathered(self, array: Array) -> Option<SectionFlags> {
match self {
Flavour::Elf => Some(elf::gathered(array)),
Flavour::Coff | Flavour::MachO => None,
}
}
pub(crate) fn stated(self, shape: crate::source::Shape) -> Option<SectionFlags> {
match self {
Flavour::Elf => {
Some(SectionFlags::Elf { sh_type: shape.sh_type(), sh_flags: shape.sh_flags() })
}
Flavour::Coff => (shape.coff != 0).then_some(SectionFlags::Coff {
characteristics: object::pe::SectionFlags(shape.coff),
}),
Flavour::MachO => Some(SectionFlags::MachO {
flags: object::macho::SectionFlags(shape.mach),
reserved2: 0,
}),
}
}
pub(crate) fn sort(self, sort: crate::source::Sort, binding: Binding) -> SymbolKind {
if self == Flavour::MachO {
return match sort {
crate::source::Sort::Func | crate::source::Sort::Ifunc => SymbolKind::Text,
crate::source::Sort::File => SymbolKind::File,
_ => SymbolKind::Data,
};
}
match sort {
crate::source::Sort::Func | crate::source::Sort::Ifunc => SymbolKind::Text,
crate::source::Sort::Object => SymbolKind::Data,
crate::source::Sort::Thread => SymbolKind::Tls,
crate::source::Sort::File => SymbolKind::File,
crate::source::Sort::Untyped => match (self, binding) {
(Flavour::Coff, Binding::Global | Binding::Weak) => SymbolKind::Data,
_ => SymbolKind::Label,
},
}
}
pub(crate) fn marker(self, obj: &mut Writer<'_>) {
match self {
Flavour::Elf => elf::marker(obj),
Flavour::Coff => coff::marker(obj),
Flavour::MachO => {}
}
}
fn property(self, obj: &mut Writer<'_>, property: Property) {
if !property.any() {
return;
}
match self {
Flavour::Elf => {
let note = obj.section_id(StandardSection::GnuProperty);
let align = if obj.architecture() == Architecture::I386 { 4 } else { 8 };
obj.append_section_data(note, &elf::record(property, align), u64::from(align));
}
Flavour::Coff | Flavour::MachO => {}
}
}
fn tables(self) -> ((&'static str, u64), Option<(&'static str, u64)>) {
match self {
Flavour::Elf => (elf::FRAMES, None),
Flavour::Coff => (coff::FUNCTIONS, Some(coff::CODES)),
Flavour::MachO => (("__TEXT,__eh_frame", 8), None),
}
}
fn finish(self, bytes: &mut [u8], ordered: &[String]) {
match self {
Flavour::Elf => elf::link(bytes, ordered),
Flavour::Coff | Flavour::MachO => {
debug_assert!(ordered.is_empty(), "a record this format cannot write");
}
}
}
}
#[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,
info: &Info,
) -> Result<Vec<u8>, Error> {
let Output { sections, property, ident, .. } = output;
let Some((flavour, machine)) = written(target) else {
return Err(Error::Format { triple: target.tuple.to_string() });
};
if flavour == Flavour::Coff {
beyond(text, data)?;
}
let mut obj = Writer::new(flavour.binary(), machine, Endianness::Little);
obj.set_mangling(Mangling::None);
let spell = |name: &str| flavour.spell(machine, name).into_bytes();
let pointer = if machine == Architecture::I386 { 4u8 } else { 8 };
let whole = obj.section_id(StandardSection::Text);
if flavour == Flavour::Elf {
obj.section_id(StandardSection::Data);
obj.section_id(StandardSection::UninitializedData);
}
if !sections.functions {
obj.append_section_data(whole, &text.bytes, u64::from(text.align));
}
let mut symbols = BTreeMap::new();
let mut split: Vec<(object::write::SectionId, u64)> = Vec::with_capacity(text.funcs.len());
let mut ordered: Vec<String> = Vec::new();
for func in &text.funcs {
let ahead = func.patch.map_or(0, |patch| patch.before);
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 - ahead..func.start + func.len];
obj.append_section_data(id, bytes, u64::from(func.align.max(1)));
(id, ahead as u64)
} else {
(whole, func.start as u64)
};
if let Some(patch) = func.patch {
let base = if sections.functions { func.start - ahead } else { 0 };
let name = elf::PATCHABLE.as_bytes().to_vec();
let id = obj.add_section(Vec::new(), name, SectionKind::Data);
obj.section_mut(id).flags = elf::ordered();
obj.append_section_data(id, &vec![0; usize::from(pointer)], u64::from(pointer));
let symbol = obj.section_symbol(section);
let flags =
flavour.reloc(machine, Reference::Address { bytes: pointer }, 0).ok_or_else(
|| Error::Refused { why: "no relocation holds an address here".to_owned() },
)?;
relocate(
&mut obj,
id,
Relocation { offset: 0, symbol, addend: (patch.at - base) as i64, flags },
)?;
ordered.push(if sections.functions {
format!(".text.{}", func.name)
} else {
".text".to_owned()
});
}
let id = obj.add_symbol(Symbol {
name: spell(&func.name),
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,
});
flavour.see(&mut obj, id, func.binding, func.visibility);
symbols.insert(func.name.clone(), id);
split.push((section, at));
}
let mut places: Places = BTreeMap::new();
for label in &text.labels {
let after = text.funcs.partition_point(|func| func.start <= label.at);
let Some(index) = after.checked_sub(1) else {
let why = format!("'{}' is at {} and in front of every function", label.name, label.at);
return Err(Error::Refused { why });
};
let func = &text.funcs[index];
let (section, at) = if sections.functions {
let base = func.start - func.patch.map_or(0, |patch| patch.before);
(split[index].0, (label.at - base) as u64)
} else {
(whole, label.at as u64)
};
if flavour == Flavour::Elf {
places.insert(label.name.clone(), (section, at));
continue;
}
let id = obj.add_symbol(Symbol {
name: label.name.clone().into_bytes(),
value: at,
size: 0,
kind: SymbolKind::Label,
scope: SymbolScope::Compilation,
weak: false,
section: SymbolSection::Section(section),
flags: SymbolFlags::None,
});
symbols.insert(label.name.clone(), id);
}
if !text.mcount.is_empty() {
let name = crate::section::MCOUNT_LOC.as_bytes().to_vec();
let id = obj.add_section(Vec::new(), name, SectionKind::ReadOnlyData);
let flags =
flavour.reloc(machine, Reference::Address { bytes: pointer }, 0).ok_or_else(|| {
Error::Refused { why: "no relocation holds an address here".to_owned() }
})?;
for &call in &text.mcount {
let after = text.funcs.partition_point(|func| func.start <= call);
let Some(index) = after.checked_sub(1) else {
let why = format!("a profiler call at {call} is in front of every function");
return Err(Error::Refused { why });
};
let func = &text.funcs[index];
let (section, at) = if sections.functions {
let base = func.start - func.patch.map_or(0, |patch| patch.before);
(split[index].0, call - base)
} else {
(whole, call)
};
let offset =
obj.append_section_data(id, &vec![0; usize::from(pointer)], u64::from(pointer));
let symbol = obj.section_symbol(section);
relocate(&mut obj, id, Relocation { offset, symbol, addend: at as i64, flags })?;
}
}
let mut placed = Vec::with_capacity(data.objects.len());
let mut named = Map::default();
for object in &data.objects {
let (section, offset) = put(&mut obj, object, &mut named, sections, flavour);
if let (Place::Pointer, Some(section)) = (&object.place, section.id()) {
obj.section_symbol(section);
}
let id = obj.add_symbol(Symbol {
name: spell(&object.name),
value: if object.place == Place::Merged { object.align } else { offset },
size: object.size,
kind: match object.place {
Place::Thread { .. } => SymbolKind::Tls,
_ => SymbolKind::Data,
},
scope: scope_of(object.binding),
weak: object.binding == Binding::Weak,
section,
flags: SymbolFlags::None,
});
flavour.see(&mut obj, id, object.binding, object.visibility);
if let (Place::Pointer, Some(section)) = (&object.place, section.id()) {
obj.add_comdat(Comdat { kind: ComdatKind::Any, symbol: id, sections: vec![section] });
}
symbols.insert(object.name.clone(), id);
placed.push((section.id(), offset));
}
let tables = tables(&mut obj, text, &split, &mut named, sections, flavour)?;
for apart in &data.apart {
let (Some(section), offset) = placed[apart.object] else { continue };
let value = distance(&obj, &symbols, &places, apart)?;
let bytes = usize::from(apart.bytes);
let at = usize::try_from(offset).map_err(|why| Error::Refused { why: why.to_string() })?;
let at = at + apart.at;
let image = obj.section_mut(section).data_mut();
image[at..at + bytes].copy_from_slice(&value.to_le_bytes()[..bytes]);
}
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: spell(&alias.name),
value,
size,
kind,
scope: scope_of(alias.binding),
weak: alias.binding == Binding::Weak,
section,
flags: SymbolFlags::None,
});
flavour.see(&mut obj, id, alias.binding, alias.visibility);
if alias.ifunc {
if flavour != Flavour::Elf {
let why = format!("'{}' is an indirect function, which only ELF has", alias.name);
return Err(Error::Refused { why });
}
elf::indirect(&mut obj, id);
}
symbols.insert(alias.name.clone(), id);
}
let weak: Set<&str> = data.weak.iter().map(String::as_str).collect();
let relocs = || text.relocs.iter().chain(data.objects.iter().flat_map(|o| &o.relocs));
let thread: Set<&str> = relocs()
.filter(|reloc| reloc.kind == Reference::Thread)
.map(|reloc| reloc.symbol.as_str())
.collect();
let wanted: Vec<&String> =
relocs().map(|reloc| &reloc.symbol).chain(data.weak.iter()).collect();
for name in wanted {
if symbols.contains_key(name) || tables.contains_key(name) || places.contains_key(name) {
continue;
}
let id = obj.add_symbol(Symbol {
name: spell(name),
value: 0,
size: 0,
kind: if thread.contains(name.as_str()) {
SymbolKind::Tls
} else {
SymbolKind::Unknown
},
scope: SymbolScope::Dynamic,
weak: weak.contains(name.as_str()),
section: SymbolSection::Undefined,
flags: SymbolFlags::None,
});
symbols.insert(name.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 });
};
let base = func.start - func.patch.map_or(0, |patch| patch.before);
(split[after - 1].0, (reloc.at - base) as u64)
} else {
(whole, reloc.at as u64)
};
if let Some(&(table, offset)) = tables.get(&reloc.symbol) {
let flags = flavour.reloc(machine, reloc.kind, reloc.after).ok_or_else(|| {
Error::Refused { why: format!("no relocation is {:?}", reloc.kind) }
})?;
let symbol = obj.section_symbol(table);
let addend = reloc.addend + offset as i64;
relocate(&mut obj, section, Relocation { offset: at, symbol, addend, flags })?;
continue;
}
add(&mut obj, section, at, reloc, &symbols, &places, flavour)?;
}
let seh_free = flavour == Flavour::Coff && machine == Architecture::I386;
if !text.unwind.bytes.is_empty() && !seh_free {
let ((name, align), second) = flavour.tables();
let align = if machine == Architecture::I386 { 4 } else { align };
let frames = obj.add_section(Vec::new(), name.into(), SectionKind::ReadOnlyData);
obj.append_section_data(frames, &text.unwind.bytes, align);
let mut described = Map::default();
if !text.unwind.info.is_empty() {
let Some((name, align)) = second else {
let why = "an unwind table here is one section and it was given two".to_owned();
return Err(Error::Refused { why });
};
let codes = obj.add_section(Vec::new(), name.into(), SectionKind::ReadOnlyData);
obj.append_section_data(codes, &text.unwind.info, align);
for label in &text.unwind.labels {
let id = obj.add_symbol(Symbol {
name: label.name.clone().into_bytes(),
value: label.at as u64,
size: 0,
kind: SymbolKind::Label,
scope: SymbolScope::Compilation,
weak: false,
section: SymbolSection::Section(codes),
flags: SymbolFlags::None,
});
described.insert(label.name.clone(), id);
}
}
if !text.unwind.except.is_empty() {
let except =
obj.add_section(Vec::new(), EXCEPT_TABLE.into(), SectionKind::ReadOnlyData);
obj.append_section_data(except, &text.unwind.except, 4);
described.insert(EXCEPT_TABLE.to_owned(), obj.section_symbol(except));
}
for reloc in &text.unwind.relocs {
let found = described.get(&reloc.symbol).or_else(|| {
let ours = data.objects.iter().any(|object| object.name == reloc.symbol);
if ours { symbols.get(&reloc.symbol) } else { None }
});
let (symbol, addend) = match found {
Some(&id) => (id, reloc.addend),
None => {
let found = text.funcs.iter().position(|func| func.name == reloc.symbol);
let Some((section, at)) = found.map(|i| split[i]) else {
let why = format!(
"'{}' has an unwind record and is not a function here",
reloc.symbol
);
return Err(Error::Refused { why });
};
(obj.section_symbol(section), reloc.addend + at as i64)
}
};
let flags = flavour.reloc(machine, reloc.kind, reloc.after).ok_or_else(|| {
Error::Refused { why: format!("no relocation is {:?}", reloc.kind) }
})?;
let record = Relocation { offset: reloc.at as u64, symbol, addend, flags };
relocate(&mut obj, frames, record)?;
}
}
let mut named = Map::default();
for chunk in &info.chunks {
let how = if flavour == Flavour::Elf && obj.architecture() != Architecture::I386 {
info.compress
} else {
Compress::None
};
let id = crate::zlib::debug_section(&mut obj, chunk, how);
named.insert(chunk.name.as_str(), id);
}
for chunk in &info.chunks {
let section = named[chunk.name.as_str()];
for reloc in &chunk.relocs {
let (symbol, addend) = match named.get(reloc.symbol.as_str()) {
Some(&id) => (obj.section_symbol(id), reloc.addend),
None => match text.funcs.iter().position(|func| func.name == reloc.symbol) {
Some(which) => {
let (section, at) = split[which];
(obj.section_symbol(section), reloc.addend + at as i64)
}
None => {
let found = data.objects.iter().position(|had| had.name == reloc.symbol);
let Some(which) = found else {
let why = format!(
"'{}' is named by the debug information and is not defined here",
reloc.symbol
);
return Err(Error::Refused { why });
};
match placed[which] {
(Some(section), at) => {
(obj.section_symbol(section), reloc.addend + at as i64)
}
(None, _) => (symbols[&reloc.symbol], reloc.addend),
}
}
},
};
let kind = flavour.debug(reloc.kind, named.contains_key(reloc.symbol.as_str()));
let flags = flavour
.reloc(machine, kind, reloc.after)
.ok_or_else(|| Error::Refused { why: format!("no relocation is {kind:?}") })?;
let record = Relocation { offset: reloc.at as u64, symbol, addend, flags };
relocate(&mut obj, section, record)?;
}
}
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, &places, flavour)?;
}
}
if !data.exports.is_empty() {
let options: String = data.exports.iter().map(Export::option).collect();
let id = obj.add_section(Vec::new(), b".drectve".to_vec(), SectionKind::Linker);
obj.append_section_data(id, options.as_bytes(), 1);
}
flavour.property(&mut obj, property);
if let Some(ident) = ident.filter(|_| flavour == Flavour::Elf) {
let id = obj.add_section(Vec::new(), b".comment".to_vec(), SectionKind::OtherString);
let bytes = [&[0][..], ident.as_bytes(), &[0]].concat();
obj.append_section_data(id, &bytes, 1);
}
flavour.marker(&mut obj);
let mut bytes = obj.write().map_err(|why| Error::Refused { why: why.to_string() })?;
flavour.finish(&mut bytes, &ordered);
Ok(bytes)
}
type Places = BTreeMap<String, (object::write::SectionId, u64)>;
fn distance(
obj: &Writer<'_>,
symbols: &BTreeMap<String, SymbolId>,
places: &Places,
apart: &Apart,
) -> Result<i64, Error> {
let find = |name: &str| match (symbols.get(name), places.get(name)) {
(Some(&id), _) => Ok((obj.symbol(id).section, obj.symbol(id).value)),
(None, Some(&(section, at))) => Ok((SymbolSection::Section(section), at)),
(None, None) => {
Err(Error::Refused { why: format!("'{name}' is measured from and is not here") })
}
};
let (to, from) = (find(&apart.to)?, find(&apart.from)?);
if to.0 != from.0 {
let why = format!("'{}' and '{}' are in different sections", apart.to, apart.from);
return Err(Error::Refused { why });
}
let value = (to.1 as i64).wrapping_sub(from.1 as i64).wrapping_add(apart.addend);
let bits = u32::from(apart.bytes) * 8;
if bits < 64 && (value >> (bits - 1)) != 0 && (value >> (bits - 1)) != -1 {
let why = format!("'{}' is too far from '{}' for {} bytes", apart.to, apart.from, bits / 8);
return Err(Error::Refused { why });
}
Ok(value)
}
fn beyond(text: &Text, data: &Data) -> Result<(), Error> {
let why = |why: String| Err(Error::Refused { why });
if text.funcs.iter().any(|func| func.patch.is_some()) {
return why("a record of where a patcher's room is has no section flags here".to_owned());
}
for reloc in text.relocs.iter().chain(data.objects.iter().flat_map(|object| &object.relocs)) {
if matches!(
reloc.kind,
Reference::Got | Reference::GotBare | Reference::GotKept | Reference::Thread
) {
return why(format!("nothing reaches '{}' through a table here", reloc.symbol));
}
}
for object in &data.objects {
if matches!(object.place, Place::Thread { zero: true }) {
return why(format!(
"'{}' is zeroed thread-local storage, which is not here",
object.name
));
}
let Place::Named(name, _) = &object.place else { continue };
if Array::of(name).is_some() {
return why(format!("'{name}' is not a list the startup code here gathers"));
}
}
Ok(())
}
pub fn defines(
text: &Text,
data: &Data,
aliases: &[Alias],
target: &TargetInfo,
) -> Result<Vec<String>, Error> {
let Some((flavour, machine)) = written(target) else {
return Err(Error::Format { triple: target.tuple.to_string() });
};
let spell = |name: &String| flavour.spell(machine, name);
let names = text
.funcs
.iter()
.filter(|func| func.binding != Binding::Local)
.map(|func| spell(&func.name))
.chain(
data.objects
.iter()
.filter(|object| object.binding != Binding::Local)
.map(|object| spell(&object.name)),
)
.chain(
aliases
.iter()
.filter(|alias| alias.binding != Binding::Local)
.map(|alias| spell(&alias.name)),
)
.collect();
Ok(names)
}
fn put(
obj: &mut Writer<'_>,
object: &Object,
named: &mut Map<String, object::write::SectionId>,
sections: Sections,
flavour: Flavour,
) -> (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 carries_no_bytes(&object.place) {
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 } => match flavour.rel_ro_local().filter(|_| *local) {
Some(name) => made(obj, named, name, SectionKind::ReadOnlyDataWithRel),
None => obj.section_id(StandardSection::ReadOnlyDataWithRel),
},
Place::Zero => obj.section_id(StandardSection::UninitializedData),
Place::Strings { align } => {
made(obj, named, &Place::strings(*align), SectionKind::ReadOnlyString)
}
Place::Thread { zero: false } => obj.section_id(StandardSection::Tls),
Place::Thread { zero: true } => obj.section_id(StandardSection::UninitializedTls),
Place::Merged => return (SymbolSection::Common, 0),
Place::Named(name, _) => {
let section = made(obj, named, name, kind_of(&object.place));
if let Some(flags) = Array::of(name).and_then(|array| flavour.gathered(array)) {
obj.section_mut(section).flags = flags;
}
section
}
Place::Pointer => {
let name = format!(".rdata${}", object.name);
made(obj, named, &name, SectionKind::ReadOnlyData)
}
};
let offset = if carries_no_bytes(&object.place) {
obj.append_section_bss(section, object.size, object.align)
} else {
obj.append_section_data(section, &object.bytes, object.align)
};
(SymbolSection::Section(section), offset)
}
fn tables(
obj: &mut Writer<'_>,
text: &Text,
split: &[(object::write::SectionId, u64)],
named: &mut Map<String, object::write::SectionId>,
sections: Sections,
flavour: Flavour,
) -> Result<Map<String, (object::write::SectionId, u64)>, Error> {
let mut placed = Map::default();
if text.tables.is_empty() {
return Ok(placed);
}
if flavour != Flavour::Elf {
let why = "a jump table outside the code is written on ELF only".to_owned();
return Err(Error::Refused { why });
}
let machine = obj.architecture();
let flags = flavour.reloc(machine, Reference::Away, 0).ok_or_else(|| Error::Refused {
why: "no relocation is a distance from where it is written".to_owned(),
})?;
let pointer = if machine == Architecture::I386 { 4u8 } else { 8 };
for table in &text.tables {
let func = text.funcs.get(table.func).ok_or_else(|| Error::Refused {
why: format!("'{}' belongs to function {}, which is not here", table.name, table.func),
})?;
let section = if sections.data {
let name = format!(".rodata.{}", func.name);
made(obj, named, &name, SectionKind::ReadOnlyData)
} else {
obj.section_id(StandardSection::ReadOnlyData)
};
let (width, flags) = if table.absolute {
let reference = Reference::Address { bytes: pointer };
let wide = flavour.reloc(machine, reference, 0).ok_or_else(|| Error::Refused {
why: format!("no relocation is an address in {pointer} bytes"),
})?;
(usize::from(pointer), wide)
} else {
(4, flags)
};
let offset =
obj.append_section_data(section, &vec![0; width * table.cells.len()], width as u64);
placed.insert(table.name.clone(), (section, offset));
let (code, at) = split[table.func];
let symbol = obj.section_symbol(code);
for (index, &cell) in table.cells.iter().enumerate() {
let place = (width * index) as u64;
let addend = at as i64 + cell as i64 + if table.absolute { 0 } else { place as i64 };
let record = Relocation { offset: offset + place, symbol, addend, flags };
relocate(obj, section, record)?;
}
}
Ok(placed)
}
fn carries_no_bytes(place: &Place) -> bool {
matches!(place, Place::Zero | Place::Thread { zero: true } | Place::Named(_, Holds::Zero))
}
fn made(
obj: &mut Writer<'_>,
named: &mut Map<String, object::write::SectionId>,
name: &str,
kind: SectionKind,
) -> object::write::SectionId {
if let Some(section) = named.get(name) {
return *section;
}
let section = obj.add_section(Vec::new(), name.as_bytes().to_vec(), kind);
named.insert(name.to_owned(), section);
section
}
fn kind_of(place: &Place) -> SectionKind {
match place {
Place::ReadOnly | Place::Pointer | Place::Named(_, Holds::ReadOnly) => {
SectionKind::ReadOnlyData
}
Place::RelocReadOnly { .. } => SectionKind::ReadOnlyDataWithRel,
Place::Strings { .. } => SectionKind::ReadOnlyString,
Place::Zero | Place::Named(_, Holds::Zero) => SectionKind::UninitializedData,
Place::Thread { zero: false } => SectionKind::Tls,
Place::Thread { zero: true } => SectionKind::UninitializedTls,
Place::Written | Place::Merged | Place::Named(_, Holds::Written) => SectionKind::Data,
}
}
fn add(
obj: &mut Writer<'_>,
section: object::write::SectionId,
at: u64,
reloc: &Reloc,
symbols: &BTreeMap<String, SymbolId>,
places: &Places,
flavour: Flavour,
) -> Result<(), Error> {
let flags = flavour
.reloc(obj.architecture(), reloc.kind, reloc.after)
.ok_or_else(|| Error::Refused { why: format!("no relocation is {:?}", reloc.kind) })?;
let (symbol, addend) = match places.get(&reloc.symbol) {
Some(&(held, offset)) => (obj.section_symbol(held), reloc.addend + offset as i64),
None => (symbols[&reloc.symbol], reloc.addend),
};
relocate(obj, section, Relocation { offset: at, symbol, addend, flags })
}
pub(crate) fn relocate(
obj: &mut Writer<'_>,
section: object::write::SectionId,
mut relocation: Relocation,
) -> Result<(), Error> {
if let (Architecture::I386, RelocationFlags::Elf { r_type }) =
(obj.architecture(), relocation.flags)
{
let Some(width) = elf::width_i386(r_type) else {
let why = format!("relocation type {} has no width this writer knows", r_type.0);
return Err(Error::Refused { why });
};
let addend = relocation.addend;
let bits = 8 * width as u32;
let least = if width == 4 { -(1i64 << bits) } else { -(1i64 << (bits - 1)) };
if addend < least || addend >= 1i64 << bits {
let why =
format!("{addend} added to a name, and there are {width} bytes to keep it in");
return Err(Error::Refused { why });
}
let at = usize::try_from(relocation.offset).unwrap_or(usize::MAX);
let data = obj.section_mut(section).data_mut();
let Some(place) = data.get_mut(at..).and_then(|rest| rest.get_mut(..width)) else {
let why = format!("a relocation at {at} is past the end of its section");
return Err(Error::Refused { why });
};
place.copy_from_slice(&addend.to_le_bytes()[..width]);
relocation.addend = 0;
}
obj.add_relocation(section, relocation).map_err(|why| Error::Refused { why: why.to_string() })
}
fn written(target: &TargetInfo) -> Option<(Flavour, Architecture)> {
let flavour = Flavour::of(target)?;
let machine = flavour.machine(target.tuple.arch())?;
(machine != Architecture::Aarch64).then_some((flavour, machine))
}
pub(crate) fn scope_of(binding: Binding) -> SymbolScope {
match binding {
Binding::Local => SymbolScope::Compilation,
Binding::Global | Binding::Weak => SymbolScope::Dynamic,
}
}
#[cfg(test)]
mod tests {
use super::*;
use object::read::elf::Sym as _;
use object::read::{Object as _, ObjectComdat as _, ObjectSection as _, ObjectSymbol as _};
use object::{elf, pe};
use rucc_target::{Arch, Env, Os, Triple};
use crate::elf::PATCHABLE;
use crate::section::{Chunk, Extent, Marker, Offer, Patch, 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,
patch: None,
landings: Vec::new(),
}
}
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,
after: 0,
}],
..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(), &Info::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(), &Info::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(), &Info::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 where_a_patcher_may_write_is_recorded_in_a_section_tied_to_the_code_it_is_about() {
let mut text = calling("puts");
text.bytes.splice(0..0, [0x90, 0x90, 0x90]);
text.funcs[0].start = 3;
text.funcs[0].patch = Some(Patch { at: 0, before: 3 });
text.relocs[0].at = 4;
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default(), &Info::default())
.expect("an object");
let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
let section = file.section_by_name(PATCHABLE).expect("a record of the room");
assert_eq!(section.size(), 8, "one address, and this file defines one function");
assert_eq!(section.align(), 8);
let header = section.elf_section_header();
assert_eq!(
header.sh_flags.get(Endianness::Little),
elf::SHF_ALLOC | elf::SHF_WRITE | elf::SHF_LINK_ORDER
);
let index = file.section_by_name(".text").expect("a text section").index().0;
assert_eq!(header.sh_link.get(Endianness::Little) as usize, index);
assert_ne!(index, 0);
let [(at, reloc)] = §ion.relocations().collect::<Vec<_>>()[..] else {
panic!("one address in the record")
};
assert_eq!(*at, 0);
assert_eq!(reloc.addend(), 0);
assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_X86_64_64 });
}
#[test]
fn a_file_that_promised_a_patcher_nothing_records_nothing() {
let text = calling("puts");
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
assert!(file.section_by_name(PATCHABLE).is_none());
}
#[test]
fn each_record_is_tied_to_its_own_function_when_they_are_split_up() {
let mut text = calling("puts");
text.funcs[0].patch = Some(Patch { at: 0, before: 0 });
text.funcs.push(extent("g".to_owned(), 16, 1, Binding::Global));
text.funcs[1].patch = Some(Patch { at: 16, before: 0 });
text.bytes.resize(17, 0x90);
let output =
Output { sections: Sections { functions: true, data: false }, ..Output::default() };
let bytes = write(&text, &Data::default(), &[], &target(), output, &Info::default())
.expect("an object");
let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
let links: Vec<usize> = file
.sections()
.filter(|section| section.name() == Ok(PATCHABLE))
.map(|section| section.elf_section_header().sh_link.get(Endianness::Little) as usize)
.collect();
let index = |name: &str| file.section_by_name(name).expect("a text section").index().0;
assert_eq!(links, [index(".text.f"), index(".text.g")]);
}
#[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(), &Info::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(), &Info::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(),
&Info::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),
(Reference::GotBare, elf::R_X86_64_GOTPCRELX),
(Reference::GotKept, elf::R_X86_64_GOTPCREL),
(Reference::Thread, elf::R_X86_64_GOTTPOFF),
] {
let mut text = calling("puts");
text.relocs[0].kind = reference;
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default(), &Info::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_wide_distance_and_a_narrow_address_have_relocations_of_their_own() {
for (reference, wanted) in [
(Reference::AwayWide, elf::R_X86_64_PC64),
(Reference::Address { bytes: 2 }, elf::R_X86_64_16),
(Reference::Address { bytes: 1 }, elf::R_X86_64_8),
] {
assert_eq!(crate::elf::r_type(reference), Some(wanted));
}
assert_eq!(crate::elf::r_type_aarch64(Reference::AwayWide), Some(elf::R_AARCH64_PREL64));
}
#[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,
after: 0,
});
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default(), &Info::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(), &Info::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(),
&Info::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, &Info::default())
.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(),
&Info::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,
after: 0,
});
}
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let mut found = points_at(&file);
found.sort_unstable();
assert_eq!(found, [(32, ".text".to_owned(), 0), (48, ".text".to_owned(), 16)]);
}
fn points_at(file: &object::File<'_>) -> Vec<(u64, String, i64)> {
let frames = file.section_by_name(".eh_frame").expect("the table");
frames
.relocations()
.map(|(offset, reloc)| {
let object::RelocationTarget::Symbol(index) = reloc.target() else {
panic!("a record points at something that is not a symbol");
};
let symbol = file.symbol_by_index(index).expect("a symbol that is in the table");
assert_eq!(symbol.kind(), SymbolKind::Section, "a record names a section");
let section = symbol.section_index().expect("a section symbol is in one");
let name = file.section_by_index(section).expect("a readable section");
(offset, name.name().expect("a named section").to_owned(), reloc.addend())
})
.collect()
}
#[test]
fn a_record_reaches_its_function_through_the_section_it_is_in() {
let mut text = two();
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::Data,
addend: 0,
after: 0,
});
}
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let mut whole = points_at(&file);
whole.sort_unstable();
assert_eq!(whole, [(32, ".text".to_owned(), 0), (48, ".text".to_owned(), 16)]);
let sections =
Output { sections: Sections { functions: true, data: false }, ..Output::default() };
let bytes = write(&text, &Data::default(), &[], &target(), sections, &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let mut split = points_at(&file);
split.sort_unstable();
assert_eq!(split, [(32, ".text.f".to_owned(), 0), (48, ".text.g".to_owned(), 0)]);
}
#[test]
fn a_record_about_something_this_file_does_not_define_is_refused() {
let mut text = calling("puts");
text.unwind.bytes = vec![0; 64];
text.unwind.relocs.push(Reloc {
at: 32,
symbol: "puts".to_owned(),
kind: Reference::Data,
addend: 0,
after: 0,
});
let why =
write(&text, &Data::default(), &[], &target(), Output::default(), &Info::default())
.expect_err("a record about a name from somewhere else");
assert!(why.to_string().contains("puts"), "{why}");
}
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,
after: 0,
});
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, &Info::default())
.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, &Info::default())
.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 switching() -> Text {
let mut text = two();
let name = ".Lg_j0".to_owned();
text.tables.push(crate::Table { name, func: 1, cells: vec![0, 5], absolute: false });
text
}
fn cells(file: &object::File<'_>, section: &str) -> Vec<(u64, String, i64)> {
let section = file.section_by_name(section).expect("the table's section");
section
.relocations()
.map(|(offset, reloc)| {
assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_X86_64_PC32 });
let object::RelocationTarget::Symbol(index) = reloc.target() else {
panic!("a cell against something that is not a symbol");
};
let symbol = file.symbol_by_index(index).expect("a symbol");
assert_eq!(symbol.kind(), SymbolKind::Section);
let at = symbol.section_index().expect("a section symbol is in one");
let name = file.section_by_index(at).expect("a section").name().expect("a name");
(offset, name.to_owned(), reloc.addend())
})
.collect()
}
#[test]
fn a_jump_table_is_read_only_data_whose_cells_the_linker_fills_in() {
let mut text = switching();
text.relocs[1].symbol = ".Lg_j0".to_owned();
text.relocs[1].kind = Reference::Data;
let bytes =
write(&text, &Data::default(), &[], &target(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let rodata = file.section_by_name(".rodata").expect("the table's section");
assert_eq!(rodata.data().expect("the bytes"), &[0; 8]);
assert_eq!(rodata.kind(), SectionKind::ReadOnlyData);
assert!(file.symbols().all(|s| s.name() != Ok(".Lg_j0")), "a table leaves no name behind");
let (at, reloc) = file
.section_by_name(".text")
.expect("the code")
.relocations()
.find(|(at, _)| *at == 17)
.expect("the reference to the table");
assert_eq!((at, reloc.addend()), (17, -4));
let object::RelocationTarget::Symbol(index) = reloc.target() else {
panic!("a reference against something that is not a symbol");
};
let symbol = file.symbol_by_index(index).expect("a symbol");
assert_eq!(symbol.section_index(), Some(rodata.index()));
assert_eq!(symbol.kind(), SymbolKind::Section);
assert_eq!(
cells(&file, ".rodata"),
[(0, ".text".to_owned(), 16), (4, ".text".to_owned(), 25)]
);
}
#[test]
fn a_jump_table_under_data_sections_is_in_a_section_named_after_its_function() {
let sections =
Output { sections: Sections { functions: true, data: true }, ..Output::default() };
let bytes =
write(&switching(), &Data::default(), &[], &target(), sections, &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
assert_eq!(
cells(&file, ".rodata.g"),
[(0, ".text.g".to_owned(), 0), (4, ".text.g".to_owned(), 9)]
);
}
#[test]
fn a_jump_table_outside_the_code_is_refused_on_windows() {
let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Windows, Env::Gnu));
let written = write(
&switching(),
&Data::default(),
&[],
&target,
Output::default(),
&Info::default(),
);
assert!(matches!(written, Err(Error::Refused { .. })), "{written:?}");
}
#[test]
fn debug_sections_on_windows_reach_each_other_by_section_offset() {
let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Windows, Env::Gnu));
let reloc = |at, symbol: &str, bytes| Reloc {
at,
symbol: symbol.to_owned(),
kind: Reference::Address { bytes },
addend: 0,
after: 0,
};
let info = Info {
chunks: vec![
Chunk { name: ".debug_abbrev".to_owned(), bytes: vec![0; 4], relocs: Vec::new() },
Chunk {
name: ".debug_info".to_owned(),
bytes: vec![0; 12],
relocs: vec![reloc(0, ".debug_abbrev", 4), reloc(4, "f", 8)],
},
],
..Info::default()
};
let bytes =
write(&calling("puts"), &Data::default(), &[], &target, Output::default(), &info)
.expect("object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let section = file.section_by_name(".debug_info").expect("the debug section");
let kinds: Vec<_> = section.relocations().map(|(at, reloc)| (at, reloc.flags())).collect();
assert_eq!(
kinds,
[
(0, RelocationFlags::Coff { typ: pe::IMAGE_REL_AMD64_SECREL }),
(4, RelocationFlags::Coff { typ: pe::IMAGE_REL_AMD64_ADDR64 }),
]
);
}
fn variable(name: &str, place: Place) -> Object {
Object {
name: name.to_owned(),
bytes: if carries_no_bytes(&place) { Vec::new() } else { vec![1, 0, 0, 0] },
size: 4,
align: 4,
place,
binding: Binding::Global,
visibility: Visibility::Default,
relocs: Vec::new(),
}
}
fn measured() -> (Text, Data) {
let mut text = calling("puts");
text.labels.push(Marker { name: ".L0".to_owned(), at: 1 });
text.labels.push(Marker { name: ".L1".to_owned(), at: 5 });
let mut table = variable("table", Place::ReadOnly);
table.bytes = vec![0; 8];
table.size = 8;
let apart = |at, to: &str, from: &str| Apart {
object: 0,
at,
to: to.to_owned(),
from: from.to_owned(),
addend: 0,
bytes: 4,
};
let apart = vec![apart(0, ".L1", ".L0"), apart(4, ".L0", ".L1")];
(text, Data { apart, exports: Vec::new(), weak: Vec::new(), objects: vec![table] })
}
#[test]
fn a_distance_between_two_labels_is_a_number_and_not_a_relocation() {
let (text, data) = measured();
let bytes = write(&text, &data, &[], &target(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let section = file.section_by_name(".rodata").expect("a read only section");
assert_eq!(section.relocations().count(), 0);
let image = section.data().expect("the image");
assert_eq!(image[..8], [4, 0, 0, 0, 0xfc, 0xff, 0xff, 0xff]);
}
#[test]
fn a_label_an_image_holds_is_not_in_the_symbol_table() {
let (text, mut data) = measured();
data.apart.clear();
data.objects[0].relocs.push(Reloc {
at: 0,
symbol: ".L1".to_owned(),
kind: Reference::Address { bytes: 8 },
addend: 0,
after: 0,
});
let bytes = write(&text, &data, &[], &target(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let names: Vec<&str> = file.symbols().filter_map(|symbol| symbol.name().ok()).collect();
assert!(names.iter().all(|name| !name.starts_with(".L")), "{names:?}");
let section = file.section_by_name(".rodata").expect("a read only section");
let relocs: Vec<_> = section.relocations().collect();
assert_eq!(relocs.len(), 1);
let (_, reloc) = &relocs[0];
let object::RelocationTarget::Symbol(index) = reloc.target() else {
panic!("a relocation against a symbol, not {reloc:?}");
};
let symbol = file.symbol_by_index(index).expect("a symbol");
assert_eq!(symbol.kind(), SymbolKind::Section);
assert_eq!(reloc.addend(), 5);
}
#[test]
fn a_distance_between_labels_in_two_sections_is_refused() {
let (mut text, data) = measured();
text.bytes.resize(22, 0x90);
text.funcs.push(extent("g".to_owned(), 16, 6, Binding::Global));
text.labels[1].at = 17;
let output =
Output { sections: Sections { functions: true, data: false }, ..Output::default() };
let refused = write(&text, &data, &[], &target(), output, &Info::default());
assert!(matches!(refused, Err(Error::Refused { .. })), "{refused:?}");
}
fn holding(object: Object) -> Vec<u8> {
let data = Data {
apart: Vec::new(),
exports: Vec::new(),
weak: Vec::new(),
objects: vec![object],
};
write(&Text::default(), &data, &[], &target(), Output::default(), &Info::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::Thread { zero: false }, ".tdata"),
(Place::Thread { zero: true }, ".tbss"),
(Place::Named(".init_array".to_owned(), Holds::Written), ".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 carries_no_bytes(&place) { 0 } else { 4 }, "{place:?}");
}
}
#[test]
fn a_thread_local_variable_is_a_thread_local_symbol_and_not_only_a_thread_local_section() {
for place in [Place::Thread { zero: false }, Place::Thread { zero: true }] {
let bytes = holding(variable("counter", place.clone()));
let file = object::File::parse(&bytes[..]).expect("a readable object");
let symbol = file
.symbols()
.find(|symbol| symbol.name() == Ok("counter"))
.unwrap_or_else(|| panic!("{place:?}"));
assert_eq!(symbol.kind(), SymbolKind::Tls, "{place:?}");
}
}
#[test]
fn a_section_of_function_addresses_carries_the_type_the_runtime_looks_for() {
for (name, wanted) in [
(".init_array", elf::SHT_INIT_ARRAY),
(".init_array.00101", elf::SHT_INIT_ARRAY),
(".fini_array", elf::SHT_FINI_ARRAY),
(".preinit_array", elf::SHT_PREINIT_ARRAY),
(".init_arrays", elf::SHT_PROGBITS),
] {
let bytes = holding(variable("x", Place::Named(name.to_owned(), Holds::Written)));
let file = object::File::parse(&bytes[..]).expect("a readable object");
let section = file.section_by_name(name).unwrap_or_else(|| panic!("{name}"));
let SectionFlags::Elf { sh_type, sh_flags } = section.flags() else {
panic!("{name} is not an elf section");
};
assert_eq!(sh_type, wanted, "{name}");
assert!(sh_flags.contains(elf::SHF_ALLOC | elf::SHF_WRITE), "{name}");
}
}
#[test]
fn a_named_section_carries_the_flags_of_what_is_in_it() {
for (name, holds, kind, flags) in [
(".mine", Holds::Written, elf::SHT_PROGBITS, elf::SHF_ALLOC | elf::SHF_WRITE),
(".roz", Holds::ReadOnly, elf::SHT_PROGBITS, elf::SHF_ALLOC),
(".bss..page_aligned", Holds::Zero, elf::SHT_NOBITS, elf::SHF_ALLOC | elf::SHF_WRITE),
] {
let bytes = holding(variable("x", Place::Named(name.to_owned(), holds)));
let file = object::File::parse(&bytes[..]).expect("a readable object");
let section = file.section_by_name(name).unwrap_or_else(|| panic!("{name}"));
let SectionFlags::Elf { sh_type, sh_flags } = section.flags() else {
panic!("{name} is not an elf section");
};
assert_eq!((sh_type, sh_flags), (kind, flags), "{name}");
assert_eq!(section.size(), 4, "{name}");
}
}
#[test]
fn two_variables_in_one_named_section_share_it() {
let objects = vec![
variable("x", Place::Named(".init_array".to_owned(), Holds::Written)),
variable("y", Place::Named(".init_array".to_owned(), Holds::Written)),
];
let data = Data { apart: Vec::new(), exports: Vec::new(), weak: Vec::new(), objects };
let bytes =
write(&Text::default(), &data, &[], &target(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let named: Vec<_> =
file.sections().filter(|section| section.name() == Ok(".init_array")).collect();
assert_eq!(named.len(), 1);
assert_eq!(named[0].size(), 8);
}
#[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"),
(Place::Thread { zero: false }, ".tdata.x"),
(Place::Thread { zero: true }, ".tbss.x"),
] {
let data = Data {
apart: Vec::new(),
exports: Vec::new(),
weak: Vec::new(),
objects: vec![variable("x", place.clone())],
};
let bytes = write(&Text::default(), &data, &[], &target(), sections, &Info::default())
.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 carries_no_bytes(&place) { 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(), Holds::Written);
let objects = vec![variable("m", Place::Merged), variable("n", named)];
let bytes = write(
&Text::default(),
&Data { apart: Vec::new(), exports: Vec::new(), weak: Vec::new(), objects },
&[],
&target(),
sections,
&Info::default(),
)
.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,
after: 0,
}],
..variable("p", Place::Written)
};
let objects = vec![variable("first", Place::Written), pointer];
let bytes = write(
&Text::default(),
&Data { apart: Vec::new(), exports: Vec::new(), weak: Vec::new(), objects },
&[],
&target(),
sections,
&Info::default(),
)
.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 {
apart: Vec::new(),
exports: Vec::new(),
weak: Vec::new(),
objects: vec![variable("first", place.clone()), variable("second", place)],
};
let bytes =
write(&Text::default(), &data, &[], &target(), Output::default(), &Info::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 {
apart: Vec::new(),
exports: Vec::new(),
weak: Vec::new(),
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(), &Info::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,
after: 0,
}],
..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_weak_undefined_name_is_one_the_link_may_leave_unfound() {
let mut text = Text::default();
text.funcs.push(extent("caller".to_owned(), 0, 8, Binding::Global));
text.bytes.resize(8, 0x90);
text.relocs.push(Reloc {
at: 1,
symbol: "hook".to_owned(),
kind: Reference::Call,
addend: -4,
after: 0,
});
let data = Data {
apart: Vec::new(),
exports: Vec::new(),
weak: vec!["hook".to_owned(), "never_called".to_owned()],
objects: vec![],
};
let bytes = write(&text, &data, &[], &target(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let hook = file.symbols().find(|s| s.name() == Ok("hook")).expect("the one called");
assert!(hook.is_undefined(), "nothing here defines it");
assert!(hook.is_weak(), "so the link may leave it alone rather than fail");
let quiet = file.symbols().find(|s| s.name() == Ok("never_called")).expect("the other");
assert!(quiet.is_undefined() && quiet.is_weak(), "{:?}", quiet.flags());
}
#[test]
fn a_thread_local_name_this_file_only_reads_is_still_written_down_as_thread_local() {
let mut text = Text::default();
text.funcs.push(extent("reader".to_owned(), 0, 16, Binding::Global));
text.bytes.resize(16, 0x90);
text.relocs.push(Reloc {
at: 3,
symbol: "flags".to_owned(),
kind: Reference::Thread,
addend: -4,
after: 0,
});
text.relocs.push(Reloc {
at: 10,
symbol: "shared".to_owned(),
kind: Reference::Got,
addend: -4,
after: 0,
});
let data =
Data { apart: Vec::new(), exports: Vec::new(), weak: Vec::new(), objects: vec![] };
let bytes = write(&text, &data, &[], &target(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let flags = file.symbols().find(|s| s.name() == Ok("flags")).expect("the thread-local one");
assert!(flags.is_undefined(), "nothing here defines it");
assert_eq!(flags.kind(), SymbolKind::Tls, "which is what the linker refuses to guess");
let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the ordinary one");
assert!(shared.is_undefined(), "nothing here defines this one either");
assert_eq!(shared.kind(), SymbolKind::Unknown, "and there is nothing to say about it");
}
#[test]
fn a_relocation_counts_from_the_start_of_the_section_and_not_of_the_image_it_is_in() {
let mut data = Data {
apart: Vec::new(),
exports: Vec::new(),
weak: Vec::new(),
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,
after: 0,
}],
..variable("second", Place::Written)
});
let bytes =
write(&Text::default(), &data, &[], &target(), Output::default(), &Info::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 {
apart: Vec::new(),
exports: Vec::new(),
weak: Vec::new(),
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,
ifunc: false,
}];
let bytes = write(
&Text::default(),
&data,
&aliases,
&target(),
Output::default(),
&Info::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,
ifunc: false,
}];
let bytes = write(
&text,
&Data::default(),
&aliases,
&target(),
Output::default(),
&Info::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 an_ifunc_is_a_symbol_of_its_own_type_at_the_resolver() {
let text = calling("puts");
for (binding, bind) in [
(Binding::Global, elf::STB_GLOBAL),
(Binding::Weak, elf::STB_WEAK),
(Binding::Local, elf::STB_LOCAL),
] {
let aliases = [Alias {
name: "g".to_owned(),
target: "f".to_owned(),
binding,
visibility: Visibility::Default,
ifunc: true,
}];
let bytes = write(
&text,
&Data::default(),
&aliases,
&target(),
Output::default(),
&Info::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 resolver");
let g = file.symbols().find(|s| s.name() == Ok("g")).expect("the ifunc");
assert_eq!((g.address(), g.section_index()), (f.address(), f.section_index()));
let SymbolFlags::Elf { st_info, .. } = g.flags() else {
panic!("an ELF symbol");
};
assert_eq!(st_info, bind | elf::STT_GNU_IFUNC, "{binding:?}");
let object::File::Elf64(elf) = &file else { panic!("a 64 bit ELF file") };
let os_abi = elf.elf_header().e_ident.os_abi;
assert_eq!(os_abi, elf::ELFOSABI_GNU, "gas marks a file with an ifunc in it as GNU");
}
}
#[test]
fn an_ifunc_is_refused_on_a_format_without_the_type() {
let aliases = [Alias {
name: "g".to_owned(),
target: "f".to_owned(),
binding: Binding::Global,
visibility: Visibility::Default,
ifunc: true,
}];
let error = write(
&calling("puts"),
&Data::default(),
&aliases,
&windows(),
Output::default(),
&Info::default(),
)
.expect_err("no ifunc on COFF");
assert!(matches!(error, Error::Refused { .. }), "{error:?}");
}
#[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,
ifunc: false,
}];
let error = write(
&Text::default(),
&Data::default(),
&aliases,
&target(),
Output::default(),
&Info::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(),
&Info::default(),
)
.expect_err("no writer");
assert!(matches!(error, Error::Format { .. }), "{error:?}");
}
}
#[test]
fn the_names_a_linker_can_find_are_the_names_the_list_gives() {
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 data = Data {
apart: Vec::new(),
exports: Vec::new(),
weak: Vec::new(),
objects: vec![variable("seen", Place::Written), {
let mut quiet = variable("quiet", Place::Zero);
quiet.binding = Binding::Local;
quiet
}],
};
let aliases = [Alias {
name: "second".to_owned(),
target: "f".to_owned(),
binding: Binding::Global,
visibility: Visibility::Default,
ifunc: false,
}];
let names = defines(&text, &data, &aliases, &target()).expect("a list");
assert_eq!(names, ["f", "shared", "seen", "second"]);
let bytes = write(&text, &data, &aliases, &target(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let found: Vec<String> = file
.symbols()
.filter(|symbol| symbol.is_global() && symbol.is_definition())
.map(|symbol| symbol.name().unwrap_or_default().to_owned())
.collect();
let mut sorted = names.clone();
sorted.sort();
let mut theirs = found;
theirs.sort();
assert_eq!(sorted, theirs, "the list and the file have to say the same thing");
}
fn windows() -> TargetInfo {
TargetInfo::new(Triple::new(Arch::X86_64, Os::Windows, Env::Gnu))
}
fn inline(bytes: &[u8], section: &str, at: usize) -> i32 {
let file = object::File::parse(bytes).expect("a readable object");
let found = file.section_by_name(section).expect("the section").data().expect("the bytes");
i32::from_le_bytes(found[at..at + 4].try_into().expect("four bytes"))
}
#[test]
fn a_windows_target_is_written_rather_than_refused() {
let text = calling("puts");
let bytes =
write(&text, &Data::default(), &[], &windows(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
assert_eq!(file.format(), BinaryFormat::Coff);
let section = file.section_by_name(".text").expect("a text section");
assert_eq!(section.data().expect("the bytes"), &text.bytes[..]);
let names: Vec<&str> = file.symbols().filter_map(|symbol| symbol.name().ok()).collect();
assert!(names.contains(&"f"), "{names:?}");
assert!(names.contains(&"puts"), "{names:?}");
}
#[test]
fn how_far_the_instruction_runs_past_the_hole_is_in_the_relocation_type() {
for (after, typ) in [
(0, pe::IMAGE_REL_AMD64_REL32),
(1, pe::IMAGE_REL_AMD64_REL32_1),
(4, pe::IMAGE_REL_AMD64_REL32_4),
(5, pe::IMAGE_REL_AMD64_REL32_5),
] {
let mut text = calling("puts");
text.relocs[0].addend = -4 - i64::from(after);
text.relocs[0].after = after;
text.bytes.resize(6 + after as usize, 0x90);
text.funcs[0].len = text.bytes.len();
let bytes = write(
&text,
&Data::default(),
&[],
&windows(),
Output::default(),
&Info::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 (_, reloc) = section.relocations().next().expect("the relocation");
assert_eq!(reloc.flags(), RelocationFlags::Coff { typ }, "{after}");
assert_eq!(inline(&bytes, ".text", 1), 0, "{after}");
}
}
#[test]
fn a_distance_the_instruction_did_not_ask_for_stays_in_the_bytes() {
let mut text = calling("puts");
text.relocs[0].addend = 12;
let bytes =
write(&text, &Data::default(), &[], &windows(), Output::default(), &Info::default())
.expect("an object");
assert_eq!(inline(&bytes, ".text", 1), 16, "twelve past the end, which is four past here");
}
#[test]
fn an_address_written_into_an_image_is_the_wide_relocation_here_too() {
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: 0,
after: 0,
}],
..variable("p", Place::Written)
};
let data = Data {
apart: Vec::new(),
exports: Vec::new(),
weak: Vec::new(),
objects: vec![object],
};
let bytes =
write(&Text::default(), &data, &[], &windows(), Output::default(), &Info::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 (_, reloc) = section.relocations().next().expect("the relocation");
let typ = pe::IMAGE_REL_AMD64_ADDR64;
assert_eq!(reloc.flags(), RelocationFlags::Coff { typ });
}
#[test]
fn a_pointer_to_a_variable_elsewhere_is_a_section_the_linker_keeps_one_copy_of() {
let pointer = Object {
bytes: vec![0; 8],
size: 8,
align: 8,
relocs: vec![Reloc {
at: 0,
symbol: "environ".to_owned(),
kind: Reference::Address { bytes: 8 },
addend: 0,
after: 0,
}],
..variable(".refptr.environ", Place::Pointer)
};
let data = Data { objects: vec![pointer], ..Data::default() };
let bytes =
write(&Text::default(), &data, &[], &windows(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let section = file.section_by_name(".rdata$.refptr.environ").expect("a section of its own");
let SectionFlags::Coff { characteristics } = section.flags() else {
panic!("a COFF section has COFF flags");
};
let read_only = pe::IMAGE_SCN_CNT_INITIALIZED_DATA.0 | pe::IMAGE_SCN_MEM_READ.0;
let set_apart = 0x00f0_0000 | pe::IMAGE_SCN_LNK_COMDAT.0;
assert_eq!(characteristics.0 & !set_apart, read_only, "{characteristics:#x}");
assert_ne!(characteristics.0 & pe::IMAGE_SCN_LNK_COMDAT.0, 0, "{characteristics:#x}");
let comdat = file.comdats().next().expect("a group the linker picks one copy of");
assert_eq!(comdat.kind(), ComdatKind::Any);
assert_eq!(comdat.name(), Ok(".refptr.environ"));
let (_, reloc) = section.relocations().next().expect("the address it holds");
assert_eq!(reloc.flags(), RelocationFlags::Coff { typ: pe::IMAGE_REL_AMD64_ADDR64 });
}
#[test]
fn a_name_offered_to_other_dlls_is_an_option_to_the_linker() {
let exports = vec![
Export { name: "offered".to_owned(), kind: Offer::Function },
Export { name: "count".to_owned(), kind: Offer::Variable },
Export { name: "kept".to_owned(), kind: Offer::Hidden },
];
let data = Data { exports, ..Data::default() };
let bytes =
write(&Text::default(), &data, &[], &windows(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let section = file.section_by_name(".drectve").expect("the options section");
assert_eq!(
section.data().expect("the options"),
b" -export:offered -export:count,data -exclude-symbols:kept"
);
let SectionFlags::Coff { characteristics } = section.flags() else {
panic!("a COFF section has COFF flags");
};
let removed = pe::IMAGE_SCN_LNK_INFO.0 | pe::IMAGE_SCN_LNK_REMOVE.0;
assert_eq!(characteristics.0 & removed, removed, "{characteristics:#x}");
let none = write(
&Text::default(),
&Data::default(),
&[],
&windows(),
Output::default(),
&Info::default(),
)
.expect("an object");
let file = object::File::parse(&none[..]).expect("a readable object");
assert!(file.section_by_name(".drectve").is_none(), "nothing to say is no section");
}
#[test]
fn a_variable_the_loader_writes_into_is_read_only_data_here() {
for local in [false, true] {
let data = Data {
apart: Vec::new(),
exports: Vec::new(),
weak: Vec::new(),
objects: vec![variable("p", Place::RelocReadOnly { local })],
};
let bytes = write(
&Text::default(),
&data,
&[],
&windows(),
Output::default(),
&Info::default(),
)
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
assert!(file.section_by_name(".rdata").is_some(), "{local}");
assert!(file.section_by_name(".data.rel.ro.local").is_none(), "{local}");
}
}
#[test]
fn the_sections_only_elf_reads_are_left_out_rather_than_written_empty() {
let text = calling("puts");
let output = Output { property: Property { features: 3 }, ..Output::default() };
let bytes = write(&text, &Data::default(), &[], &windows(), output, &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
assert!(file.section_by_name(".note.GNU-stack").is_none());
assert!(file.section_by_name(".note.gnu.property").is_none());
}
#[test]
fn what_this_format_cannot_say_is_refused_by_name() {
let ordinary = Text::default();
let empty = Data::default();
let mut thread = Data::default();
thread.objects.push(variable("t", Place::Thread { zero: true }));
let mut gathered = Data::default();
gathered
.objects
.push(variable("c", Place::Named(".init_array".to_owned(), Holds::Written)));
let mut table = calling("puts");
table.relocs[0].kind = Reference::Got;
let mut room = calling("puts");
room.funcs[0].patch = Some(Patch { at: 0, before: 0 });
let cases: [(&str, &Text, &Data); 4] = [
("thread-local", &ordinary, &thread),
("startup", &ordinary, &gathered),
("table", &table, &empty),
("patcher", &room, &empty),
];
for (what, text, data) in cases {
let error = write(text, data, &[], &windows(), Output::default(), &Info::default())
.expect_err("something this format cannot write");
assert!(matches!(error, Error::Refused { .. }), "{what}: {error:?}");
}
}
#[test]
fn a_thread_local_variable_goes_in_the_tls_section() {
let mut thread = Data::default();
thread.objects.push(variable("t", Place::Thread { zero: false }));
let bytes =
write(&Text::default(), &thread, &[], &windows(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
assert!(file.section_by_name(".tls$").is_some());
}
#[test]
fn a_visibility_this_format_cannot_keep_changes_nothing_rather_than_failing() {
let mut text = calling("puts");
text.funcs[0].visibility = Visibility::Hidden;
let bytes =
write(&text, &Data::default(), &[], &windows(), Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
let symbol = file.symbols().find(|symbol| symbol.name() == Ok("f")).expect("the function");
assert!(symbol.is_global(), "a name others may use either way");
}
#[test]
fn the_names_a_linker_can_find_are_the_same_list_on_either_format() {
let text = calling("puts");
let data = Data {
apart: Vec::new(),
exports: Vec::new(),
weak: Vec::new(),
objects: vec![variable("shared", Place::Written)],
};
let theirs = defines(&text, &data, &[], &windows()).expect("a list");
assert_eq!(theirs, defines(&text, &data, &[], &target()).expect("a list"));
}
#[test]
fn a_platform_this_does_not_write_has_no_list_of_names_either() {
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 = defines(&text, &Data::default(), &[], &TargetInfo::new(triple))
.expect_err("no writer");
assert!(matches!(error, Error::Format { .. }), "{error:?}");
}
}
fn i386() -> TargetInfo {
TargetInfo::new(Triple::new(Arch::X86, Os::Linux, Env::Gnu))
}
#[test]
fn a_compilation_for_i386_is_32_bit_elf_with_rel_relocations() {
let mut text = calling("puts");
text.bytes.splice(0..0, [0x90, 0x90, 0x90]);
text.funcs[0].start = 3;
text.funcs[0].patch = Some(Patch { at: 0, before: 3 });
text.relocs[0].at = 4;
let data = Data {
objects: vec![Object {
name: "p".to_owned(),
bytes: vec![0; 4],
size: 4,
align: 4,
place: Place::Written,
binding: Binding::Global,
visibility: Visibility::Default,
relocs: vec![Reloc {
at: 0,
symbol: "x".to_owned(),
kind: Reference::Address { bytes: 4 },
addend: 12,
after: 0,
}],
}],
..Data::default()
};
let property = Property { features: Property::IBT | Property::SHSTK };
let output = Output { property, ..Output::default() };
let bytes = write(&text, &data, &[], &i386(), output, &Info::default()).expect("an object");
let file = object::read::elf::ElfFile32::<Endianness>::parse(&bytes[..]).expect("readable");
assert_eq!(file.architecture(), Architecture::I386);
assert_eq!(file.elf_header().e_machine.get(Endianness::Little), elf::EM_386);
assert!(file.section_by_name(".rela.text").is_none(), "i386 has no addend field");
let code = file.section_by_name(".text").expect("a text section");
let [(at, reloc)] = &code.relocations().collect::<Vec<_>>()[..] else {
panic!("one relocation in the text")
};
assert_eq!(*at, 4);
assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_386_PLT32 });
assert!(reloc.has_implicit_addend());
assert_eq!(&code.data().expect("the bytes")[4..8], &(-4i32).to_le_bytes());
let variable = file.section_by_name(".data").expect("a data section");
let [(at, reloc)] = &variable.relocations().collect::<Vec<_>>()[..] else {
panic!("one relocation in the data")
};
assert_eq!(*at, 0);
assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_386_32 });
assert_eq!(variable.data().expect("the bytes"), &12u32.to_le_bytes());
let record = file.section_by_name(PATCHABLE).expect("a record of the room");
assert_eq!(record.size(), 4);
assert_eq!(record.align(), 4);
let index = code.index().0;
assert_eq!(record.elf_section_header().sh_link.get(Endianness::Little) as usize, index);
let [(_, reloc)] = &record.relocations().collect::<Vec<_>>()[..] else {
panic!("one address in the record")
};
assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_386_32 });
let note = file.section_by_name(".note.gnu.property").expect("the note");
assert_eq!(note.align(), 4);
let want: Vec<u8> = [
4u32,
12,
5,
u32::from_le_bytes(*b"GNU\0"),
Property::X86_FEATURES,
4,
Property::IBT | Property::SHSTK,
]
.iter()
.flat_map(|word| word.to_le_bytes())
.collect();
assert_eq!(note.data().expect("the bytes"), &want[..]);
}
#[test]
fn an_i386_address_less_three_gigabytes_wraps_in_its_four_bytes() {
let object = |bytes: u8| Object {
name: "cr3".to_owned(),
bytes: vec![0; usize::from(bytes)],
size: u64::from(bytes),
align: u64::from(bytes),
place: Place::Written,
binding: Binding::Global,
visibility: Visibility::Default,
relocs: vec![Reloc {
at: 0,
symbol: "swapper_pg_dir".to_owned(),
kind: Reference::Address { bytes },
addend: -0xC000_0000,
after: 0,
}],
};
let data = Data { objects: vec![object(4)], ..Data::default() };
let bytes =
write(&Text::default(), &data, &[], &i386(), Output::default(), &Info::default())
.expect("an object");
let file = object::read::elf::ElfFile32::<Endianness>::parse(&bytes[..]).expect("readable");
let variable = file.section_by_name(".data").expect("a data section");
assert_eq!(variable.data().expect("the bytes"), &0x4000_0000u32.to_le_bytes());
let data = Data { objects: vec![object(2)], ..Data::default() };
let refused =
write(&Text::default(), &data, &[], &i386(), Output::default(), &Info::default());
assert!(refused.is_err(), "two bytes cannot hold a name less three gigabytes");
}
#[test]
fn an_i386_debug_section_keeps_its_addends_in_the_bytes_under_gz() {
let info = Info {
chunks: vec![Chunk {
name: ".debug_info".to_owned(),
bytes: vec![0; 64],
relocs: vec![Reloc {
at: 8,
symbol: "f".to_owned(),
kind: Reference::Address { bytes: 4 },
addend: 7,
after: 0,
}],
}],
compress: Compress::Zlib,
};
let bytes =
write(&calling("puts"), &Data::default(), &[], &i386(), Output::default(), &info)
.expect("an object");
let file = object::read::elf::ElfFile32::<Endianness>::parse(&bytes[..]).expect("readable");
let section = file.section_by_name(".debug_info").expect("the debug section");
let packed =
SectionFlags::Elf { sh_type: elf::SHT_PROGBITS, sh_flags: elf::SHF_COMPRESSED };
assert_ne!(section.flags(), packed);
let data = section.data().expect("the bytes");
assert_eq!(data.len(), 64);
assert_eq!(&data[8..12], &7u32.to_le_bytes());
}
fn i386_windows() -> TargetInfo {
TargetInfo::new(Triple::new(Arch::X86, Os::Windows, Env::Gnu))
}
#[test]
fn a_compilation_for_i386_windows_is_coff_with_decorated_names() {
let mut text = calling("puts");
text.bytes.extend([0xe8, 0, 0, 0, 0, 0xc3]);
text.funcs.push(extent("@fast@8".to_owned(), 6, 6, Binding::Global));
text.relocs.push(Reloc {
at: 7,
symbol: "__imp_GetTickCount".to_owned(),
kind: Reference::Call,
addend: -4,
after: 0,
});
let data = Data {
objects: vec![Object {
name: "p".to_owned(),
bytes: vec![0; 12],
size: 12,
align: 4,
place: Place::Written,
binding: Binding::Global,
visibility: Visibility::Default,
relocs: vec![
Reloc {
at: 0,
symbol: "x".to_owned(),
kind: Reference::Address { bytes: 4 },
addend: 12,
after: 0,
},
Reloc {
at: 4,
symbol: "f".to_owned(),
kind: Reference::Image,
addend: 0,
after: 0,
},
Reloc {
at: 8,
symbol: "x".to_owned(),
kind: Reference::Away,
addend: 0,
after: 0,
},
],
}],
..Data::default()
};
let aliases = [Alias {
name: "g".to_owned(),
target: "f".to_owned(),
binding: Binding::Global,
visibility: Visibility::Default,
ifunc: false,
}];
let target = i386_windows();
let bytes = write(&text, &data, &aliases, &target, Output::default(), &Info::default())
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
assert_eq!(file.format(), BinaryFormat::Coff);
assert_eq!(file.architecture(), Architecture::I386);
assert!(!file.is_64());
let named = |name: &str| file.symbol_by_name(name).is_some();
for name in ["_f", "@fast@8", "_p", "_g", "_puts", "__imp__GetTickCount", "_x"] {
assert!(named(name), "{name}");
}
for name in ["f", "p", "puts", "_@fast@8", "___imp_GetTickCount"] {
assert!(!named(name), "{name}");
}
let code = file.section_by_name(".text").expect("a text section");
let relocs: Vec<_> = code.relocations().collect();
assert_eq!(relocs.len(), 2);
for (at, reloc) in &relocs {
assert_eq!(reloc.flags(), RelocationFlags::Coff { typ: pe::IMAGE_REL_I386_REL32 });
let at = *at as usize;
assert_eq!(&code.data().expect("the bytes")[at..at + 4], &0i32.to_le_bytes());
}
let variable = file.section_by_name(".data").expect("a data section");
let types: Vec<_> = variable
.relocations()
.map(|(at, reloc)| match reloc.flags() {
RelocationFlags::Coff { typ } => (at, typ),
flags => panic!("{flags:?}"),
})
.collect();
assert_eq!(
types,
[
(0, pe::IMAGE_REL_I386_DIR32),
(4, pe::IMAGE_REL_I386_DIR32NB),
(8, pe::IMAGE_REL_I386_REL32)
]
);
let image = variable.data().expect("the bytes");
assert_eq!(&image[0..4], &12u32.to_le_bytes());
assert_eq!(&image[8..12], &4u32.to_le_bytes());
let listed = defines(&text, &data, &aliases, &target).expect("a list");
assert_eq!(listed, ["_f", "@fast@8", "_p", "_g"]);
}
#[test]
fn an_i386_windows_object_has_no_unwind_table() {
let mut text = calling("puts");
text.unwind.bytes = vec![0; 12];
let bytes = write(
&text,
&Data::default(),
&[],
&i386_windows(),
Output::default(),
&Info::default(),
)
.expect("an object");
let file = object::File::parse(&bytes[..]).expect("a readable object");
assert!(file.section_by_name(".pdata").is_none());
assert!(file.section_by_name(".xdata").is_none());
assert!(file.section_by_name(".eh_frame").is_none());
}
#[test]
fn an_i386_windows_object_says_it_is_safe_for_safeseh() {
let write_for = |target: &TargetInfo| {
write(
&calling("puts"),
&Data::default(),
&[],
target,
Output::default(),
&Info::default(),
)
.expect("an object")
};
let bytes = write_for(&i386_windows());
let file = object::File::parse(&bytes[..]).expect("a readable object");
let feat = file.symbol_by_name("@feat.00").expect("the feature symbol");
let coff = object::read::coff::CoffFile::<&[u8]>::parse(&bytes[..]).expect("COFF");
let raw = coff.symbol_by_name("@feat.00").expect("the feature symbol");
assert_eq!(object::read::coff::Symbol::value(raw.coff_symbol()), 1);
assert_eq!(feat.section(), object::SymbolSection::Absolute);
assert!(feat.is_local());
let bytes = write_for(&windows());
let file = object::File::parse(&bytes[..]).expect("a readable object");
assert!(file.symbol_by_name("@feat.00").is_none());
}
#[test]
fn i386_windows_has_no_eight_byte_or_table_relocations() {
for kind in [
Reference::Address { bytes: 8 },
Reference::AwayWide,
Reference::Got,
Reference::GotOffset,
Reference::Slot,
Reference::Thread,
] {
assert_eq!(Flavour::Coff.reloc(Architecture::I386, kind, 0), None, "{kind:?}");
}
assert_eq!(
Flavour::Coff.reloc(Architecture::I386, Reference::Section, 0),
Some(RelocationFlags::Coff { typ: pe::IMAGE_REL_I386_SECREL })
);
assert_eq!(
Flavour::Coff.reloc(Architecture::I386, Reference::Signed, 0),
Some(RelocationFlags::Coff { typ: pe::IMAGE_REL_I386_DIR32 }),
"an address an instruction holds"
);
}
}