Compare commits

...

14 Commits

Author SHA1 Message Date
Luke Street 676488433f Fix resolving symbols for section-relative relocations
Also fixes MIPS `j` handling when jumping within the function.

Reworks `ObjReloc` struct to be a little more sensible.
2024-10-11 18:09:18 -06:00
Luke Street 83de98b5ee Version v2.3.1 2024-10-10 22:58:33 -06:00
Luke Street c1ba4e91d1 ci: Setup python venv for cargo-zigbuild 2024-10-10 22:39:31 -06:00
Luke Street 575900024d Avoid resetting diff state on unit config reload 2024-10-10 22:31:04 -06:00
Luke Street cbe299e859 Fix logic issue with 0-sized symbols
Fixes #119
2024-10-10 22:20:48 -06:00
Luke Street 741d93e211 Add symbol mapping feature (#118)
This allows users to "map" (or "link") symbols with different names so that they can be compared without having to update either the target or base objects. Symbol mappings are persisted in objdiff.json, so generators will need to ensure that they're preserved when updating. (Example: https://github.com/encounter/dtk-template/commit/d1334bb79e71af1a7b3b090bffda4adc483f722c)

Resolves #117
2024-10-09 21:44:18 -06:00
Luke Street 603dbd6882 Round match percent down before display
Ensures that 100% isn't displayed until it's a
perfect match.
2024-10-07 20:17:56 -06:00
Luke Street 6fb0a63de2 Click on empty space in row to clear highlight
Resolves #116
2024-10-07 19:53:16 -06:00
Luke Street ab2e84a2c6 Deprioritize generated GCC symbols in find_section_symbol
Resolves #115
2024-10-07 19:49:52 -06:00
Luke Street 9596051cb4 Allow collapsing sidebar in symbols view 2024-10-07 19:46:16 -06:00
Luke Street a5d9d8282e Update all dependencies 2024-10-03 22:00:43 -06:00
Amber Brault 3287a0f65c Bump cwextab again to 1.0.2 (#114)
* Bump cwextab

* Updated cwextab to not error on null actions

* Bump cwextab again
2024-10-03 01:12:37 -06:00
Amber Brault fab9c62dfb Bump cwextab (#113)
* Bump cwextab

* Updated cwextab to not error on null actions
2024-10-01 23:20:09 -06:00
Luke Street 08cd768260 Add total_units, complete_units to progress report 2024-09-30 21:41:57 -06:00
34 changed files with 2754 additions and 1226 deletions
+5 -1
View File
@@ -142,7 +142,11 @@ jobs:
uses: actions/checkout@v4
- name: Install cargo-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
uses: dtolnay/rust-toolchain@stable
with:
Generated
+132 -129
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -13,7 +13,7 @@ strip = "debuginfo"
codegen-units = 1
[workspace.package]
version = "2.2.0"
version = "2.3.2"
authors = ["Luke Street <luke@street.dev>"]
edition = "2021"
license = "MIT OR Apache-2.0"
+7
View File
@@ -133,6 +133,13 @@
},
"metadata": {
"ref": "#/$defs/metadata"
},
"symbol_mappings": {
"type": "object",
"description": "Manual symbol mappings from target to base.",
"additionalProperties": {
"type": "string"
}
}
}
},
+1 -2
View File
@@ -70,7 +70,6 @@ feature-depth = 1
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
"RUSTSEC-2024-0370",
#{ 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
#{ 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]
# github.com organizations to allow git sources for
github = ["encounter"]
github = ["notify-rs"]
# gitlab.com organizations to allow git sources for
gitlab = []
# bitbucket.org organizations to allow git sources for
+33 -21
View File
@@ -102,26 +102,32 @@ pub fn run(args: Args) -> Result<()> {
let unit_path =
PathBuf::from_str(u).ok().and_then(|p| fs::canonicalize(p).ok());
let Some(object) = project_config.objects.iter_mut().find_map(|obj| {
if obj.name.as_deref() == Some(u) {
let Some(object) = project_config
.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);
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);
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);
}
None
}) else {
None
})
else {
bail!("Unit not found: {}", u)
};
@@ -129,7 +135,13 @@ pub fn run(args: Args) -> Result<()> {
} else if let Some(symbol_name) = &args.symbol {
let mut idx = None;
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);
if obj
@@ -148,7 +160,7 @@ pub fn run(args: Args) -> Result<()> {
}
match (count, idx) {
(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!(
"Multiple instances of {} were found, try specifying a unit",
symbol_name
@@ -303,7 +315,7 @@ fn find_function(obj: &ObjInfo, name: &str) -> Option<SymbolRef> {
None
}
#[allow(dead_code)]
#[expect(dead_code)]
struct FunctionDiffUi {
relax_reloc_diffs: bool,
left_highlight: HighlightKind,
@@ -758,7 +770,7 @@ impl FunctionDiffUi {
self.scroll_y += self.per_page / if half { 2 } else { 1 };
}
#[allow(clippy::too_many_arguments)]
#[expect(clippy::too_many_arguments)]
fn print_sym(
&self,
out: &mut Text<'static>,
+8 -5
View File
@@ -94,7 +94,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
};
info!(
"Generating report for {} units (using {} threads)",
project.objects.len(),
project.units().len(),
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();
if args.deduplicate {
// 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(
object,
project_dir,
@@ -116,7 +116,9 @@ fn generate(args: GenerateArgs) -> Result<()> {
}
} else {
let vec = project
.objects
.units
.as_deref_mut()
.unwrap_or_default()
.par_iter_mut()
.map(|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 mut categories = Vec::new();
for category in &project.progress_categories {
for category in project.progress_categories() {
categories.push(ReportCategory {
id: category.id.clone(),
name: category.name.clone(),
@@ -199,7 +201,7 @@ fn report_object(
.unwrap_or_default(),
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 functions = vec![];
@@ -280,6 +282,7 @@ fn report_object(
if metadata.complete.unwrap_or(false) {
measures.complete_code = measures.total_code;
measures.complete_data = measures.total_data;
measures.complete_units = 1;
}
measures.calc_fuzzy_match_percent();
measures.calc_matched_percent();
+4 -3
View File
@@ -17,8 +17,8 @@ crate-type = ["cdylib", "rlib"]
[features]
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "bindings"]
any-arch = [] # Implicit, used to check if any arch is enabled
config = ["globset", "semver", "serde_json", "serde_yaml"]
any-arch = ["bimap"] # Implicit, used to check if any arch is enabled
config = ["bimap", "globset", "semver", "serde_json", "serde_yaml"]
dwarf = ["gimli"]
mips = ["any-arch", "rabbitizer"]
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
@@ -32,6 +32,7 @@ features = ["all"]
[dependencies]
anyhow = "1.0"
bimap = { version = "0.6", features = ["serde"], optional = true }
byteorder = "1.5"
filetime = "0.2"
flagset = "0.4"
@@ -60,7 +61,7 @@ gimli = { version = "0.31", default-features = false, features = ["read-all"], o
# ppc
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 }
# mips
Binary file not shown.
+4
View File
@@ -32,6 +32,10 @@ message Measures {
uint64 complete_data = 13;
// Completed (or "linked") data percent
float complete_data_percent = 14;
// Total number of units
uint32 total_units = 15;
// Completed (or "linked") units
uint32 complete_units = 16;
}
// Project progress report
+12 -5
View File
@@ -83,7 +83,7 @@ impl ObjArch for ObjArchMips {
&self,
address: u64,
code: &[u8],
_section_index: usize,
section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig,
@@ -140,11 +140,18 @@ impl ObjArch for ObjArchMips {
| OperandType::cpu_label
| OperandType::cpu_branch_target_label => {
if let Some(reloc) = reloc {
if matches!(&reloc.target_section, Some(s) if s == ".text")
&& reloc.target.address > start_address
&& reloc.target.address < end_address
// If the relocation target is within the current function, we can
// convert it into a relative branch target. Note that we check
// 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 {
push_reloc(&mut args, reloc)?;
branch_dest = None;
+46 -45
View File
@@ -70,9 +70,13 @@ impl FunctionDiff {
// let (_section, symbol) = object.section_symbol(symbol_ref);
// 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 {
symbol: Some(Symbol::from(symbol)),
symbol: Some(Symbol::new(symbol)),
// diff_symbol,
instructions,
match_percent: symbol_diff.match_percent,
@@ -90,8 +94,8 @@ impl DataDiff {
}
}
impl<'a> From<&'a ObjSymbol> for Symbol {
fn from(value: &'a ObjSymbol) -> Self {
impl Symbol {
pub fn new(value: &ObjSymbol) -> Self {
Self {
name: value.name.to_string(),
demangled_name: value.demangled_name.clone(),
@@ -122,29 +126,29 @@ fn symbol_flags(value: ObjSymbolFlagSet) -> u32 {
flags
}
impl<'a> From<&'a ObjIns> for Instruction {
fn from(value: &'a ObjIns) -> Self {
impl Instruction {
pub fn new(object: &ObjInfo, instruction: &ObjIns) -> Self {
Self {
address: value.address,
size: value.size as u32,
opcode: value.op as u32,
mnemonic: value.mnemonic.clone(),
formatted: value.formatted.clone(),
arguments: value.args.iter().map(Argument::from).collect(),
relocation: value.reloc.as_ref().map(Relocation::from),
branch_dest: value.branch_dest,
line_number: value.line,
original: value.orig.clone(),
address: instruction.address,
size: instruction.size as u32,
opcode: instruction.op as u32,
mnemonic: instruction.mnemonic.clone(),
formatted: instruction.formatted.clone(),
arguments: instruction.args.iter().map(Argument::new).collect(),
relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)),
branch_dest: instruction.branch_dest,
line_number: instruction.line,
original: instruction.orig.clone(),
}
}
}
impl<'a> From<&'a ObjInsArg> for Argument {
fn from(value: &'a ObjInsArg) -> Self {
impl Argument {
pub fn new(value: &ObjInsArg) -> Self {
Self {
value: Some(match value {
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::BranchDest(dest) => argument::Value::BranchDest(*dest),
}),
@@ -152,8 +156,8 @@ impl<'a> From<&'a ObjInsArg> for Argument {
}
}
impl From<&ObjInsArgValue> for ArgumentValue {
fn from(value: &ObjInsArgValue) -> Self {
impl ArgumentValue {
pub fn new(value: &ObjInsArgValue) -> Self {
Self {
value: Some(match value {
ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v),
@@ -164,42 +168,39 @@ impl From<&ObjInsArgValue> for ArgumentValue {
}
}
impl<'a> From<&'a ObjReloc> for Relocation {
fn from(value: &ObjReloc) -> Self {
impl Relocation {
pub fn new(object: &ObjInfo, reloc: &ObjReloc) -> Self {
Self {
r#type: match value.flags {
r#type: match reloc.flags {
object::RelocationFlags::Elf { r_type } => r_type,
object::RelocationFlags::MachO { r_type, .. } => r_type as u32,
object::RelocationFlags::Coff { typ } => typ as u32,
object::RelocationFlags::Xcoff { r_rtype, .. } => r_rtype as u32,
_ => unreachable!(),
},
type_name: String::new(), // TODO
target: Some(RelocationTarget::from(&value.target)),
type_name: object.arch.display_reloc(reloc.flags).into_owned(),
target: Some(RelocationTarget {
symbol: Some(Symbol::new(&reloc.target)),
addend: reloc.addend,
}),
}
}
}
impl<'a> From<&'a ObjSymbol> for RelocationTarget {
fn from(value: &'a ObjSymbol) -> Self {
Self { symbol: Some(Symbol::from(value)), addend: value.addend }
}
}
impl<'a> From<&'a ObjInsDiff> for InstructionDiff {
fn from(value: &'a ObjInsDiff) -> Self {
impl InstructionDiff {
pub fn new(object: &ObjInfo, instruction_diff: &ObjInsDiff) -> Self {
Self {
instruction: value.ins.as_ref().map(Instruction::from),
diff_kind: DiffKind::from(value.kind) as i32,
branch_from: value.branch_from.as_ref().map(InstructionBranchFrom::from),
branch_to: value.branch_to.as_ref().map(InstructionBranchTo::from),
arg_diff: value.arg_diff.iter().map(ArgumentDiff::from).collect(),
instruction: instruction_diff.ins.as_ref().map(|ins| Instruction::new(object, ins)),
diff_kind: DiffKind::from(instruction_diff.kind) as i32,
branch_from: instruction_diff.branch_from.as_ref().map(InstructionBranchFrom::new),
branch_to: instruction_diff.branch_to.as_ref().map(InstructionBranchTo::new),
arg_diff: instruction_diff.arg_diff.iter().map(ArgumentDiff::new).collect(),
}
}
}
impl From<&Option<ObjInsArgDiff>> for ArgumentDiff {
fn from(value: &Option<ObjInsArgDiff>) -> Self {
impl ArgumentDiff {
pub fn new(value: &Option<ObjInsArgDiff>) -> Self {
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 {
fn from(value: &'a ObjInsBranchFrom) -> Self {
impl InstructionBranchFrom {
pub fn new(value: &ObjInsBranchFrom) -> Self {
Self {
instruction_index: value.ins_idx.iter().map(|&x| x as u32).collect(),
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 {
fn from(value: &'a ObjInsBranchTo) -> Self {
impl InstructionBranchTo {
pub fn new(value: &ObjInsBranchTo) -> Self {
Self { instruction_index: value.ins_idx as u32, branch_index: value.branch_idx as u32 }
}
}
+53 -9
View File
@@ -8,9 +8,10 @@ use serde_json::error::Category;
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
pub const REPORT_VERSION: u32 = 1;
pub const REPORT_VERSION: u32 = 2;
impl Report {
/// Attempts to parse the report as binary protobuf or JSON.
pub fn parse(data: &[u8]) -> Result<Self> {
if data.is_empty() {
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
@@ -25,6 +26,7 @@ impl 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> {
match serde_json::from_slice::<Self>(bytes) {
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<()> {
if self.version == 0 {
self.migrate_v0()?;
}
if self.version == 1 {
self.migrate_v1()?;
}
if self.version != REPORT_VERSION {
bail!("Unsupported report version: {}", self.version);
}
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<()> {
let Some(measures) = &mut self.measures else {
bail!("Missing measures in report");
@@ -61,15 +70,16 @@ impl Report {
let Some(unit_measures) = &mut unit.measures else {
bail!("Missing measures in report unit");
};
let Some(metadata) = &mut unit.metadata else {
bail!("Missing metadata in report unit");
let mut complete = false;
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() {
metadata.progress_categories = vec!["modules".to_string()];
} else {
metadata.progress_categories = vec!["dol".to_string()];
}
if metadata.complete.unwrap_or(false) {
if complete {
unit_measures.complete_code = unit_measures.total_code;
unit_measures.complete_data = unit_measures.total_data;
unit_measures.complete_code_percent = 100.0;
@@ -84,10 +94,42 @@ impl Report {
measures.complete_data += unit_measures.complete_data;
}
measures.calc_matched_percent();
self.calculate_progress_categories();
self.version = 1;
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) {
for unit in &self.units {
let Some(metadata) = unit.metadata.as_ref() else {
@@ -242,6 +284,8 @@ impl AddAssign for Measures {
self.matched_functions += other.matched_functions;
self.complete_code += other.complete_code;
self.complete_data += other.complete_data;
self.total_units += other.total_units;
self.complete_units += other.complete_units;
}
}
+97 -45
View File
@@ -1,77 +1,100 @@
use std::{
fs,
fs::File,
io::{BufReader, Read},
io::{BufReader, BufWriter, Read},
path::{Path, PathBuf},
};
use anyhow::{anyhow, Context, Result};
use bimap::BiBTreeMap;
use filetime::FileTime;
use globset::{Glob, GlobSet, GlobSetBuilder};
#[inline]
fn bool_true() -> bool { true }
#[derive(Default, Clone, serde::Deserialize)]
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProjectConfig {
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_version: Option<String>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub custom_make: Option<String>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub custom_args: Option<Vec<String>>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target_dir: Option<PathBuf>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_dir: Option<PathBuf>,
#[serde(default = "bool_true")]
pub build_base: bool,
#[serde(default)]
pub build_target: bool,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub build_base: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub build_target: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub watch_patterns: Option<Vec<Glob>>,
#[serde(default, alias = "units")]
pub objects: Vec<ProjectObject>,
#[serde(default)]
pub progress_categories: Vec<ProjectProgressCategory>,
#[serde(default, alias = "objects", skip_serializing_if = "Option::is_none")]
pub units: Option<Vec<ProjectObject>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
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 {
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<PathBuf>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target_path: Option<PathBuf>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_path: Option<PathBuf>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
#[deprecated(note = "Use metadata.reverse_fn_order")]
pub reverse_fn_order: Option<bool>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
#[deprecated(note = "Use metadata.complete")]
pub complete: Option<bool>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scratch: Option<ScratchConfig>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
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 {
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub complete: Option<bool>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reverse_fn_order: Option<bool>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_path: Option<String>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub progress_categories: Option<Vec<String>>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_generated: Option<bool>,
}
#[derive(Default, Clone, serde::Deserialize)]
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProjectProgressCategory {
#[serde(default)]
pub id: String,
@@ -112,12 +135,12 @@ impl ProjectObject {
}
pub fn complete(&self) -> Option<bool> {
#[allow(deprecated)]
#[expect(deprecated)]
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
}
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)
}
@@ -132,16 +155,16 @@ impl ProjectObject {
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct ScratchConfig {
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub platform: Option<String>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub compiler: Option<String>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub c_flags: Option<String>,
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ctx_path: Option<PathBuf>,
#[serde(default)]
pub build_ctx: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub build_ctx: Option<bool>,
}
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)]
pub struct ProjectConfigInfo {
pub path: PathBuf,
pub timestamp: FileTime,
pub timestamp: Option<FileTime>,
}
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);
}
}
return Some((result, ProjectConfigInfo { path: config_path, timestamp: ts }));
return Some((result, ProjectConfigInfo { path: config_path, timestamp: Some(ts) }));
}
}
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<()> {
let Some(min_version) = &config.min_version else { return Ok(()) };
let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
+46 -16
View File
@@ -9,7 +9,7 @@ use crate::{
DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind,
ObjSymbolDiff,
},
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbol, ObjSymbolFlags, SymbolRef},
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbolFlags, SymbolRef},
};
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);
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(
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_out: &ProcessCodeResult,
right_out: &ProcessCodeResult,
left_symbol_ref: SymbolRef,
@@ -60,14 +62,14 @@ pub fn diff_code(
let mut diff_state = InsDiffState::default();
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;
right.kind = result.kind;
left.arg_diff = result.left_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 {
0.0
} else {
@@ -77,13 +79,13 @@ pub fn diff_code(
Ok((
ObjSymbolDiff {
symbol_ref: left_symbol_ref,
diff_symbol: Some(right_symbol_ref),
target_symbol: Some(right_symbol_ref),
instructions: left_diff,
match_percent: Some(percent),
},
ObjSymbolDiff {
symbol_ref: right_symbol_ref,
diff_symbol: Some(left_symbol_ref),
target_symbol: Some(left_symbol_ref),
instructions: right_diff,
match_percent: Some(percent),
},
@@ -170,12 +172,33 @@ fn resolve_branches(vec: &mut [ObjInsDiff]) {
}
}
fn address_eq(left: &ObjSymbol, right: &ObjSymbol) -> bool {
left.address as i64 + left.addend == right.address as i64 + right.addend
fn address_eq(left: &ObjReloc, right: &ObjReloc) -> bool {
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(
config: &DiffObjConfig,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_reloc: Option<&ObjReloc>,
right_reloc: Option<&ObjReloc>,
) -> bool {
@@ -189,29 +212,32 @@ fn reloc_eq(
return true;
}
let name_matches = left.target.name == right.target.name;
match (&left.target_section, &right.target_section) {
let symbol_name_matches = left.target.name == right.target.name;
match (&left.target.orig_section_index, &right.target.orig_section_index) {
(Some(sl), Some(sr)) => {
// 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,
(None, Some(_)) => {
// 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(
config: &DiffObjConfig,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left: &ObjInsArg,
right: &ObjInsArg,
left_diff: &ObjInsDiff,
right_diff: &ObjInsDiff,
) -> bool {
return match left {
match left {
ObjInsArg::PlainText(l) => match right {
ObjInsArg::PlainText(r) => l == r,
_ => false,
@@ -227,6 +253,8 @@ fn arg_eq(
matches!(right, ObjInsArg::Reloc)
&& reloc_eq(
config,
left_obj,
right_obj,
left_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)
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
}
};
}
}
#[derive(Default)]
@@ -257,6 +285,8 @@ struct InsDiffResult {
fn compare_ins(
config: &DiffObjConfig,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left: &ObjInsDiff,
right: &ObjInsDiff,
state: &mut InsDiffState,
@@ -283,7 +313,7 @@ fn compare_ins(
state.diff_count += 1;
}
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.right_args_diff.push(None);
} else {
+5 -5
View File
@@ -20,13 +20,13 @@ pub fn diff_bss_symbol(
Ok((
ObjSymbolDiff {
symbol_ref: left_symbol_ref,
diff_symbol: Some(right_symbol_ref),
target_symbol: Some(right_symbol_ref),
instructions: vec![],
match_percent: Some(percent),
},
ObjSymbolDiff {
symbol_ref: right_symbol_ref,
diff_symbol: Some(left_symbol_ref),
target_symbol: Some(left_symbol_ref),
instructions: vec![],
match_percent: Some(percent),
},
@@ -34,7 +34,7 @@ pub fn diff_bss_symbol(
}
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.
@@ -158,13 +158,13 @@ pub fn diff_data_symbol(
Ok((
ObjSymbolDiff {
symbol_ref: left_symbol_ref,
diff_symbol: Some(right_symbol_ref),
target_symbol: Some(right_symbol_ref),
instructions: vec![],
match_percent: Some(match_percent),
},
ObjSymbolDiff {
symbol_ref: right_symbol_ref,
diff_symbol: Some(left_symbol_ref),
target_symbol: Some(left_symbol_ref),
instructions: vec![],
match_percent: Some(match_percent),
},
+4 -4
View File
@@ -29,7 +29,7 @@ pub enum DiffText<'a> {
Eol,
}
#[derive(Default, Clone, PartialEq, Eq)]
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub enum HighlightKind {
#[default]
None,
@@ -94,9 +94,9 @@ fn display_reloc_name<E>(
mut cb: impl FnMut(DiffText) -> Result<(), E>,
) -> Result<(), E> {
cb(DiffText::Symbol(&reloc.target))?;
match reloc.target.addend.cmp(&0i64) {
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.target.addend))),
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.target.addend))),
match reloc.addend.cmp(&0i64) {
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.addend))),
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.addend))),
_ => Ok(()),
}
}
+192 -6
View File
@@ -3,6 +3,7 @@ use std::collections::HashSet;
use anyhow::Result;
use crate::{
config::SymbolMappings,
diff::{
code::{diff_code, no_diff_code, process_code_symbol},
data::{
@@ -161,6 +162,8 @@ pub struct DiffObjConfig {
#[serde(default = "default_true")]
pub space_between_args: bool,
pub combine_data_sections: bool,
#[serde(default)]
pub symbol_mappings: MappingConfig,
// x86
pub x86_formatter: X86Formatter,
// MIPS
@@ -182,6 +185,7 @@ impl Default for DiffObjConfig {
relax_reloc_diffs: false,
space_between_args: true,
combine_data_sections: false,
symbol_mappings: Default::default(),
x86_formatter: Default::default(),
mips_abi: Default::default(),
mips_instr_category: Default::default(),
@@ -223,8 +227,10 @@ impl ObjSectionDiff {
#[derive(Debug, Clone, Default)]
pub struct ObjSymbolDiff {
/// The symbol ref this object
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 match_percent: Option<f32>,
}
@@ -294,8 +300,13 @@ pub struct ObjInsBranchTo {
#[derive(Default)]
pub struct ObjDiff {
/// A list of all section diffs in the object.
pub sections: Vec<ObjSectionDiff>,
/// Common BSS symbols don't live in a section, so they're stored separately.
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 {
@@ -303,13 +314,14 @@ impl ObjDiff {
let mut result = Self {
sections: Vec::with_capacity(obj.sections.len()),
common: Vec::with_capacity(obj.common.len()),
mapping_symbols: vec![],
};
for (section_idx, section) in obj.sections.iter().enumerate() {
let mut symbols = Vec::with_capacity(section.symbols.len());
for (symbol_idx, _) in section.symbols.iter().enumerate() {
symbols.push(ObjSymbolDiff {
symbol_ref: SymbolRef { section_idx, symbol_idx },
diff_symbol: None,
target_symbol: None,
instructions: vec![],
match_percent: None,
});
@@ -328,7 +340,7 @@ impl ObjDiff {
for (symbol_idx, _) in obj.common.iter().enumerate() {
result.common.push(ObjSymbolDiff {
symbol_ref: SymbolRef { section_idx: obj.sections.len(), symbol_idx },
diff_symbol: None,
target_symbol: None,
instructions: vec![],
match_percent: None,
});
@@ -378,7 +390,7 @@ pub fn diff_objs(
right: Option<&ObjInfo>,
prev: Option<&ObjInfo>,
) -> 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 mut left = left.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 right_code = process_code_symbol(right_obj, right_symbol_ref, config)?;
let (left_diff, right_diff) = diff_code(
left_obj,
right_obj,
&left_code,
&right_code,
left_symbol_ref,
@@ -412,6 +426,8 @@ pub fn diff_objs(
let (prev_obj, prev_out) = prev.as_mut().unwrap();
let prev_code = process_code_symbol(prev_obj, prev_symbol_ref, config)?;
let (_, prev_diff) = diff_code(
left_obj,
right_obj,
&right_code,
&prev_code,
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 {
left: left.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)]
struct SymbolMatch {
left: Option<SymbolRef>,
@@ -551,19 +637,115 @@ struct SectionMatch {
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.
fn matching_symbols(
left: Option<&ObjInfo>,
right: Option<&ObjInfo>,
prev: Option<&ObjInfo>,
mappings: &MappingConfig,
) -> Result<Vec<SymbolMatch>> {
let mut matches = Vec::new();
let mut left_used = HashSet::new();
let mut right_used = HashSet::new();
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 (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 {
left: Some(SymbolRef { section_idx, symbol_idx }),
left: Some(symbol_ref),
right: find_symbol(right, symbol, section, Some(&right_used)),
prev: find_symbol(prev, symbol, section, None),
section_kind: section.kind,
@@ -575,8 +757,12 @@ fn matching_symbols(
}
}
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 {
left: Some(SymbolRef { section_idx: left.sections.len(), symbol_idx }),
left: Some(symbol_ref),
right: find_common_symbol(right, symbol),
prev: find_common_symbol(prev, symbol),
section_kind: ObjSectionKind::Bss,
+12 -2
View File
@@ -112,6 +112,15 @@ pub struct ObjIns {
pub orig: Option<String>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub enum ObjSymbolKind {
#[default]
Unknown,
Function,
Object,
Section,
}
#[derive(Debug, Clone)]
pub struct ObjSymbol {
pub name: String,
@@ -120,8 +129,9 @@ pub struct ObjSymbol {
pub section_address: u64,
pub size: u64,
pub size_known: bool,
pub kind: ObjSymbolKind,
pub flags: ObjSymbolFlagSet,
pub addend: i64,
pub orig_section_index: Option<usize>,
/// Original virtual address (from .note.split section)
pub virtual_address: Option<u64>,
/// Original index in object symbol table
@@ -145,7 +155,7 @@ pub struct ObjReloc {
pub flags: RelocationFlags,
pub address: u64,
pub target: ObjSymbol,
pub target_section: Option<String>,
pub addend: i64,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
+138 -68
View File
@@ -13,8 +13,8 @@ use object::{
endian::LittleEndian as LE,
pe::{ImageAuxSymbolFunctionBeginEnd, ImageLinenumber},
read::coff::{CoffFile, CoffHeader, ImageSymbol},
BinaryFormat, File, Object, ObjectSection, ObjectSymbol, RelocationTarget, SectionIndex,
SectionKind, Symbol, SymbolIndex, SymbolKind, SymbolScope, SymbolSection,
BinaryFormat, File, Object, ObjectSection, ObjectSymbol, RelocationTarget, Section,
SectionIndex, SectionKind, Symbol, SymbolIndex, SymbolKind, SymbolScope,
};
use crate::{
@@ -23,6 +23,7 @@ use crate::{
obj::{
split_meta::{SplitMeta, SPLITMETA_SECTION},
ObjInfo, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags,
ObjSymbolKind,
},
util::{read_u16, read_u32},
};
@@ -40,7 +41,6 @@ fn to_obj_symbol(
arch: &dyn ObjArch,
obj_file: &File<'_>,
symbol: &Symbol<'_, '_>,
addend: i64,
split_meta: Option<&SplitMeta>,
) -> Result<ObjSymbol> {
let mut name = symbol.name().context("Failed to process symbol name")?;
@@ -94,6 +94,13 @@ fn to_obj_symbol(
})
.unwrap_or(&[]);
let kind = match symbol.kind() {
SymbolKind::Text => ObjSymbolKind::Function,
SymbolKind::Data => ObjSymbolKind::Object,
SymbolKind::Section => ObjSymbolKind::Section,
_ => ObjSymbolKind::Unknown,
};
Ok(ObjSymbol {
name: name.to_string(),
demangled_name,
@@ -101,8 +108,9 @@ fn to_obj_symbol(
section_address,
size: symbol.size(),
size_known: symbol.size() != 0,
kind,
flags,
addend,
orig_section_index: symbol.section_index().map(|i| i.0),
virtual_address,
original_index: Some(symbol.index().0),
bytes: bytes.to_vec(),
@@ -152,26 +160,23 @@ fn symbols_by_section(
arch: &dyn ObjArch,
obj_file: &File<'_>,
section: &ObjSection,
section_symbols: &[Symbol<'_, '_>],
split_meta: Option<&SplitMeta>,
name_counts: &mut HashMap<String, u32>,
) -> Result<Vec<ObjSymbol>> {
let mut result = Vec::<ObjSymbol>::new();
for symbol in obj_file.symbols() {
for symbol in section_symbols {
if symbol.kind() == SymbolKind::Section {
continue;
}
if let Some(index) = symbol.section().index() {
if index.0 == section.orig_index {
if symbol.is_local() && section.kind == ObjSectionKind::Code {
// TODO strip local syms in diff?
let name = symbol.name().context("Failed to process symbol name")?;
if symbol.size() == 0 || name.starts_with("lbl_") {
continue;
}
}
result.push(to_obj_symbol(arch, obj_file, &symbol, 0, split_meta)?);
if symbol.is_local() && section.kind == ObjSectionKind::Code {
// TODO strip local syms in diff?
let name = symbol.name().context("Failed to process symbol name")?;
if symbol.size() == 0 || name.starts_with("lbl_") {
continue;
}
}
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)));
let mut iter = result.iter_mut().peekable();
@@ -182,6 +187,13 @@ fn symbols_by_section(
} else {
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() {
@@ -199,8 +211,12 @@ fn symbols_by_section(
section_address: 0,
size: section.size,
size_known: true,
kind: match section.kind {
ObjSectionKind::Code => ObjSymbolKind::Function,
ObjSectionKind::Data | ObjSectionKind::Bss => ObjSymbolKind::Object,
},
flags: Default::default(),
addend: 0,
orig_section_index: Some(section.orig_index),
virtual_address: None,
original_index: None,
bytes: Vec::new(),
@@ -217,51 +233,75 @@ fn common_symbols(
obj_file
.symbols()
.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>>>()
}
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(
arch: &dyn ObjArch,
obj_file: &File<'_>,
target: &Symbol<'_, '_>,
section: &Section,
section_symbols: &[Symbol<'_, '_>],
address: u64,
split_meta: Option<&SplitMeta>,
) -> Result<ObjSymbol> {
let section_index =
target.section_index().ok_or_else(|| anyhow::Error::msg("Unknown section index"))?;
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);
if let Some(symbol) = best_symbol(section_symbols, address) {
return to_obj_symbol(arch, obj_file, symbol, split_meta);
}
let (name, offset) = closest_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;
// Fallback to section symbol
Ok(ObjSymbol {
name: name.to_string(),
name: section.name()?.to_string(),
demangled_name: None,
address: offset,
section_address: address - section.address(),
address: section.address(),
section_address: 0,
size: 0,
size_known: false,
kind: ObjSymbolKind::Section,
flags: Default::default(),
addend: offset_addr as i64,
orig_section_index: Some(section.index().0),
virtual_address: None,
original_index: None,
bytes: Vec::new(),
@@ -272,6 +312,7 @@ fn relocations_by_section(
arch: &dyn ObjArch,
obj_file: &File<'_>,
section: &ObjSection,
section_symbols: &[Vec<Symbol<'_, '_>>],
split_meta: Option<&SplitMeta>,
) -> Result<Vec<ObjReloc>> {
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()),
};
let flags = reloc.flags(); // TODO validate reloc here?
let target_section = match symbol.section() {
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() {
let mut addend = if reloc.has_implicit_addend() {
arch.implcit_addend(obj_file, section, address, &reloc)?
} else {
reloc.addend()
};
// println!("Reloc: {reloc:?}, symbol: {symbol:?}, addend: {addend:#x}");
let target = match symbol.kind() {
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 => {
ensure!(addend >= 0, "Negative addend in reloc: {addend}");
find_section_symbol(arch, obj_file, &symbol, addend as u64, split_meta)
ensure!(addend >= 0, "Negative addend in section reloc: {addend}");
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,
&section,
&section_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:?}")),
}?;
relocations.push(ObjReloc { flags, address, target, target_section });
kind => bail!("Unhandled relocation symbol type {kind:?}"),
};
relocations.push(ObjReloc { flags, address, target, addend });
}
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()?,
size: symbol.size,
size_known: symbol.size_known,
kind: symbol.kind,
flags: symbol.flags,
addend: symbol.addend,
orig_section_index: symbol.orig_section_index,
virtual_address: if let Some(virtual_address) = symbol.virtual_address {
Some((virtual_address as i64 + address_change).try_into()?)
} else {
@@ -566,8 +614,8 @@ fn combine_sections(section: ObjSection, combine: ObjSection) -> Result<ObjSecti
relocations.push(ObjReloc {
flags: reloc.flags,
address: (reloc.address as i64 + address_change).try_into()?,
target: reloc.target, // TODO: Should be updated?
target_section: reloc.target_section, // TODO: Same as above
target: reloc.target, // TODO: Should be updated?
addend: reloc.addend,
});
}
@@ -647,18 +695,40 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<ObjInfo> {
let obj_file = File::parse(data)?;
let arch = new_arch(&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 name_counts: HashMap<String, u32> = HashMap::new();
let mut section_name_counts: HashMap<String, u32> = HashMap::new();
for section in &mut sections {
section.symbols = symbols_by_section(
arch.as_ref(),
&obj_file,
section,
&section_symbols[section.orig_index],
split_meta.as_ref(),
&mut section_name_counts,
)?;
section.relocations = relocations_by_section(
arch.as_ref(),
&obj_file,
section,
&section_symbols,
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 {
combine_data_sections(&mut sections)?;

Some files were not shown because too many files have changed in this diff Show More