From c3f3ea58e8965365c67f4e602c5b7a0bc8f0f1b0 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 19 May 2024 22:49:40 -0600 Subject: [PATCH] Support `block_relocations` and `add_relocations` in `config.yml` This allows more granular control over generated relocations. Also optimizes relocation address validity checks, leading to ~20% faster relocation analysis. Config example: ``` block_relocations: # Block any relocation pointing to this address. - target: .data:0x80130140 # Block any relocation originating from this address. - source: .text:0x80047160 # (optional) End address to make it a range. end: .text:0x800471A8 add_relocations: # Inserts or overwrites a relocation. # From: `subi r3, r3, 0x7657` # To: `li r3, mesWInsert-0x1@sda21` - source: .text:0x800473F4 type: sda21 target: mesWInsert addend: -1 ``` Resolves #33 Resolves #52 --- src/analysis/tracker.rs | 71 ++++++++--------- src/cmd/dol.rs | 172 +++++++++++++++++++++++++++++----------- src/obj/addresses.rs | 100 +++++++++++++++++++++++ src/obj/mod.rs | 8 +- src/obj/relocations.rs | 35 +++++++- src/obj/symbols.rs | 8 +- src/util/config.rs | 161 +++++++++++++++++++++++++++++++++---- 7 files changed, 449 insertions(+), 106 deletions(-) create mode 100644 src/obj/addresses.rs diff --git a/src/analysis/tracker.rs b/src/analysis/tracker.rs index 3ea05d9..10feb87 100644 --- a/src/analysis/tracker.rs +++ b/src/analysis/tracker.rs @@ -283,7 +283,7 @@ impl Tracker { Ok(ExecCbResult::Continue) } StepResult::LoadStore { address, source, source_reg } => { - if self.is_valid_section_address(obj, ins_addr) { + if !obj.blocked_relocation_sources.contains(ins_addr) { if (source_reg == 2 && matches!(self.sda2_base, Some(v) if source.value == GprValue::Constant(v))) || (source_reg == 13 @@ -503,28 +503,12 @@ impl Tracker { Ok(()) } - fn is_valid_section_address(&self, obj: &ObjInfo, from: SectionAddress) -> bool { - if let Some((&start, &end)) = obj.blocked_ranges.range(..=from).next_back() { - if from.section == start.section && from.address >= start.address && from.address < end - { - return false; - } - } - true - } - fn is_valid_address( &self, obj: &ObjInfo, from: SectionAddress, addr: u32, ) -> Option { - if let Some((&start, &end)) = obj.blocked_ranges.range(..=from).next_back() { - if from.section == start.section && from.address >= start.address && from.address < end - { - return None; - } - } // Check for an existing relocation if cfg!(debug_assertions) { let relocation_target = relocation_target_for(obj, from, None).ok().flatten(); @@ -537,33 +521,42 @@ impl Tracker { if obj.kind == ObjKind::Relocatable { return None; } - if self.known_relocations.contains(&from) { - let section_index = - obj.sections.at_address(addr).ok().map(|(idx, _)| idx).unwrap_or(usize::MAX); - return Some(SectionAddress::new(section_index, addr)); + // Check blocked relocation sources + if obj.blocked_relocation_sources.contains(from) { + return None; } - if self.stack_address == Some(addr) - || self.stack_end == Some(addr) - || self.db_stack_addr == Some(addr) - || self.arena_lo == Some(addr) - || self.arena_hi == Some(addr) - || self.sda2_base == Some(addr) - || self.sda_base == Some(addr) - { - let section_index = - obj.sections.at_address(addr).ok().map(|(idx, _)| idx).unwrap_or(usize::MAX); - return Some(SectionAddress::new(section_index, addr)); - } - // if addr > 0x80000000 && addr < 0x80003100 { - // return true; - // } + // Find the section containing the address if let Ok((section_index, section)) = obj.sections.at_address(addr) { // References to code sections will never be unaligned - if section.kind != ObjSectionKind::Code || addr & 3 == 0 { - return Some(SectionAddress::new(section_index, addr)); + if section.kind == ObjSectionKind::Code && addr & 3 != 0 { + return None; } + let section_address = SectionAddress::new(section_index, addr); + // Check blocked relocation targets + if obj.blocked_relocation_targets.contains(section_address) { + return None; + } + // It's valid + Some(section_address) + } else { + // Check known relocations (function signature matching) + if self.known_relocations.contains(&from) { + return Some(SectionAddress::new(usize::MAX, addr)); + } + // Check special symbols + if self.stack_address == Some(addr) + || self.stack_end == Some(addr) + || self.db_stack_addr == Some(addr) + || self.arena_lo == Some(addr) + || self.arena_hi == Some(addr) + || self.sda2_base == Some(addr) + || self.sda_base == Some(addr) + { + return Some(SectionAddress::new(usize::MAX, addr)); + } + // Not valid + None } - None } fn special_symbol( diff --git a/src/cmd/dol.rs b/src/cmd/dol.rs index 8ab976f..6391a26 100644 --- a/src/cmd/dol.rs +++ b/src/cmd/dol.rs @@ -13,6 +13,7 @@ use std::{ use anyhow::{anyhow, bail, Context, Result}; use argp::FromArgs; +use cwdemangle::demangle; use itertools::Itertools; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -40,8 +41,8 @@ use crate::{ bin2c::bin2c, comment::MWComment, config::{ - apply_splits_file, apply_symbols_file, is_auto_symbol, write_splits_file, - write_symbols_file, + apply_splits_file, apply_symbols_file, is_auto_symbol, signed_hex_serde, + write_splits_file, write_symbols_file, SectionAddressRef, }, dep::DepFile, dol::process_dol, @@ -236,7 +237,27 @@ pub struct ProjectConfig { pub export_all: bool, } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +impl Default for ProjectConfig { + fn default() -> Self { + Self { + base: Default::default(), + selfile: None, + selfile_hash: None, + mw_comment_version: None, + quick_analysis: false, + modules: vec![], + detect_objects: true, + detect_strings: true, + write_asm: true, + common_start: None, + symbols_known: false, + fill_gaps: true, + export_all: true, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] pub struct ModuleConfig { /// Object name. If not specified, the file name without extension will be used. #[serde(skip_serializing_if = "is_default")] @@ -261,6 +282,10 @@ pub struct ModuleConfig { pub links: Option>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub extract: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub block_relocations: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub add_relocations: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] @@ -277,6 +302,36 @@ pub struct ExtractConfig { pub header: Option, } +/// A relocation that should be blocked. +/// Only one of `source` or `target` should be specified. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct BlockRelocationConfig { + /// Match by the address of the relocation. + /// Format: `section:address`, e.g. `.text:0x80001234`. + pub source: Option, + /// Match by the address of the relocation target. + /// Format: `section:address`, e.g. `.text:0x80001234`. + pub target: Option, + /// An optional end address for the (exclusive) range. + /// Format: `section:address`, e.g. `.text:0x80001234`. + pub end: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct AddRelocationConfig { + /// The address of the relocation to add. + /// Format: `section:address`, e.g. `.text:0x80001234`. + pub source: SectionAddressRef, + /// The relocation type to add. + #[serde(rename = "type")] + pub kind: ObjRelocKind, + /// The target symbol name. + pub target: String, + /// The addend for the relocation. (optional) + #[serde(with = "signed_hex_serde", default, skip_serializing_if = "is_default")] + pub addend: i64, +} + impl ModuleConfig { pub fn file_name(&self) -> Cow<'_, str> { self.object.file_name().unwrap_or(self.object.as_os_str()).to_string_lossy() @@ -763,6 +818,9 @@ fn load_analyze_dol(config: &ProjectConfig) -> Result { None }; + // Apply block relocations from config + apply_block_relocations(&mut obj, &config.base.block_relocations)?; + if !config.symbols_known { // TODO move before symbols? debug!("Performing signature analysis"); @@ -793,6 +851,9 @@ fn load_analyze_dol(config: &ProjectConfig) -> Result { // Create _ctors and _dtors symbols if missing update_ctors_dtors(&mut obj)?; + // Apply additional relocations from config + apply_add_relocations(&mut obj, &config.base.add_relocations)?; + Ok(AnalyzeResult { obj, dep, symbols_cache, splits_cache }) } @@ -988,6 +1049,9 @@ fn load_analyze_rel(config: &ProjectConfig, module_config: &ModuleConfig) -> Res None }; + // Apply block relocations from config + apply_block_relocations(&mut module_obj, &module_config.block_relocations)?; + if !config.symbols_known { debug!("Analyzing module {}", module_obj.module_id); if !config.quick_analysis { @@ -1007,6 +1071,9 @@ fn load_analyze_rel(config: &ProjectConfig, module_config: &ModuleConfig) -> Res // Determine REL section alignment update_rel_section_alignment(&mut module_obj, &header)?; + // Apply additional relocations from config + apply_add_relocations(&mut module_obj, &module_config.add_relocations)?; + Ok(AnalyzeResult { obj: module_obj, dep, symbols_cache, splits_cache }) } @@ -1750,33 +1817,7 @@ fn apply(args: ApplyArgs) -> Result<()> { } fn config(args: ConfigArgs) -> Result<()> { - let mut config = ProjectConfig { - base: ModuleConfig { - name: None, - object: Default::default(), - hash: None, - splits: None, - symbols: None, - map: None, - force_active: vec![], - ldscript_template: None, - links: None, - extract: vec![], - }, - selfile: None, - selfile_hash: None, - mw_comment_version: None, - quick_analysis: false, - modules: vec![], - detect_objects: true, - detect_strings: true, - write_asm: true, - common_start: None, - symbols_known: false, - fill_gaps: true, - export_all: true, - }; - + let mut config = ProjectConfig::default(); let mut modules = Vec::<(u32, ModuleConfig)>::new(); for result in FileIterator::new(&args.objects)? { let (path, entry) = result?; @@ -1790,16 +1831,9 @@ fn config(args: ConfigArgs) -> Result<()> { Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("rel")) => { let header = process_rel_header(&mut entry.as_reader())?; modules.push((header.module_id, ModuleConfig { - name: None, object: path, hash: Some(file_sha1_string(&mut entry.as_reader())?), - splits: None, - symbols: None, - map: None, - force_active: vec![], - ldscript_template: None, - links: None, - extract: vec![], + ..Default::default() })); } Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("sel")) => { @@ -1808,16 +1842,9 @@ fn config(args: ConfigArgs) -> Result<()> { } Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("rso")) => { config.modules.push(ModuleConfig { - name: None, object: path, hash: Some(file_sha1_string(&mut entry.as_reader())?), - splits: None, - symbols: None, - map: None, - force_active: vec![], - ldscript_template: None, - links: None, - extract: vec![], + ..Default::default() }); } _ => bail!("Unknown file extension: '{}'", path.display()), @@ -1834,3 +1861,56 @@ fn config(args: ConfigArgs) -> Result<()> { out.flush()?; Ok(()) } + +/// Applies the blocked relocation ranges from module config `blocked_relocations` +fn apply_block_relocations( + obj: &mut ObjInfo, + block_relocations: &[BlockRelocationConfig], +) -> Result<()> { + for reloc in block_relocations { + let end = reloc.end.as_ref().map(|end| end.resolve(obj)).transpose()?; + match (&reloc.source, &reloc.target) { + (Some(_), Some(_)) => { + bail!("Cannot specify both source and target for blocked relocation"); + } + (Some(source), None) => { + let start = source.resolve(obj)?; + obj.blocked_relocation_sources.insert(start, end.unwrap_or(start + 1)); + } + (None, Some(target)) => { + let start = target.resolve(obj)?; + obj.blocked_relocation_targets.insert(start, end.unwrap_or(start + 1)); + } + (None, None) => { + bail!("Blocked relocation must specify either source or target"); + } + } + } + Ok(()) +} + +/// Applies the relocations from module config `add_relocations`. +fn apply_add_relocations(obj: &mut ObjInfo, relocations: &[AddRelocationConfig]) -> Result<()> { + for reloc in relocations { + let SectionAddress { section, address } = reloc.source.resolve(obj)?; + let (target_symbol, _) = match obj.symbols.by_name(&reloc.target)? { + Some(v) => v, + None => { + // Assume external symbol + let symbol_index = obj.symbols.add_direct(ObjSymbol { + name: reloc.target.clone(), + demangled_name: demangle(&reloc.target, &Default::default()), + ..Default::default() + })?; + (symbol_index, &obj.symbols[symbol_index]) + } + }; + obj.sections[section].relocations.replace(address, ObjReloc { + kind: reloc.kind, + target_symbol, + addend: reloc.addend, + module: None, + }); + } + Ok(()) +} diff --git a/src/obj/addresses.rs b/src/obj/addresses.rs new file mode 100644 index 0000000..798eb8b --- /dev/null +++ b/src/obj/addresses.rs @@ -0,0 +1,100 @@ +use crate::analysis::cfa::SectionAddress; + +/// A collection of address ranges. +/// Slow to insert, but fast to check if an address is contained in any of the ranges. +#[derive(Debug, Clone)] +pub struct AddressRanges { + /// (start, end) pairs of addresses. + inner: Vec<(SectionAddress, u32)>, +} + +impl Default for AddressRanges { + fn default() -> Self { Self::new() } +} + +impl AddressRanges { + #[inline] + pub fn new() -> Self { Self { inner: vec![] } } + + pub fn insert(&mut self, start: SectionAddress, end: SectionAddress) { + debug_assert_eq!( + start.section, end.section, + "AddressIntervals::insert: start and end must be in the same section" + ); + // TODO: Handle overlapping ranges? + match self.inner.binary_search_by_key(&start, |&(start, _)| start) { + Ok(pos) => { + let (_, end_ref) = &mut self.inner[pos]; + *end_ref = end.address.max(*end_ref); + } + Err(pos) => self.inner.insert(pos, (start, end.address)), + } + } + + pub fn contains(&self, address: SectionAddress) -> bool { + let pos = match self.inner.binary_search_by_key(&address, |&(start, _)| start) { + Ok(_) => return true, + Err(pos) => pos, + }; + if pos == 0 { + return false; + } + let (start, end) = &self.inner[pos - 1]; + start.section == address.section + && address.address >= start.address + && address.address < *end + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_contains() { + let mut intervals = AddressRanges::new(); + intervals.insert(SectionAddress { section: 0, address: 0x80000000 }, SectionAddress { + section: 0, + address: 0x80000004, + }); + intervals.insert(SectionAddress { section: 0, address: 0x80000008 }, SectionAddress { + section: 0, + address: 0x8000000C, + }); + intervals.insert(SectionAddress { section: 12, address: 0x80004000 }, SectionAddress { + section: 12, + address: 0x80004004, + }); + intervals.insert(SectionAddress { section: 12, address: 0x80004008 }, SectionAddress { + section: 12, + address: 0x8000400C, + }); + + assert!(intervals.contains(SectionAddress { section: 0, address: 0x80000000 })); + assert!(intervals.contains(SectionAddress { section: 0, address: 0x80000001 })); + assert!(intervals.contains(SectionAddress { section: 0, address: 0x80000002 })); + assert!(intervals.contains(SectionAddress { section: 0, address: 0x80000003 })); + assert!(!intervals.contains(SectionAddress { section: 0, address: 0x80000004 })); + assert!(!intervals.contains(SectionAddress { section: 0, address: 0x80000005 })); + assert!(!intervals.contains(SectionAddress { section: 0, address: 0x80000006 })); + assert!(!intervals.contains(SectionAddress { section: 0, address: 0x80000007 })); + assert!(intervals.contains(SectionAddress { section: 0, address: 0x80000008 })); + assert!(intervals.contains(SectionAddress { section: 0, address: 0x80000009 })); + assert!(intervals.contains(SectionAddress { section: 0, address: 0x8000000A })); + assert!(intervals.contains(SectionAddress { section: 0, address: 0x8000000B })); + + assert!(intervals.contains(SectionAddress { section: 12, address: 0x80004000 })); + assert!(intervals.contains(SectionAddress { section: 12, address: 0x80004001 })); + assert!(intervals.contains(SectionAddress { section: 12, address: 0x80004002 })); + assert!(intervals.contains(SectionAddress { section: 12, address: 0x80004003 })); + assert!(!intervals.contains(SectionAddress { section: 12, address: 0x80004004 })); + assert!(!intervals.contains(SectionAddress { section: 12, address: 0x80004005 })); + assert!(!intervals.contains(SectionAddress { section: 12, address: 0x80004006 })); + assert!(!intervals.contains(SectionAddress { section: 12, address: 0x80004007 })); + assert!(intervals.contains(SectionAddress { section: 12, address: 0x80004008 })); + assert!(intervals.contains(SectionAddress { section: 12, address: 0x80004009 })); + assert!(intervals.contains(SectionAddress { section: 12, address: 0x8000400A })); + assert!(intervals.contains(SectionAddress { section: 12, address: 0x8000400B })); + assert!(!intervals.contains(SectionAddress { section: 12, address: 0x8000400C })); + } +} diff --git a/src/obj/mod.rs b/src/obj/mod.rs index f7257c6..ad2349f 100644 --- a/src/obj/mod.rs +++ b/src/obj/mod.rs @@ -1,3 +1,4 @@ +mod addresses; mod relocations; mod sections; mod splits; @@ -21,6 +22,7 @@ pub use symbols::{ use crate::{ analysis::cfa::SectionAddress, + obj::addresses::AddressRanges, util::{comment::MWComment, rel::RelReloc}, }; @@ -69,7 +71,8 @@ pub struct ObjInfo { // Extracted pub link_order: Vec, - pub blocked_ranges: BTreeMap, // start -> end + pub blocked_relocation_sources: AddressRanges, + pub blocked_relocation_targets: AddressRanges, // From .ctors, .dtors and extab pub known_functions: BTreeMap>, @@ -105,7 +108,8 @@ impl ObjInfo { arena_lo: None, arena_hi: None, link_order: vec![], - blocked_ranges: Default::default(), + blocked_relocation_sources: Default::default(), + blocked_relocation_targets: Default::default(), known_functions: Default::default(), module_id: 0, unresolved_relocations: vec![], diff --git a/src/obj/relocations.rs b/src/obj/relocations.rs index 8e4bc94..180717b 100644 --- a/src/obj/relocations.rs +++ b/src/obj/relocations.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use crate::obj::SymbolIndex; -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum ObjRelocKind { Absolute, PpcAddr16Hi, @@ -21,6 +21,39 @@ pub enum ObjRelocKind { PpcEmbSda21, } +impl Serialize for ObjRelocKind { + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer { + serializer.serialize_str(match self { + ObjRelocKind::Absolute => "abs", + ObjRelocKind::PpcAddr16Hi => "hi", + ObjRelocKind::PpcAddr16Ha => "ha", + ObjRelocKind::PpcAddr16Lo => "l", + ObjRelocKind::PpcRel24 => "rel24", + ObjRelocKind::PpcRel14 => "rel14", + ObjRelocKind::PpcEmbSda21 => "sda21", + }) + } +} + +impl<'de> Deserialize<'de> for ObjRelocKind { + fn deserialize(deserializer: D) -> Result + where D: serde::Deserializer<'de> { + match String::deserialize(deserializer)?.as_str() { + "Absolute" | "abs" => Ok(ObjRelocKind::Absolute), + "PpcAddr16Hi" | "hi" => Ok(ObjRelocKind::PpcAddr16Hi), + "PpcAddr16Ha" | "ha" => Ok(ObjRelocKind::PpcAddr16Ha), + "PpcAddr16Lo" | "l" => Ok(ObjRelocKind::PpcAddr16Lo), + "PpcRel24" | "rel24" => Ok(ObjRelocKind::PpcRel24), + "PpcRel14" | "rel14" => Ok(ObjRelocKind::PpcRel14), + "PpcEmbSda21" | "sda21" => Ok(ObjRelocKind::PpcEmbSda21), + s => Err(serde::de::Error::unknown_variant(s, &[ + "abs", "hi", "ha", "l", "rel24", "rel14", "sda21", + ])), + } + } +} + #[derive(Debug, Clone)] pub struct ObjReloc { pub kind: ObjRelocKind, diff --git a/src/obj/symbols.rs b/src/obj/symbols.rs index bad062f..731189d 100644 --- a/src/obj/symbols.rs +++ b/src/obj/symbols.rs @@ -49,6 +49,8 @@ flags! { Stripped, /// Disable automatic export of symbol NoExport, + /// Symbol does not contain any relocations + NoReloc, } } @@ -99,6 +101,9 @@ impl ObjSymbolFlagSet { #[inline] pub fn is_no_export(&self) -> bool { self.0.contains(ObjSymbolFlags::NoExport) } + #[inline] + pub fn is_no_reloc(&self) -> bool { self.0.contains(ObjSymbolFlags::NoReloc) } + #[inline] pub fn set_scope(&mut self, scope: ObjSymbolScope) { match scope { @@ -137,7 +142,8 @@ impl ObjSymbolFlagSet { | ObjSymbolFlags::NoWrite | ObjSymbolFlags::RelocationIgnore | ObjSymbolFlags::Stripped - | ObjSymbolFlags::NoExport) + | ObjSymbolFlags::NoExport + | ObjSymbolFlags::NoReloc) } } diff --git a/src/util/config.rs b/src/util/config.rs index 8b4902e..db76e00 100644 --- a/src/util/config.rs +++ b/src/util/config.rs @@ -26,9 +26,9 @@ use crate::{ }, }; -fn parse_hex(s: &str) -> Result { - if s.starts_with("0x") { - u32::from_str_radix(s.trim_start_matches("0x"), 16) +pub fn parse_u32(s: &str) -> Result { + if let Some(s) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { + u32::from_str_radix(s, 16) } else { s.parse::() } @@ -65,7 +65,7 @@ pub fn parse_symbol_line(line: &str, obj: &mut ObjInfo) -> Result Result { - symbol.size = parse_hex(value)? as u64; + symbol.size = parse_u32(value)? as u64; symbol.size_known = true; } "scope" => { @@ -104,21 +104,21 @@ pub fn parse_symbol_line(line: &str, obj: &mut ObjInfo) -> Result { - symbol.align = Some(parse_hex(value)?); + symbol.align = Some(parse_u32(value)?); } "data" => { symbol.data_kind = symbol_data_kind_from_str(value) .ok_or_else(|| anyhow!("Unknown symbol data type '{}'", value))?; } "hash" => { - let hash = parse_hex(value)?; + let hash = parse_u32(value)?; symbol.name_hash = Some(hash); if symbol.demangled_name_hash.is_none() { symbol.demangled_name_hash = Some(hash); } } "dhash" => { - symbol.demangled_name_hash = Some(parse_hex(value)?); + symbol.demangled_name_hash = Some(parse_u32(value)?); } _ => bail!("Unknown symbol attribute '{name}'"), } @@ -145,7 +145,8 @@ pub fn parse_symbol_line(line: &str, obj: &mut ObjInfo) -> Result { symbol.flags.0 |= ObjSymbolFlags::NoExport; @@ -285,10 +286,8 @@ where W: Write + ?Sized { if symbol.flags.is_stripped() { write!(w, " stripped")?; } - if let Some(section) = symbol.section { - if obj.blocked_ranges.contains_key(&SectionAddress::new(section, symbol.address as u32)) { - write!(w, " noreloc")?; - } + if symbol.flags.is_no_reloc() { + write!(w, " noreloc")?; } if symbol.flags.is_no_export() { write!(w, " noexport")?; @@ -547,7 +546,7 @@ fn parse_section_line(captures: Captures, state: &SplitState) -> Result { - section.align = Some(parse_hex(value)?); + section.align = Some(parse_u32(value)?); } _ => bail!("Unknown section attribute '{attr}'"), } @@ -574,9 +573,9 @@ fn parse_section_line(captures: Captures, state: &SplitState) -> Result start = Some(parse_hex(value)?), - "end" => end = Some(parse_hex(value)?), - "align" => section.align = Some(parse_hex(value)?), + "start" => start = Some(parse_u32(value)?), + "end" => end = Some(parse_u32(value)?), + "align" => section.align = Some(parse_u32(value)?), "rename" => section.rename = Some(value.to_string()), _ => bail!("Unknown split attribute '{attr}'"), } @@ -758,3 +757,131 @@ where P: AsRef { Ok(Some(sections)) } } + +pub mod signed_hex_serde { + use serde::{Deserializer, Serializer}; + + pub fn serialize(value: &i64, serializer: S) -> Result + where S: Serializer { + if *value < 0 { + serializer.serialize_str(&format!("-{:#X}", -value)) + } else { + serializer.serialize_str(&format!("{:#X}", value)) + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where D: Deserializer<'de> { + struct SignedHexVisitor; + + impl<'de> serde::de::Visitor<'de> for SignedHexVisitor { + type Value = i64; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a signed hexadecimal number") + } + + fn visit_i64(self, v: i64) -> Result + where E: serde::de::Error { + Ok(v) + } + + fn visit_u64(self, v: u64) -> Result + where E: serde::de::Error { + v.try_into().map_err(serde::de::Error::custom) + } + + fn visit_str(self, value: &str) -> Result + where E: serde::de::Error { + if let Some(s) = value.strip_prefix("-0x").or_else(|| value.strip_prefix("-0X")) { + i64::from_str_radix(s, 16).map(|v| -v).map_err(serde::de::Error::custom) + } else if let Some(s) = + value.strip_prefix("0x").or_else(|| value.strip_prefix("0X")) + { + i64::from_str_radix(s, 16).map_err(serde::de::Error::custom) + } else { + value.parse::().map_err(serde::de::Error::custom) + } + } + } + + deserializer.deserialize_any(SignedHexVisitor) + } +} + +/// A reference to a section and address within that section. +/// For executable objects, section can be omitted and the address is treated as absolute. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SectionAddressRef { + pub section: Option, + pub address: u32, +} + +impl SectionAddressRef { + pub fn new(section: Option, address: u32) -> Self { Self { section, address } } + + pub fn resolve(&self, obj: &ObjInfo) -> Result { + let (section_index, section) = if let Some(section) = &self.section { + obj.sections + .by_name(section)? + .ok_or_else(|| anyhow!("Section {} not found", section))? + } else if obj.kind == ObjKind::Executable { + obj.sections.at_address(self.address)? + } else { + bail!("Section required for relocatable object address reference: {:#X}", self.address) + }; + ensure!( + section.contains(self.address), + "Address {:#X} not in section {} ({:#X}..{:#X})", + self.address, + section.name, + section.address, + section.address + section.size, + ); + Ok(SectionAddress::new(section_index, self.address)) + } +} + +impl<'de> serde::Deserialize<'de> for SectionAddressRef { + fn deserialize(deserializer: D) -> Result + where D: serde::Deserializer<'de> { + struct SectionAddressRefVisitor; + + impl<'de> serde::de::Visitor<'de> for SectionAddressRefVisitor { + type Value = SectionAddressRef; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a section address reference") + } + + fn visit_u64(self, v: u64) -> Result + where E: serde::de::Error { + Ok(SectionAddressRef::new(None, v.try_into().map_err(serde::de::Error::custom)?)) + } + + fn visit_str(self, value: &str) -> Result + where E: serde::de::Error { + let mut parts = value.splitn(2, ':'); + let section = parts.next().map(|s| s.to_string()); + let address = parts.next().ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Str(value), &self) + })?; + let address = parse_u32(address).map_err(serde::de::Error::custom)?; + Ok(SectionAddressRef::new(section, address)) + } + } + + deserializer.deserialize_any(SectionAddressRefVisitor) + } +} + +impl serde::Serialize for SectionAddressRef { + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer { + if let Some(section) = &self.section { + serializer.serialize_str(&format!("{}:{:#X}", section, self.address)) + } else { + serializer.serialize_str(&format!("{:#X}", self.address)) + } + } +}