Skip to content
Merged
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
50 changes: 47 additions & 3 deletions objdiff-cli/src/cmd/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crossterm::{
},
};
use objdiff_core::{
bindings::diff::DiffResult,
build::{
BuildConfig, BuildStatus,
watcher::{Watcher, create_watcher},
Expand All @@ -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},
Expand All @@ -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},
};

Expand All @@ -60,6 +64,12 @@ pub struct Args {
#[argp(option, short = 'u')]
/// Unit name within project
unit: Option<String>,
#[argp(option, short = 'o', from_str_fn(platform_path))]
/// Output file (one-shot mode) ("-" for stdout)
output: Option<Utf8PlatformPathBuf>,
#[argp(option)]
/// Output format (json, json-pretty, proto) (default: json)
format: Option<String>,
#[argp(positional)]
/// Function symbol to diff
symbol: Option<String>,
Expand Down Expand Up @@ -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<ProjectOptions>,
) -> 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(
Expand Down
162 changes: 162 additions & 0 deletions objdiff-core/protos/diff.proto
Original file line number Diff line number Diff line change
@@ -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;
}
Binary file modified objdiff-core/protos/proto_descriptor.bin
Binary file not shown.
Loading
Loading