mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-11 06:27:55 +00:00
Experimental objdiff-cli (WIP)
This commit is contained in:
148
objdiff-core/src/config/mod.rs
Normal file
148
objdiff-core/src/config/mod.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use filetime::FileTime;
|
||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
|
||||
#[inline]
|
||||
fn bool_true() -> bool { true }
|
||||
|
||||
#[derive(Default, Clone, serde::Deserialize)]
|
||||
pub struct ProjectConfig {
|
||||
#[serde(default)]
|
||||
pub min_version: Option<String>,
|
||||
#[serde(default)]
|
||||
pub custom_make: Option<String>,
|
||||
#[serde(default)]
|
||||
pub target_dir: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub base_dir: Option<PathBuf>,
|
||||
#[serde(default = "bool_true")]
|
||||
pub build_base: bool,
|
||||
#[serde(default)]
|
||||
pub build_target: bool,
|
||||
#[serde(default)]
|
||||
pub watch_patterns: Option<Vec<Glob>>,
|
||||
#[serde(default, alias = "units")]
|
||||
pub objects: Vec<ProjectObject>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, serde::Deserialize)]
|
||||
pub struct ProjectObject {
|
||||
#[serde(default)]
|
||||
pub name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub path: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub target_path: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub base_path: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub reverse_fn_order: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub complete: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub scratch: Option<ScratchConfig>,
|
||||
}
|
||||
|
||||
impl ProjectObject {
|
||||
pub fn name(&self) -> &str {
|
||||
if let Some(name) = &self.name {
|
||||
name
|
||||
} else if let Some(path) = &self.path {
|
||||
path.to_str().unwrap_or("[invalid path]")
|
||||
} else {
|
||||
"[unknown]"
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_paths(
|
||||
&mut self,
|
||||
project_dir: &Path,
|
||||
target_obj_dir: Option<&Path>,
|
||||
base_obj_dir: Option<&Path>,
|
||||
) {
|
||||
if let (Some(target_obj_dir), Some(path), None) =
|
||||
(target_obj_dir, &self.path, &self.target_path)
|
||||
{
|
||||
self.target_path = Some(target_obj_dir.join(path));
|
||||
} else if let Some(path) = &self.target_path {
|
||||
self.target_path = Some(project_dir.join(path));
|
||||
}
|
||||
if let (Some(base_obj_dir), Some(path), None) = (base_obj_dir, &self.path, &self.base_path)
|
||||
{
|
||||
self.base_path = Some(base_obj_dir.join(path));
|
||||
} else if let Some(path) = &self.base_path {
|
||||
self.base_path = Some(project_dir.join(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ScratchConfig {
|
||||
#[serde(default)]
|
||||
pub platform: Option<String>,
|
||||
#[serde(default)]
|
||||
pub compiler: Option<String>,
|
||||
#[serde(default)]
|
||||
pub c_flags: Option<String>,
|
||||
#[serde(default)]
|
||||
pub ctx_path: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub build_ctx: bool,
|
||||
}
|
||||
|
||||
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.yml", "objdiff.yaml", "objdiff.json"];
|
||||
|
||||
pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
|
||||
"*.c", "*.cp", "*.cpp", "*.cxx", "*.h", "*.hp", "*.hpp", "*.hxx", "*.s", "*.S", "*.asm",
|
||||
"*.inc", "*.py", "*.yml", "*.txt", "*.json",
|
||||
];
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct ProjectConfigInfo {
|
||||
pub path: PathBuf,
|
||||
pub timestamp: FileTime,
|
||||
}
|
||||
|
||||
pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
||||
for filename in CONFIG_FILENAMES.iter() {
|
||||
let config_path = dir.join(filename);
|
||||
let Ok(mut file) = File::open(&config_path) else {
|
||||
continue;
|
||||
};
|
||||
let metadata = file.metadata();
|
||||
if let Ok(metadata) = metadata {
|
||||
if !metadata.is_file() {
|
||||
continue;
|
||||
}
|
||||
let ts = FileTime::from_last_modification_time(&metadata);
|
||||
let config = match filename.contains("json") {
|
||||
true => read_json_config(&mut file),
|
||||
false => read_yml_config(&mut file),
|
||||
};
|
||||
return Some((config, ProjectConfigInfo { path: config_path, timestamp: ts }));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn read_yml_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> {
|
||||
Ok(serde_yaml::from_reader(reader)?)
|
||||
}
|
||||
|
||||
fn read_json_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> {
|
||||
Ok(serde_json::from_reader(reader)?)
|
||||
}
|
||||
|
||||
pub fn build_globset(vec: &[Glob]) -> std::result::Result<GlobSet, globset::Error> {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
for glob in vec {
|
||||
builder.add(glob.clone());
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
175
objdiff-core/src/diff/display.rs
Normal file
175
objdiff-core/src/diff/display.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::obj::{
|
||||
ObjInsArg, ObjInsArgDiff, ObjInsArgValue, ObjInsDiff, ObjReloc, ObjRelocKind, ObjSymbol,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DiffText<'a> {
|
||||
/// Basic text
|
||||
Basic(&'a str),
|
||||
/// Colored text
|
||||
BasicColor(&'a str, usize),
|
||||
/// Line number
|
||||
Line(usize),
|
||||
/// Instruction address
|
||||
Address(u32),
|
||||
/// Instruction mnemonic
|
||||
Opcode(&'a str, u8),
|
||||
/// Instruction argument
|
||||
Argument(&'a ObjInsArgValue, Option<&'a ObjInsArgDiff>),
|
||||
/// Branch target
|
||||
BranchTarget(u32),
|
||||
/// Symbol name
|
||||
Symbol(&'a ObjSymbol),
|
||||
/// Number of spaces
|
||||
Spacing(usize),
|
||||
/// End of line
|
||||
Eol,
|
||||
}
|
||||
|
||||
pub fn display_diff(
|
||||
ins_diff: &ObjInsDiff,
|
||||
base_addr: u32,
|
||||
mut cb: impl FnMut(DiffText) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
let Some(ins) = &ins_diff.ins else {
|
||||
cb(DiffText::Eol)?;
|
||||
return Ok(());
|
||||
};
|
||||
if let Some(line) = ins.line {
|
||||
cb(DiffText::Line(line as usize))?;
|
||||
}
|
||||
cb(DiffText::Address(ins.address - base_addr))?;
|
||||
if let Some(branch) = &ins_diff.branch_from {
|
||||
cb(DiffText::BasicColor(" ~> ", branch.branch_idx))?;
|
||||
} else {
|
||||
cb(DiffText::Spacing(4))?;
|
||||
}
|
||||
cb(DiffText::Opcode(&ins.mnemonic, ins.op))?;
|
||||
let mut writing_offset = false;
|
||||
for (i, arg) in ins.args.iter().enumerate() {
|
||||
if i == 0 {
|
||||
cb(DiffText::Spacing(1))?;
|
||||
}
|
||||
if i > 0 && !writing_offset {
|
||||
cb(DiffText::Basic(", "))?;
|
||||
}
|
||||
let mut new_writing_offset = false;
|
||||
match arg {
|
||||
ObjInsArg::Arg(v) => {
|
||||
let diff = ins_diff.arg_diff.get(i).and_then(|o| o.as_ref());
|
||||
cb(DiffText::Argument(v, diff))?;
|
||||
}
|
||||
ObjInsArg::ArgWithBase(v) => {
|
||||
let diff = ins_diff.arg_diff.get(i).and_then(|o| o.as_ref());
|
||||
cb(DiffText::Argument(v, diff))?;
|
||||
cb(DiffText::Basic("("))?;
|
||||
new_writing_offset = true;
|
||||
}
|
||||
ObjInsArg::Reloc => {
|
||||
display_reloc(ins.reloc.as_ref().unwrap(), &mut cb)?;
|
||||
}
|
||||
ObjInsArg::RelocWithBase => {
|
||||
display_reloc(ins.reloc.as_ref().unwrap(), &mut cb)?;
|
||||
cb(DiffText::Basic("("))?;
|
||||
new_writing_offset = true;
|
||||
}
|
||||
ObjInsArg::BranchOffset(offset) => {
|
||||
let addr = offset + ins.address as i32 - base_addr as i32;
|
||||
cb(DiffText::BranchTarget(addr as u32))?;
|
||||
}
|
||||
}
|
||||
if writing_offset {
|
||||
cb(DiffText::Basic(")"))?;
|
||||
}
|
||||
writing_offset = new_writing_offset;
|
||||
}
|
||||
if let Some(branch) = &ins_diff.branch_to {
|
||||
cb(DiffText::BasicColor(" ~>", branch.branch_idx))?;
|
||||
}
|
||||
cb(DiffText::Eol)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_reloc_name(reloc: &ObjReloc, mut cb: impl FnMut(DiffText) -> Result<()>) -> Result<()> {
|
||||
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))),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn display_reloc(reloc: &ObjReloc, mut cb: impl FnMut(DiffText) -> Result<()>) -> Result<()> {
|
||||
match reloc.kind {
|
||||
#[cfg(feature = "ppc")]
|
||||
ObjRelocKind::PpcAddr16Lo => {
|
||||
display_reloc_name(reloc, &mut cb)?;
|
||||
cb(DiffText::Basic("@l"))?;
|
||||
}
|
||||
#[cfg(feature = "ppc")]
|
||||
ObjRelocKind::PpcAddr16Hi => {
|
||||
display_reloc_name(reloc, &mut cb)?;
|
||||
cb(DiffText::Basic("@h"))?;
|
||||
}
|
||||
#[cfg(feature = "ppc")]
|
||||
ObjRelocKind::PpcAddr16Ha => {
|
||||
display_reloc_name(reloc, &mut cb)?;
|
||||
cb(DiffText::Basic("@ha"))?;
|
||||
}
|
||||
#[cfg(feature = "ppc")]
|
||||
ObjRelocKind::PpcEmbSda21 => {
|
||||
display_reloc_name(reloc, &mut cb)?;
|
||||
cb(DiffText::Basic("@sda21"))?;
|
||||
}
|
||||
#[cfg(feature = "ppc")]
|
||||
ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 => {
|
||||
display_reloc_name(reloc, &mut cb)?;
|
||||
}
|
||||
#[cfg(feature = "mips")]
|
||||
ObjRelocKind::MipsHi16 => {
|
||||
cb(DiffText::Basic("%hi("))?;
|
||||
display_reloc_name(reloc, &mut cb)?;
|
||||
cb(DiffText::Basic(")"))?;
|
||||
}
|
||||
#[cfg(feature = "mips")]
|
||||
ObjRelocKind::MipsLo16 => {
|
||||
cb(DiffText::Basic("%lo("))?;
|
||||
display_reloc_name(reloc, &mut cb)?;
|
||||
cb(DiffText::Basic(")"))?;
|
||||
}
|
||||
#[cfg(feature = "mips")]
|
||||
ObjRelocKind::MipsGot16 => {
|
||||
cb(DiffText::Basic("%got("))?;
|
||||
display_reloc_name(reloc, &mut cb)?;
|
||||
cb(DiffText::Basic(")"))?;
|
||||
}
|
||||
#[cfg(feature = "mips")]
|
||||
ObjRelocKind::MipsCall16 => {
|
||||
cb(DiffText::Basic("%call16("))?;
|
||||
display_reloc_name(reloc, &mut cb)?;
|
||||
cb(DiffText::Basic(")"))?;
|
||||
}
|
||||
#[cfg(feature = "mips")]
|
||||
ObjRelocKind::MipsGpRel16 => {
|
||||
cb(DiffText::Basic("%gp_rel("))?;
|
||||
display_reloc_name(reloc, &mut cb)?;
|
||||
cb(DiffText::Basic(")"))?;
|
||||
}
|
||||
#[cfg(feature = "mips")]
|
||||
ObjRelocKind::Mips26 => {
|
||||
display_reloc_name(reloc, &mut cb)?;
|
||||
}
|
||||
#[cfg(feature = "mips")]
|
||||
ObjRelocKind::MipsGpRel32 => {
|
||||
bail!("unimplemented: mips gp_rel32");
|
||||
}
|
||||
ObjRelocKind::Absolute => {
|
||||
cb(DiffText::Basic("[INVALID]"))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod code;
|
||||
pub mod data;
|
||||
pub mod display;
|
||||
pub mod editops;
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -22,6 +23,7 @@ pub enum DiffAlg {
|
||||
Lcs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
||||
pub struct DiffObjConfig {
|
||||
pub code_alg: DiffAlg,
|
||||
pub data_alg: DiffAlg,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#[cfg(feature = "config")]
|
||||
pub mod config;
|
||||
pub mod diff;
|
||||
pub mod obj;
|
||||
pub mod util;
|
||||
|
||||
@@ -78,13 +78,17 @@ pub fn process_code(
|
||||
ObjRelocKind::PpcAddr16Hi
|
||||
| ObjRelocKind::PpcAddr16Ha
|
||||
| ObjRelocKind::PpcAddr16Lo => {
|
||||
let arg = args.iter_mut().rfind(|a| is_rel_abs_arg(a)).ok_or_else(|| {
|
||||
anyhow::Error::msg("Failed to locate rel/abs arg for reloc")
|
||||
})?;
|
||||
*arg = if is_offset_arg(arg) {
|
||||
ObjInsArg::RelocWithBase
|
||||
} else {
|
||||
ObjInsArg::Reloc
|
||||
match args.iter_mut().rfind(|a| is_rel_abs_arg(a)) {
|
||||
Some(arg) => {
|
||||
*arg = if is_offset_arg(arg) {
|
||||
ObjInsArg::RelocWithBase
|
||||
} else {
|
||||
ObjInsArg::Reloc
|
||||
};
|
||||
}
|
||||
None => {
|
||||
log::warn!("Failed to locate rel/abs arg for reloc");
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
|
||||
Reference in New Issue
Block a user