Experimental objdiff-cli (WIP)

This commit is contained in:
2024-02-27 18:47:51 -07:00
parent 4eba5f71b0
commit 9a7d2bcebf
23 changed files with 1541 additions and 501 deletions

View 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()
}

View 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(())
}

View File

@@ -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,

View File

@@ -1,3 +1,5 @@
#[cfg(feature = "config")]
pub mod config;
pub mod diff;
pub mod obj;
pub mod util;

View File

@@ -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");
}
};
}
_ => {}