Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions objdiff-cli/src/cmd/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,17 @@ fn run_oneshot(
.with_context(|| format!("Loading {p}"))
})
.transpose()?;
let result =
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
let result = if let Some(symbol_name) = args.symbol.as_deref() {
diff::diff_objs_for_symbol(
target.as_ref(),
base.as_ref(),
symbol_name,
&diff_config,
&mapping_config,
)?
} else {
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?
};
let left = target.as_ref().zip(result.left.as_ref());
let right = base.as_ref().zip(result.right.as_ref());
let diff_result = DiffResult::new(left, right, &diff_config)?;
Expand Down
102 changes: 75 additions & 27 deletions objdiff-cli/src/cmd/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use objdiff_core::{
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION,
Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
},
config::{ProjectObject, ProjectOptions, apply_project_options, path::platform_path},
config::{
ProjectConfig, ProjectObject, ProjectOptions, apply_project_options, path::platform_path,
},
diff,
obj::{self, SectionKind, SymbolFlag, SymbolKind},
};
Expand Down Expand Up @@ -37,9 +39,15 @@ pub enum SubCommand {
}

#[derive(FromArgs, PartialEq, Debug)]
/// Generate a progress report for a project.
/// Generate a progress report for a project or a pair of object files.
#[argp(subcommand, name = "generate")]
pub struct GenerateArgs {
#[argp(option, short = '1', from_str_fn(platform_path))]
/// Target object file
target: Option<Utf8PlatformPathBuf>,
#[argp(option, short = '2', from_str_fn(platform_path))]
/// Base object file
base: Option<Utf8PlatformPathBuf>,
#[argp(option, short = 'p', from_str_fn(platform_path))]
/// Project directory
project: Option<Utf8PlatformPathBuf>,
Expand Down Expand Up @@ -92,34 +100,62 @@ fn generate(args: GenerateArgs) -> Result<()> {
};

let output_format = OutputFormat::from_option(args.format.as_deref())?;
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
info!("Loading project {}", project_dir);
let direct_input = args.target.is_some() || args.base.is_some();
if direct_input && args.project.is_some() {
bail!("--project cannot be combined with --target or --base");
}

let project = match objdiff_core::config::try_project_config(project_dir.as_ref()) {
Some((Ok(config), _)) => config,
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
None => bail!("No project configuration found"),
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
let project = if direct_input {
info!("Loading input objects");
ProjectConfig::default()
} else {
info!("Loading project {}", project_dir);
match objdiff_core::config::try_project_config(project_dir.as_ref()) {
Some((Ok(config), _)) => config,
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
None => bail!("No project configuration found"),
}
};
let target_obj_dir =
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
let base_obj_dir =
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
let project_units = project.units.as_deref().unwrap_or_default();
let objects = project_units
.iter()
.enumerate()
.map(|(idx, o)| {
(
ObjectConfig::new(
o,
project_dir,
target_obj_dir.as_deref(),
base_obj_dir.as_deref(),
),
idx,
)
})
.collect::<Vec<_>>();
let objects = if direct_input {
let name = args
.target
.as_deref()
.or(args.base.as_deref())
.and_then(Utf8PlatformPath::file_name)
.unwrap_or("input")
.to_string();
vec![(
ObjectConfig {
name,
target_path: args.target.clone(),
base_path: args.base.clone(),
..Default::default()
},
0,
)]
} else {
project_units
.iter()
.enumerate()
.map(|(idx, o)| {
(
ObjectConfig::new(
o,
project_dir,
target_obj_dir.as_deref(),
base_obj_dir.as_deref(),
),
idx,
)
})
.collect::<Vec<_>>()
};
info!(
"Generating report for {} units (using {} threads)",
objects.len(),
Expand Down Expand Up @@ -215,24 +251,36 @@ fn report_object(
selecting_left: None,
selecting_right: None,
};
let target = object
let mut target = object
.target_path
.as_ref()
.map(|p| {
obj::read::read(p.as_ref(), diff_config, diff::DiffSide::Target)
.with_context(|| format!("Failed to open {p}"))
})
.transpose()?;
let base = object
let mut base = object
.base_path
.as_ref()
.map(|p| {
obj::read::read(p.as_ref(), diff_config, diff::DiffSide::Base)
.with_context(|| format!("Failed to open {p}"))
})
.transpose()?;
let result =
diff::diff_objs(target.as_ref(), base.as_ref(), None, diff_config, &mapping_config)?;
for obj in target.iter_mut().chain(base.iter_mut()) {
for symbol in &mut obj.symbols {
if symbol.kind == SymbolKind::Section {
symbol.flags |= SymbolFlag::Ignored;
}
}
}
let result = diff::diff_objs_summary(
target.as_ref(),
base.as_ref(),
None,
diff_config,
&mapping_config,
)?;

let metadata = ReportUnitMetadata {
complete: object.metadata.complete,
Expand Down
66 changes: 63 additions & 3 deletions objdiff-core/src/diff/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,66 @@ pub fn diff_data_section(
Ok((left_section_diff, right_section_diff))
}

/// Calculate a data section's match percentage without constructing a byte-level edit script.
///
/// Reports only consume the percentage, so comparing fixed-size blocks avoids pathological Myers
/// diff behavior on large linked sections while still accounting for inserted and deleted data.
pub fn diff_data_section_summary(
left_obj: &Object,
right_obj: &Object,
left_diff: &ObjectDiff,
right_diff: &ObjectDiff,
left_section_idx: usize,
right_section_idx: usize,
) -> Result<(SectionDiff, SectionDiff)> {
const BLOCK_SIZE: usize = 16;

let left_section = &left_obj.sections[left_section_idx];
let right_section = &right_obj.sections[right_section_idx];
let left_max = symbols_matching_section(&left_obj.symbols, left_section_idx)
.filter_map(|(_, s)| s.address.checked_sub(left_section.address).map(|a| a + s.size))
.max()
.unwrap_or(0)
.min(left_section.size);
let right_max = symbols_matching_section(&right_obj.symbols, right_section_idx)
.filter_map(|(_, s)| s.address.checked_sub(right_section.address).map(|a| a + s.size))
.max()
.unwrap_or(0)
.min(right_section.size);
let left_data = &left_section.data[..left_max as usize];
let right_data = &right_section.data[..right_max as usize];
let left_blocks = left_data.chunks(BLOCK_SIZE).collect::<Vec<_>>();
let right_blocks = right_data.chunks(BLOCK_SIZE).collect::<Vec<_>>();
let ops = capture_diff_slices(Algorithm::Patience, &left_blocks, &right_blocks);
let bytes_match_percent = diff_ratio(&ops, left_blocks.len(), right_blocks.len()) * 100.0;

let all_left_relocs_match = diff_data_relocs_for_range(
left_obj,
right_obj,
left_section_idx,
right_section_idx,
0..left_max as usize,
0..right_max as usize,
)
.iter()
.all(|(kind, left, _)| left.is_none() || *kind == DataDiffKind::None);

let (mut left_section_diff, right_section_diff) = diff_generic_section(
left_obj,
right_obj,
left_diff,
right_diff,
left_section_idx,
right_section_idx,
)?;
if all_left_relocs_match
&& left_section_diff.match_percent.unwrap_or(-1.0) < bytes_match_percent
{
left_section_diff.match_percent = Some(bytes_match_percent);
}
Ok((left_section_diff, right_section_diff))
}

pub fn no_diff_data_symbol(obj: &Object, symbol_index: usize) -> Result<SymbolDiff> {
let symbol = &obj.symbols[symbol_index];
let section_idx = symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
Expand All @@ -320,7 +380,7 @@ pub fn no_diff_data_symbol(obj: &Object, symbol_index: usize) -> Result<SymbolDi
let start = symbol
.address
.checked_sub(section.address)
.ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
.ok_or_else(|| anyhow!("Symbol {} address out of section bounds", symbol.name))?;
let end = start + symbol.size;
if end > section.size {
return Err(anyhow!(
Expand Down Expand Up @@ -383,11 +443,11 @@ pub fn diff_data_symbol(
let left_start = left_symbol
.address
.checked_sub(left_section.address)
.ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
.ok_or_else(|| anyhow!("Symbol {} address out of section bounds", left_symbol.name))?;
let right_start = right_symbol
.address
.checked_sub(right_section.address)
.ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
.ok_or_else(|| anyhow!("Symbol {} address out of section bounds", right_symbol.name))?;
let left_end = left_start + left_symbol.size;
if left_end > left_section.size {
return Err(anyhow!(
Expand Down
Loading
Loading