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
This commit is contained in:
parent
e1c80655b7
commit
c3f3ea58e8
|
@ -283,7 +283,7 @@ impl Tracker {
|
||||||
Ok(ExecCbResult::Continue)
|
Ok(ExecCbResult::Continue)
|
||||||
}
|
}
|
||||||
StepResult::LoadStore { address, source, source_reg } => {
|
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
|
if (source_reg == 2
|
||||||
&& matches!(self.sda2_base, Some(v) if source.value == GprValue::Constant(v)))
|
&& matches!(self.sda2_base, Some(v) if source.value == GprValue::Constant(v)))
|
||||||
|| (source_reg == 13
|
|| (source_reg == 13
|
||||||
|
@ -503,28 +503,12 @@ impl Tracker {
|
||||||
Ok(())
|
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(
|
fn is_valid_address(
|
||||||
&self,
|
&self,
|
||||||
obj: &ObjInfo,
|
obj: &ObjInfo,
|
||||||
from: SectionAddress,
|
from: SectionAddress,
|
||||||
addr: u32,
|
addr: u32,
|
||||||
) -> Option<SectionAddress> {
|
) -> Option<SectionAddress> {
|
||||||
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
|
// Check for an existing relocation
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
let relocation_target = relocation_target_for(obj, from, None).ok().flatten();
|
let relocation_target = relocation_target_for(obj, from, None).ok().flatten();
|
||||||
|
@ -537,33 +521,42 @@ impl Tracker {
|
||||||
if obj.kind == ObjKind::Relocatable {
|
if obj.kind == ObjKind::Relocatable {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if self.known_relocations.contains(&from) {
|
// Check blocked relocation sources
|
||||||
let section_index =
|
if obj.blocked_relocation_sources.contains(from) {
|
||||||
obj.sections.at_address(addr).ok().map(|(idx, _)| idx).unwrap_or(usize::MAX);
|
return None;
|
||||||
return Some(SectionAddress::new(section_index, addr));
|
|
||||||
}
|
}
|
||||||
if self.stack_address == Some(addr)
|
// Find the section containing the address
|
||||||
|| 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;
|
|
||||||
// }
|
|
||||||
if let Ok((section_index, section)) = obj.sections.at_address(addr) {
|
if let Ok((section_index, section)) = obj.sections.at_address(addr) {
|
||||||
// References to code sections will never be unaligned
|
// References to code sections will never be unaligned
|
||||||
if section.kind != ObjSectionKind::Code || addr & 3 == 0 {
|
if section.kind == ObjSectionKind::Code && addr & 3 != 0 {
|
||||||
return Some(SectionAddress::new(section_index, addr));
|
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(
|
fn special_symbol(
|
||||||
|
|
172
src/cmd/dol.rs
172
src/cmd/dol.rs
|
@ -13,6 +13,7 @@ use std::{
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use argp::FromArgs;
|
use argp::FromArgs;
|
||||||
|
use cwdemangle::demangle;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -40,8 +41,8 @@ use crate::{
|
||||||
bin2c::bin2c,
|
bin2c::bin2c,
|
||||||
comment::MWComment,
|
comment::MWComment,
|
||||||
config::{
|
config::{
|
||||||
apply_splits_file, apply_symbols_file, is_auto_symbol, write_splits_file,
|
apply_splits_file, apply_symbols_file, is_auto_symbol, signed_hex_serde,
|
||||||
write_symbols_file,
|
write_splits_file, write_symbols_file, SectionAddressRef,
|
||||||
},
|
},
|
||||||
dep::DepFile,
|
dep::DepFile,
|
||||||
dol::process_dol,
|
dol::process_dol,
|
||||||
|
@ -236,7 +237,27 @@ pub struct ProjectConfig {
|
||||||
pub export_all: bool,
|
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 {
|
pub struct ModuleConfig {
|
||||||
/// Object name. If not specified, the file name without extension will be used.
|
/// Object name. If not specified, the file name without extension will be used.
|
||||||
#[serde(skip_serializing_if = "is_default")]
|
#[serde(skip_serializing_if = "is_default")]
|
||||||
|
@ -261,6 +282,10 @@ pub struct ModuleConfig {
|
||||||
pub links: Option<Vec<String>>,
|
pub links: Option<Vec<String>>,
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub extract: Vec<ExtractConfig>,
|
pub extract: Vec<ExtractConfig>,
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub block_relocations: Vec<BlockRelocationConfig>,
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub add_relocations: Vec<AddRelocationConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
|
@ -277,6 +302,36 @@ pub struct ExtractConfig {
|
||||||
pub header: Option<PathBuf>,
|
pub header: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<SectionAddressRef>,
|
||||||
|
/// Match by the address of the relocation target.
|
||||||
|
/// Format: `section:address`, e.g. `.text:0x80001234`.
|
||||||
|
pub target: Option<SectionAddressRef>,
|
||||||
|
/// An optional end address for the (exclusive) range.
|
||||||
|
/// Format: `section:address`, e.g. `.text:0x80001234`.
|
||||||
|
pub end: Option<SectionAddressRef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {
|
impl ModuleConfig {
|
||||||
pub fn file_name(&self) -> Cow<'_, str> {
|
pub fn file_name(&self) -> Cow<'_, str> {
|
||||||
self.object.file_name().unwrap_or(self.object.as_os_str()).to_string_lossy()
|
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<AnalyzeResult> {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Apply block relocations from config
|
||||||
|
apply_block_relocations(&mut obj, &config.base.block_relocations)?;
|
||||||
|
|
||||||
if !config.symbols_known {
|
if !config.symbols_known {
|
||||||
// TODO move before symbols?
|
// TODO move before symbols?
|
||||||
debug!("Performing signature analysis");
|
debug!("Performing signature analysis");
|
||||||
|
@ -793,6 +851,9 @@ fn load_analyze_dol(config: &ProjectConfig) -> Result<AnalyzeResult> {
|
||||||
// Create _ctors and _dtors symbols if missing
|
// Create _ctors and _dtors symbols if missing
|
||||||
update_ctors_dtors(&mut obj)?;
|
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 })
|
Ok(AnalyzeResult { obj, dep, symbols_cache, splits_cache })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -988,6 +1049,9 @@ fn load_analyze_rel(config: &ProjectConfig, module_config: &ModuleConfig) -> Res
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Apply block relocations from config
|
||||||
|
apply_block_relocations(&mut module_obj, &module_config.block_relocations)?;
|
||||||
|
|
||||||
if !config.symbols_known {
|
if !config.symbols_known {
|
||||||
debug!("Analyzing module {}", module_obj.module_id);
|
debug!("Analyzing module {}", module_obj.module_id);
|
||||||
if !config.quick_analysis {
|
if !config.quick_analysis {
|
||||||
|
@ -1007,6 +1071,9 @@ fn load_analyze_rel(config: &ProjectConfig, module_config: &ModuleConfig) -> Res
|
||||||
// Determine REL section alignment
|
// Determine REL section alignment
|
||||||
update_rel_section_alignment(&mut module_obj, &header)?;
|
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 })
|
Ok(AnalyzeResult { obj: module_obj, dep, symbols_cache, splits_cache })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1750,33 +1817,7 @@ fn apply(args: ApplyArgs) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config(args: ConfigArgs) -> Result<()> {
|
fn config(args: ConfigArgs) -> Result<()> {
|
||||||
let mut config = ProjectConfig {
|
let mut config = ProjectConfig::default();
|
||||||
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 modules = Vec::<(u32, ModuleConfig)>::new();
|
let mut modules = Vec::<(u32, ModuleConfig)>::new();
|
||||||
for result in FileIterator::new(&args.objects)? {
|
for result in FileIterator::new(&args.objects)? {
|
||||||
let (path, entry) = result?;
|
let (path, entry) = result?;
|
||||||
|
@ -1790,16 +1831,9 @@ fn config(args: ConfigArgs) -> Result<()> {
|
||||||
Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("rel")) => {
|
Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("rel")) => {
|
||||||
let header = process_rel_header(&mut entry.as_reader())?;
|
let header = process_rel_header(&mut entry.as_reader())?;
|
||||||
modules.push((header.module_id, ModuleConfig {
|
modules.push((header.module_id, ModuleConfig {
|
||||||
name: None,
|
|
||||||
object: path,
|
object: path,
|
||||||
hash: Some(file_sha1_string(&mut entry.as_reader())?),
|
hash: Some(file_sha1_string(&mut entry.as_reader())?),
|
||||||
splits: None,
|
..Default::default()
|
||||||
symbols: None,
|
|
||||||
map: None,
|
|
||||||
force_active: vec![],
|
|
||||||
ldscript_template: None,
|
|
||||||
links: None,
|
|
||||||
extract: vec![],
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("sel")) => {
|
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")) => {
|
Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("rso")) => {
|
||||||
config.modules.push(ModuleConfig {
|
config.modules.push(ModuleConfig {
|
||||||
name: None,
|
|
||||||
object: path,
|
object: path,
|
||||||
hash: Some(file_sha1_string(&mut entry.as_reader())?),
|
hash: Some(file_sha1_string(&mut entry.as_reader())?),
|
||||||
splits: None,
|
..Default::default()
|
||||||
symbols: None,
|
|
||||||
map: None,
|
|
||||||
force_active: vec![],
|
|
||||||
ldscript_template: None,
|
|
||||||
links: None,
|
|
||||||
extract: vec![],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_ => bail!("Unknown file extension: '{}'", path.display()),
|
_ => bail!("Unknown file extension: '{}'", path.display()),
|
||||||
|
@ -1834,3 +1861,56 @@ fn config(args: ConfigArgs) -> Result<()> {
|
||||||
out.flush()?;
|
out.flush()?;
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|
|
@ -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 }));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod addresses;
|
||||||
mod relocations;
|
mod relocations;
|
||||||
mod sections;
|
mod sections;
|
||||||
mod splits;
|
mod splits;
|
||||||
|
@ -21,6 +22,7 @@ pub use symbols::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
analysis::cfa::SectionAddress,
|
analysis::cfa::SectionAddress,
|
||||||
|
obj::addresses::AddressRanges,
|
||||||
util::{comment::MWComment, rel::RelReloc},
|
util::{comment::MWComment, rel::RelReloc},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,7 +71,8 @@ pub struct ObjInfo {
|
||||||
|
|
||||||
// Extracted
|
// Extracted
|
||||||
pub link_order: Vec<ObjUnit>,
|
pub link_order: Vec<ObjUnit>,
|
||||||
pub blocked_ranges: BTreeMap<SectionAddress, u32>, // start -> end
|
pub blocked_relocation_sources: AddressRanges,
|
||||||
|
pub blocked_relocation_targets: AddressRanges,
|
||||||
|
|
||||||
// From .ctors, .dtors and extab
|
// From .ctors, .dtors and extab
|
||||||
pub known_functions: BTreeMap<SectionAddress, Option<u32>>,
|
pub known_functions: BTreeMap<SectionAddress, Option<u32>>,
|
||||||
|
@ -105,7 +108,8 @@ impl ObjInfo {
|
||||||
arena_lo: None,
|
arena_lo: None,
|
||||||
arena_hi: None,
|
arena_hi: None,
|
||||||
link_order: vec![],
|
link_order: vec![],
|
||||||
blocked_ranges: Default::default(),
|
blocked_relocation_sources: Default::default(),
|
||||||
|
blocked_relocation_targets: Default::default(),
|
||||||
known_functions: Default::default(),
|
known_functions: Default::default(),
|
||||||
module_id: 0,
|
module_id: 0,
|
||||||
unresolved_relocations: vec![],
|
unresolved_relocations: vec![],
|
||||||
|
|
|
@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::obj::SymbolIndex;
|
use crate::obj::SymbolIndex;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum ObjRelocKind {
|
pub enum ObjRelocKind {
|
||||||
Absolute,
|
Absolute,
|
||||||
PpcAddr16Hi,
|
PpcAddr16Hi,
|
||||||
|
@ -21,6 +21,39 @@ pub enum ObjRelocKind {
|
||||||
PpcEmbSda21,
|
PpcEmbSda21,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serialize for ObjRelocKind {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
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<D>(deserializer: D) -> Result<ObjRelocKind, D::Error>
|
||||||
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ObjReloc {
|
pub struct ObjReloc {
|
||||||
pub kind: ObjRelocKind,
|
pub kind: ObjRelocKind,
|
||||||
|
|
|
@ -49,6 +49,8 @@ flags! {
|
||||||
Stripped,
|
Stripped,
|
||||||
/// Disable automatic export of symbol
|
/// Disable automatic export of symbol
|
||||||
NoExport,
|
NoExport,
|
||||||
|
/// Symbol does not contain any relocations
|
||||||
|
NoReloc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +101,9 @@ impl ObjSymbolFlagSet {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_no_export(&self) -> bool { self.0.contains(ObjSymbolFlags::NoExport) }
|
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]
|
#[inline]
|
||||||
pub fn set_scope(&mut self, scope: ObjSymbolScope) {
|
pub fn set_scope(&mut self, scope: ObjSymbolScope) {
|
||||||
match scope {
|
match scope {
|
||||||
|
@ -137,7 +142,8 @@ impl ObjSymbolFlagSet {
|
||||||
| ObjSymbolFlags::NoWrite
|
| ObjSymbolFlags::NoWrite
|
||||||
| ObjSymbolFlags::RelocationIgnore
|
| ObjSymbolFlags::RelocationIgnore
|
||||||
| ObjSymbolFlags::Stripped
|
| ObjSymbolFlags::Stripped
|
||||||
| ObjSymbolFlags::NoExport)
|
| ObjSymbolFlags::NoExport
|
||||||
|
| ObjSymbolFlags::NoReloc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,9 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn parse_hex(s: &str) -> Result<u32, ParseIntError> {
|
pub fn parse_u32(s: &str) -> Result<u32, ParseIntError> {
|
||||||
if s.starts_with("0x") {
|
if let Some(s) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
|
||||||
u32::from_str_radix(s.trim_start_matches("0x"), 16)
|
u32::from_str_radix(s, 16)
|
||||||
} else {
|
} else {
|
||||||
s.parse::<u32>()
|
s.parse::<u32>()
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ pub fn parse_symbol_line(line: &str, obj: &mut ObjInfo) -> Result<Option<ObjSymb
|
||||||
|
|
||||||
if let Some(captures) = SYMBOL_LINE.captures(line) {
|
if let Some(captures) = SYMBOL_LINE.captures(line) {
|
||||||
let name = captures["name"].to_string();
|
let name = captures["name"].to_string();
|
||||||
let addr = parse_hex(&captures["addr"])?;
|
let addr = parse_u32(&captures["addr"])?;
|
||||||
let section_name = captures["section"].to_string();
|
let section_name = captures["section"].to_string();
|
||||||
let section = if section_name == "ABS" {
|
let section = if section_name == "ABS" {
|
||||||
None
|
None
|
||||||
|
@ -96,7 +96,7 @@ pub fn parse_symbol_line(line: &str, obj: &mut ObjInfo) -> Result<Option<ObjSymb
|
||||||
.ok_or_else(|| anyhow!("Unknown symbol type '{}'", value))?;
|
.ok_or_else(|| anyhow!("Unknown symbol type '{}'", value))?;
|
||||||
}
|
}
|
||||||
"size" => {
|
"size" => {
|
||||||
symbol.size = parse_hex(value)? as u64;
|
symbol.size = parse_u32(value)? as u64;
|
||||||
symbol.size_known = true;
|
symbol.size_known = true;
|
||||||
}
|
}
|
||||||
"scope" => {
|
"scope" => {
|
||||||
|
@ -104,21 +104,21 @@ pub fn parse_symbol_line(line: &str, obj: &mut ObjInfo) -> Result<Option<ObjSymb
|
||||||
.ok_or_else(|| anyhow!("Unknown symbol scope '{}'", value))?;
|
.ok_or_else(|| anyhow!("Unknown symbol scope '{}'", value))?;
|
||||||
}
|
}
|
||||||
"align" => {
|
"align" => {
|
||||||
symbol.align = Some(parse_hex(value)?);
|
symbol.align = Some(parse_u32(value)?);
|
||||||
}
|
}
|
||||||
"data" => {
|
"data" => {
|
||||||
symbol.data_kind = symbol_data_kind_from_str(value)
|
symbol.data_kind = symbol_data_kind_from_str(value)
|
||||||
.ok_or_else(|| anyhow!("Unknown symbol data type '{}'", value))?;
|
.ok_or_else(|| anyhow!("Unknown symbol data type '{}'", value))?;
|
||||||
}
|
}
|
||||||
"hash" => {
|
"hash" => {
|
||||||
let hash = parse_hex(value)?;
|
let hash = parse_u32(value)?;
|
||||||
symbol.name_hash = Some(hash);
|
symbol.name_hash = Some(hash);
|
||||||
if symbol.demangled_name_hash.is_none() {
|
if symbol.demangled_name_hash.is_none() {
|
||||||
symbol.demangled_name_hash = Some(hash);
|
symbol.demangled_name_hash = Some(hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"dhash" => {
|
"dhash" => {
|
||||||
symbol.demangled_name_hash = Some(parse_hex(value)?);
|
symbol.demangled_name_hash = Some(parse_u32(value)?);
|
||||||
}
|
}
|
||||||
_ => bail!("Unknown symbol attribute '{name}'"),
|
_ => bail!("Unknown symbol attribute '{name}'"),
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,8 @@ pub fn parse_symbol_line(line: &str, obj: &mut ObjInfo) -> Result<Option<ObjSymb
|
||||||
symbol.name
|
symbol.name
|
||||||
);
|
);
|
||||||
let addr = SectionAddress::new(section.unwrap(), symbol.address as u32);
|
let addr = SectionAddress::new(section.unwrap(), symbol.address as u32);
|
||||||
obj.blocked_ranges.insert(addr, addr.address + symbol.size as u32);
|
obj.blocked_relocation_sources.insert(addr, addr + symbol.size as u32);
|
||||||
|
symbol.flags.0 |= ObjSymbolFlags::NoReloc;
|
||||||
}
|
}
|
||||||
"noexport" => {
|
"noexport" => {
|
||||||
symbol.flags.0 |= ObjSymbolFlags::NoExport;
|
symbol.flags.0 |= ObjSymbolFlags::NoExport;
|
||||||
|
@ -285,10 +286,8 @@ where W: Write + ?Sized {
|
||||||
if symbol.flags.is_stripped() {
|
if symbol.flags.is_stripped() {
|
||||||
write!(w, " stripped")?;
|
write!(w, " stripped")?;
|
||||||
}
|
}
|
||||||
if let Some(section) = symbol.section {
|
if symbol.flags.is_no_reloc() {
|
||||||
if obj.blocked_ranges.contains_key(&SectionAddress::new(section, symbol.address as u32)) {
|
write!(w, " noreloc")?;
|
||||||
write!(w, " noreloc")?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if symbol.flags.is_no_export() {
|
if symbol.flags.is_no_export() {
|
||||||
write!(w, " noexport")?;
|
write!(w, " noexport")?;
|
||||||
|
@ -547,7 +546,7 @@ fn parse_section_line(captures: Captures, state: &SplitState) -> Result<SplitLin
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"align" => {
|
"align" => {
|
||||||
section.align = Some(parse_hex(value)?);
|
section.align = Some(parse_u32(value)?);
|
||||||
}
|
}
|
||||||
_ => bail!("Unknown section attribute '{attr}'"),
|
_ => bail!("Unknown section attribute '{attr}'"),
|
||||||
}
|
}
|
||||||
|
@ -574,9 +573,9 @@ fn parse_section_line(captures: Captures, state: &SplitState) -> Result<SplitLin
|
||||||
for attr in captures["attrs"].split(' ').filter(|&s| !s.is_empty()) {
|
for attr in captures["attrs"].split(' ').filter(|&s| !s.is_empty()) {
|
||||||
if let Some((attr, value)) = attr.split_once(':') {
|
if let Some((attr, value)) = attr.split_once(':') {
|
||||||
match attr {
|
match attr {
|
||||||
"start" => start = Some(parse_hex(value)?),
|
"start" => start = Some(parse_u32(value)?),
|
||||||
"end" => end = Some(parse_hex(value)?),
|
"end" => end = Some(parse_u32(value)?),
|
||||||
"align" => section.align = Some(parse_hex(value)?),
|
"align" => section.align = Some(parse_u32(value)?),
|
||||||
"rename" => section.rename = Some(value.to_string()),
|
"rename" => section.rename = Some(value.to_string()),
|
||||||
_ => bail!("Unknown split attribute '{attr}'"),
|
_ => bail!("Unknown split attribute '{attr}'"),
|
||||||
}
|
}
|
||||||
|
@ -758,3 +757,131 @@ where P: AsRef<Path> {
|
||||||
Ok(Some(sections))
|
Ok(Some(sections))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod signed_hex_serde {
|
||||||
|
use serde::{Deserializer, Serializer};
|
||||||
|
|
||||||
|
pub fn serialize<S>(value: &i64, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
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<i64, D::Error>
|
||||||
|
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<E>(self, v: i64) -> Result<Self::Value, E>
|
||||||
|
where E: serde::de::Error {
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||||
|
where E: serde::de::Error {
|
||||||
|
v.try_into().map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<i64, E>
|
||||||
|
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::<i64>().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<String>,
|
||||||
|
pub address: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SectionAddressRef {
|
||||||
|
pub fn new(section: Option<String>, address: u32) -> Self { Self { section, address } }
|
||||||
|
|
||||||
|
pub fn resolve(&self, obj: &ObjInfo) -> Result<SectionAddress> {
|
||||||
|
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<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
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<E>(self, v: u64) -> Result<Self::Value, E>
|
||||||
|
where E: serde::de::Error {
|
||||||
|
Ok(SectionAddressRef::new(None, v.try_into().map_err(serde::de::Error::custom)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue