use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use crate::error::LinkError;
use crate::image::{Image, OutputSection};
use crate::object::{Object, Width};
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct Linker {
base_address: u64,
entry: Option<String>,
}
impl Linker {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn base_address(mut self, address: u64) -> Self {
self.base_address = address;
self
}
#[must_use]
pub fn entry(mut self, symbol: impl Into<String>) -> Self {
self.entry = Some(symbol.into());
self
}
pub fn link(&self, objects: &[Object]) -> Result<Image, LinkError> {
let (mut sections, contrib) = merge_sections(objects)?;
place_sections(&mut sections, self.base_address)?;
let symbols = resolve_symbols(objects, §ions, &contrib)?;
patch_relocations(objects, &mut sections, &symbols, &contrib)?;
let entry = resolve_entry(self.entry.as_deref(), &symbols)?;
Ok(Image {
sections,
symbols,
entry,
})
}
}
type SectionIndex<'a> = BTreeMap<&'a str, usize>;
type Contributions<'a> = Vec<BTreeMap<&'a str, u64>>;
fn merge_sections(
objects: &[Object],
) -> Result<(Vec<OutputSection>, Contributions<'_>), LinkError> {
let mut index: SectionIndex<'_> = BTreeMap::new();
let mut sections: Vec<OutputSection> = Vec::new();
let mut contrib: Contributions<'_> = Vec::with_capacity(objects.len());
for object in objects {
let mut object_contrib: BTreeMap<&str, u64> = BTreeMap::new();
for section in &object.sections {
let name = section.name.as_str();
let slot = match index.get(name) {
Some(&i) => i,
None => {
let i = sections.len();
index.insert(name, i);
sections.push(OutputSection {
name: section.name.clone(),
address: 0,
data: Vec::new(),
});
i
}
};
let base = sections[slot].data.len() as u64;
sections[slot].data.extend_from_slice(§ion.data);
object_contrib.insert(name, base);
}
contrib.push(object_contrib);
}
Ok((sections, contrib))
}
fn place_sections(sections: &mut [OutputSection], base_address: u64) -> Result<(), LinkError> {
let mut cursor = base_address;
for section in sections.iter_mut() {
section.address = cursor;
cursor = cursor
.checked_add(section.data.len() as u64)
.ok_or(LinkError::LayoutOverflow)?;
}
Ok(())
}
fn resolve_symbols(
objects: &[Object],
sections: &[OutputSection],
contrib: &Contributions<'_>,
) -> Result<BTreeMap<String, u64>, LinkError> {
let index = section_index(sections);
let mut symbols: BTreeMap<String, u64> = BTreeMap::new();
for (object, object_contrib) in objects.iter().zip(contrib) {
for symbol in &object.symbols {
let address = symbol_address(
object,
object_contrib,
&index,
sections,
&symbol.section,
symbol.offset,
)?;
if symbols.insert(symbol.name.clone(), address).is_some() {
return Err(LinkError::DuplicateSymbol {
name: symbol.name.clone(),
});
}
}
}
Ok(symbols)
}
fn patch_relocations(
objects: &[Object],
sections: &mut [OutputSection],
symbols: &BTreeMap<String, u64>,
contrib: &Contributions<'_>,
) -> Result<(), LinkError> {
let index: BTreeMap<String, usize> = sections
.iter()
.enumerate()
.map(|(i, section)| (section.name.clone(), i))
.collect();
for (object, object_contrib) in objects.iter().zip(contrib) {
for relocation in &object.relocations {
let base = section_base(object, object_contrib, &relocation.section)?;
let slot = index
.get(relocation.section.as_str())
.copied()
.ok_or_else(|| LinkError::InvalidSection {
object: object.name.clone(),
section: relocation.section.clone(),
})?;
let section_len = object
.section_data(&relocation.section)
.map_or(0, <[u8]>::len) as u64;
let end = relocation
.offset
.checked_add(relocation.width.bytes() as u64)
.filter(|&end| end <= section_len)
.ok_or_else(|| LinkError::RelocationOutOfRange {
object: object.name.clone(),
section: relocation.section.clone(),
offset: relocation.offset,
})?;
let target = symbols
.get(relocation.target.as_str())
.copied()
.ok_or_else(|| LinkError::UndefinedSymbol {
name: relocation.target.clone(),
object: object.name.clone(),
})?;
let value = i128::from(target) + i128::from(relocation.addend);
if value < 0 || value > relocation.width.max_value() {
return Err(LinkError::RelocationOverflow {
target: relocation.target.clone(),
width: relocation.width,
});
}
let start = (base + relocation.offset) as usize;
let bytes = &mut sections[slot].data[start..(base + end) as usize];
match relocation.width {
Width::U32 => bytes.copy_from_slice(&(value as u32).to_le_bytes()),
Width::U64 => bytes.copy_from_slice(&(value as u64).to_le_bytes()),
}
}
}
Ok(())
}
fn resolve_entry(
entry: Option<&str>,
symbols: &BTreeMap<String, u64>,
) -> Result<Option<u64>, LinkError> {
match entry {
Some(name) => symbols
.get(name)
.copied()
.map(Some)
.ok_or_else(|| LinkError::UndefinedEntry { name: name.into() }),
None => Ok(None),
}
}
fn section_index(sections: &[OutputSection]) -> SectionIndex<'_> {
sections
.iter()
.enumerate()
.map(|(i, section)| (section.name.as_str(), i))
.collect()
}
fn section_base(
object: &Object,
object_contrib: &BTreeMap<&str, u64>,
section: &str,
) -> Result<u64, LinkError> {
object_contrib
.get(section)
.copied()
.ok_or_else(|| LinkError::InvalidSection {
object: object.name.clone(),
section: section.into(),
})
}
fn symbol_address(
object: &Object,
object_contrib: &BTreeMap<&str, u64>,
index: &SectionIndex<'_>,
sections: &[OutputSection],
section: &str,
offset: u64,
) -> Result<u64, LinkError> {
let base = section_base(object, object_contrib, section)?;
let slot = index
.get(section)
.copied()
.ok_or_else(|| LinkError::InvalidSection {
object: object.name.clone(),
section: section.into(),
})?;
sections[slot]
.address
.checked_add(base)
.and_then(|a| a.checked_add(offset))
.ok_or(LinkError::LayoutOverflow)
}
pub fn link(objects: &[Object]) -> Result<Image, LinkError> {
Linker::new().link(objects)
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
reason = "tests build known-valid links, so they cannot fail"
)]
mod tests {
use super::{Linker, link};
use crate::error::LinkError;
use crate::object::{Object, Width};
#[test]
fn test_same_named_sections_merge_in_object_order() {
let mut a = Object::new("a");
a.section(".text", [1, 2]);
let mut b = Object::new("b");
b.section(".text", [3, 4, 5]);
let image = link(&[a, b]).unwrap();
assert_eq!(image.section(".text").unwrap().data(), &[1, 2, 3, 4, 5]);
}
#[test]
fn test_sections_are_placed_end_to_end_from_the_base() {
let mut obj = Object::new("o");
obj.section(".text", [0u8; 4]);
obj.section(".data", [0u8; 8]);
let image = Linker::new().base_address(0x1000).link(&[obj]).unwrap();
assert_eq!(image.section(".text").unwrap().address(), 0x1000);
assert_eq!(image.section(".data").unwrap().address(), 0x1004);
}
#[test]
fn test_symbol_resolves_to_section_plus_contribution_plus_offset() {
let mut a = Object::new("a");
a.section(".text", [0u8; 4]);
a.define("a_fn", ".text", 2);
let mut b = Object::new("b");
b.section(".text", [0u8; 4]);
b.define("b_fn", ".text", 1);
let image = Linker::new().base_address(0x10).link(&[a, b]).unwrap();
assert_eq!(image.symbol("a_fn"), Some(0x12));
assert_eq!(image.symbol("b_fn"), Some(0x10 + 4 + 1));
}
#[test]
fn test_relocation_is_patched_with_the_target_address() {
let mut code = Object::new("code");
code.section(".text", [0u8; 8]);
code.define("target", ".text", 4);
let mut data = Object::new("data");
data.section(".data", [0u8; 4]);
data.relocate(".data", 0, "target", Width::U32, 0);
let image = Linker::new()
.base_address(0x100)
.link(&[code, data])
.unwrap();
let slot = image.section(".data").unwrap().data();
assert_eq!(u32::from_le_bytes(slot.try_into().unwrap()), 0x104);
}
#[test]
fn test_relocation_addend_is_added_to_the_address() {
let mut obj = Object::new("o");
obj.section(".text", [0u8; 8]);
obj.define("base", ".text", 0);
obj.section(".data", [0u8; 8]);
obj.relocate(".data", 0, "base", Width::U64, 16);
let image = Linker::new().base_address(0x1000).link(&[obj]).unwrap();
let slot = image.section(".data").unwrap().data();
assert_eq!(u64::from_le_bytes(slot.try_into().unwrap()), 0x1010);
}
#[test]
fn test_duplicate_symbol_is_rejected() {
let mut a = Object::new("a");
a.section(".text", [0u8; 1]);
a.define("dup", ".text", 0);
let mut b = Object::new("b");
b.section(".text", [0u8; 1]);
b.define("dup", ".text", 0);
assert_eq!(
link(&[a, b]),
Err(LinkError::DuplicateSymbol { name: "dup".into() })
);
}
#[test]
fn test_undefined_relocation_target_is_rejected() {
let mut obj = Object::new("o");
obj.section(".data", [0u8; 8]);
obj.relocate(".data", 0, "nowhere", Width::U64, 0);
assert_eq!(
link(&[obj]),
Err(LinkError::UndefinedSymbol {
name: "nowhere".into(),
object: "o".into(),
})
);
}
#[test]
fn test_undefined_entry_is_rejected() {
let mut obj = Object::new("o");
obj.section(".text", [0u8; 1]);
assert_eq!(
Linker::new().entry("_start").link(&[obj]),
Err(LinkError::UndefinedEntry {
name: "_start".into()
})
);
}
#[test]
fn test_symbol_in_a_missing_section_is_rejected() {
let mut obj = Object::new("o");
obj.section(".text", [0u8; 4]);
obj.define("ghost", ".rodata", 0);
assert_eq!(
link(&[obj]),
Err(LinkError::InvalidSection {
object: "o".into(),
section: ".rodata".into(),
})
);
}
#[test]
fn test_relocation_past_the_section_end_is_rejected() {
let mut obj = Object::new("o");
obj.section(".text", [0u8; 4]);
obj.define("t", ".text", 0);
obj.section(".data", [0u8; 4]);
obj.relocate(".data", 1, "t", Width::U64, 0);
assert_eq!(
link(&[obj]),
Err(LinkError::RelocationOutOfRange {
object: "o".into(),
section: ".data".into(),
offset: 1,
})
);
}
#[test]
fn test_address_too_large_for_width_is_rejected() {
let mut obj = Object::new("o");
obj.section(".text", [0u8; 4]);
obj.define("t", ".text", 0);
obj.section(".data", [0u8; 4]);
obj.relocate(".data", 0, "t", Width::U32, 0);
assert_eq!(
Linker::new().base_address(0x1_0000_0000).link(&[obj]),
Err(LinkError::RelocationOverflow {
target: "t".into(),
width: Width::U32,
})
);
}
#[test]
fn test_negative_addend_below_zero_is_rejected() {
let mut obj = Object::new("o");
obj.section(".text", [0u8; 4]);
obj.define("t", ".text", 0); obj.section(".data", [0u8; 8]);
obj.relocate(".data", 0, "t", Width::U64, -1);
assert_eq!(
link(&[obj]),
Err(LinkError::RelocationOverflow {
target: "t".into(),
width: Width::U64,
})
);
}
#[test]
fn test_negative_addend_within_range_resolves() {
let mut obj = Object::new("o");
obj.section(".text", [0u8; 8]);
obj.define("t", ".text", 4); obj.section(".data", [0u8; 8]);
obj.relocate(".data", 0, "t", Width::U64, -4);
let image = Linker::new().base_address(0x100).link(&[obj]).unwrap();
let slot = image.section(".data").unwrap().data();
assert_eq!(u64::from_le_bytes(slot.try_into().unwrap()), 0x100);
}
#[test]
fn test_link_is_deterministic() {
let build = || {
let mut a = Object::new("a");
a.section(".text", [1, 2, 3]);
a.define("a", ".text", 0);
let mut b = Object::new("b");
b.section(".data", [4, 5]);
b.define("b", ".data", 1);
[a, b]
};
assert_eq!(link(&build()).unwrap(), link(&build()).unwrap());
}
}