From da6ea88852fe97ce2b4a9c59c22be602730c8ff2 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sat, 24 Jan 2026 19:45:21 -0700 Subject: [PATCH] Restore objdiff-cli oneshot mode (JSON output) --- objdiff-cli/src/cmd/diff.rs | 50 +++- objdiff-core/protos/diff.proto | 162 +++++++++++ objdiff-core/protos/proto_descriptor.bin | Bin 9379 -> 17936 bytes objdiff-core/src/bindings/diff.rs | 350 +++++++++++++++++++++++ objdiff-core/src/bindings/mod.rs | 2 + 5 files changed, 561 insertions(+), 3 deletions(-) create mode 100644 objdiff-core/protos/diff.proto create mode 100644 objdiff-core/src/bindings/diff.rs diff --git a/objdiff-cli/src/cmd/diff.rs b/objdiff-cli/src/cmd/diff.rs index 13f64e5f..062e0b89 100644 --- a/objdiff-cli/src/cmd/diff.rs +++ b/objdiff-cli/src/cmd/diff.rs @@ -19,6 +19,7 @@ use crossterm::{ }, }; use objdiff_core::{ + bindings::diff::DiffResult, build::{ BuildConfig, BuildStatus, watcher::{Watcher, create_watcher}, @@ -28,7 +29,7 @@ use objdiff_core::{ build_globset, path::{check_path_buf, platform_path, platform_path_serde_option}, }, - diff::{DiffObjConfig, MappingConfig, ObjectDiff}, + diff::{self, DiffObjConfig, DiffSide, MappingConfig, ObjectDiff}, jobs::{ Job, JobQueue, JobResult, objdiff::{ObjDiffConfig, start_build}, @@ -40,7 +41,10 @@ use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf}; use crate::{ cmd::apply_config_args, - util::term::crossterm_panic_handler, + util::{ + output::{OutputFormat, write_output}, + term::crossterm_panic_handler, + }, views::{EventControlFlow, EventResult, UiView, function_diff::FunctionDiffUi}, }; @@ -60,6 +64,12 @@ pub struct Args { #[argp(option, short = 'u')] /// Unit name within project unit: Option, + #[argp(option, short = 'o', from_str_fn(platform_path))] + /// Output file (one-shot mode) ("-" for stdout) + output: Option, + #[argp(option)] + /// Output format (json, json-pretty, proto) (default: json) + format: Option, #[argp(positional)] /// Function symbol to diff symbol: Option, @@ -158,7 +168,41 @@ pub fn run(args: Args) -> Result<()> { _ => bail!("Either target and base or project and unit must be specified"), }; - run_interactive(args, target_path, base_path, project_config, unit_options) + if let Some(output) = &args.output { + run_oneshot(&args, output, target_path.as_deref(), base_path.as_deref(), unit_options) + } else { + run_interactive(args, target_path, base_path, project_config, unit_options) + } +} + +fn run_oneshot( + args: &Args, + output: &Utf8PlatformPath, + target_path: Option<&Utf8PlatformPath>, + base_path: Option<&Utf8PlatformPath>, + unit_options: Option, +) -> Result<()> { + let output_format = OutputFormat::from_option(args.format.as_deref())?; + let (diff_config, mapping_config) = build_config_from_args(args, None, unit_options.as_ref())?; + let target = target_path + .map(|p| { + obj::read::read(p.as_ref(), &diff_config, DiffSide::Target) + .with_context(|| format!("Loading {p}")) + }) + .transpose()?; + let base = base_path + .map(|p| { + obj::read::read(p.as_ref(), &diff_config, DiffSide::Base) + .with_context(|| format!("Loading {p}")) + }) + .transpose()?; + let result = + diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?; + let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d))); + let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d))); + let diff_result = DiffResult::new(left, right, &diff_config)?; + write_output(&diff_result, Some(output), output_format)?; + Ok(()) } fn build_config_from_args( diff --git a/objdiff-core/protos/diff.proto b/objdiff-core/protos/diff.proto new file mode 100644 index 00000000..56dfa279 --- /dev/null +++ b/objdiff-core/protos/diff.proto @@ -0,0 +1,162 @@ +syntax = "proto3"; + +package objdiff.diff; + +// Top-level diff result containing left and right object diffs +message DiffResult { + optional DiffObject left = 1; + optional DiffObject right = 2; +} + +// Diff information for a single object file +message DiffObject { + repeated DiffSection sections = 1; + repeated DiffSymbol symbols = 2; +} + +// Diff information for a section +message DiffSection { + string name = 1; + DiffSectionKind kind = 2; + uint64 size = 3; + uint64 address = 4; + optional float match_percent = 5; + repeated DiffDataSegment data_diff = 6; + repeated DiffDataRelocation reloc_diff = 7; +} + +enum DiffSectionKind { + SECTION_UNKNOWN = 0; + SECTION_CODE = 1; + SECTION_DATA = 2; + SECTION_BSS = 3; + SECTION_COMMON = 4; +} + +// Symbol with diff information +message DiffSymbol { + string name = 1; + optional string demangled_name = 2; + uint64 address = 3; + uint64 size = 4; + uint32 flags = 5; + // The symbol index in the _other_ object that this symbol was diffed against + optional uint32 target_symbol = 6; + optional float match_percent = 7; + // Instruction diff rows (for code symbols) + repeated DiffInstructionRow instructions = 8; + // Data diff (for data symbols) + repeated DiffDataSegment data_diff = 9; +} + +// Symbol visibility flags (bitmask) +enum DiffSymbolFlag { + SYMBOL_NONE = 0; + SYMBOL_GLOBAL = 1; + SYMBOL_LOCAL = 2; + SYMBOL_WEAK = 4; + SYMBOL_COMMON = 8; + SYMBOL_HIDDEN = 16; +} + +// A single instruction diff row +message DiffInstructionRow { + DiffKind diff_kind = 1; + optional DiffInstruction instruction = 2; + repeated DiffInstructionArgDiff arg_diff = 3; +} + +// Parsed instruction information +message DiffInstruction { + uint64 address = 1; + uint32 size = 2; + // Formatted instruction string + string formatted = 3; + // Formatted instruction parts + repeated DiffInstructionPart parts = 4; + // Relocation information (if present) + optional DiffRelocation relocation = 5; + // Branch destination address + optional uint64 branch_dest = 6; + // Source line number (if present) + optional uint32 line_number = 7; +} + +// An instruction part +message DiffInstructionPart { + oneof part { + // Basic text (whitespace, punctuation, etc.) + string basic = 1; + // Opcode/mnemonic + DiffOpcode opcode = 2; + // Argument + DiffInstructionArg arg = 3; + // Separator between arguments (comma) + bool separator = 4; + } +} + +message DiffOpcode { + // Mnemonic string + string mnemonic = 1; + // Base opcode ID + uint32 opcode = 2; +} + +// An instruction argument +message DiffInstructionArg { + oneof arg { + // Signed immediate value + sint64 signed = 1; + // Unsigned immediate value + uint64 unsigned = 2; + // Opaque string value (register names, etc.) + string opaque = 3; + // Relocation + bool reloc = 4; + // Branch destination address + uint64 branch_dest = 5; + } +} + +// Relocation information +message DiffRelocation { + uint32 type = 1; + string type_name = 2; + uint32 target_symbol = 3; + int64 addend = 4; +} + +// Argument diff information +message DiffInstructionArgDiff { + // If set, this argument differs from the other side. + // The value is a rotating index for coloring. + optional uint32 diff_index = 1; +} + +// Diff kind for instructions and data +enum DiffKind { + DIFF_NONE = 0; + DIFF_REPLACE = 1; + DIFF_DELETE = 2; + DIFF_INSERT = 3; + DIFF_OP_MISMATCH = 4; + DIFF_ARG_MISMATCH = 5; +} + +// Data diff segment +message DiffDataSegment { + DiffKind kind = 1; + bytes data = 2; + // Size may be larger than data length for BSS + uint64 size = 3; +} + +// Data relocation diff +message DiffDataRelocation { + DiffRelocation relocation = 1; + DiffKind kind = 2; + // Address range this relocation covers + uint64 start = 3; + uint64 end = 4; +} diff --git a/objdiff-core/protos/proto_descriptor.bin b/objdiff-core/protos/proto_descriptor.bin index 8b3ce1e5cc42540c426829f3bd9503b68e597ade..ac74171896c4bc2f696e72801a953341187e5474 100644 GIT binary patch literal 17936 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 b12!V_Yx)8NgtcDN#smn$U+e8Nk@^1v0ZxX2 delta 10 RcmbQx!?@UUbFSiI6#y5>1QGxM diff --git a/objdiff-core/src/bindings/diff.rs b/objdiff-core/src/bindings/diff.rs new file mode 100644 index 00000000..9a9c36dc --- /dev/null +++ b/objdiff-core/src/bindings/diff.rs @@ -0,0 +1,350 @@ +#![allow(clippy::needless_lifetimes)] // Generated serde code + +use alloc::{ + string::{String, ToString}, + vec, + vec::Vec, +}; +use core::fmt::Write; + +use anyhow::Result; + +use crate::{ + diff::{self, DiffObjConfig, display::InstructionPart}, + obj::{self, Object, SymbolFlag}, +}; + +// Protobuf diff types +include!(concat!(env!("OUT_DIR"), "/objdiff.diff.rs")); +#[cfg(feature = "serde")] +include!(concat!(env!("OUT_DIR"), "/objdiff.diff.serde.rs")); + +impl DiffResult { + pub fn new( + left: Option<(&Object, &diff::ObjectDiff)>, + right: Option<(&Object, &diff::ObjectDiff)>, + diff_config: &DiffObjConfig, + ) -> Result { + Ok(Self { + left: left.map(|(obj, diff)| DiffObject::new(obj, diff, diff_config)).transpose()?, + right: right.map(|(obj, diff)| DiffObject::new(obj, diff, diff_config)).transpose()?, + }) + } +} + +impl DiffObject { + pub fn new(obj: &Object, diff: &diff::ObjectDiff, diff_config: &DiffObjConfig) -> Result { + let mut sections = Vec::with_capacity(obj.sections.len()); + for (section_idx, section) in obj.sections.iter().enumerate() { + let section_diff = &diff.sections[section_idx]; + sections.push(DiffSection::new(obj, section, section_diff)); + } + + let mut symbols = Vec::with_capacity(obj.symbols.len()); + for (symbol_idx, symbol) in obj.symbols.iter().enumerate() { + let symbol_diff = &diff.symbols[symbol_idx]; + if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) { + continue; + } + symbols.push(DiffSymbol::new(obj, symbol_idx, symbol, symbol_diff, diff_config)?); + } + + Ok(Self { sections, symbols }) + } +} + +impl DiffSection { + pub fn new(obj: &Object, section: &obj::Section, section_diff: &diff::SectionDiff) -> Self { + Self { + name: section.name.clone(), + kind: DiffSectionKind::from(section.kind) as i32, + size: section.size, + address: section.address, + match_percent: section_diff.match_percent, + data_diff: section_diff.data_diff.iter().map(DiffDataSegment::from).collect(), + reloc_diff: section_diff + .reloc_diff + .iter() + .map(|r| DiffDataRelocation::new(obj, r)) + .collect(), + } + } +} + +impl From for DiffSectionKind { + fn from(value: obj::SectionKind) -> Self { + match value { + obj::SectionKind::Unknown => DiffSectionKind::SectionUnknown, + obj::SectionKind::Code => DiffSectionKind::SectionCode, + obj::SectionKind::Data => DiffSectionKind::SectionData, + obj::SectionKind::Bss => DiffSectionKind::SectionBss, + obj::SectionKind::Common => DiffSectionKind::SectionCommon, + } + } +} + +impl DiffSymbol { + pub fn new( + obj: &Object, + symbol_idx: usize, + symbol: &obj::Symbol, + symbol_diff: &diff::SymbolDiff, + diff_config: &DiffObjConfig, + ) -> Result { + // Convert instruction rows + let instructions = symbol_diff + .instruction_rows + .iter() + .map(|row| DiffInstructionRow::new(obj, symbol_idx, row, diff_config)) + .collect::>>()?; + + // Convert data diff - flatten DataDiffRow segments into a single list + let data_diff: Vec = symbol_diff + .data_rows + .iter() + .flat_map(|row| row.segments.iter().map(DiffDataSegment::from)) + .collect(); + + Ok(Self { + // Symbol metadata + name: symbol.name.clone(), + demangled_name: symbol.demangled_name.clone(), + address: symbol.address, + size: symbol.size, + flags: symbol_flags(&symbol.flags), + // Diff information + target_symbol: symbol_diff.target_symbol.map(|i| i as u32), + match_percent: symbol_diff.match_percent, + instructions, + data_diff, + }) + } +} + +fn symbol_flags(flags: &obj::SymbolFlagSet) -> u32 { + let mut result = 0u32; + if flags.contains(SymbolFlag::Global) { + result |= DiffSymbolFlag::SymbolGlobal as u32; + } + if flags.contains(SymbolFlag::Local) { + result |= DiffSymbolFlag::SymbolLocal as u32; + } + if flags.contains(SymbolFlag::Weak) { + result |= DiffSymbolFlag::SymbolWeak as u32; + } + if flags.contains(SymbolFlag::Common) { + result |= DiffSymbolFlag::SymbolCommon as u32; + } + if flags.contains(SymbolFlag::Hidden) { + result |= DiffSymbolFlag::SymbolHidden as u32; + } + result +} + +impl DiffInstructionRow { + pub fn new( + obj: &Object, + symbol_idx: usize, + row: &diff::InstructionDiffRow, + diff_config: &DiffObjConfig, + ) -> Result { + let instruction = if let Some(ins_ref) = row.ins_ref { + let resolved = obj.resolve_instruction_ref(symbol_idx, ins_ref); + resolved.map(|r| DiffInstruction::new(obj, r, diff_config)).transpose()? + } else { + None + }; + + let arg_diff = + row.arg_diff.iter().map(|d| DiffInstructionArgDiff { diff_index: d.get() }).collect(); + + Ok(Self { diff_kind: DiffKind::from(row.kind) as i32, instruction, arg_diff }) + } +} + +impl DiffInstruction { + pub fn new( + obj: &Object, + resolved: obj::ResolvedInstructionRef, + diff_config: &DiffObjConfig, + ) -> Result { + let mut formatted = String::new(); + let mut parts = vec![]; + let separator = diff_config.separator(); + + // Use the arch's display_instruction to get formatted parts + obj.arch.display_instruction(resolved, diff_config, &mut |part| { + write_instruction_part(&mut formatted, &part, separator, resolved.relocation); + parts + .push(DiffInstructionPart { part: Some(diff_instruction_part::Part::from(&part)) }); + Ok(()) + })?; + + let relocation = resolved.relocation.map(|r| DiffRelocation::new(obj, r)); + + let line_number = resolved + .section + .line_info + .range(..=resolved.ins_ref.address) + .last() + .map(|(_, &line)| line); + + Ok(Self { + address: resolved.ins_ref.address, + size: resolved.ins_ref.size as u32, + formatted, + parts, + relocation, + branch_dest: resolved.ins_ref.branch_dest, + line_number, + }) + } +} + +fn write_instruction_part( + out: &mut String, + part: &InstructionPart, + separator: &str, + reloc: Option, +) { + match part { + InstructionPart::Basic(s) => out.push_str(s), + InstructionPart::Opcode(s, _) => { + out.push_str(s); + out.push(' '); + } + InstructionPart::Arg(arg) => match arg { + obj::InstructionArg::Value(v) => { + let _ = write!(out, "{}", v); + } + obj::InstructionArg::Reloc => { + if let Some(resolved) = reloc { + out.push_str(&resolved.symbol.name); + if resolved.relocation.addend != 0 { + if resolved.relocation.addend < 0 { + let _ = write!(out, "-{:#x}", -resolved.relocation.addend); + } else { + let _ = write!(out, "+{:#x}", resolved.relocation.addend); + } + } + } + } + obj::InstructionArg::BranchDest(dest) => { + let _ = write!(out, "{:#x}", dest); + } + }, + InstructionPart::Separator => out.push_str(separator), + } +} + +impl diff_instruction_part::Part { + fn from(part: &InstructionPart) -> Self { + match part { + InstructionPart::Basic(s) => diff_instruction_part::Part::Basic(s.to_string()), + InstructionPart::Opcode(mnemonic, opcode) => { + diff_instruction_part::Part::Opcode(DiffOpcode { + mnemonic: mnemonic.to_string(), + opcode: *opcode as u32, + }) + } + InstructionPart::Arg(arg) => { + diff_instruction_part::Part::Arg(DiffInstructionArg::from(arg)) + } + InstructionPart::Separator => diff_instruction_part::Part::Separator(true), + } + } +} + +impl From<&obj::InstructionArg<'_>> for DiffInstructionArg { + fn from(arg: &obj::InstructionArg) -> Self { + let arg = match arg { + obj::InstructionArg::Value(v) => match v { + obj::InstructionArgValue::Signed(v) => diff_instruction_arg::Arg::Signed(*v), + obj::InstructionArgValue::Unsigned(v) => diff_instruction_arg::Arg::Unsigned(*v), + obj::InstructionArgValue::Opaque(v) => { + diff_instruction_arg::Arg::Opaque(v.to_string()) + } + }, + obj::InstructionArg::Reloc => diff_instruction_arg::Arg::Reloc(true), + obj::InstructionArg::BranchDest(dest) => diff_instruction_arg::Arg::BranchDest(*dest), + }; + DiffInstructionArg { arg: Some(arg) } + } +} + +impl DiffRelocation { + pub fn new(obj: &Object, resolved: obj::ResolvedRelocation) -> Self { + let type_val = relocation_type(resolved.relocation.flags); + let type_name = obj + .arch + .reloc_name(resolved.relocation.flags) + .map(|s| s.to_string()) + .unwrap_or_default(); + Self { + r#type: type_val, + type_name, + target_symbol: resolved.relocation.target_symbol as u32, + addend: resolved.relocation.addend, + } + } +} + +fn relocation_type(flags: obj::RelocationFlags) -> u32 { + match flags { + obj::RelocationFlags::Elf(r_type) => r_type, + obj::RelocationFlags::Coff(typ) => typ as u32, + } +} + +impl From for DiffKind { + fn from(value: diff::InstructionDiffKind) -> Self { + match value { + diff::InstructionDiffKind::None => DiffKind::DiffNone, + diff::InstructionDiffKind::OpMismatch => DiffKind::DiffOpMismatch, + diff::InstructionDiffKind::ArgMismatch => DiffKind::DiffArgMismatch, + diff::InstructionDiffKind::Replace => DiffKind::DiffReplace, + diff::InstructionDiffKind::Delete => DiffKind::DiffDelete, + diff::InstructionDiffKind::Insert => DiffKind::DiffInsert, + } + } +} + +impl From for DiffKind { + fn from(value: diff::DataDiffKind) -> Self { + match value { + diff::DataDiffKind::None => DiffKind::DiffNone, + diff::DataDiffKind::Replace => DiffKind::DiffReplace, + diff::DataDiffKind::Delete => DiffKind::DiffDelete, + diff::DataDiffKind::Insert => DiffKind::DiffInsert, + } + } +} + +impl From<&diff::DataDiff> for DiffDataSegment { + fn from(value: &diff::DataDiff) -> Self { + Self { + kind: DiffKind::from(value.kind) as i32, + data: value.data.clone(), + size: value.size as u64, + } + } +} + +impl DiffDataRelocation { + pub fn new(obj: &Object, value: &diff::DataRelocationDiff) -> Self { + let type_val = relocation_type(value.reloc.flags); + let type_name = + obj.arch.reloc_name(value.reloc.flags).map(|s| s.to_string()).unwrap_or_default(); + Self { + relocation: Some(DiffRelocation { + r#type: type_val, + type_name, + target_symbol: value.reloc.target_symbol as u32, + addend: value.reloc.addend, + }), + kind: DiffKind::from(value.kind) as i32, + start: value.range.start, + end: value.range.end, + } + } +} diff --git a/objdiff-core/src/bindings/mod.rs b/objdiff-core/src/bindings/mod.rs index 7352eef8..4b67b72a 100644 --- a/objdiff-core/src/bindings/mod.rs +++ b/objdiff-core/src/bindings/mod.rs @@ -1 +1,3 @@ +#[cfg(feature = "any-arch")] +pub mod diff; pub mod report;