mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 676488433f | |||
| 83de98b5ee | |||
| c1ba4e91d1 | |||
| 575900024d | |||
| cbe299e859 | |||
| 741d93e211 | |||
| 603dbd6882 | |||
| 6fb0a63de2 | |||
| ab2e84a2c6 | |||
| 9596051cb4 | |||
| a5d9d8282e | |||
| 3287a0f65c | |||
| fab9c62dfb | |||
| 08cd768260 |
@@ -142,7 +142,11 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Install cargo-zigbuild
|
- name: Install cargo-zigbuild
|
||||||
if: matrix.build == 'zigbuild'
|
if: matrix.build == 'zigbuild'
|
||||||
run: pip install ziglang==0.13.0 cargo-zigbuild==0.19.1
|
run: |
|
||||||
|
python3 -m venv .venv
|
||||||
|
. .venv/bin/activate
|
||||||
|
echo PATH=$PATH >> $GITHUB_ENV
|
||||||
|
pip install ziglang==0.13.0 cargo-zigbuild==0.19.1
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
|
|||||||
Generated
+132
-129
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -13,7 +13,7 @@ strip = "debuginfo"
|
|||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "2.2.0"
|
version = "2.3.2"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|||||||
@@ -133,6 +133,13 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"ref": "#/$defs/metadata"
|
"ref": "#/$defs/metadata"
|
||||||
|
},
|
||||||
|
"symbol_mappings": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Manual symbol mappings from target to base.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ feature-depth = 1
|
|||||||
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||||
# output a note when they are encountered.
|
# output a note when they are encountered.
|
||||||
ignore = [
|
ignore = [
|
||||||
"RUSTSEC-2024-0370",
|
|
||||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||||
@@ -240,7 +239,7 @@ allow-git = []
|
|||||||
|
|
||||||
[sources.allow-org]
|
[sources.allow-org]
|
||||||
# github.com organizations to allow git sources for
|
# github.com organizations to allow git sources for
|
||||||
github = ["encounter"]
|
github = ["notify-rs"]
|
||||||
# gitlab.com organizations to allow git sources for
|
# gitlab.com organizations to allow git sources for
|
||||||
gitlab = []
|
gitlab = []
|
||||||
# bitbucket.org organizations to allow git sources for
|
# bitbucket.org organizations to allow git sources for
|
||||||
|
|||||||
+33
-21
@@ -102,26 +102,32 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
let unit_path =
|
let unit_path =
|
||||||
PathBuf::from_str(u).ok().and_then(|p| fs::canonicalize(p).ok());
|
PathBuf::from_str(u).ok().and_then(|p| fs::canonicalize(p).ok());
|
||||||
|
|
||||||
let Some(object) = project_config.objects.iter_mut().find_map(|obj| {
|
let Some(object) = project_config
|
||||||
if obj.name.as_deref() == Some(u) {
|
.units
|
||||||
|
.as_deref_mut()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.iter_mut()
|
||||||
|
.find_map(|obj| {
|
||||||
|
if obj.name.as_deref() == Some(u) {
|
||||||
|
resolve_paths(obj);
|
||||||
|
return Some(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
let up = unit_path.as_deref()?;
|
||||||
|
|
||||||
resolve_paths(obj);
|
resolve_paths(obj);
|
||||||
return Some(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
let up = unit_path.as_deref()?;
|
if [&obj.base_path, &obj.target_path]
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|p| p.as_ref().and_then(|p| p.canonicalize().ok()))
|
||||||
|
.any(|p| p == up)
|
||||||
|
{
|
||||||
|
return Some(obj);
|
||||||
|
}
|
||||||
|
|
||||||
resolve_paths(obj);
|
None
|
||||||
|
})
|
||||||
if [&obj.base_path, &obj.target_path]
|
else {
|
||||||
.into_iter()
|
|
||||||
.filter_map(|p| p.as_ref().and_then(|p| p.canonicalize().ok()))
|
|
||||||
.any(|p| p == up)
|
|
||||||
{
|
|
||||||
return Some(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}) else {
|
|
||||||
bail!("Unit not found: {}", u)
|
bail!("Unit not found: {}", u)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -129,7 +135,13 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
} else if let Some(symbol_name) = &args.symbol {
|
} else if let Some(symbol_name) = &args.symbol {
|
||||||
let mut idx = None;
|
let mut idx = None;
|
||||||
let mut count = 0usize;
|
let mut count = 0usize;
|
||||||
for (i, obj) in project_config.objects.iter_mut().enumerate() {
|
for (i, obj) in project_config
|
||||||
|
.units
|
||||||
|
.as_deref_mut()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
resolve_paths(obj);
|
resolve_paths(obj);
|
||||||
|
|
||||||
if obj
|
if obj
|
||||||
@@ -148,7 +160,7 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
}
|
}
|
||||||
match (count, idx) {
|
match (count, idx) {
|
||||||
(0, None) => bail!("Symbol not found: {}", symbol_name),
|
(0, None) => bail!("Symbol not found: {}", symbol_name),
|
||||||
(1, Some(i)) => &mut project_config.objects[i],
|
(1, Some(i)) => &mut project_config.units_mut()[i],
|
||||||
(2.., Some(_)) => bail!(
|
(2.., Some(_)) => bail!(
|
||||||
"Multiple instances of {} were found, try specifying a unit",
|
"Multiple instances of {} were found, try specifying a unit",
|
||||||
symbol_name
|
symbol_name
|
||||||
@@ -303,7 +315,7 @@ fn find_function(obj: &ObjInfo, name: &str) -> Option<SymbolRef> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[expect(dead_code)]
|
||||||
struct FunctionDiffUi {
|
struct FunctionDiffUi {
|
||||||
relax_reloc_diffs: bool,
|
relax_reloc_diffs: bool,
|
||||||
left_highlight: HighlightKind,
|
left_highlight: HighlightKind,
|
||||||
@@ -758,7 +770,7 @@ impl FunctionDiffUi {
|
|||||||
self.scroll_y += self.per_page / if half { 2 } else { 1 };
|
self.scroll_y += self.per_page / if half { 2 } else { 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[expect(clippy::too_many_arguments)]
|
||||||
fn print_sym(
|
fn print_sym(
|
||||||
&self,
|
&self,
|
||||||
out: &mut Text<'static>,
|
out: &mut Text<'static>,
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
};
|
};
|
||||||
info!(
|
info!(
|
||||||
"Generating report for {} units (using {} threads)",
|
"Generating report for {} units (using {} threads)",
|
||||||
project.objects.len(),
|
project.units().len(),
|
||||||
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
let mut existing_functions: HashSet<String> = HashSet::new();
|
let mut existing_functions: HashSet<String> = HashSet::new();
|
||||||
if args.deduplicate {
|
if args.deduplicate {
|
||||||
// If deduplicating, we need to run single-threaded
|
// If deduplicating, we need to run single-threaded
|
||||||
for object in &mut project.objects {
|
for object in project.units.as_deref_mut().unwrap_or_default() {
|
||||||
if let Some(unit) = report_object(
|
if let Some(unit) = report_object(
|
||||||
object,
|
object,
|
||||||
project_dir,
|
project_dir,
|
||||||
@@ -116,7 +116,9 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let vec = project
|
let vec = project
|
||||||
.objects
|
.units
|
||||||
|
.as_deref_mut()
|
||||||
|
.unwrap_or_default()
|
||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.map(|object| {
|
.map(|object| {
|
||||||
report_object(
|
report_object(
|
||||||
@@ -132,7 +134,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
}
|
}
|
||||||
let measures = units.iter().flat_map(|u| u.measures.into_iter()).collect();
|
let measures = units.iter().flat_map(|u| u.measures.into_iter()).collect();
|
||||||
let mut categories = Vec::new();
|
let mut categories = Vec::new();
|
||||||
for category in &project.progress_categories {
|
for category in project.progress_categories() {
|
||||||
categories.push(ReportCategory {
|
categories.push(ReportCategory {
|
||||||
id: category.id.clone(),
|
id: category.id.clone(),
|
||||||
name: category.name.clone(),
|
name: category.name.clone(),
|
||||||
@@ -199,7 +201,7 @@ fn report_object(
|
|||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated),
|
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated),
|
||||||
};
|
};
|
||||||
let mut measures = Measures::default();
|
let mut measures = Measures { total_units: 1, ..Default::default() };
|
||||||
let mut sections = vec![];
|
let mut sections = vec![];
|
||||||
let mut functions = vec![];
|
let mut functions = vec![];
|
||||||
|
|
||||||
@@ -280,6 +282,7 @@ fn report_object(
|
|||||||
if metadata.complete.unwrap_or(false) {
|
if metadata.complete.unwrap_or(false) {
|
||||||
measures.complete_code = measures.total_code;
|
measures.complete_code = measures.total_code;
|
||||||
measures.complete_data = measures.total_data;
|
measures.complete_data = measures.total_data;
|
||||||
|
measures.complete_units = 1;
|
||||||
}
|
}
|
||||||
measures.calc_fuzzy_match_percent();
|
measures.calc_fuzzy_match_percent();
|
||||||
measures.calc_matched_percent();
|
measures.calc_matched_percent();
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "bindings"]
|
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "bindings"]
|
||||||
any-arch = [] # Implicit, used to check if any arch is enabled
|
any-arch = ["bimap"] # Implicit, used to check if any arch is enabled
|
||||||
config = ["globset", "semver", "serde_json", "serde_yaml"]
|
config = ["bimap", "globset", "semver", "serde_json", "serde_yaml"]
|
||||||
dwarf = ["gimli"]
|
dwarf = ["gimli"]
|
||||||
mips = ["any-arch", "rabbitizer"]
|
mips = ["any-arch", "rabbitizer"]
|
||||||
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
|
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
|
||||||
@@ -32,6 +32,7 @@ features = ["all"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
bimap = { version = "0.6", features = ["serde"], optional = true }
|
||||||
byteorder = "1.5"
|
byteorder = "1.5"
|
||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
flagset = "0.4"
|
flagset = "0.4"
|
||||||
@@ -60,7 +61,7 @@ gimli = { version = "0.31", default-features = false, features = ["read-all"], o
|
|||||||
|
|
||||||
# ppc
|
# ppc
|
||||||
cwdemangle = { version = "1.0", optional = true }
|
cwdemangle = { version = "1.0", optional = true }
|
||||||
cwextab = { version = "0.3", optional = true }
|
cwextab = { version = "1.0.2", optional = true }
|
||||||
ppc750cl = { version = "0.3", optional = true }
|
ppc750cl = { version = "0.3", optional = true }
|
||||||
|
|
||||||
# mips
|
# mips
|
||||||
|
|||||||
Binary file not shown.
@@ -32,6 +32,10 @@ message Measures {
|
|||||||
uint64 complete_data = 13;
|
uint64 complete_data = 13;
|
||||||
// Completed (or "linked") data percent
|
// Completed (or "linked") data percent
|
||||||
float complete_data_percent = 14;
|
float complete_data_percent = 14;
|
||||||
|
// Total number of units
|
||||||
|
uint32 total_units = 15;
|
||||||
|
// Completed (or "linked") units
|
||||||
|
uint32 complete_units = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Project progress report
|
// Project progress report
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ impl ObjArch for ObjArchMips {
|
|||||||
&self,
|
&self,
|
||||||
address: u64,
|
address: u64,
|
||||||
code: &[u8],
|
code: &[u8],
|
||||||
_section_index: usize,
|
section_index: usize,
|
||||||
relocations: &[ObjReloc],
|
relocations: &[ObjReloc],
|
||||||
line_info: &BTreeMap<u64, u32>,
|
line_info: &BTreeMap<u64, u32>,
|
||||||
config: &DiffObjConfig,
|
config: &DiffObjConfig,
|
||||||
@@ -140,11 +140,18 @@ impl ObjArch for ObjArchMips {
|
|||||||
| OperandType::cpu_label
|
| OperandType::cpu_label
|
||||||
| OperandType::cpu_branch_target_label => {
|
| OperandType::cpu_branch_target_label => {
|
||||||
if let Some(reloc) = reloc {
|
if let Some(reloc) = reloc {
|
||||||
if matches!(&reloc.target_section, Some(s) if s == ".text")
|
// If the relocation target is within the current function, we can
|
||||||
&& reloc.target.address > start_address
|
// convert it into a relative branch target. Note that we check
|
||||||
&& reloc.target.address < end_address
|
// target_address > start_address instead of >= so that recursive
|
||||||
|
// tail calls are not considered branch targets.
|
||||||
|
let target_address =
|
||||||
|
reloc.target.address.checked_add_signed(reloc.addend);
|
||||||
|
if reloc.target.orig_section_index == Some(section_index)
|
||||||
|
&& matches!(target_address, Some(addr) if addr > start_address && addr < end_address)
|
||||||
{
|
{
|
||||||
args.push(ObjInsArg::BranchDest(reloc.target.address));
|
let target_address = target_address.unwrap();
|
||||||
|
args.push(ObjInsArg::BranchDest(target_address));
|
||||||
|
branch_dest = Some(target_address);
|
||||||
} else {
|
} else {
|
||||||
push_reloc(&mut args, reloc)?;
|
push_reloc(&mut args, reloc)?;
|
||||||
branch_dest = None;
|
branch_dest = None;
|
||||||
|
|||||||
@@ -70,9 +70,13 @@ impl FunctionDiff {
|
|||||||
// let (_section, symbol) = object.section_symbol(symbol_ref);
|
// let (_section, symbol) = object.section_symbol(symbol_ref);
|
||||||
// Symbol::from(symbol)
|
// Symbol::from(symbol)
|
||||||
// });
|
// });
|
||||||
let instructions = symbol_diff.instructions.iter().map(InstructionDiff::from).collect();
|
let instructions = symbol_diff
|
||||||
|
.instructions
|
||||||
|
.iter()
|
||||||
|
.map(|ins_diff| InstructionDiff::new(object, ins_diff))
|
||||||
|
.collect();
|
||||||
Self {
|
Self {
|
||||||
symbol: Some(Symbol::from(symbol)),
|
symbol: Some(Symbol::new(symbol)),
|
||||||
// diff_symbol,
|
// diff_symbol,
|
||||||
instructions,
|
instructions,
|
||||||
match_percent: symbol_diff.match_percent,
|
match_percent: symbol_diff.match_percent,
|
||||||
@@ -90,8 +94,8 @@ impl DataDiff {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjSymbol> for Symbol {
|
impl Symbol {
|
||||||
fn from(value: &'a ObjSymbol) -> Self {
|
pub fn new(value: &ObjSymbol) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: value.name.to_string(),
|
name: value.name.to_string(),
|
||||||
demangled_name: value.demangled_name.clone(),
|
demangled_name: value.demangled_name.clone(),
|
||||||
@@ -122,29 +126,29 @@ fn symbol_flags(value: ObjSymbolFlagSet) -> u32 {
|
|||||||
flags
|
flags
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjIns> for Instruction {
|
impl Instruction {
|
||||||
fn from(value: &'a ObjIns) -> Self {
|
pub fn new(object: &ObjInfo, instruction: &ObjIns) -> Self {
|
||||||
Self {
|
Self {
|
||||||
address: value.address,
|
address: instruction.address,
|
||||||
size: value.size as u32,
|
size: instruction.size as u32,
|
||||||
opcode: value.op as u32,
|
opcode: instruction.op as u32,
|
||||||
mnemonic: value.mnemonic.clone(),
|
mnemonic: instruction.mnemonic.clone(),
|
||||||
formatted: value.formatted.clone(),
|
formatted: instruction.formatted.clone(),
|
||||||
arguments: value.args.iter().map(Argument::from).collect(),
|
arguments: instruction.args.iter().map(Argument::new).collect(),
|
||||||
relocation: value.reloc.as_ref().map(Relocation::from),
|
relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)),
|
||||||
branch_dest: value.branch_dest,
|
branch_dest: instruction.branch_dest,
|
||||||
line_number: value.line,
|
line_number: instruction.line,
|
||||||
original: value.orig.clone(),
|
original: instruction.orig.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjInsArg> for Argument {
|
impl Argument {
|
||||||
fn from(value: &'a ObjInsArg) -> Self {
|
pub fn new(value: &ObjInsArg) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: Some(match value {
|
value: Some(match value {
|
||||||
ObjInsArg::PlainText(s) => argument::Value::PlainText(s.to_string()),
|
ObjInsArg::PlainText(s) => argument::Value::PlainText(s.to_string()),
|
||||||
ObjInsArg::Arg(v) => argument::Value::Argument(ArgumentValue::from(v)),
|
ObjInsArg::Arg(v) => argument::Value::Argument(ArgumentValue::new(v)),
|
||||||
ObjInsArg::Reloc => argument::Value::Relocation(ArgumentRelocation {}),
|
ObjInsArg::Reloc => argument::Value::Relocation(ArgumentRelocation {}),
|
||||||
ObjInsArg::BranchDest(dest) => argument::Value::BranchDest(*dest),
|
ObjInsArg::BranchDest(dest) => argument::Value::BranchDest(*dest),
|
||||||
}),
|
}),
|
||||||
@@ -152,8 +156,8 @@ impl<'a> From<&'a ObjInsArg> for Argument {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&ObjInsArgValue> for ArgumentValue {
|
impl ArgumentValue {
|
||||||
fn from(value: &ObjInsArgValue) -> Self {
|
pub fn new(value: &ObjInsArgValue) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: Some(match value {
|
value: Some(match value {
|
||||||
ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v),
|
ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v),
|
||||||
@@ -164,42 +168,39 @@ impl From<&ObjInsArgValue> for ArgumentValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjReloc> for Relocation {
|
impl Relocation {
|
||||||
fn from(value: &ObjReloc) -> Self {
|
pub fn new(object: &ObjInfo, reloc: &ObjReloc) -> Self {
|
||||||
Self {
|
Self {
|
||||||
r#type: match value.flags {
|
r#type: match reloc.flags {
|
||||||
object::RelocationFlags::Elf { r_type } => r_type,
|
object::RelocationFlags::Elf { r_type } => r_type,
|
||||||
object::RelocationFlags::MachO { r_type, .. } => r_type as u32,
|
object::RelocationFlags::MachO { r_type, .. } => r_type as u32,
|
||||||
object::RelocationFlags::Coff { typ } => typ as u32,
|
object::RelocationFlags::Coff { typ } => typ as u32,
|
||||||
object::RelocationFlags::Xcoff { r_rtype, .. } => r_rtype as u32,
|
object::RelocationFlags::Xcoff { r_rtype, .. } => r_rtype as u32,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
type_name: String::new(), // TODO
|
type_name: object.arch.display_reloc(reloc.flags).into_owned(),
|
||||||
target: Some(RelocationTarget::from(&value.target)),
|
target: Some(RelocationTarget {
|
||||||
|
symbol: Some(Symbol::new(&reloc.target)),
|
||||||
|
addend: reloc.addend,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjSymbol> for RelocationTarget {
|
impl InstructionDiff {
|
||||||
fn from(value: &'a ObjSymbol) -> Self {
|
pub fn new(object: &ObjInfo, instruction_diff: &ObjInsDiff) -> Self {
|
||||||
Self { symbol: Some(Symbol::from(value)), addend: value.addend }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a ObjInsDiff> for InstructionDiff {
|
|
||||||
fn from(value: &'a ObjInsDiff) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
instruction: value.ins.as_ref().map(Instruction::from),
|
instruction: instruction_diff.ins.as_ref().map(|ins| Instruction::new(object, ins)),
|
||||||
diff_kind: DiffKind::from(value.kind) as i32,
|
diff_kind: DiffKind::from(instruction_diff.kind) as i32,
|
||||||
branch_from: value.branch_from.as_ref().map(InstructionBranchFrom::from),
|
branch_from: instruction_diff.branch_from.as_ref().map(InstructionBranchFrom::new),
|
||||||
branch_to: value.branch_to.as_ref().map(InstructionBranchTo::from),
|
branch_to: instruction_diff.branch_to.as_ref().map(InstructionBranchTo::new),
|
||||||
arg_diff: value.arg_diff.iter().map(ArgumentDiff::from).collect(),
|
arg_diff: instruction_diff.arg_diff.iter().map(ArgumentDiff::new).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Option<ObjInsArgDiff>> for ArgumentDiff {
|
impl ArgumentDiff {
|
||||||
fn from(value: &Option<ObjInsArgDiff>) -> Self {
|
pub fn new(value: &Option<ObjInsArgDiff>) -> Self {
|
||||||
Self { diff_index: value.as_ref().map(|v| v.idx as u32) }
|
Self { diff_index: value.as_ref().map(|v| v.idx as u32) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,8 +229,8 @@ impl From<ObjDataDiffKind> for DiffKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjInsBranchFrom> for InstructionBranchFrom {
|
impl InstructionBranchFrom {
|
||||||
fn from(value: &'a ObjInsBranchFrom) -> Self {
|
pub fn new(value: &ObjInsBranchFrom) -> Self {
|
||||||
Self {
|
Self {
|
||||||
instruction_index: value.ins_idx.iter().map(|&x| x as u32).collect(),
|
instruction_index: value.ins_idx.iter().map(|&x| x as u32).collect(),
|
||||||
branch_index: value.branch_idx as u32,
|
branch_index: value.branch_idx as u32,
|
||||||
@@ -237,8 +238,8 @@ impl<'a> From<&'a ObjInsBranchFrom> for InstructionBranchFrom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjInsBranchTo> for InstructionBranchTo {
|
impl InstructionBranchTo {
|
||||||
fn from(value: &'a ObjInsBranchTo) -> Self {
|
pub fn new(value: &ObjInsBranchTo) -> Self {
|
||||||
Self { instruction_index: value.ins_idx as u32, branch_index: value.branch_idx as u32 }
|
Self { instruction_index: value.ins_idx as u32, branch_index: value.branch_idx as u32 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ use serde_json::error::Category;
|
|||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
|
||||||
|
|
||||||
pub const REPORT_VERSION: u32 = 1;
|
pub const REPORT_VERSION: u32 = 2;
|
||||||
|
|
||||||
impl Report {
|
impl Report {
|
||||||
|
/// Attempts to parse the report as binary protobuf or JSON.
|
||||||
pub fn parse(data: &[u8]) -> Result<Self> {
|
pub fn parse(data: &[u8]) -> Result<Self> {
|
||||||
if data.is_empty() {
|
if data.is_empty() {
|
||||||
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
|
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
|
||||||
@@ -25,6 +26,7 @@ impl Report {
|
|||||||
Ok(report)
|
Ok(report)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to parse the report as JSON, migrating from the legacy report format if necessary.
|
||||||
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
||||||
match serde_json::from_slice::<Self>(bytes) {
|
match serde_json::from_slice::<Self>(bytes) {
|
||||||
Ok(report) => Ok(report),
|
Ok(report) => Ok(report),
|
||||||
@@ -43,16 +45,23 @@ impl Report {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Migrates the report to the latest version.
|
||||||
|
/// Fails if the report version is newer than supported.
|
||||||
pub fn migrate(&mut self) -> Result<()> {
|
pub fn migrate(&mut self) -> Result<()> {
|
||||||
if self.version == 0 {
|
if self.version == 0 {
|
||||||
self.migrate_v0()?;
|
self.migrate_v0()?;
|
||||||
}
|
}
|
||||||
|
if self.version == 1 {
|
||||||
|
self.migrate_v1()?;
|
||||||
|
}
|
||||||
if self.version != REPORT_VERSION {
|
if self.version != REPORT_VERSION {
|
||||||
bail!("Unsupported report version: {}", self.version);
|
bail!("Unsupported report version: {}", self.version);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds `complete_code`, `complete_data`, `complete_code_percent`, and `complete_data_percent`
|
||||||
|
/// to measures, and sets `progress_categories` in unit metadata.
|
||||||
fn migrate_v0(&mut self) -> Result<()> {
|
fn migrate_v0(&mut self) -> Result<()> {
|
||||||
let Some(measures) = &mut self.measures else {
|
let Some(measures) = &mut self.measures else {
|
||||||
bail!("Missing measures in report");
|
bail!("Missing measures in report");
|
||||||
@@ -61,15 +70,16 @@ impl Report {
|
|||||||
let Some(unit_measures) = &mut unit.measures else {
|
let Some(unit_measures) = &mut unit.measures else {
|
||||||
bail!("Missing measures in report unit");
|
bail!("Missing measures in report unit");
|
||||||
};
|
};
|
||||||
let Some(metadata) = &mut unit.metadata else {
|
let mut complete = false;
|
||||||
bail!("Missing metadata in report unit");
|
if let Some(metadata) = &mut unit.metadata {
|
||||||
|
if metadata.module_name.is_some() || metadata.module_id.is_some() {
|
||||||
|
metadata.progress_categories = vec!["modules".to_string()];
|
||||||
|
} else {
|
||||||
|
metadata.progress_categories = vec!["dol".to_string()];
|
||||||
|
}
|
||||||
|
complete = metadata.complete.unwrap_or(false);
|
||||||
};
|
};
|
||||||
if metadata.module_name.is_some() || metadata.module_id.is_some() {
|
if complete {
|
||||||
metadata.progress_categories = vec!["modules".to_string()];
|
|
||||||
} else {
|
|
||||||
metadata.progress_categories = vec!["dol".to_string()];
|
|
||||||
}
|
|
||||||
if metadata.complete.unwrap_or(false) {
|
|
||||||
unit_measures.complete_code = unit_measures.total_code;
|
unit_measures.complete_code = unit_measures.total_code;
|
||||||
unit_measures.complete_data = unit_measures.total_data;
|
unit_measures.complete_data = unit_measures.total_data;
|
||||||
unit_measures.complete_code_percent = 100.0;
|
unit_measures.complete_code_percent = 100.0;
|
||||||
@@ -84,10 +94,42 @@ impl Report {
|
|||||||
measures.complete_data += unit_measures.complete_data;
|
measures.complete_data += unit_measures.complete_data;
|
||||||
}
|
}
|
||||||
measures.calc_matched_percent();
|
measures.calc_matched_percent();
|
||||||
|
self.calculate_progress_categories();
|
||||||
self.version = 1;
|
self.version = 1;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds `total_units` and `complete_units` to measures.
|
||||||
|
fn migrate_v1(&mut self) -> Result<()> {
|
||||||
|
let Some(total_measures) = &mut self.measures else {
|
||||||
|
bail!("Missing measures in report");
|
||||||
|
};
|
||||||
|
for unit in &mut self.units {
|
||||||
|
let Some(measures) = &mut unit.measures else {
|
||||||
|
bail!("Missing measures in report unit");
|
||||||
|
};
|
||||||
|
let complete = unit.metadata.as_ref().and_then(|m| m.complete).unwrap_or(false) as u32;
|
||||||
|
let progress_categories =
|
||||||
|
unit.metadata.as_ref().map(|m| m.progress_categories.as_slice()).unwrap_or(&[]);
|
||||||
|
measures.total_units = 1;
|
||||||
|
measures.complete_units = complete;
|
||||||
|
total_measures.total_units += 1;
|
||||||
|
total_measures.complete_units += complete;
|
||||||
|
for id in progress_categories {
|
||||||
|
if let Some(category) = self.categories.iter_mut().find(|c| &c.id == id) {
|
||||||
|
let Some(measures) = &mut category.measures else {
|
||||||
|
bail!("Missing measures in category");
|
||||||
|
};
|
||||||
|
measures.total_units += 1;
|
||||||
|
measures.complete_units += complete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.version = 2;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate progress categories based on unit metadata.
|
||||||
pub fn calculate_progress_categories(&mut self) {
|
pub fn calculate_progress_categories(&mut self) {
|
||||||
for unit in &self.units {
|
for unit in &self.units {
|
||||||
let Some(metadata) = unit.metadata.as_ref() else {
|
let Some(metadata) = unit.metadata.as_ref() else {
|
||||||
@@ -242,6 +284,8 @@ impl AddAssign for Measures {
|
|||||||
self.matched_functions += other.matched_functions;
|
self.matched_functions += other.matched_functions;
|
||||||
self.complete_code += other.complete_code;
|
self.complete_code += other.complete_code;
|
||||||
self.complete_data += other.complete_data;
|
self.complete_data += other.complete_data;
|
||||||
|
self.total_units += other.total_units;
|
||||||
|
self.complete_units += other.complete_units;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,77 +1,100 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
fs,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{BufReader, Read},
|
io::{BufReader, BufWriter, Read},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use bimap::BiBTreeMap;
|
||||||
use filetime::FileTime;
|
use filetime::FileTime;
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
|
||||||
#[inline]
|
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
fn bool_true() -> bool { true }
|
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Deserialize)]
|
|
||||||
pub struct ProjectConfig {
|
pub struct ProjectConfig {
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub min_version: Option<String>,
|
pub min_version: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub custom_make: Option<String>,
|
pub custom_make: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub custom_args: Option<Vec<String>>,
|
pub custom_args: Option<Vec<String>>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub target_dir: Option<PathBuf>,
|
pub target_dir: Option<PathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub base_dir: Option<PathBuf>,
|
pub base_dir: Option<PathBuf>,
|
||||||
#[serde(default = "bool_true")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub build_base: bool,
|
pub build_base: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub build_target: bool,
|
pub build_target: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub watch_patterns: Option<Vec<Glob>>,
|
pub watch_patterns: Option<Vec<Glob>>,
|
||||||
#[serde(default, alias = "units")]
|
#[serde(default, alias = "objects", skip_serializing_if = "Option::is_none")]
|
||||||
pub objects: Vec<ProjectObject>,
|
pub units: Option<Vec<ProjectObject>>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub progress_categories: Vec<ProjectProgressCategory>,
|
pub progress_categories: Option<Vec<ProjectProgressCategory>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Deserialize)]
|
impl ProjectConfig {
|
||||||
|
#[inline]
|
||||||
|
pub fn units(&self) -> &[ProjectObject] { self.units.as_deref().unwrap_or_default() }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn units_mut(&mut self) -> &mut Vec<ProjectObject> {
|
||||||
|
self.units.get_or_insert_with(Vec::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn progress_categories(&self) -> &[ProjectProgressCategory] {
|
||||||
|
self.progress_categories.as_deref().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn progress_categories_mut(&mut self) -> &mut Vec<ProjectProgressCategory> {
|
||||||
|
self.progress_categories.get_or_insert_with(Vec::new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct ProjectObject {
|
pub struct ProjectObject {
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub path: Option<PathBuf>,
|
pub path: Option<PathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub target_path: Option<PathBuf>,
|
pub target_path: Option<PathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub base_path: Option<PathBuf>,
|
pub base_path: Option<PathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
#[deprecated(note = "Use metadata.reverse_fn_order")]
|
#[deprecated(note = "Use metadata.reverse_fn_order")]
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
#[deprecated(note = "Use metadata.complete")]
|
#[deprecated(note = "Use metadata.complete")]
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub scratch: Option<ScratchConfig>,
|
pub scratch: Option<ScratchConfig>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub metadata: Option<ProjectObjectMetadata>,
|
pub metadata: Option<ProjectObjectMetadata>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub symbol_mappings: Option<SymbolMappings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Deserialize)]
|
pub type SymbolMappings = BiBTreeMap<String, String>;
|
||||||
|
|
||||||
|
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct ProjectObjectMetadata {
|
pub struct ProjectObjectMetadata {
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub source_path: Option<String>,
|
pub source_path: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub progress_categories: Option<Vec<String>>,
|
pub progress_categories: Option<Vec<String>>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub auto_generated: Option<bool>,
|
pub auto_generated: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Deserialize)]
|
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct ProjectProgressCategory {
|
pub struct ProjectProgressCategory {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub id: String,
|
pub id: String,
|
||||||
@@ -112,12 +135,12 @@ impl ProjectObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn complete(&self) -> Option<bool> {
|
pub fn complete(&self) -> Option<bool> {
|
||||||
#[allow(deprecated)]
|
#[expect(deprecated)]
|
||||||
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
|
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reverse_fn_order(&self) -> Option<bool> {
|
pub fn reverse_fn_order(&self) -> Option<bool> {
|
||||||
#[allow(deprecated)]
|
#[expect(deprecated)]
|
||||||
self.metadata.as_ref().and_then(|m| m.reverse_fn_order).or(self.reverse_fn_order)
|
self.metadata.as_ref().and_then(|m| m.reverse_fn_order).or(self.reverse_fn_order)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,16 +155,16 @@ impl ProjectObject {
|
|||||||
|
|
||||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct ScratchConfig {
|
pub struct ScratchConfig {
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub platform: Option<String>,
|
pub platform: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub compiler: Option<String>,
|
pub compiler: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub c_flags: Option<String>,
|
pub c_flags: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub ctx_path: Option<PathBuf>,
|
pub ctx_path: Option<PathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub build_ctx: bool,
|
pub build_ctx: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];
|
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];
|
||||||
@@ -154,7 +177,7 @@ pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
|
|||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub struct ProjectConfigInfo {
|
pub struct ProjectConfigInfo {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub timestamp: FileTime,
|
pub timestamp: Option<FileTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
||||||
@@ -180,12 +203,41 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
|
|||||||
result = Err(e);
|
result = Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Some((result, ProjectConfigInfo { path: config_path, timestamp: ts }));
|
return Some((result, ProjectConfigInfo { path: config_path, timestamp: Some(ts) }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn save_project_config(
|
||||||
|
config: &ProjectConfig,
|
||||||
|
info: &ProjectConfigInfo,
|
||||||
|
) -> Result<ProjectConfigInfo> {
|
||||||
|
if let Some(last_ts) = info.timestamp {
|
||||||
|
// Check if the file has changed since we last read it
|
||||||
|
if let Ok(metadata) = fs::metadata(&info.path) {
|
||||||
|
let ts = FileTime::from_last_modification_time(&metadata);
|
||||||
|
if ts != last_ts {
|
||||||
|
return Err(anyhow!("Config file has changed since last read"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut writer =
|
||||||
|
BufWriter::new(File::create(&info.path).context("Failed to create config file")?);
|
||||||
|
let ext = info.path.extension().and_then(|ext| ext.to_str()).unwrap_or("json");
|
||||||
|
match ext {
|
||||||
|
"json" => serde_json::to_writer_pretty(&mut writer, config).context("Failed to write JSON"),
|
||||||
|
"yml" | "yaml" => {
|
||||||
|
serde_yaml::to_writer(&mut writer, config).context("Failed to write YAML")
|
||||||
|
}
|
||||||
|
_ => Err(anyhow!("Unknown config file extension: {ext}")),
|
||||||
|
}?;
|
||||||
|
let file = writer.into_inner().context("Failed to flush file")?;
|
||||||
|
let metadata = file.metadata().context("Failed to get file metadata")?;
|
||||||
|
let ts = FileTime::from_last_modification_time(&metadata);
|
||||||
|
Ok(ProjectConfigInfo { path: info.path.clone(), timestamp: Some(ts) })
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_min_version(config: &ProjectConfig) -> Result<()> {
|
fn validate_min_version(config: &ProjectConfig) -> Result<()> {
|
||||||
let Some(min_version) = &config.min_version else { return Ok(()) };
|
let Some(min_version) = &config.min_version else { return Ok(()) };
|
||||||
let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
|
let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind,
|
DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind,
|
||||||
ObjSymbolDiff,
|
ObjSymbolDiff,
|
||||||
},
|
},
|
||||||
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbol, ObjSymbolFlags, SymbolRef},
|
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbolFlags, SymbolRef},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn process_code_symbol(
|
pub fn process_code_symbol(
|
||||||
@@ -41,10 +41,12 @@ pub fn no_diff_code(out: &ProcessCodeResult, symbol_ref: SymbolRef) -> Result<Ob
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
resolve_branches(&mut diff);
|
resolve_branches(&mut diff);
|
||||||
Ok(ObjSymbolDiff { symbol_ref, diff_symbol: None, instructions: diff, match_percent: None })
|
Ok(ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: diff, match_percent: None })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diff_code(
|
pub fn diff_code(
|
||||||
|
left_obj: &ObjInfo,
|
||||||
|
right_obj: &ObjInfo,
|
||||||
left_out: &ProcessCodeResult,
|
left_out: &ProcessCodeResult,
|
||||||
right_out: &ProcessCodeResult,
|
right_out: &ProcessCodeResult,
|
||||||
left_symbol_ref: SymbolRef,
|
left_symbol_ref: SymbolRef,
|
||||||
@@ -60,14 +62,14 @@ pub fn diff_code(
|
|||||||
|
|
||||||
let mut diff_state = InsDiffState::default();
|
let mut diff_state = InsDiffState::default();
|
||||||
for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) {
|
for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) {
|
||||||
let result = compare_ins(config, left, right, &mut diff_state)?;
|
let result = compare_ins(config, left_obj, right_obj, left, right, &mut diff_state)?;
|
||||||
left.kind = result.kind;
|
left.kind = result.kind;
|
||||||
right.kind = result.kind;
|
right.kind = result.kind;
|
||||||
left.arg_diff = result.left_args_diff;
|
left.arg_diff = result.left_args_diff;
|
||||||
right.arg_diff = result.right_args_diff;
|
right.arg_diff = result.right_args_diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
let total = left_out.insts.len();
|
let total = left_out.insts.len().max(right_out.insts.len());
|
||||||
let percent = if diff_state.diff_count >= total {
|
let percent = if diff_state.diff_count >= total {
|
||||||
0.0
|
0.0
|
||||||
} else {
|
} else {
|
||||||
@@ -77,13 +79,13 @@ pub fn diff_code(
|
|||||||
Ok((
|
Ok((
|
||||||
ObjSymbolDiff {
|
ObjSymbolDiff {
|
||||||
symbol_ref: left_symbol_ref,
|
symbol_ref: left_symbol_ref,
|
||||||
diff_symbol: Some(right_symbol_ref),
|
target_symbol: Some(right_symbol_ref),
|
||||||
instructions: left_diff,
|
instructions: left_diff,
|
||||||
match_percent: Some(percent),
|
match_percent: Some(percent),
|
||||||
},
|
},
|
||||||
ObjSymbolDiff {
|
ObjSymbolDiff {
|
||||||
symbol_ref: right_symbol_ref,
|
symbol_ref: right_symbol_ref,
|
||||||
diff_symbol: Some(left_symbol_ref),
|
target_symbol: Some(left_symbol_ref),
|
||||||
instructions: right_diff,
|
instructions: right_diff,
|
||||||
match_percent: Some(percent),
|
match_percent: Some(percent),
|
||||||
},
|
},
|
||||||
@@ -170,12 +172,33 @@ fn resolve_branches(vec: &mut [ObjInsDiff]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn address_eq(left: &ObjSymbol, right: &ObjSymbol) -> bool {
|
fn address_eq(left: &ObjReloc, right: &ObjReloc) -> bool {
|
||||||
left.address as i64 + left.addend == right.address as i64 + right.addend
|
left.target.address as i64 + left.addend == right.target.address as i64 + right.addend
|
||||||
|
}
|
||||||
|
|
||||||
|
fn section_name_eq(
|
||||||
|
left_obj: &ObjInfo,
|
||||||
|
right_obj: &ObjInfo,
|
||||||
|
left_orig_section_index: usize,
|
||||||
|
right_orig_section_index: usize,
|
||||||
|
) -> bool {
|
||||||
|
let Some(left_section) =
|
||||||
|
left_obj.sections.iter().find(|s| s.orig_index == left_orig_section_index)
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(right_section) =
|
||||||
|
right_obj.sections.iter().find(|s| s.orig_index == right_orig_section_index)
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
left_section.name == right_section.name
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reloc_eq(
|
fn reloc_eq(
|
||||||
config: &DiffObjConfig,
|
config: &DiffObjConfig,
|
||||||
|
left_obj: &ObjInfo,
|
||||||
|
right_obj: &ObjInfo,
|
||||||
left_reloc: Option<&ObjReloc>,
|
left_reloc: Option<&ObjReloc>,
|
||||||
right_reloc: Option<&ObjReloc>,
|
right_reloc: Option<&ObjReloc>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
@@ -189,29 +212,32 @@ fn reloc_eq(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let name_matches = left.target.name == right.target.name;
|
let symbol_name_matches = left.target.name == right.target.name;
|
||||||
match (&left.target_section, &right.target_section) {
|
match (&left.target.orig_section_index, &right.target.orig_section_index) {
|
||||||
(Some(sl), Some(sr)) => {
|
(Some(sl), Some(sr)) => {
|
||||||
// Match if section and name or address match
|
// Match if section and name or address match
|
||||||
sl == sr && (name_matches || address_eq(&left.target, &right.target))
|
section_name_eq(left_obj, right_obj, *sl, *sr)
|
||||||
|
&& (symbol_name_matches || address_eq(left, right))
|
||||||
}
|
}
|
||||||
(Some(_), None) => false,
|
(Some(_), None) => false,
|
||||||
(None, Some(_)) => {
|
(None, Some(_)) => {
|
||||||
// Match if possibly stripped weak symbol
|
// Match if possibly stripped weak symbol
|
||||||
name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak)
|
symbol_name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak)
|
||||||
}
|
}
|
||||||
(None, None) => name_matches,
|
(None, None) => symbol_name_matches,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arg_eq(
|
fn arg_eq(
|
||||||
config: &DiffObjConfig,
|
config: &DiffObjConfig,
|
||||||
|
left_obj: &ObjInfo,
|
||||||
|
right_obj: &ObjInfo,
|
||||||
left: &ObjInsArg,
|
left: &ObjInsArg,
|
||||||
right: &ObjInsArg,
|
right: &ObjInsArg,
|
||||||
left_diff: &ObjInsDiff,
|
left_diff: &ObjInsDiff,
|
||||||
right_diff: &ObjInsDiff,
|
right_diff: &ObjInsDiff,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
return match left {
|
match left {
|
||||||
ObjInsArg::PlainText(l) => match right {
|
ObjInsArg::PlainText(l) => match right {
|
||||||
ObjInsArg::PlainText(r) => l == r,
|
ObjInsArg::PlainText(r) => l == r,
|
||||||
_ => false,
|
_ => false,
|
||||||
@@ -227,6 +253,8 @@ fn arg_eq(
|
|||||||
matches!(right, ObjInsArg::Reloc)
|
matches!(right, ObjInsArg::Reloc)
|
||||||
&& reloc_eq(
|
&& reloc_eq(
|
||||||
config,
|
config,
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||||
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||||
)
|
)
|
||||||
@@ -236,7 +264,7 @@ fn arg_eq(
|
|||||||
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
||||||
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -257,6 +285,8 @@ struct InsDiffResult {
|
|||||||
|
|
||||||
fn compare_ins(
|
fn compare_ins(
|
||||||
config: &DiffObjConfig,
|
config: &DiffObjConfig,
|
||||||
|
left_obj: &ObjInfo,
|
||||||
|
right_obj: &ObjInfo,
|
||||||
left: &ObjInsDiff,
|
left: &ObjInsDiff,
|
||||||
right: &ObjInsDiff,
|
right: &ObjInsDiff,
|
||||||
state: &mut InsDiffState,
|
state: &mut InsDiffState,
|
||||||
@@ -283,7 +313,7 @@ fn compare_ins(
|
|||||||
state.diff_count += 1;
|
state.diff_count += 1;
|
||||||
}
|
}
|
||||||
for (a, b) in left_ins.args.iter().zip(&right_ins.args) {
|
for (a, b) in left_ins.args.iter().zip(&right_ins.args) {
|
||||||
if arg_eq(config, a, b, left, right) {
|
if arg_eq(config, left_obj, right_obj, a, b, left, right) {
|
||||||
result.left_args_diff.push(None);
|
result.left_args_diff.push(None);
|
||||||
result.right_args_diff.push(None);
|
result.right_args_diff.push(None);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ pub fn diff_bss_symbol(
|
|||||||
Ok((
|
Ok((
|
||||||
ObjSymbolDiff {
|
ObjSymbolDiff {
|
||||||
symbol_ref: left_symbol_ref,
|
symbol_ref: left_symbol_ref,
|
||||||
diff_symbol: Some(right_symbol_ref),
|
target_symbol: Some(right_symbol_ref),
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: Some(percent),
|
match_percent: Some(percent),
|
||||||
},
|
},
|
||||||
ObjSymbolDiff {
|
ObjSymbolDiff {
|
||||||
symbol_ref: right_symbol_ref,
|
symbol_ref: right_symbol_ref,
|
||||||
diff_symbol: Some(left_symbol_ref),
|
target_symbol: Some(left_symbol_ref),
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: Some(percent),
|
match_percent: Some(percent),
|
||||||
},
|
},
|
||||||
@@ -34,7 +34,7 @@ pub fn diff_bss_symbol(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff {
|
pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff {
|
||||||
ObjSymbolDiff { symbol_ref, diff_symbol: None, instructions: vec![], match_percent: None }
|
ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: vec![], match_percent: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compare the data sections of two object files.
|
/// Compare the data sections of two object files.
|
||||||
@@ -158,13 +158,13 @@ pub fn diff_data_symbol(
|
|||||||
Ok((
|
Ok((
|
||||||
ObjSymbolDiff {
|
ObjSymbolDiff {
|
||||||
symbol_ref: left_symbol_ref,
|
symbol_ref: left_symbol_ref,
|
||||||
diff_symbol: Some(right_symbol_ref),
|
target_symbol: Some(right_symbol_ref),
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: Some(match_percent),
|
match_percent: Some(match_percent),
|
||||||
},
|
},
|
||||||
ObjSymbolDiff {
|
ObjSymbolDiff {
|
||||||
symbol_ref: right_symbol_ref,
|
symbol_ref: right_symbol_ref,
|
||||||
diff_symbol: Some(left_symbol_ref),
|
target_symbol: Some(left_symbol_ref),
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: Some(match_percent),
|
match_percent: Some(match_percent),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ pub enum DiffText<'a> {
|
|||||||
Eol,
|
Eol,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
pub enum HighlightKind {
|
pub enum HighlightKind {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
@@ -94,9 +94,9 @@ fn display_reloc_name<E>(
|
|||||||
mut cb: impl FnMut(DiffText) -> Result<(), E>,
|
mut cb: impl FnMut(DiffText) -> Result<(), E>,
|
||||||
) -> Result<(), E> {
|
) -> Result<(), E> {
|
||||||
cb(DiffText::Symbol(&reloc.target))?;
|
cb(DiffText::Symbol(&reloc.target))?;
|
||||||
match reloc.target.addend.cmp(&0i64) {
|
match reloc.addend.cmp(&0i64) {
|
||||||
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.target.addend))),
|
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.addend))),
|
||||||
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.target.addend))),
|
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.addend))),
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::collections::HashSet;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
config::SymbolMappings,
|
||||||
diff::{
|
diff::{
|
||||||
code::{diff_code, no_diff_code, process_code_symbol},
|
code::{diff_code, no_diff_code, process_code_symbol},
|
||||||
data::{
|
data::{
|
||||||
@@ -161,6 +162,8 @@ pub struct DiffObjConfig {
|
|||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub space_between_args: bool,
|
pub space_between_args: bool,
|
||||||
pub combine_data_sections: bool,
|
pub combine_data_sections: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub symbol_mappings: MappingConfig,
|
||||||
// x86
|
// x86
|
||||||
pub x86_formatter: X86Formatter,
|
pub x86_formatter: X86Formatter,
|
||||||
// MIPS
|
// MIPS
|
||||||
@@ -182,6 +185,7 @@ impl Default for DiffObjConfig {
|
|||||||
relax_reloc_diffs: false,
|
relax_reloc_diffs: false,
|
||||||
space_between_args: true,
|
space_between_args: true,
|
||||||
combine_data_sections: false,
|
combine_data_sections: false,
|
||||||
|
symbol_mappings: Default::default(),
|
||||||
x86_formatter: Default::default(),
|
x86_formatter: Default::default(),
|
||||||
mips_abi: Default::default(),
|
mips_abi: Default::default(),
|
||||||
mips_instr_category: Default::default(),
|
mips_instr_category: Default::default(),
|
||||||
@@ -223,8 +227,10 @@ impl ObjSectionDiff {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ObjSymbolDiff {
|
pub struct ObjSymbolDiff {
|
||||||
|
/// The symbol ref this object
|
||||||
pub symbol_ref: SymbolRef,
|
pub symbol_ref: SymbolRef,
|
||||||
pub diff_symbol: Option<SymbolRef>,
|
/// The symbol ref in the _other_ object that this symbol was diffed against
|
||||||
|
pub target_symbol: Option<SymbolRef>,
|
||||||
pub instructions: Vec<ObjInsDiff>,
|
pub instructions: Vec<ObjInsDiff>,
|
||||||
pub match_percent: Option<f32>,
|
pub match_percent: Option<f32>,
|
||||||
}
|
}
|
||||||
@@ -294,8 +300,13 @@ pub struct ObjInsBranchTo {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ObjDiff {
|
pub struct ObjDiff {
|
||||||
|
/// A list of all section diffs in the object.
|
||||||
pub sections: Vec<ObjSectionDiff>,
|
pub sections: Vec<ObjSectionDiff>,
|
||||||
|
/// Common BSS symbols don't live in a section, so they're stored separately.
|
||||||
pub common: Vec<ObjSymbolDiff>,
|
pub common: Vec<ObjSymbolDiff>,
|
||||||
|
/// If `selecting_left` or `selecting_right` is set, this is the list of symbols
|
||||||
|
/// that are being mapped to the other object.
|
||||||
|
pub mapping_symbols: Vec<ObjSymbolDiff>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjDiff {
|
impl ObjDiff {
|
||||||
@@ -303,13 +314,14 @@ impl ObjDiff {
|
|||||||
let mut result = Self {
|
let mut result = Self {
|
||||||
sections: Vec::with_capacity(obj.sections.len()),
|
sections: Vec::with_capacity(obj.sections.len()),
|
||||||
common: Vec::with_capacity(obj.common.len()),
|
common: Vec::with_capacity(obj.common.len()),
|
||||||
|
mapping_symbols: vec![],
|
||||||
};
|
};
|
||||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||||
let mut symbols = Vec::with_capacity(section.symbols.len());
|
let mut symbols = Vec::with_capacity(section.symbols.len());
|
||||||
for (symbol_idx, _) in section.symbols.iter().enumerate() {
|
for (symbol_idx, _) in section.symbols.iter().enumerate() {
|
||||||
symbols.push(ObjSymbolDiff {
|
symbols.push(ObjSymbolDiff {
|
||||||
symbol_ref: SymbolRef { section_idx, symbol_idx },
|
symbol_ref: SymbolRef { section_idx, symbol_idx },
|
||||||
diff_symbol: None,
|
target_symbol: None,
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: None,
|
match_percent: None,
|
||||||
});
|
});
|
||||||
@@ -328,7 +340,7 @@ impl ObjDiff {
|
|||||||
for (symbol_idx, _) in obj.common.iter().enumerate() {
|
for (symbol_idx, _) in obj.common.iter().enumerate() {
|
||||||
result.common.push(ObjSymbolDiff {
|
result.common.push(ObjSymbolDiff {
|
||||||
symbol_ref: SymbolRef { section_idx: obj.sections.len(), symbol_idx },
|
symbol_ref: SymbolRef { section_idx: obj.sections.len(), symbol_idx },
|
||||||
diff_symbol: None,
|
target_symbol: None,
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: None,
|
match_percent: None,
|
||||||
});
|
});
|
||||||
@@ -378,7 +390,7 @@ pub fn diff_objs(
|
|||||||
right: Option<&ObjInfo>,
|
right: Option<&ObjInfo>,
|
||||||
prev: Option<&ObjInfo>,
|
prev: Option<&ObjInfo>,
|
||||||
) -> Result<DiffObjsResult> {
|
) -> Result<DiffObjsResult> {
|
||||||
let symbol_matches = matching_symbols(left, right, prev)?;
|
let symbol_matches = matching_symbols(left, right, prev, &config.symbol_mappings)?;
|
||||||
let section_matches = matching_sections(left, right)?;
|
let section_matches = matching_sections(left, right)?;
|
||||||
let mut left = left.map(|p| (p, ObjDiff::new_from_obj(p)));
|
let mut left = left.map(|p| (p, ObjDiff::new_from_obj(p)));
|
||||||
let mut right = right.map(|p| (p, ObjDiff::new_from_obj(p)));
|
let mut right = right.map(|p| (p, ObjDiff::new_from_obj(p)));
|
||||||
@@ -399,6 +411,8 @@ pub fn diff_objs(
|
|||||||
let left_code = process_code_symbol(left_obj, left_symbol_ref, config)?;
|
let left_code = process_code_symbol(left_obj, left_symbol_ref, config)?;
|
||||||
let right_code = process_code_symbol(right_obj, right_symbol_ref, config)?;
|
let right_code = process_code_symbol(right_obj, right_symbol_ref, config)?;
|
||||||
let (left_diff, right_diff) = diff_code(
|
let (left_diff, right_diff) = diff_code(
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
&left_code,
|
&left_code,
|
||||||
&right_code,
|
&right_code,
|
||||||
left_symbol_ref,
|
left_symbol_ref,
|
||||||
@@ -412,6 +426,8 @@ pub fn diff_objs(
|
|||||||
let (prev_obj, prev_out) = prev.as_mut().unwrap();
|
let (prev_obj, prev_out) = prev.as_mut().unwrap();
|
||||||
let prev_code = process_code_symbol(prev_obj, prev_symbol_ref, config)?;
|
let prev_code = process_code_symbol(prev_obj, prev_symbol_ref, config)?;
|
||||||
let (_, prev_diff) = diff_code(
|
let (_, prev_diff) = diff_code(
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
&right_code,
|
&right_code,
|
||||||
&prev_code,
|
&prev_code,
|
||||||
right_symbol_ref,
|
right_symbol_ref,
|
||||||
@@ -529,6 +545,17 @@ pub fn diff_objs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let (Some((right_obj, right_out)), Some((left_obj, left_out))) =
|
||||||
|
(right.as_mut(), left.as_mut())
|
||||||
|
{
|
||||||
|
if let Some(right_name) = &config.symbol_mappings.selecting_left {
|
||||||
|
generate_mapping_symbols(right_obj, right_name, left_obj, left_out, config)?;
|
||||||
|
}
|
||||||
|
if let Some(left_name) = &config.symbol_mappings.selecting_right {
|
||||||
|
generate_mapping_symbols(left_obj, left_name, right_obj, right_out, config)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(DiffObjsResult {
|
Ok(DiffObjsResult {
|
||||||
left: left.map(|(_, o)| o),
|
left: left.map(|(_, o)| o),
|
||||||
right: right.map(|(_, o)| o),
|
right: right.map(|(_, o)| o),
|
||||||
@@ -536,6 +563,65 @@ pub fn diff_objs(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When we're selecting a symbol to use as a comparison, we'll create comparisons for all
|
||||||
|
/// symbols in the other object that match the selected symbol's section and kind. This allows
|
||||||
|
/// us to display match percentages for all symbols in the other object that could be selected.
|
||||||
|
fn generate_mapping_symbols(
|
||||||
|
base_obj: &ObjInfo,
|
||||||
|
base_name: &str,
|
||||||
|
target_obj: &ObjInfo,
|
||||||
|
target_out: &mut ObjDiff,
|
||||||
|
config: &DiffObjConfig,
|
||||||
|
) -> Result<()> {
|
||||||
|
let Some(base_symbol_ref) = symbol_ref_by_name(base_obj, base_name) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let (base_section, base_symbol) = base_obj.section_symbol(base_symbol_ref);
|
||||||
|
let Some(base_section) = base_section else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let base_code = match base_section.kind {
|
||||||
|
ObjSectionKind::Code => Some(process_code_symbol(base_obj, base_symbol_ref, config)?),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
for (target_section_index, target_section) in
|
||||||
|
target_obj.sections.iter().enumerate().filter(|(_, s)| s.kind == base_section.kind)
|
||||||
|
{
|
||||||
|
for (target_symbol_index, _target_symbol) in
|
||||||
|
target_section.symbols.iter().enumerate().filter(|(_, s)| s.kind == base_symbol.kind)
|
||||||
|
{
|
||||||
|
let target_symbol_ref =
|
||||||
|
SymbolRef { section_idx: target_section_index, symbol_idx: target_symbol_index };
|
||||||
|
match base_section.kind {
|
||||||
|
ObjSectionKind::Code => {
|
||||||
|
let target_code = process_code_symbol(target_obj, target_symbol_ref, config)?;
|
||||||
|
let (left_diff, _right_diff) = diff_code(
|
||||||
|
target_obj,
|
||||||
|
base_obj,
|
||||||
|
&target_code,
|
||||||
|
base_code.as_ref().unwrap(),
|
||||||
|
target_symbol_ref,
|
||||||
|
base_symbol_ref,
|
||||||
|
config,
|
||||||
|
)?;
|
||||||
|
target_out.mapping_symbols.push(left_diff);
|
||||||
|
}
|
||||||
|
ObjSectionKind::Data => {
|
||||||
|
let (left_diff, _right_diff) =
|
||||||
|
diff_data_symbol(target_obj, base_obj, target_symbol_ref, base_symbol_ref)?;
|
||||||
|
target_out.mapping_symbols.push(left_diff);
|
||||||
|
}
|
||||||
|
ObjSectionKind::Bss => {
|
||||||
|
let (left_diff, _right_diff) =
|
||||||
|
diff_bss_symbol(target_obj, base_obj, target_symbol_ref, base_symbol_ref)?;
|
||||||
|
target_out.mapping_symbols.push(left_diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
struct SymbolMatch {
|
struct SymbolMatch {
|
||||||
left: Option<SymbolRef>,
|
left: Option<SymbolRef>,
|
||||||
@@ -551,19 +637,115 @@ struct SectionMatch {
|
|||||||
section_kind: ObjSectionKind,
|
section_kind: ObjSectionKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct MappingConfig {
|
||||||
|
/// Manual symbol mappings
|
||||||
|
pub mappings: SymbolMappings,
|
||||||
|
/// The right object symbol name that we're selecting a left symbol for
|
||||||
|
pub selecting_left: Option<String>,
|
||||||
|
/// The left object symbol name that we're selecting a right symbol for
|
||||||
|
pub selecting_right: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol_ref_by_name(obj: &ObjInfo, name: &str) -> Option<SymbolRef> {
|
||||||
|
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||||
|
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
||||||
|
if symbol.name == name {
|
||||||
|
return Some(SymbolRef { section_idx, symbol_idx });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_symbol_mappings(
|
||||||
|
left: &ObjInfo,
|
||||||
|
right: &ObjInfo,
|
||||||
|
mapping_config: &MappingConfig,
|
||||||
|
left_used: &mut HashSet<SymbolRef>,
|
||||||
|
right_used: &mut HashSet<SymbolRef>,
|
||||||
|
matches: &mut Vec<SymbolMatch>,
|
||||||
|
) -> Result<()> {
|
||||||
|
// If we're selecting a symbol to use as a comparison, mark it as used
|
||||||
|
// This ensures that we don't match it to another symbol at any point
|
||||||
|
if let Some(left_name) = &mapping_config.selecting_left {
|
||||||
|
if let Some(left_symbol) = symbol_ref_by_name(left, left_name) {
|
||||||
|
left_used.insert(left_symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(right_name) = &mapping_config.selecting_right {
|
||||||
|
if let Some(right_symbol) = symbol_ref_by_name(right, right_name) {
|
||||||
|
right_used.insert(right_symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply manual symbol mappings
|
||||||
|
for (left_name, right_name) in &mapping_config.mappings {
|
||||||
|
let Some(left_symbol) = symbol_ref_by_name(left, left_name) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if left_used.contains(&left_symbol) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some(right_symbol) = symbol_ref_by_name(right, right_name) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if right_used.contains(&right_symbol) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let left_section = &left.sections[left_symbol.section_idx];
|
||||||
|
let right_section = &right.sections[right_symbol.section_idx];
|
||||||
|
if left_section.kind != right_section.kind {
|
||||||
|
log::warn!(
|
||||||
|
"Symbol section kind mismatch: {} ({:?}) vs {} ({:?})",
|
||||||
|
left_name,
|
||||||
|
left_section.kind,
|
||||||
|
right_name,
|
||||||
|
right_section.kind
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
matches.push(SymbolMatch {
|
||||||
|
left: Some(left_symbol),
|
||||||
|
right: Some(right_symbol),
|
||||||
|
prev: None, // TODO
|
||||||
|
section_kind: left_section.kind,
|
||||||
|
});
|
||||||
|
left_used.insert(left_symbol);
|
||||||
|
right_used.insert(right_symbol);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Find matching symbols between each object.
|
/// Find matching symbols between each object.
|
||||||
fn matching_symbols(
|
fn matching_symbols(
|
||||||
left: Option<&ObjInfo>,
|
left: Option<&ObjInfo>,
|
||||||
right: Option<&ObjInfo>,
|
right: Option<&ObjInfo>,
|
||||||
prev: Option<&ObjInfo>,
|
prev: Option<&ObjInfo>,
|
||||||
|
mappings: &MappingConfig,
|
||||||
) -> Result<Vec<SymbolMatch>> {
|
) -> Result<Vec<SymbolMatch>> {
|
||||||
let mut matches = Vec::new();
|
let mut matches = Vec::new();
|
||||||
|
let mut left_used = HashSet::new();
|
||||||
let mut right_used = HashSet::new();
|
let mut right_used = HashSet::new();
|
||||||
if let Some(left) = left {
|
if let Some(left) = left {
|
||||||
|
if let Some(right) = right {
|
||||||
|
apply_symbol_mappings(
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
mappings,
|
||||||
|
&mut left_used,
|
||||||
|
&mut right_used,
|
||||||
|
&mut matches,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
for (section_idx, section) in left.sections.iter().enumerate() {
|
for (section_idx, section) in left.sections.iter().enumerate() {
|
||||||
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
||||||
|
let symbol_ref = SymbolRef { section_idx, symbol_idx };
|
||||||
|
if left_used.contains(&symbol_ref) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let symbol_match = SymbolMatch {
|
let symbol_match = SymbolMatch {
|
||||||
left: Some(SymbolRef { section_idx, symbol_idx }),
|
left: Some(symbol_ref),
|
||||||
right: find_symbol(right, symbol, section, Some(&right_used)),
|
right: find_symbol(right, symbol, section, Some(&right_used)),
|
||||||
prev: find_symbol(prev, symbol, section, None),
|
prev: find_symbol(prev, symbol, section, None),
|
||||||
section_kind: section.kind,
|
section_kind: section.kind,
|
||||||
@@ -575,8 +757,12 @@ fn matching_symbols(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (symbol_idx, symbol) in left.common.iter().enumerate() {
|
for (symbol_idx, symbol) in left.common.iter().enumerate() {
|
||||||
|
let symbol_ref = SymbolRef { section_idx: left.sections.len(), symbol_idx };
|
||||||
|
if left_used.contains(&symbol_ref) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let symbol_match = SymbolMatch {
|
let symbol_match = SymbolMatch {
|
||||||
left: Some(SymbolRef { section_idx: left.sections.len(), symbol_idx }),
|
left: Some(symbol_ref),
|
||||||
right: find_common_symbol(right, symbol),
|
right: find_common_symbol(right, symbol),
|
||||||
prev: find_common_symbol(prev, symbol),
|
prev: find_common_symbol(prev, symbol),
|
||||||
section_kind: ObjSectionKind::Bss,
|
section_kind: ObjSectionKind::Bss,
|
||||||
|
|||||||
@@ -112,6 +112,15 @@ pub struct ObjIns {
|
|||||||
pub orig: Option<String>,
|
pub orig: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
||||||
|
pub enum ObjSymbolKind {
|
||||||
|
#[default]
|
||||||
|
Unknown,
|
||||||
|
Function,
|
||||||
|
Object,
|
||||||
|
Section,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ObjSymbol {
|
pub struct ObjSymbol {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -120,8 +129,9 @@ pub struct ObjSymbol {
|
|||||||
pub section_address: u64,
|
pub section_address: u64,
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
pub size_known: bool,
|
pub size_known: bool,
|
||||||
|
pub kind: ObjSymbolKind,
|
||||||
pub flags: ObjSymbolFlagSet,
|
pub flags: ObjSymbolFlagSet,
|
||||||
pub addend: i64,
|
pub orig_section_index: Option<usize>,
|
||||||
/// Original virtual address (from .note.split section)
|
/// Original virtual address (from .note.split section)
|
||||||
pub virtual_address: Option<u64>,
|
pub virtual_address: Option<u64>,
|
||||||
/// Original index in object symbol table
|
/// Original index in object symbol table
|
||||||
@@ -145,7 +155,7 @@ pub struct ObjReloc {
|
|||||||
pub flags: RelocationFlags,
|
pub flags: RelocationFlags,
|
||||||
pub address: u64,
|
pub address: u64,
|
||||||
pub target: ObjSymbol,
|
pub target: ObjSymbol,
|
||||||
pub target_section: Option<String>,
|
pub addend: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
|
|||||||
+138
-68
@@ -13,8 +13,8 @@ use object::{
|
|||||||
endian::LittleEndian as LE,
|
endian::LittleEndian as LE,
|
||||||
pe::{ImageAuxSymbolFunctionBeginEnd, ImageLinenumber},
|
pe::{ImageAuxSymbolFunctionBeginEnd, ImageLinenumber},
|
||||||
read::coff::{CoffFile, CoffHeader, ImageSymbol},
|
read::coff::{CoffFile, CoffHeader, ImageSymbol},
|
||||||
BinaryFormat, File, Object, ObjectSection, ObjectSymbol, RelocationTarget, SectionIndex,
|
BinaryFormat, File, Object, ObjectSection, ObjectSymbol, RelocationTarget, Section,
|
||||||
SectionKind, Symbol, SymbolIndex, SymbolKind, SymbolScope, SymbolSection,
|
SectionIndex, SectionKind, Symbol, SymbolIndex, SymbolKind, SymbolScope,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -23,6 +23,7 @@ use crate::{
|
|||||||
obj::{
|
obj::{
|
||||||
split_meta::{SplitMeta, SPLITMETA_SECTION},
|
split_meta::{SplitMeta, SPLITMETA_SECTION},
|
||||||
ObjInfo, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags,
|
ObjInfo, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags,
|
||||||
|
ObjSymbolKind,
|
||||||
},
|
},
|
||||||
util::{read_u16, read_u32},
|
util::{read_u16, read_u32},
|
||||||
};
|
};
|
||||||
@@ -40,7 +41,6 @@ fn to_obj_symbol(
|
|||||||
arch: &dyn ObjArch,
|
arch: &dyn ObjArch,
|
||||||
obj_file: &File<'_>,
|
obj_file: &File<'_>,
|
||||||
symbol: &Symbol<'_, '_>,
|
symbol: &Symbol<'_, '_>,
|
||||||
addend: i64,
|
|
||||||
split_meta: Option<&SplitMeta>,
|
split_meta: Option<&SplitMeta>,
|
||||||
) -> Result<ObjSymbol> {
|
) -> Result<ObjSymbol> {
|
||||||
let mut name = symbol.name().context("Failed to process symbol name")?;
|
let mut name = symbol.name().context("Failed to process symbol name")?;
|
||||||
@@ -94,6 +94,13 @@ fn to_obj_symbol(
|
|||||||
})
|
})
|
||||||
.unwrap_or(&[]);
|
.unwrap_or(&[]);
|
||||||
|
|
||||||
|
let kind = match symbol.kind() {
|
||||||
|
SymbolKind::Text => ObjSymbolKind::Function,
|
||||||
|
SymbolKind::Data => ObjSymbolKind::Object,
|
||||||
|
SymbolKind::Section => ObjSymbolKind::Section,
|
||||||
|
_ => ObjSymbolKind::Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(ObjSymbol {
|
Ok(ObjSymbol {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
demangled_name,
|
demangled_name,
|
||||||
@@ -101,8 +108,9 @@ fn to_obj_symbol(
|
|||||||
section_address,
|
section_address,
|
||||||
size: symbol.size(),
|
size: symbol.size(),
|
||||||
size_known: symbol.size() != 0,
|
size_known: symbol.size() != 0,
|
||||||
|
kind,
|
||||||
flags,
|
flags,
|
||||||
addend,
|
orig_section_index: symbol.section_index().map(|i| i.0),
|
||||||
virtual_address,
|
virtual_address,
|
||||||
original_index: Some(symbol.index().0),
|
original_index: Some(symbol.index().0),
|
||||||
bytes: bytes.to_vec(),
|
bytes: bytes.to_vec(),
|
||||||
@@ -152,26 +160,23 @@ fn symbols_by_section(
|
|||||||
arch: &dyn ObjArch,
|
arch: &dyn ObjArch,
|
||||||
obj_file: &File<'_>,
|
obj_file: &File<'_>,
|
||||||
section: &ObjSection,
|
section: &ObjSection,
|
||||||
|
section_symbols: &[Symbol<'_, '_>],
|
||||||
split_meta: Option<&SplitMeta>,
|
split_meta: Option<&SplitMeta>,
|
||||||
name_counts: &mut HashMap<String, u32>,
|
name_counts: &mut HashMap<String, u32>,
|
||||||
) -> Result<Vec<ObjSymbol>> {
|
) -> Result<Vec<ObjSymbol>> {
|
||||||
let mut result = Vec::<ObjSymbol>::new();
|
let mut result = Vec::<ObjSymbol>::new();
|
||||||
for symbol in obj_file.symbols() {
|
for symbol in section_symbols {
|
||||||
if symbol.kind() == SymbolKind::Section {
|
if symbol.kind() == SymbolKind::Section {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(index) = symbol.section().index() {
|
if symbol.is_local() && section.kind == ObjSectionKind::Code {
|
||||||
if index.0 == section.orig_index {
|
// TODO strip local syms in diff?
|
||||||
if symbol.is_local() && section.kind == ObjSectionKind::Code {
|
let name = symbol.name().context("Failed to process symbol name")?;
|
||||||
// TODO strip local syms in diff?
|
if symbol.size() == 0 || name.starts_with("lbl_") {
|
||||||
let name = symbol.name().context("Failed to process symbol name")?;
|
continue;
|
||||||
if symbol.size() == 0 || name.starts_with("lbl_") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.push(to_obj_symbol(arch, obj_file, &symbol, 0, split_meta)?);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
result.push(to_obj_symbol(arch, obj_file, symbol, split_meta)?);
|
||||||
}
|
}
|
||||||
result.sort_by(|a, b| a.address.cmp(&b.address).then(a.size.cmp(&b.size)));
|
result.sort_by(|a, b| a.address.cmp(&b.address).then(a.size.cmp(&b.size)));
|
||||||
let mut iter = result.iter_mut().peekable();
|
let mut iter = result.iter_mut().peekable();
|
||||||
@@ -182,6 +187,13 @@ fn symbols_by_section(
|
|||||||
} else {
|
} else {
|
||||||
symbol.size = (section.address + section.size) - symbol.address;
|
symbol.size = (section.address + section.size) - symbol.address;
|
||||||
}
|
}
|
||||||
|
// Set symbol kind if we ended up with a non-zero size
|
||||||
|
if symbol.kind == ObjSymbolKind::Unknown && symbol.size > 0 {
|
||||||
|
symbol.kind = match section.kind {
|
||||||
|
ObjSectionKind::Code => ObjSymbolKind::Function,
|
||||||
|
ObjSectionKind::Data | ObjSectionKind::Bss => ObjSymbolKind::Object,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if result.is_empty() {
|
if result.is_empty() {
|
||||||
@@ -199,8 +211,12 @@ fn symbols_by_section(
|
|||||||
section_address: 0,
|
section_address: 0,
|
||||||
size: section.size,
|
size: section.size,
|
||||||
size_known: true,
|
size_known: true,
|
||||||
|
kind: match section.kind {
|
||||||
|
ObjSectionKind::Code => ObjSymbolKind::Function,
|
||||||
|
ObjSectionKind::Data | ObjSectionKind::Bss => ObjSymbolKind::Object,
|
||||||
|
},
|
||||||
flags: Default::default(),
|
flags: Default::default(),
|
||||||
addend: 0,
|
orig_section_index: Some(section.orig_index),
|
||||||
virtual_address: None,
|
virtual_address: None,
|
||||||
original_index: None,
|
original_index: None,
|
||||||
bytes: Vec::new(),
|
bytes: Vec::new(),
|
||||||
@@ -217,51 +233,75 @@ fn common_symbols(
|
|||||||
obj_file
|
obj_file
|
||||||
.symbols()
|
.symbols()
|
||||||
.filter(Symbol::is_common)
|
.filter(Symbol::is_common)
|
||||||
.map(|symbol| to_obj_symbol(arch, obj_file, &symbol, 0, split_meta))
|
.map(|symbol| to_obj_symbol(arch, obj_file, &symbol, split_meta))
|
||||||
.collect::<Result<Vec<ObjSymbol>>>()
|
.collect::<Result<Vec<ObjSymbol>>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LOW_PRIORITY_SYMBOLS: &[&str] =
|
||||||
|
&["__gnu_compiled_c", "__gnu_compiled_cplusplus", "gcc2_compiled."];
|
||||||
|
|
||||||
|
fn best_symbol<'r, 'data, 'file>(
|
||||||
|
symbols: &'r [Symbol<'data, 'file>],
|
||||||
|
address: u64,
|
||||||
|
) -> Option<&'r Symbol<'data, 'file>> {
|
||||||
|
let mut closest_symbol_index = match symbols.binary_search_by_key(&address, |s| s.address()) {
|
||||||
|
Ok(index) => Some(index),
|
||||||
|
Err(index) => index.checked_sub(1),
|
||||||
|
}?;
|
||||||
|
// The binary search may not find the first symbol at the address, so work backwards
|
||||||
|
let target_address = symbols[closest_symbol_index].address();
|
||||||
|
while let Some(prev_index) = closest_symbol_index.checked_sub(1) {
|
||||||
|
if symbols[prev_index].address() != target_address {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
closest_symbol_index = prev_index;
|
||||||
|
}
|
||||||
|
let mut best_symbol: Option<&'r Symbol<'data, 'file>> = None;
|
||||||
|
for symbol in symbols.iter().skip(closest_symbol_index) {
|
||||||
|
if symbol.address() > address {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if symbol.kind() == SymbolKind::Section
|
||||||
|
|| (symbol.size() > 0 && (symbol.address() + symbol.size()) <= address)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// TODO priority ranking with visibility, etc
|
||||||
|
if let Some(best) = best_symbol {
|
||||||
|
if LOW_PRIORITY_SYMBOLS.contains(&best.name().unwrap_or_default())
|
||||||
|
&& !LOW_PRIORITY_SYMBOLS.contains(&symbol.name().unwrap_or_default())
|
||||||
|
{
|
||||||
|
best_symbol = Some(symbol);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
best_symbol = Some(symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
best_symbol
|
||||||
|
}
|
||||||
|
|
||||||
fn find_section_symbol(
|
fn find_section_symbol(
|
||||||
arch: &dyn ObjArch,
|
arch: &dyn ObjArch,
|
||||||
obj_file: &File<'_>,
|
obj_file: &File<'_>,
|
||||||
target: &Symbol<'_, '_>,
|
section: &Section,
|
||||||
|
section_symbols: &[Symbol<'_, '_>],
|
||||||
address: u64,
|
address: u64,
|
||||||
split_meta: Option<&SplitMeta>,
|
split_meta: Option<&SplitMeta>,
|
||||||
) -> Result<ObjSymbol> {
|
) -> Result<ObjSymbol> {
|
||||||
let section_index =
|
if let Some(symbol) = best_symbol(section_symbols, address) {
|
||||||
target.section_index().ok_or_else(|| anyhow::Error::msg("Unknown section index"))?;
|
return to_obj_symbol(arch, obj_file, symbol, split_meta);
|
||||||
let section = obj_file.section_by_index(section_index)?;
|
|
||||||
let mut closest_symbol: Option<Symbol<'_, '_>> = None;
|
|
||||||
for symbol in obj_file.symbols() {
|
|
||||||
if !matches!(symbol.section_index(), Some(idx) if idx == section_index) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if symbol.kind() == SymbolKind::Section || symbol.address() != address {
|
|
||||||
if symbol.address() < address
|
|
||||||
&& symbol.size() != 0
|
|
||||||
&& (closest_symbol.is_none()
|
|
||||||
|| matches!(&closest_symbol, Some(s) if s.address() <= symbol.address()))
|
|
||||||
{
|
|
||||||
closest_symbol = Some(symbol);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return to_obj_symbol(arch, obj_file, &symbol, 0, split_meta);
|
|
||||||
}
|
}
|
||||||
let (name, offset) = closest_symbol
|
// Fallback to section symbol
|
||||||
.and_then(|s| s.name().map(|n| (n, s.address())).ok())
|
|
||||||
.or_else(|| section.name().map(|n| (n, section.address())).ok())
|
|
||||||
.unwrap_or(("<unknown>", 0));
|
|
||||||
let offset_addr = address - offset;
|
|
||||||
Ok(ObjSymbol {
|
Ok(ObjSymbol {
|
||||||
name: name.to_string(),
|
name: section.name()?.to_string(),
|
||||||
demangled_name: None,
|
demangled_name: None,
|
||||||
address: offset,
|
address: section.address(),
|
||||||
section_address: address - section.address(),
|
section_address: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
size_known: false,
|
size_known: false,
|
||||||
|
kind: ObjSymbolKind::Section,
|
||||||
flags: Default::default(),
|
flags: Default::default(),
|
||||||
addend: offset_addr as i64,
|
orig_section_index: Some(section.index().0),
|
||||||
virtual_address: None,
|
virtual_address: None,
|
||||||
original_index: None,
|
original_index: None,
|
||||||
bytes: Vec::new(),
|
bytes: Vec::new(),
|
||||||
@@ -272,6 +312,7 @@ fn relocations_by_section(
|
|||||||
arch: &dyn ObjArch,
|
arch: &dyn ObjArch,
|
||||||
obj_file: &File<'_>,
|
obj_file: &File<'_>,
|
||||||
section: &ObjSection,
|
section: &ObjSection,
|
||||||
|
section_symbols: &[Vec<Symbol<'_, '_>>],
|
||||||
split_meta: Option<&SplitMeta>,
|
split_meta: Option<&SplitMeta>,
|
||||||
) -> Result<Vec<ObjReloc>> {
|
) -> Result<Vec<ObjReloc>> {
|
||||||
let obj_section = obj_file.section_by_index(SectionIndex(section.orig_index))?;
|
let obj_section = obj_file.section_by_index(SectionIndex(section.orig_index))?;
|
||||||
@@ -296,30 +337,36 @@ fn relocations_by_section(
|
|||||||
_ => bail!("Unhandled relocation target: {:?}", reloc.target()),
|
_ => bail!("Unhandled relocation target: {:?}", reloc.target()),
|
||||||
};
|
};
|
||||||
let flags = reloc.flags(); // TODO validate reloc here?
|
let flags = reloc.flags(); // TODO validate reloc here?
|
||||||
let target_section = match symbol.section() {
|
let mut addend = if reloc.has_implicit_addend() {
|
||||||
SymbolSection::Common => Some(".comm".to_string()),
|
|
||||||
SymbolSection::Section(idx) => {
|
|
||||||
obj_file.section_by_index(idx).and_then(|s| s.name().map(|s| s.to_string())).ok()
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let addend = if reloc.has_implicit_addend() {
|
|
||||||
arch.implcit_addend(obj_file, section, address, &reloc)?
|
arch.implcit_addend(obj_file, section, address, &reloc)?
|
||||||
} else {
|
} else {
|
||||||
reloc.addend()
|
reloc.addend()
|
||||||
};
|
};
|
||||||
// println!("Reloc: {reloc:?}, symbol: {symbol:?}, addend: {addend:#x}");
|
|
||||||
let target = match symbol.kind() {
|
let target = match symbol.kind() {
|
||||||
SymbolKind::Text | SymbolKind::Data | SymbolKind::Label | SymbolKind::Unknown => {
|
SymbolKind::Text | SymbolKind::Data | SymbolKind::Label | SymbolKind::Unknown => {
|
||||||
to_obj_symbol(arch, obj_file, &symbol, addend, split_meta)
|
to_obj_symbol(arch, obj_file, &symbol, split_meta)?
|
||||||
}
|
}
|
||||||
SymbolKind::Section => {
|
SymbolKind::Section => {
|
||||||
ensure!(addend >= 0, "Negative addend in reloc: {addend}");
|
ensure!(addend >= 0, "Negative addend in section reloc: {addend}");
|
||||||
find_section_symbol(arch, obj_file, &symbol, addend as u64, split_meta)
|
let section_index = symbol
|
||||||
|
.section_index()
|
||||||
|
.ok_or_else(|| anyhow!("Section symbol {symbol:?} has no section index"))?;
|
||||||
|
let section = obj_file.section_by_index(section_index)?;
|
||||||
|
let symbol = find_section_symbol(
|
||||||
|
arch,
|
||||||
|
obj_file,
|
||||||
|
§ion,
|
||||||
|
§ion_symbols[section_index.0],
|
||||||
|
addend as u64,
|
||||||
|
split_meta,
|
||||||
|
)?;
|
||||||
|
// Adjust addend to be relative to the selected symbol
|
||||||
|
addend = (symbol.address - section.address()) as i64;
|
||||||
|
symbol
|
||||||
}
|
}
|
||||||
kind => Err(anyhow!("Unhandled relocation symbol type {kind:?}")),
|
kind => bail!("Unhandled relocation symbol type {kind:?}"),
|
||||||
}?;
|
};
|
||||||
relocations.push(ObjReloc { flags, address, target, target_section });
|
relocations.push(ObjReloc { flags, address, target, addend });
|
||||||
}
|
}
|
||||||
Ok(relocations)
|
Ok(relocations)
|
||||||
}
|
}
|
||||||
@@ -539,8 +586,9 @@ fn update_combined_symbol(symbol: ObjSymbol, address_change: i64) -> Result<ObjS
|
|||||||
section_address: (symbol.section_address as i64 + address_change).try_into()?,
|
section_address: (symbol.section_address as i64 + address_change).try_into()?,
|
||||||
size: symbol.size,
|
size: symbol.size,
|
||||||
size_known: symbol.size_known,
|
size_known: symbol.size_known,
|
||||||
|
kind: symbol.kind,
|
||||||
flags: symbol.flags,
|
flags: symbol.flags,
|
||||||
addend: symbol.addend,
|
orig_section_index: symbol.orig_section_index,
|
||||||
virtual_address: if let Some(virtual_address) = symbol.virtual_address {
|
virtual_address: if let Some(virtual_address) = symbol.virtual_address {
|
||||||
Some((virtual_address as i64 + address_change).try_into()?)
|
Some((virtual_address as i64 + address_change).try_into()?)
|
||||||
} else {
|
} else {
|
||||||
@@ -566,8 +614,8 @@ fn combine_sections(section: ObjSection, combine: ObjSection) -> Result<ObjSecti
|
|||||||
relocations.push(ObjReloc {
|
relocations.push(ObjReloc {
|
||||||
flags: reloc.flags,
|
flags: reloc.flags,
|
||||||
address: (reloc.address as i64 + address_change).try_into()?,
|
address: (reloc.address as i64 + address_change).try_into()?,
|
||||||
target: reloc.target, // TODO: Should be updated?
|
target: reloc.target, // TODO: Should be updated?
|
||||||
target_section: reloc.target_section, // TODO: Same as above
|
addend: reloc.addend,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,18 +695,40 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<ObjInfo> {
|
|||||||
let obj_file = File::parse(data)?;
|
let obj_file = File::parse(data)?;
|
||||||
let arch = new_arch(&obj_file)?;
|
let arch = new_arch(&obj_file)?;
|
||||||
let split_meta = split_meta(&obj_file)?;
|
let split_meta = split_meta(&obj_file)?;
|
||||||
|
|
||||||
|
// Create sorted symbol list for each section
|
||||||
|
let mut section_symbols = Vec::with_capacity(obj_file.sections().count());
|
||||||
|
for section in obj_file.sections() {
|
||||||
|
let mut symbols = obj_file
|
||||||
|
.symbols()
|
||||||
|
.filter(|s| s.section_index() == Some(section.index()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
symbols.sort_by_key(|s| s.address());
|
||||||
|
let section_index = section.index().0;
|
||||||
|
if section_index >= section_symbols.len() {
|
||||||
|
section_symbols.resize_with(section_index + 1, Vec::new);
|
||||||
|
}
|
||||||
|
section_symbols[section_index] = symbols;
|
||||||
|
}
|
||||||
|
|
||||||
let mut sections = filter_sections(&obj_file, split_meta.as_ref())?;
|
let mut sections = filter_sections(&obj_file, split_meta.as_ref())?;
|
||||||
let mut name_counts: HashMap<String, u32> = HashMap::new();
|
let mut section_name_counts: HashMap<String, u32> = HashMap::new();
|
||||||
for section in &mut sections {
|
for section in &mut sections {
|
||||||
section.symbols = symbols_by_section(
|
section.symbols = symbols_by_section(
|
||||||
arch.as_ref(),
|
arch.as_ref(),
|
||||||
&obj_file,
|
&obj_file,
|
||||||
section,
|
section,
|
||||||
|
§ion_symbols[section.orig_index],
|
||||||
|
split_meta.as_ref(),
|
||||||
|
&mut section_name_counts,
|
||||||
|
)?;
|
||||||
|
section.relocations = relocations_by_section(
|
||||||
|
arch.as_ref(),
|
||||||
|
&obj_file,
|
||||||
|
section,
|
||||||
|
§ion_symbols,
|
||||||
split_meta.as_ref(),
|
split_meta.as_ref(),
|
||||||
&mut name_counts,
|
|
||||||
)?;
|
)?;
|
||||||
section.relocations =
|
|
||||||
relocations_by_section(arch.as_ref(), &obj_file, section, split_meta.as_ref())?;
|
|
||||||
}
|
}
|
||||||
if config.combine_data_sections {
|
if config.combine_data_sections {
|
||||||
combine_data_sections(&mut sections)?;
|
combine_data_sections(&mut sections)?;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user