use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
#[derive(Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OutputSection {
pub(crate) name: String,
pub(crate) address: u64,
pub(crate) data: Vec<u8>,
}
impl OutputSection {
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub const fn address(&self) -> u64 {
self.address
}
#[must_use]
pub fn data(&self) -> &[u8] {
&self.data
}
#[must_use]
pub fn len(&self) -> usize {
self.data.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Image {
pub(crate) sections: Vec<OutputSection>,
pub(crate) symbols: BTreeMap<String, u64>,
pub(crate) entry: Option<u64>,
}
impl Image {
#[must_use]
pub fn sections(&self) -> &[OutputSection] {
&self.sections
}
#[must_use]
pub fn section(&self, name: &str) -> Option<&OutputSection> {
self.sections.iter().find(|s| s.name == name)
}
#[must_use]
pub fn symbol(&self, name: &str) -> Option<u64> {
self.symbols.get(name).copied()
}
pub fn symbols(&self) -> impl Iterator<Item = (&str, u64)> {
self.symbols
.iter()
.map(|(name, &addr)| (name.as_str(), addr))
}
#[must_use]
pub const fn entry(&self) -> Option<u64> {
self.entry
}
#[must_use]
pub fn len(&self) -> usize {
self.sections.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.sections.is_empty()
}
}
impl fmt::Display for Image {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.entry {
Some(addr) => writeln!(f, "entry = {addr:#018x}")?,
None => writeln!(f, "entry = none")?,
}
for section in &self.sections {
writeln!(
f,
"{} @ {:#018x} ({} bytes)",
section.name,
section.address,
section.data.len()
)?;
}
if !self.symbols.is_empty() {
f.write_str("symbols:\n")?;
for (name, addr) in &self.symbols {
writeln!(f, " {addr:#018x} {name}")?;
}
}
Ok(())
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
reason = "tests build known-valid links, so they cannot fail"
)]
mod tests {
use crate::{Linker, Object, link};
use alloc::string::ToString;
use alloc::vec::Vec;
#[test]
fn test_output_section_accessors_report_layout() {
let mut obj = Object::new("o");
obj.section(".text", [1, 2, 3, 4]);
let image = link(&[obj]).unwrap();
let text = image.section(".text").unwrap();
assert_eq!(text.name(), ".text");
assert_eq!(text.address(), 0);
assert_eq!(text.data(), &[1, 2, 3, 4]);
assert_eq!(text.len(), 4);
assert!(!text.is_empty());
}
#[test]
fn test_symbols_iterate_in_name_order() {
let mut obj = Object::new("o");
obj.section(".text", [0u8; 8]);
obj.define("zebra", ".text", 0);
obj.define("alpha", ".text", 4);
let image = link(&[obj]).unwrap();
let names: Vec<&str> = image.symbols().map(|(n, _)| n).collect();
assert_eq!(names, ["alpha", "zebra"]);
}
#[test]
fn test_display_renders_a_link_map() {
let mut obj = Object::new("o");
obj.section(".text", [0u8; 4]);
obj.define("main", ".text", 0);
let image = Linker::new().entry("main").link(&[obj]).unwrap();
let map = image.to_string();
assert!(map.contains("entry = 0x0000000000000000"));
assert!(map.contains(".text @ 0x0000000000000000 (4 bytes)"));
assert!(map.contains("0x0000000000000000 main"));
}
#[test]
fn test_display_without_entry_says_none() {
let mut obj = Object::new("o");
obj.section(".text", [0u8; 1]);
let image = link(&[obj]).unwrap();
assert!(image.to_string().contains("entry = none"));
}
#[test]
fn test_empty_link_is_an_empty_image() {
let image = link(&[]).unwrap();
assert!(image.is_empty());
assert_eq!(image.len(), 0);
assert_eq!(image.entry(), None);
assert_eq!(image.symbols().count(), 0);
}
}