use alloc::string::String;
use alloc::vec::Vec;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Width {
U32,
U64,
}
impl Width {
#[must_use]
pub const fn bytes(self) -> usize {
match self {
Width::U32 => 4,
Width::U64 => 8,
}
}
pub(crate) const fn max_value(self) -> i128 {
match self {
Width::U32 => u32::MAX as i128,
Width::U64 => u64::MAX as i128,
}
}
}
pub(crate) struct Section {
pub(crate) name: String,
pub(crate) data: Vec<u8>,
}
pub(crate) struct SymbolDef {
pub(crate) name: String,
pub(crate) section: String,
pub(crate) offset: u64,
}
pub(crate) struct Relocation {
pub(crate) section: String,
pub(crate) offset: u64,
pub(crate) target: String,
pub(crate) width: Width,
pub(crate) addend: i64,
}
pub struct Object {
pub(crate) name: String,
pub(crate) sections: Vec<Section>,
pub(crate) symbols: Vec<SymbolDef>,
pub(crate) relocations: Vec<Relocation>,
}
impl Object {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Object {
name: name.into(),
sections: Vec::new(),
symbols: Vec::new(),
relocations: Vec::new(),
}
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
pub fn section(&mut self, name: impl Into<String>, data: impl Into<Vec<u8>>) -> u64 {
let name = name.into();
let data = data.into();
if let Some(section) = self.sections.iter_mut().find(|s| s.name == name) {
let start = section.data.len() as u64;
section.data.extend_from_slice(&data);
start
} else {
self.sections.push(Section { name, data });
0
}
}
pub fn define(&mut self, name: impl Into<String>, section: impl Into<String>, offset: u64) {
self.symbols.push(SymbolDef {
name: name.into(),
section: section.into(),
offset,
});
}
pub fn relocate(
&mut self,
section: impl Into<String>,
offset: u64,
target: impl Into<String>,
width: Width,
addend: i64,
) {
self.relocations.push(Relocation {
section: section.into(),
offset,
target: target.into(),
width,
addend,
});
}
pub(crate) fn section_data(&self, name: &str) -> Option<&[u8]> {
self.sections
.iter()
.find(|s| s.name == name)
.map(|s| s.data.as_slice())
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
reason = "tests assert on known values; a missing one should fail the test loudly"
)]
mod tests {
use super::{Object, Width};
#[test]
fn test_width_bytes_match_the_variant() {
assert_eq!(Width::U32.bytes(), 4);
assert_eq!(Width::U64.bytes(), 8);
}
#[test]
fn test_new_object_is_named_and_empty() {
let obj = Object::new("unit");
assert_eq!(obj.name(), "unit");
assert!(obj.sections.is_empty());
assert!(obj.symbols.is_empty());
assert!(obj.relocations.is_empty());
}
#[test]
fn test_section_returns_zero_for_a_fresh_section() {
let mut obj = Object::new("o");
assert_eq!(obj.section(".text", [1, 2, 3]), 0);
}
#[test]
fn test_section_appends_to_an_existing_name_and_returns_the_join_offset() {
let mut obj = Object::new("o");
assert_eq!(obj.section(".data", [0u8; 4]), 0);
assert_eq!(obj.section(".data", [9u8; 2]), 4);
assert_eq!(obj.section_data(".data").unwrap().len(), 6);
assert_eq!(obj.sections.len(), 1);
}
#[test]
fn test_distinct_names_make_distinct_sections() {
let mut obj = Object::new("o");
let _ = obj.section(".text", [0u8; 3]);
let _ = obj.section(".data", [0u8; 5]);
assert_eq!(obj.sections.len(), 2);
assert_eq!(obj.section_data(".text").unwrap().len(), 3);
assert_eq!(obj.section_data(".data").unwrap().len(), 5);
}
#[test]
fn test_section_data_is_none_for_an_unknown_name() {
let obj = Object::new("o");
assert!(obj.section_data(".bss").is_none());
}
}