mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a4fdb61f04 | |||
| 2876be37a3 | |||
| 11171763eb | |||
| 6037a79ba2 | |||
| f7efe5fdff | |||
| 0692deac59 | |||
| c3e3d175c5 | |||
| c45f4bbc99 | |||
| b0c5431ac5 | |||
| 9ab246367b | |||
| dcafe51eda |
@@ -65,7 +65,7 @@ jobs:
|
||||
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
with:
|
||||
command: check ${{ matrix.checks }}
|
||||
|
||||
|
||||
Generated
+451
-487
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -13,9 +13,9 @@ strip = "debuginfo"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.package]
|
||||
version = "2.5.0"
|
||||
version = "2.7.0"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/encounter/objdiff"
|
||||
rust-version = "1.74"
|
||||
rust-version = "1.81"
|
||||
|
||||
@@ -73,7 +73,6 @@ ignore = [
|
||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||
{ id = "RUSTSEC-2024-0384", reason = "Unmaintained indirect dependency" },
|
||||
]
|
||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||
# If this is false, then it uses a built-in git library.
|
||||
@@ -240,7 +239,7 @@ allow-git = []
|
||||
|
||||
[sources.allow-org]
|
||||
# github.com organizations to allow git sources for
|
||||
github = ["notify-rs"]
|
||||
github = []
|
||||
# gitlab.com organizations to allow git sources for
|
||||
gitlab = []
|
||||
# bitbucket.org organizations to allow git sources for
|
||||
|
||||
+75
-30
@@ -12,7 +12,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use argp::FromArgs;
|
||||
use crossterm::{
|
||||
event,
|
||||
@@ -27,9 +27,11 @@ use objdiff_core::{
|
||||
watcher::{create_watcher, Watcher},
|
||||
BuildConfig,
|
||||
},
|
||||
config::{build_globset, default_watch_patterns, ProjectConfig, ProjectObject},
|
||||
config::{build_globset, ProjectConfig, ProjectObject},
|
||||
diff,
|
||||
diff::ObjDiff,
|
||||
diff::{
|
||||
ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig, ObjDiff,
|
||||
},
|
||||
jobs::{
|
||||
objdiff::{start_build, ObjDiffConfig},
|
||||
Job, JobQueue, JobResult,
|
||||
@@ -63,9 +65,6 @@ pub struct Args {
|
||||
#[argp(option, short = 'u')]
|
||||
/// Unit name within project
|
||||
unit: Option<String>,
|
||||
#[argp(switch, short = 'x')]
|
||||
/// Relax relocation diffs
|
||||
relax_reloc_diffs: bool,
|
||||
#[argp(option, short = 'o')]
|
||||
/// Output file (one-shot mode) ("-" for stdout)
|
||||
output: Option<PathBuf>,
|
||||
@@ -75,6 +74,18 @@ pub struct Args {
|
||||
#[argp(positional)]
|
||||
/// Function symbol to diff
|
||||
symbol: Option<String>,
|
||||
#[argp(option, short = 'c')]
|
||||
/// Configuration property (key=value)
|
||||
config: Vec<String>,
|
||||
#[argp(option, short = 'm')]
|
||||
/// Symbol mapping (target=base)
|
||||
mapping: Vec<String>,
|
||||
#[argp(option)]
|
||||
/// Left symbol name for selection
|
||||
selecting_left: Option<String>,
|
||||
#[argp(option)]
|
||||
/// Right symbol name for selection
|
||||
selecting_right: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
@@ -84,7 +95,9 @@ pub fn run(args: Args) -> Result<()> {
|
||||
&args.project,
|
||||
&args.unit,
|
||||
) {
|
||||
(Some(t), Some(b), None, None) => (Some(t.clone()), Some(b.clone()), None),
|
||||
(Some(_), Some(_), None, None)
|
||||
| (Some(_), None, None, None)
|
||||
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None),
|
||||
(None, None, p, u) => {
|
||||
let project = match p {
|
||||
Some(project) => project.clone(),
|
||||
@@ -193,6 +206,43 @@ pub fn run(args: Args) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> {
|
||||
let mut diff_config = DiffObjConfig::default();
|
||||
for config in &args.config {
|
||||
let (key, value) = config.split_once('=').context("--config expects \"key=value\"")?;
|
||||
let property_id = ConfigPropertyId::from_str(key)
|
||||
.map_err(|()| anyhow!("Invalid configuration property: {}", key))?;
|
||||
diff_config.set_property_value_str(property_id, value).map_err(|()| {
|
||||
let mut options = String::new();
|
||||
match property_id.kind() {
|
||||
ConfigPropertyKind::Boolean => {
|
||||
options = "true, false".to_string();
|
||||
}
|
||||
ConfigPropertyKind::Choice(variants) => {
|
||||
for (i, variant) in variants.iter().enumerate() {
|
||||
if i > 0 {
|
||||
options.push_str(", ");
|
||||
}
|
||||
options.push_str(variant.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
anyhow!("Invalid value for {}. Expected one of: {}", property_id.name(), options)
|
||||
})?;
|
||||
}
|
||||
let mut mapping_config = MappingConfig {
|
||||
mappings: Default::default(),
|
||||
selecting_left: args.selecting_left.clone(),
|
||||
selecting_right: args.selecting_right.clone(),
|
||||
};
|
||||
for mapping in &args.mapping {
|
||||
let (target, base) =
|
||||
mapping.split_once('=').context("--mapping expects \"target=base\"")?;
|
||||
mapping_config.mappings.insert(target.to_string(), base.to_string());
|
||||
}
|
||||
Ok((diff_config, mapping_config))
|
||||
}
|
||||
|
||||
fn run_oneshot(
|
||||
args: &Args,
|
||||
output: &Path,
|
||||
@@ -200,17 +250,19 @@ fn run_oneshot(
|
||||
base_path: Option<&Path>,
|
||||
) -> Result<()> {
|
||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||
let config = diff::DiffObjConfig {
|
||||
relax_reloc_diffs: args.relax_reloc_diffs,
|
||||
..Default::default() // TODO
|
||||
};
|
||||
let (diff_config, mapping_config) = build_config_from_args(args)?;
|
||||
let target = target_path
|
||||
.map(|p| obj::read::read(p, &config).with_context(|| format!("Loading {}", p.display())))
|
||||
.map(|p| {
|
||||
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
|
||||
})
|
||||
.transpose()?;
|
||||
let base = base_path
|
||||
.map(|p| obj::read::read(p, &config).with_context(|| format!("Loading {}", p.display())))
|
||||
.map(|p| {
|
||||
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
|
||||
})
|
||||
.transpose()?;
|
||||
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
|
||||
let result =
|
||||
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?;
|
||||
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)));
|
||||
write_output(&DiffResult::new(left, right), Some(output), output_format)?;
|
||||
@@ -229,9 +281,10 @@ pub struct AppState {
|
||||
pub prev_obj: Option<(ObjInfo, ObjDiff)>,
|
||||
pub reload_time: Option<time::OffsetDateTime>,
|
||||
pub time_format: Vec<time::format_description::FormatItem<'static>>,
|
||||
pub relax_reloc_diffs: bool,
|
||||
pub watcher: Option<Watcher>,
|
||||
pub modified: Arc<AtomicBool>,
|
||||
pub diff_obj_config: DiffObjConfig,
|
||||
pub mapping_config: MappingConfig,
|
||||
}
|
||||
|
||||
fn create_objdiff_config(state: &AppState) -> ObjDiffConfig {
|
||||
@@ -257,13 +310,8 @@ fn create_objdiff_config(state: &AppState) -> ObjDiffConfig {
|
||||
.is_some_and(|p| p.build_target.unwrap_or(false)),
|
||||
target_path: state.target_path.clone(),
|
||||
base_path: state.base_path.clone(),
|
||||
diff_obj_config: diff::DiffObjConfig {
|
||||
relax_reloc_diffs: state.relax_reloc_diffs,
|
||||
..Default::default() // TODO
|
||||
},
|
||||
symbol_mappings: Default::default(),
|
||||
selecting_left: None,
|
||||
selecting_right: None,
|
||||
diff_obj_config: state.diff_obj_config.clone(),
|
||||
mapping_config: state.mapping_config.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +362,7 @@ fn run_interactive(
|
||||
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
|
||||
let time_format = time::format_description::parse_borrowed::<2>("[hour]:[minute]:[second]")
|
||||
.context("Failed to parse time format")?;
|
||||
let (diff_obj_config, mapping_config) = build_config_from_args(&args)?;
|
||||
let mut state = AppState {
|
||||
jobs: Default::default(),
|
||||
waker: Default::default(),
|
||||
@@ -326,17 +375,13 @@ fn run_interactive(
|
||||
prev_obj: None,
|
||||
reload_time: None,
|
||||
time_format,
|
||||
relax_reloc_diffs: args.relax_reloc_diffs,
|
||||
watcher: None,
|
||||
modified: Default::default(),
|
||||
diff_obj_config,
|
||||
mapping_config,
|
||||
};
|
||||
if let Some(project_dir) = &state.project_dir {
|
||||
let watch_patterns = state
|
||||
.project_config
|
||||
.as_ref()
|
||||
.and_then(|c| c.watch_patterns.as_ref())
|
||||
.cloned()
|
||||
.unwrap_or_else(default_watch_patterns);
|
||||
if let (Some(project_dir), Some(project_config)) = (&state.project_dir, &state.project_config) {
|
||||
let watch_patterns = project_config.build_watch_patterns()?;
|
||||
state.watcher = Some(create_watcher(
|
||||
state.modified.clone(),
|
||||
project_dir,
|
||||
|
||||
@@ -169,22 +169,29 @@ fn report_object(
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let config = diff::DiffObjConfig { relax_reloc_diffs: true, ..Default::default() };
|
||||
let diff_config = diff::DiffObjConfig {
|
||||
function_reloc_diffs: diff::FunctionRelocDiffs::None,
|
||||
..Default::default()
|
||||
};
|
||||
let mapping_config = diff::MappingConfig::default();
|
||||
let target = object
|
||||
.target_path
|
||||
.as_ref()
|
||||
.map(|p| {
|
||||
obj::read::read(p, &config).with_context(|| format!("Failed to open {}", p.display()))
|
||||
obj::read::read(p, &diff_config)
|
||||
.with_context(|| format!("Failed to open {}", p.display()))
|
||||
})
|
||||
.transpose()?;
|
||||
let base = object
|
||||
.base_path
|
||||
.as_ref()
|
||||
.map(|p| {
|
||||
obj::read::read(p, &config).with_context(|| format!("Failed to open {}", p.display()))
|
||||
obj::read::read(p, &diff_config)
|
||||
.with_context(|| format!("Failed to open {}", p.display()))
|
||||
})
|
||||
.transpose()?;
|
||||
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
|
||||
let result =
|
||||
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?;
|
||||
|
||||
let metadata = ReportUnitMetadata {
|
||||
complete: object.complete(),
|
||||
|
||||
@@ -3,7 +3,7 @@ use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton,
|
||||
use objdiff_core::{
|
||||
diff::{
|
||||
display::{display_diff, DiffText, HighlightKind},
|
||||
ObjDiff, ObjInsDiffKind, ObjSymbolDiff,
|
||||
FunctionRelocDiffs, ObjDiff, ObjInsDiffKind, ObjSymbolDiff,
|
||||
},
|
||||
obj::{ObjInfo, ObjSectionKind, ObjSymbol, SymbolRef},
|
||||
};
|
||||
@@ -368,9 +368,15 @@ impl UiView for FunctionDiffUi {
|
||||
self.scroll_x = self.scroll_x.saturating_sub(1);
|
||||
result.redraw = true;
|
||||
}
|
||||
// Toggle relax relocation diffs
|
||||
// Cycle through function relocation diff mode
|
||||
KeyCode::Char('x') => {
|
||||
state.relax_reloc_diffs = !state.relax_reloc_diffs;
|
||||
state.diff_obj_config.function_reloc_diffs =
|
||||
match state.diff_obj_config.function_reloc_diffs {
|
||||
FunctionRelocDiffs::None => FunctionRelocDiffs::NameAddress,
|
||||
FunctionRelocDiffs::NameAddress => FunctionRelocDiffs::DataValue,
|
||||
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
|
||||
FunctionRelocDiffs::All => FunctionRelocDiffs::None,
|
||||
};
|
||||
result.redraw = true;
|
||||
return EventControlFlow::Reload;
|
||||
}
|
||||
|
||||
+111
-18
@@ -16,18 +16,104 @@ documentation = "https://docs.rs/objdiff-core"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "arm64", "bindings", "build"]
|
||||
any-arch = ["config", "dep:bimap", "dep:strum", "dep:similar", "dep:flagset", "dep:log", "dep:memmap2", "dep:byteorder", "dep:num-traits"] # Implicit, used to check if any arch is enabled
|
||||
bindings = ["dep:serde_json", "dep:prost", "dep:pbjson", "dep:serde", "dep:prost-build", "dep:pbjson-build"]
|
||||
build = ["dep:shell-escape", "dep:path-slash", "dep:winapi", "dep:notify", "dep:notify-debouncer-full", "dep:reqwest", "dep:self_update", "dep:tempfile", "dep:time"]
|
||||
config = ["dep:bimap", "dep:globset", "dep:semver", "dep:serde_json", "dep:serde_yaml", "dep:serde", "dep:filetime"]
|
||||
all = [
|
||||
# Features
|
||||
"bindings",
|
||||
"build",
|
||||
"config",
|
||||
"dwarf",
|
||||
# Architectures
|
||||
"mips",
|
||||
"ppc",
|
||||
"x86",
|
||||
"arm",
|
||||
"arm64",
|
||||
]
|
||||
# Implicit, used to check if any arch is enabled
|
||||
any-arch = [
|
||||
"config",
|
||||
"dep:bimap",
|
||||
"dep:byteorder",
|
||||
"dep:flagset",
|
||||
"dep:heck",
|
||||
"dep:log",
|
||||
"dep:memmap2",
|
||||
"dep:num-traits",
|
||||
"dep:prettyplease",
|
||||
"dep:proc-macro2",
|
||||
"dep:quote",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
"dep:similar",
|
||||
"dep:strum",
|
||||
"dep:syn",
|
||||
]
|
||||
bindings = [
|
||||
"dep:pbjson",
|
||||
"dep:pbjson-build",
|
||||
"dep:prost",
|
||||
"dep:prost-build",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
]
|
||||
build = [
|
||||
"dep:notify",
|
||||
"dep:notify-debouncer-full",
|
||||
"dep:path-slash",
|
||||
"dep:reqwest",
|
||||
"dep:self_update",
|
||||
"dep:shell-escape",
|
||||
"dep:tempfile",
|
||||
"dep:time",
|
||||
"dep:winapi",
|
||||
]
|
||||
config = [
|
||||
"dep:bimap",
|
||||
"dep:filetime",
|
||||
"dep:globset",
|
||||
"dep:semver",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
"dep:serde_yaml",
|
||||
]
|
||||
dwarf = ["dep:gimli"]
|
||||
mips = ["any-arch", "dep:rabbitizer"]
|
||||
ppc = ["any-arch", "dep:cwdemangle", "dep:cwextab", "dep:ppc750cl"]
|
||||
x86 = ["any-arch", "dep:cpp_demangle", "dep:iced-x86", "dep:msvc-demangler"]
|
||||
arm = ["any-arch", "dep:cpp_demangle", "dep:unarm", "dep:arm-attr"]
|
||||
arm64 = ["any-arch", "dep:cpp_demangle", "dep:yaxpeax-arch", "dep:yaxpeax-arm"]
|
||||
wasm = ["bindings", "any-arch", "dep:console_error_panic_hook", "dep:console_log", "dep:wasm-bindgen", "dep:tsify-next", "dep:log"]
|
||||
mips = [
|
||||
"any-arch",
|
||||
"dep:rabbitizer",
|
||||
]
|
||||
ppc = [
|
||||
"any-arch",
|
||||
"dep:cwdemangle",
|
||||
"dep:cwextab",
|
||||
"dep:ppc750cl",
|
||||
]
|
||||
x86 = [
|
||||
"any-arch",
|
||||
"dep:cpp_demangle",
|
||||
"dep:iced-x86",
|
||||
"dep:msvc-demangler",
|
||||
]
|
||||
arm = [
|
||||
"any-arch",
|
||||
"dep:arm-attr",
|
||||
"dep:cpp_demangle",
|
||||
"dep:unarm",
|
||||
]
|
||||
arm64 = [
|
||||
"any-arch",
|
||||
"dep:cpp_demangle",
|
||||
"dep:yaxpeax-arch",
|
||||
"dep:yaxpeax-arm",
|
||||
]
|
||||
wasm = [
|
||||
"any-arch",
|
||||
"bindings",
|
||||
"dep:console_error_panic_hook",
|
||||
"dep:console_log",
|
||||
"dep:log",
|
||||
"dep:tsify-next",
|
||||
"dep:wasm-bindgen",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["all"]
|
||||
@@ -63,7 +149,7 @@ gimli = { version = "0.31", default-features = false, features = ["read-all"], o
|
||||
|
||||
# ppc
|
||||
cwdemangle = { version = "1.0", optional = true }
|
||||
cwextab = { version = "1.0.2", optional = true }
|
||||
cwextab = { version = "1.0", optional = true }
|
||||
ppc750cl = { version = "0.3", optional = true }
|
||||
|
||||
# mips
|
||||
@@ -83,10 +169,10 @@ yaxpeax-arch = { version = "0.3", default-features = false, features = ["std"],
|
||||
yaxpeax-arm = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
||||
|
||||
# build
|
||||
notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2", optional = true }
|
||||
notify-debouncer-full = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2", optional = true }
|
||||
notify = { version = "8.0.0", optional = true }
|
||||
notify-debouncer-full = { version = "0.5.0", optional = true }
|
||||
shell-escape = { version = "0.1", optional = true }
|
||||
tempfile = { version = "3.14", optional = true }
|
||||
tempfile = { version = "3.15", optional = true }
|
||||
time = { version = "0.3", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
@@ -96,13 +182,20 @@ winapi = { version = "0.3", optional = true }
|
||||
# For Linux static binaries, use rustls
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"], optional = true }
|
||||
self_update = { version = "0.41", default-features = false, features = ["rustls"], optional = true }
|
||||
self_update = { version = "0.42", default-features = false, features = ["rustls"], optional = true }
|
||||
|
||||
# For all other platforms, use native TLS
|
||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "default-tls"], optional = true }
|
||||
self_update = { version = "0.41", optional = true }
|
||||
self_update = { version = "0.42", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = { version = "0.13", optional = true }
|
||||
heck = { version = "0.5", optional = true }
|
||||
pbjson-build = { version = "0.7", optional = true }
|
||||
prettyplease = { version = "0.2", optional = true }
|
||||
proc-macro2 = { version = "1.0", optional = true }
|
||||
prost-build = { version = "0.13", optional = true }
|
||||
quote = { version = "1.0", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
syn = { version = "2.0", optional = true }
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#[cfg(feature = "any-arch")]
|
||||
mod config_gen;
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "bindings")]
|
||||
compile_protos();
|
||||
#[cfg(feature = "any-arch")]
|
||||
config_gen::generate_diff_config();
|
||||
}
|
||||
|
||||
#[cfg(feature = "bindings")]
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"id": "functionRelocDiffs",
|
||||
"type": "choice",
|
||||
"default": "name_address",
|
||||
"name": "Function relocation diffs",
|
||||
"description": "How relocation targets will be diffed in the function view.",
|
||||
"items": [
|
||||
{
|
||||
"value": "none",
|
||||
"name": "None"
|
||||
},
|
||||
{
|
||||
"value": "name_address",
|
||||
"name": "Name or address"
|
||||
},
|
||||
{
|
||||
"value": "data_value",
|
||||
"name": "Data value"
|
||||
},
|
||||
{
|
||||
"value": "all",
|
||||
"name": "Name or address, data value"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "spaceBetweenArgs",
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"name": "Space between args",
|
||||
"description": "Adds a space between arguments in the diff output."
|
||||
},
|
||||
{
|
||||
"id": "combineDataSections",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Combine data sections",
|
||||
"description": "Combines data sections with equal names."
|
||||
},
|
||||
{
|
||||
"id": "arm.archVersion",
|
||||
"type": "choice",
|
||||
"default": "auto",
|
||||
"name": "Architecture version",
|
||||
"description": "ARM architecture version to use for disassembly.",
|
||||
"items": [
|
||||
{
|
||||
"value": "auto",
|
||||
"name": "Auto"
|
||||
},
|
||||
{
|
||||
"value": "v4t",
|
||||
"name": "ARMv4T (GBA)"
|
||||
},
|
||||
{
|
||||
"value": "v5te",
|
||||
"name": "ARMv5TE (DS)"
|
||||
},
|
||||
{
|
||||
"value": "v6k",
|
||||
"name": "ARMv6K (3DS)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "arm.unifiedSyntax",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Unified syntax",
|
||||
"description": "Disassemble as unified assembly language (UAL)."
|
||||
},
|
||||
{
|
||||
"id": "arm.avRegisters",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Use A/V registers",
|
||||
"description": "Display R0-R3 as A1-A4 and R4-R11 as V1-V8."
|
||||
},
|
||||
{
|
||||
"id": "arm.r9Usage",
|
||||
"type": "choice",
|
||||
"default": "generalPurpose",
|
||||
"name": "Display R9 as",
|
||||
"items": [
|
||||
{
|
||||
"value": "generalPurpose",
|
||||
"name": "R9 or V6",
|
||||
"description": "Use R9 as a general-purpose register."
|
||||
},
|
||||
{
|
||||
"value": "sb",
|
||||
"name": "SB (static base)",
|
||||
"description": "Used for position-independent data (PID)."
|
||||
},
|
||||
{
|
||||
"value": "tr",
|
||||
"name": "TR (TLS register)",
|
||||
"description": "Used for thread-local storage."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "arm.slUsage",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Display R10 as SL",
|
||||
"description": "Used for explicit stack limits."
|
||||
},
|
||||
{
|
||||
"id": "arm.fpUsage",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Display R11 as FP",
|
||||
"description": "Used for frame pointers."
|
||||
},
|
||||
{
|
||||
"id": "arm.ipUsage",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Display R12 as IP",
|
||||
"description": "Used for interworking and long branches."
|
||||
},
|
||||
{
|
||||
"id": "mips.abi",
|
||||
"type": "choice",
|
||||
"default": "auto",
|
||||
"name": "ABI",
|
||||
"description": "MIPS ABI to use for disassembly.",
|
||||
"items": [
|
||||
{
|
||||
"value": "auto",
|
||||
"name": "Auto"
|
||||
},
|
||||
{
|
||||
"value": "o32",
|
||||
"name": "O32"
|
||||
},
|
||||
{
|
||||
"value": "n32",
|
||||
"name": "N32"
|
||||
},
|
||||
{
|
||||
"value": "n64",
|
||||
"name": "N64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mips.instrCategory",
|
||||
"type": "choice",
|
||||
"default": "auto",
|
||||
"name": "Instruction category",
|
||||
"description": "MIPS instruction category to use for disassembly.",
|
||||
"items": [
|
||||
{
|
||||
"value": "auto",
|
||||
"name": "Auto"
|
||||
},
|
||||
{
|
||||
"value": "cpu",
|
||||
"name": "CPU"
|
||||
},
|
||||
{
|
||||
"value": "rsp",
|
||||
"name": "RSP (N64)"
|
||||
},
|
||||
{
|
||||
"value": "r3000gte",
|
||||
"name": "R3000 GTE (PS1)"
|
||||
},
|
||||
{
|
||||
"value": "r4000allegrex",
|
||||
"name": "R4000 ALLEGREX (PSP)"
|
||||
},
|
||||
{
|
||||
"value": "r5900",
|
||||
"name": "R5900 EE (PS2)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "x86.formatter",
|
||||
"type": "choice",
|
||||
"default": "intel",
|
||||
"name": "Format",
|
||||
"description": "x86 disassembly syntax.",
|
||||
"items": [
|
||||
{
|
||||
"value": "intel",
|
||||
"name": "Intel"
|
||||
},
|
||||
{
|
||||
"value": "gas",
|
||||
"name": "AT&T"
|
||||
},
|
||||
{
|
||||
"value": "nasm",
|
||||
"name": "NASM"
|
||||
},
|
||||
{
|
||||
"value": "masm",
|
||||
"name": "MASM"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": "general",
|
||||
"name": "General",
|
||||
"properties": [
|
||||
"functionRelocDiffs",
|
||||
"spaceBetweenArgs",
|
||||
"combineDataSections"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "arm",
|
||||
"name": "ARM",
|
||||
"properties": [
|
||||
"arm.archVersion",
|
||||
"arm.unifiedSyntax",
|
||||
"arm.avRegisters",
|
||||
"arm.r9Usage",
|
||||
"arm.slUsage",
|
||||
"arm.fpUsage",
|
||||
"arm.ipUsage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mips",
|
||||
"name": "MIPS",
|
||||
"properties": [
|
||||
"mips.abi",
|
||||
"mips.instrCategory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "x86",
|
||||
"name": "x86",
|
||||
"properties": [
|
||||
"x86.formatter"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ConfigSchema {
|
||||
pub properties: Vec<ConfigProperty>,
|
||||
pub groups: Vec<ConfigGroup>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ConfigProperty {
|
||||
#[serde(rename = "boolean")]
|
||||
Boolean(ConfigPropertyBoolean),
|
||||
#[serde(rename = "choice")]
|
||||
Choice(ConfigPropertyChoice),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ConfigPropertyBase {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ConfigPropertyBoolean {
|
||||
#[serde(flatten)]
|
||||
pub base: ConfigPropertyBase,
|
||||
pub default: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ConfigPropertyChoice {
|
||||
#[serde(flatten)]
|
||||
pub base: ConfigPropertyBase,
|
||||
pub default: String,
|
||||
pub items: Vec<ConfigPropertyChoiceItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ConfigPropertyChoiceItem {
|
||||
pub value: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ConfigGroup {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub properties: Vec<String>,
|
||||
}
|
||||
|
||||
fn build_doc(name: &str, description: Option<&str>) -> TokenStream {
|
||||
let mut doc = format!(" {}", name);
|
||||
let mut out = quote! { #[doc = #doc] };
|
||||
if let Some(description) = description {
|
||||
doc = format!(" {}", description);
|
||||
out.extend(quote! { #[doc = ""] });
|
||||
out.extend(quote! { #[doc = #doc] });
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn generate_diff_config() {
|
||||
let schema_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("config-schema.json");
|
||||
println!("cargo:rerun-if-changed={}", schema_path.display());
|
||||
let schema_file = File::open(schema_path).expect("Failed to open config schema file");
|
||||
let schema: ConfigSchema =
|
||||
serde_json::from_reader(schema_file).expect("Failed to parse config schema");
|
||||
|
||||
let mut enums = TokenStream::new();
|
||||
for property in &schema.properties {
|
||||
let ConfigProperty::Choice(choice) = property else {
|
||||
continue;
|
||||
};
|
||||
let enum_ident = format_ident!("{}", choice.base.id.to_upper_camel_case());
|
||||
let mut variants = TokenStream::new();
|
||||
let mut full_variants = TokenStream::new();
|
||||
let mut variant_info = TokenStream::new();
|
||||
let mut variant_to_str = TokenStream::new();
|
||||
let mut variant_to_name = TokenStream::new();
|
||||
let mut variant_to_description = TokenStream::new();
|
||||
let mut variant_from_str = TokenStream::new();
|
||||
for item in &choice.items {
|
||||
let variant_name = item.value.to_upper_camel_case();
|
||||
let variant_ident = format_ident!("{}", variant_name);
|
||||
let is_default = item.value == choice.default;
|
||||
variants.extend(build_doc(&item.name, item.description.as_deref()));
|
||||
if is_default {
|
||||
variants.extend(quote! { #[default] });
|
||||
}
|
||||
let value = &item.value;
|
||||
variants.extend(quote! {
|
||||
#[serde(rename = #value, alias = #variant_name)]
|
||||
#variant_ident,
|
||||
});
|
||||
full_variants.extend(quote! { #enum_ident::#variant_ident, });
|
||||
variant_to_str.extend(quote! { #enum_ident::#variant_ident => #value, });
|
||||
let name = &item.name;
|
||||
variant_to_name.extend(quote! { #enum_ident::#variant_ident => #name, });
|
||||
if let Some(description) = &item.description {
|
||||
variant_to_description.extend(quote! {
|
||||
#enum_ident::#variant_ident => Some(#description),
|
||||
});
|
||||
} else {
|
||||
variant_to_description.extend(quote! {
|
||||
#enum_ident::#variant_ident => None,
|
||||
});
|
||||
}
|
||||
let description = if let Some(description) = &item.description {
|
||||
quote! { Some(#description) }
|
||||
} else {
|
||||
quote! { None }
|
||||
};
|
||||
variant_info.extend(quote! {
|
||||
ConfigEnumVariantInfo {
|
||||
value: #value,
|
||||
name: #name,
|
||||
description: #description,
|
||||
is_default: #is_default,
|
||||
},
|
||||
});
|
||||
variant_from_str.extend(quote! {
|
||||
if s.eq_ignore_ascii_case(#value) { return Ok(#enum_ident::#variant_ident); }
|
||||
});
|
||||
}
|
||||
enums.extend(quote! {
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
pub enum #enum_ident {
|
||||
#variants
|
||||
}
|
||||
impl ConfigEnum for #enum_ident {
|
||||
#[inline]
|
||||
fn variants() -> &'static [Self] {
|
||||
static VARIANTS: &[#enum_ident] = &[#full_variants];
|
||||
VARIANTS
|
||||
}
|
||||
#[inline]
|
||||
fn variant_info() -> &'static [ConfigEnumVariantInfo] {
|
||||
static VARIANT_INFO: &[ConfigEnumVariantInfo] = &[
|
||||
#variant_info
|
||||
];
|
||||
VARIANT_INFO
|
||||
}
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
#variant_to_str
|
||||
}
|
||||
}
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
#variant_to_name
|
||||
}
|
||||
}
|
||||
fn description(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
#variant_to_description
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for #enum_ident {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
#variant_from_str
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut groups = TokenStream::new();
|
||||
let mut group_idents = Vec::new();
|
||||
for group in &schema.groups {
|
||||
let ident = format_ident!("CONFIG_GROUP_{}", group.id.to_shouty_snake_case());
|
||||
let id = &group.id;
|
||||
let name = &group.name;
|
||||
let description = if let Some(description) = &group.description {
|
||||
quote! { Some(#description) }
|
||||
} else {
|
||||
quote! { None }
|
||||
};
|
||||
let properties =
|
||||
group.properties.iter().map(|p| format_ident!("{}", p.to_upper_camel_case()));
|
||||
groups.extend(quote! {
|
||||
ConfigPropertyGroup {
|
||||
id: #id,
|
||||
name: #name,
|
||||
description: #description,
|
||||
properties: &[#(ConfigPropertyId::#properties,)*],
|
||||
},
|
||||
});
|
||||
group_idents.push(ident);
|
||||
}
|
||||
|
||||
let mut property_idents = Vec::new();
|
||||
let mut property_variants = TokenStream::new();
|
||||
let mut variant_info = TokenStream::new();
|
||||
let mut config_property_id_to_str = TokenStream::new();
|
||||
let mut config_property_id_to_name = TokenStream::new();
|
||||
let mut config_property_id_to_description = TokenStream::new();
|
||||
let mut config_property_id_to_kind = TokenStream::new();
|
||||
let mut property_fields = TokenStream::new();
|
||||
let mut default_fields = TokenStream::new();
|
||||
let mut get_property_value_variants = TokenStream::new();
|
||||
let mut set_property_value_variants = TokenStream::new();
|
||||
let mut set_property_value_str_variants = TokenStream::new();
|
||||
let mut config_property_id_from_str = TokenStream::new();
|
||||
for property in &schema.properties {
|
||||
let base = match property {
|
||||
ConfigProperty::Boolean(b) => &b.base,
|
||||
ConfigProperty::Choice(c) => &c.base,
|
||||
};
|
||||
let id = &base.id;
|
||||
let enum_ident = format_ident!("{}", id.to_upper_camel_case());
|
||||
property_idents.push(enum_ident.clone());
|
||||
config_property_id_to_str.extend(quote! { Self::#enum_ident => #id, });
|
||||
let name = &base.name;
|
||||
config_property_id_to_name.extend(quote! { Self::#enum_ident => #name, });
|
||||
if let Some(description) = &base.description {
|
||||
config_property_id_to_description.extend(quote! {
|
||||
Self::#enum_ident => Some(#description),
|
||||
});
|
||||
} else {
|
||||
config_property_id_to_description.extend(quote! {
|
||||
Self::#enum_ident => None,
|
||||
});
|
||||
}
|
||||
let doc = build_doc(name, base.description.as_deref());
|
||||
property_variants.extend(quote! { #doc #enum_ident, });
|
||||
property_fields.extend(doc);
|
||||
let field_ident = format_ident!("{}", id.to_snake_case());
|
||||
match property {
|
||||
ConfigProperty::Boolean(b) => {
|
||||
let default = b.default;
|
||||
if default {
|
||||
property_fields.extend(quote! {
|
||||
#[serde(default = "default_true")]
|
||||
});
|
||||
}
|
||||
property_fields.extend(quote! {
|
||||
pub #field_ident: bool,
|
||||
});
|
||||
default_fields.extend(quote! {
|
||||
#field_ident: #default,
|
||||
});
|
||||
}
|
||||
ConfigProperty::Choice(_) => {
|
||||
property_fields.extend(quote! {
|
||||
pub #field_ident: #enum_ident,
|
||||
});
|
||||
default_fields.extend(quote! {
|
||||
#field_ident: #enum_ident::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
let property_value = match property {
|
||||
ConfigProperty::Boolean(_) => {
|
||||
quote! { ConfigPropertyValue::Boolean(self.#field_ident) }
|
||||
}
|
||||
ConfigProperty::Choice(_) => {
|
||||
quote! { ConfigPropertyValue::Choice(self.#field_ident.as_str()) }
|
||||
}
|
||||
};
|
||||
get_property_value_variants.extend(quote! {
|
||||
ConfigPropertyId::#enum_ident => #property_value,
|
||||
});
|
||||
match property {
|
||||
ConfigProperty::Boolean(_) => {
|
||||
set_property_value_variants.extend(quote! {
|
||||
ConfigPropertyId::#enum_ident => {
|
||||
if let ConfigPropertyValue::Boolean(value) = value {
|
||||
self.#field_ident = value;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
},
|
||||
});
|
||||
set_property_value_str_variants.extend(quote! {
|
||||
ConfigPropertyId::#enum_ident => {
|
||||
if let Ok(value) = value.parse() {
|
||||
self.#field_ident = value;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
ConfigProperty::Choice(_) => {
|
||||
set_property_value_variants.extend(quote! {
|
||||
ConfigPropertyId::#enum_ident => {
|
||||
if let ConfigPropertyValue::Choice(value) = value {
|
||||
if let Ok(value) = value.parse() {
|
||||
self.#field_ident = value;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
},
|
||||
});
|
||||
set_property_value_str_variants.extend(quote! {
|
||||
ConfigPropertyId::#enum_ident => {
|
||||
if let Ok(value) = value.parse() {
|
||||
self.#field_ident = value;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
let description = if let Some(description) = &base.description {
|
||||
quote! { Some(#description) }
|
||||
} else {
|
||||
quote! { None }
|
||||
};
|
||||
variant_info.extend(quote! {
|
||||
ConfigEnumVariantInfo {
|
||||
value: #id,
|
||||
name: #name,
|
||||
description: #description,
|
||||
is_default: false,
|
||||
},
|
||||
});
|
||||
match property {
|
||||
ConfigProperty::Boolean(_) => {
|
||||
config_property_id_to_kind.extend(quote! {
|
||||
Self::#enum_ident => ConfigPropertyKind::Boolean,
|
||||
});
|
||||
}
|
||||
ConfigProperty::Choice(_) => {
|
||||
config_property_id_to_kind.extend(quote! {
|
||||
Self::#enum_ident => ConfigPropertyKind::Choice(#enum_ident::variant_info()),
|
||||
});
|
||||
}
|
||||
}
|
||||
let snake_id = id.to_snake_case();
|
||||
config_property_id_from_str.extend(quote! {
|
||||
if s.eq_ignore_ascii_case(#id) || s.eq_ignore_ascii_case(#snake_id) {
|
||||
return Ok(Self::#enum_ident);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let tokens = quote! {
|
||||
pub trait ConfigEnum: Sized {
|
||||
fn variants() -> &'static [Self];
|
||||
fn variant_info() -> &'static [ConfigEnumVariantInfo];
|
||||
fn as_str(&self) -> &'static str;
|
||||
fn name(&self) -> &'static str;
|
||||
fn description(&self) -> Option<&'static str>;
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ConfigEnumVariantInfo {
|
||||
pub value: &'static str,
|
||||
pub name: &'static str,
|
||||
pub description: Option<&'static str>,
|
||||
pub is_default: bool,
|
||||
}
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum ConfigPropertyId {
|
||||
#property_variants
|
||||
}
|
||||
impl ConfigEnum for ConfigPropertyId {
|
||||
#[inline]
|
||||
fn variants() -> &'static [Self] {
|
||||
static VARIANTS: &[ConfigPropertyId] = &[#(ConfigPropertyId::#property_idents,)*];
|
||||
VARIANTS
|
||||
}
|
||||
#[inline]
|
||||
fn variant_info() -> &'static [ConfigEnumVariantInfo] {
|
||||
static VARIANT_INFO: &[ConfigEnumVariantInfo] = &[
|
||||
#variant_info
|
||||
];
|
||||
VARIANT_INFO
|
||||
}
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
#config_property_id_to_str
|
||||
}
|
||||
}
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
#config_property_id_to_name
|
||||
}
|
||||
}
|
||||
fn description(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
#config_property_id_to_description
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ConfigPropertyId {
|
||||
pub fn kind(&self) -> ConfigPropertyKind {
|
||||
match self {
|
||||
#config_property_id_to_kind
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for ConfigPropertyId {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
#config_property_id_from_str
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ConfigPropertyGroup {
|
||||
pub id: &'static str,
|
||||
pub name: &'static str,
|
||||
pub description: Option<&'static str>,
|
||||
pub properties: &'static [ConfigPropertyId],
|
||||
}
|
||||
pub static CONFIG_GROUPS: &[ConfigPropertyGroup] = &[#groups];
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum ConfigPropertyValue {
|
||||
Boolean(bool),
|
||||
Choice(&'static str),
|
||||
}
|
||||
impl ConfigPropertyValue {
|
||||
pub fn to_json(&self) -> serde_json::Value {
|
||||
match self {
|
||||
ConfigPropertyValue::Boolean(value) => serde_json::Value::Bool(*value),
|
||||
ConfigPropertyValue::Choice(value) => serde_json::Value::String(value.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ConfigPropertyKind {
|
||||
Boolean,
|
||||
Choice(&'static [ConfigEnumVariantInfo]),
|
||||
}
|
||||
#enums
|
||||
#[inline(always)]
|
||||
fn default_true() -> bool { true }
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
#[serde(default)]
|
||||
pub struct DiffObjConfig {
|
||||
#property_fields
|
||||
}
|
||||
impl Default for DiffObjConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#default_fields
|
||||
}
|
||||
}
|
||||
}
|
||||
impl DiffObjConfig {
|
||||
pub fn get_property_value(&self, id: ConfigPropertyId) -> ConfigPropertyValue {
|
||||
match id {
|
||||
#get_property_value_variants
|
||||
}
|
||||
}
|
||||
#[allow(clippy::result_unit_err)]
|
||||
pub fn set_property_value(&mut self, id: ConfigPropertyId, value: ConfigPropertyValue) -> Result<(), ()> {
|
||||
match id {
|
||||
#set_property_value_variants
|
||||
}
|
||||
}
|
||||
#[allow(clippy::result_unit_err)]
|
||||
pub fn set_property_value_str(&mut self, id: ConfigPropertyId, value: &str) -> Result<(), ()> {
|
||||
match id {
|
||||
#set_property_value_str_variants
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let file = syn::parse2(tokens).unwrap();
|
||||
let formatted = prettyplease::unparse(&file);
|
||||
std::fs::write(
|
||||
PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("config.gen.rs"),
|
||||
formatted,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -21,9 +21,9 @@ enum SymbolFlag {
|
||||
SYMBOL_NONE = 0;
|
||||
SYMBOL_GLOBAL = 1;
|
||||
SYMBOL_LOCAL = 2;
|
||||
SYMBOL_WEAK = 3;
|
||||
SYMBOL_COMMON = 4;
|
||||
SYMBOL_HIDDEN = 5;
|
||||
SYMBOL_WEAK = 4;
|
||||
SYMBOL_COMMON = 8;
|
||||
SYMBOL_HIDDEN = 16;
|
||||
}
|
||||
|
||||
// A single parsed instruction
|
||||
@@ -122,10 +122,17 @@ message InstructionBranchTo {
|
||||
uint32 branch_index = 2;
|
||||
}
|
||||
|
||||
message FunctionDiff {
|
||||
message SymbolRef {
|
||||
optional uint32 section_index = 1;
|
||||
uint32 symbol_index = 2;
|
||||
}
|
||||
|
||||
message SymbolDiff {
|
||||
Symbol symbol = 1;
|
||||
repeated InstructionDiff instructions = 2;
|
||||
optional float match_percent = 3;
|
||||
// The symbol ref in the _other_ object that this symbol was diffed against
|
||||
optional SymbolRef target = 5;
|
||||
}
|
||||
|
||||
message DataDiff {
|
||||
@@ -140,7 +147,7 @@ message SectionDiff {
|
||||
SectionKind kind = 2;
|
||||
uint64 size = 3;
|
||||
uint64 address = 4;
|
||||
repeated FunctionDiff functions = 5;
|
||||
repeated SymbolDiff symbols = 5;
|
||||
repeated DataDiff data = 6;
|
||||
optional float match_percent = 7;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -139,9 +139,9 @@ impl ObjArch for ObjArchArm {
|
||||
|
||||
let version = match config.arm_arch_version {
|
||||
ArmArchVersion::Auto => self.detected_version.unwrap_or(ArmVersion::V5Te),
|
||||
ArmArchVersion::V4T => ArmVersion::V4T,
|
||||
ArmArchVersion::V5TE => ArmVersion::V5Te,
|
||||
ArmArchVersion::V6K => ArmVersion::V6K,
|
||||
ArmArchVersion::V4t => ArmVersion::V4T,
|
||||
ArmArchVersion::V5te => ArmVersion::V5Te,
|
||||
ArmArchVersion::V6k => ArmVersion::V6K,
|
||||
};
|
||||
let endian = match self.endianness {
|
||||
object::Endianness::Little => unarm::Endian::Little,
|
||||
@@ -276,6 +276,19 @@ impl ObjArch for ObjArchArm {
|
||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
|
||||
Cow::Owned(format!("<{flags:?}>"))
|
||||
}
|
||||
|
||||
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
|
||||
match flags {
|
||||
RelocationFlags::Elf { r_type } => match r_type {
|
||||
elf::R_ARM_ABS32 => 4,
|
||||
elf::R_ARM_REL32 => 4,
|
||||
elf::R_ARM_ABS16 => 2,
|
||||
elf::R_ARM_ABS8 => 1,
|
||||
_ => 1,
|
||||
},
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
||||
@@ -173,6 +173,21 @@ impl ObjArch for ObjArchArm64 {
|
||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
|
||||
match flags {
|
||||
RelocationFlags::Elf { r_type } => match r_type {
|
||||
elf::R_AARCH64_ABS64 => 8,
|
||||
elf::R_AARCH64_ABS32 => 4,
|
||||
elf::R_AARCH64_ABS16 => 2,
|
||||
elf::R_AARCH64_PREL64 => 8,
|
||||
elf::R_AARCH64_PREL32 => 4,
|
||||
elf::R_AARCH64_PREL16 => 2,
|
||||
_ => 1,
|
||||
},
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayCtx<'a> {
|
||||
|
||||
@@ -99,8 +99,8 @@ impl ObjArch for ObjArchMips {
|
||||
MipsInstrCategory::Auto => self.instr_category,
|
||||
MipsInstrCategory::Cpu => InstrCategory::CPU,
|
||||
MipsInstrCategory::Rsp => InstrCategory::RSP,
|
||||
MipsInstrCategory::R3000Gte => InstrCategory::R3000GTE,
|
||||
MipsInstrCategory::R4000Allegrex => InstrCategory::R4000ALLEGREX,
|
||||
MipsInstrCategory::R3000gte => InstrCategory::R3000GTE,
|
||||
MipsInstrCategory::R4000allegrex => InstrCategory::R4000ALLEGREX,
|
||||
MipsInstrCategory::R5900 => InstrCategory::R5900,
|
||||
};
|
||||
|
||||
@@ -271,6 +271,17 @@ impl ObjArch for ObjArchMips {
|
||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
|
||||
match flags {
|
||||
RelocationFlags::Elf { r_type } => match r_type {
|
||||
elf::R_MIPS_16 => 2,
|
||||
elf::R_MIPS_32 => 4,
|
||||
_ => 1,
|
||||
},
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
|
||||
|
||||
@@ -148,6 +148,8 @@ pub trait ObjArch: Send + Sync {
|
||||
|
||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str>;
|
||||
|
||||
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize;
|
||||
|
||||
fn symbol_address(&self, symbol: &Symbol) -> u64 { symbol.address() }
|
||||
|
||||
fn guess_data_type(&self, _instruction: &ObjIns) -> Option<DataType> { None }
|
||||
@@ -156,6 +158,17 @@ pub trait ObjArch: Send + Sync {
|
||||
Some(format!("Bytes: {:#x?}", bytes))
|
||||
}
|
||||
|
||||
fn display_ins_data(&self, ins: &ObjIns) -> Option<String> {
|
||||
let reloc = ins.reloc.as_ref()?;
|
||||
if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize {
|
||||
self.guess_data_type(ins).and_then(|ty| {
|
||||
self.display_data_type(ty, &reloc.target.bytes[reloc.addend as usize..])
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Downcast methods
|
||||
#[cfg(feature = "ppc")]
|
||||
fn ppc(&self) -> Option<&ppc::ObjArchPpc> { None }
|
||||
|
||||
+264
-101
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{BTreeMap, HashMap},
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
};
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
@@ -10,7 +10,7 @@ use object::{
|
||||
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget,
|
||||
Symbol, SymbolKind,
|
||||
};
|
||||
use ppc750cl::{Argument, InsIter, Opcode, ParsedIns, GPR};
|
||||
use ppc750cl::{Argument, Arguments, Ins, InsIter, Opcode, ParsedIns, GPR};
|
||||
|
||||
use crate::{
|
||||
arch::{DataType, ObjArch, ProcessCodeResult},
|
||||
@@ -143,6 +143,15 @@ impl ObjArch for ObjArchPpc {
|
||||
}
|
||||
}
|
||||
|
||||
if reloc.is_none() {
|
||||
if let Some(fake_pool_reloc) = fake_pool_reloc_for_addr.get(&cur_addr) {
|
||||
// If this instruction has a fake pool relocation, show it as a fake argument
|
||||
// at the end of the line.
|
||||
args.push(ObjInsArg::PlainText(" ".into()));
|
||||
push_reloc(&mut args, fake_pool_reloc)?;
|
||||
}
|
||||
}
|
||||
|
||||
ops.push(ins.op as u16);
|
||||
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
|
||||
insts.push(ObjIns {
|
||||
@@ -193,24 +202,23 @@ impl ObjArch for ObjArchPpc {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
|
||||
match flags {
|
||||
RelocationFlags::Elf { r_type } => match r_type {
|
||||
elf::R_PPC_ADDR32 => 4,
|
||||
elf::R_PPC_UADDR32 => 4,
|
||||
_ => 1,
|
||||
},
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn guess_data_type(&self, instruction: &ObjIns) -> Option<super::DataType> {
|
||||
if instruction.reloc.as_ref().is_some_and(|r| r.target.name.starts_with("@stringBase")) {
|
||||
return Some(DataType::String);
|
||||
}
|
||||
|
||||
let op = Opcode::from(instruction.op as u8);
|
||||
if let Some(ty) = guess_data_type_from_load_store_inst_op(op) {
|
||||
Some(ty)
|
||||
} else if op == Opcode::Addi {
|
||||
// Assume that any addi instruction that references a local symbol is loading a string.
|
||||
// This hack is not ideal and results in tons of false positives where it will show
|
||||
// garbage strings (e.g. misinterpreting arrays, float literals, etc).
|
||||
// But not all strings are in the @stringBase pool, so the condition above that checks
|
||||
// the target symbol name would miss some.
|
||||
Some(DataType::String)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
guess_data_type_from_load_store_inst_op(Opcode::from(instruction.op as u8))
|
||||
}
|
||||
|
||||
fn display_data_type(&self, ty: DataType, bytes: &[u8]) -> Option<String> {
|
||||
@@ -248,6 +256,12 @@ fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
|
||||
elf::R_PPC_ADDR32 | elf::R_PPC_UADDR32 | elf::R_PPC_REL24 | elf::R_PPC_REL14 => {
|
||||
args.push(ObjInsArg::Reloc);
|
||||
}
|
||||
elf::R_PPC_NONE => {
|
||||
// Fake pool relocation.
|
||||
args.push(ObjInsArg::PlainText("<".into()));
|
||||
args.push(ObjInsArg::Reloc);
|
||||
args.push(ObjInsArg::PlainText(">".into()));
|
||||
}
|
||||
_ => bail!("Unsupported ELF PPC relocation type {r_type}"),
|
||||
},
|
||||
flags => bail!("Unsupported PPC relocation kind: {flags:?}"),
|
||||
@@ -442,16 +456,43 @@ fn get_offset_and_addr_gpr_for_possible_pool_reference(
|
||||
Argument::Simm(simm),
|
||||
) => Some((simm.0, addr_src_gpr, Some(addr_dst_gpr))),
|
||||
(
|
||||
// `mr` or `mr.`
|
||||
Opcode::Or,
|
||||
Argument::GPR(addr_dst_gpr),
|
||||
Argument::GPR(addr_src_gpr),
|
||||
Argument::None,
|
||||
) => Some((0, addr_src_gpr, Some(addr_dst_gpr))), // `mr` or `mr.`
|
||||
) => Some((0, addr_src_gpr, Some(addr_dst_gpr))),
|
||||
(
|
||||
Opcode::Add,
|
||||
Argument::GPR(addr_dst_gpr),
|
||||
Argument::GPR(addr_src_gpr),
|
||||
Argument::GPR(_offset_gpr),
|
||||
) => Some((0, addr_src_gpr, Some(addr_dst_gpr))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the relocation we're keeping track of in a particular register when an instruction reuses
|
||||
// that register to hold some other value, unrelated to pool relocation addresses.
|
||||
fn clear_overwritten_gprs(ins: Ins, gpr_pool_relocs: &mut HashMap<u8, ObjReloc>) {
|
||||
let mut def_args = Arguments::default();
|
||||
ins.parse_defs(&mut def_args);
|
||||
for arg in def_args {
|
||||
if let Argument::GPR(gpr) = arg {
|
||||
if ins.op == Opcode::Lmw {
|
||||
// `lmw` overwrites all registers from rd to r31.
|
||||
// ppc750cl only returns rd itself, so we manually clear the rest of them.
|
||||
for reg in gpr.0..31 {
|
||||
gpr_pool_relocs.remove(®);
|
||||
}
|
||||
break;
|
||||
}
|
||||
gpr_pool_relocs.remove(&gpr.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We create a fake relocation for an instruction, vaguely simulating what the actual relocation
|
||||
// might have looked like if it wasn't pooled. This is so minimal changes are needed to display
|
||||
// pooled accesses vs non-pooled accesses. We set the relocation type to R_PPC_NONE to indicate that
|
||||
@@ -461,35 +502,51 @@ fn get_offset_and_addr_gpr_for_possible_pool_reference(
|
||||
fn make_fake_pool_reloc(offset: i16, cur_addr: u32, pool_reloc: &ObjReloc) -> Option<ObjReloc> {
|
||||
let offset_from_pool = pool_reloc.addend + offset as i64;
|
||||
let target_address = pool_reloc.target.address.checked_add_signed(offset_from_pool)?;
|
||||
let orig_section_index = pool_reloc.target.orig_section_index?;
|
||||
// We also need to create a fake target symbol to go inside our fake relocation.
|
||||
// This is because we don't have access to list of all symbols in this section, so we can't find
|
||||
// the real symbol yet. Instead we make a placeholder that has the correct `orig_section_index`
|
||||
// and `address` fields, and then later on when this information is displayed to the user, we
|
||||
// can find the real symbol by searching through the object's section's symbols for one that
|
||||
// contains this address.
|
||||
let fake_target_symbol = ObjSymbol {
|
||||
name: "".to_string(),
|
||||
demangled_name: None,
|
||||
address: target_address,
|
||||
section_address: 0,
|
||||
size: 0,
|
||||
size_known: false,
|
||||
kind: Default::default(),
|
||||
flags: Default::default(),
|
||||
orig_section_index: Some(orig_section_index),
|
||||
virtual_address: None,
|
||||
original_index: None,
|
||||
bytes: vec![],
|
||||
let target;
|
||||
let addend;
|
||||
if pool_reloc.target.orig_section_index.is_some() {
|
||||
// If the target symbol is within this current object, then we also need to create a fake
|
||||
// target symbol to go inside our fake relocation. This is because we don't have access to
|
||||
// list of all symbols in this section, so we can't find the real symbol within the pool
|
||||
// based on its address yet. Instead we make a placeholder that has the correct
|
||||
// `orig_section_index` and `address` fields, and then later on when this information is
|
||||
// displayed to the user, we can find the real symbol by searching through the object's
|
||||
// section's symbols for one that contains this address.
|
||||
target = ObjSymbol {
|
||||
name: "".to_string(),
|
||||
demangled_name: None,
|
||||
address: target_address,
|
||||
section_address: 0,
|
||||
size: 0,
|
||||
size_known: false,
|
||||
kind: Default::default(),
|
||||
flags: Default::default(),
|
||||
orig_section_index: pool_reloc.target.orig_section_index,
|
||||
virtual_address: None,
|
||||
original_index: None,
|
||||
bytes: vec![],
|
||||
};
|
||||
// The addend is also fake because we don't know yet if the `target_address` here is the exact
|
||||
// start of the symbol or if it's in the middle of it.
|
||||
addend = 0;
|
||||
} else {
|
||||
// But if the target symbol is in a different object (extern), then we simply copy the pool
|
||||
// relocation's target. This is because it won't be possible to locate the actual symbol
|
||||
// later on based only off of an offset without knowing the object or section it's in. And
|
||||
// doing that for external symbols would also be unnecessary, because when the compiler
|
||||
// generates an instruction that accesses an external "pool" plus some offset, that won't be
|
||||
// a normal pool that contains other symbols within it that we want to display. It will be
|
||||
// something like a vtable for a class with multiple inheritance (for example, dCcD_Cyl in
|
||||
// The Wind Waker). So just showing that vtable symbol plus an addend to represent the
|
||||
// offset into it works fine in this case, no fake symbol to hold an address is necessary.
|
||||
target = pool_reloc.target.clone();
|
||||
addend = pool_reloc.addend;
|
||||
};
|
||||
// The addend is also fake because we don't know yet if the `target_address` here is the exact
|
||||
// start of the symbol or if it's in the middle of it.
|
||||
let fake_addend = 0;
|
||||
Some(ObjReloc {
|
||||
flags: RelocationFlags::Elf { r_type: elf::R_PPC_NONE },
|
||||
address: cur_addr as u64,
|
||||
target: fake_target_symbol,
|
||||
addend: fake_addend,
|
||||
target,
|
||||
addend,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -497,78 +554,184 @@ fn make_fake_pool_reloc(offset: i16, cur_addr: u32, pool_reloc: &ObjReloc) -> Op
|
||||
// of pooled data relocations in them, finding which instructions load data from those addresses,
|
||||
// and constructing a mapping of the address of that instruction to a "fake pool relocation" that
|
||||
// simulates what that instruction's relocation would look like if data hadn't been pooled.
|
||||
// Limitations: This method currently only goes through the instructions in a function in linear
|
||||
// order, from start to finish. It does *not* follow any branches. This means that it could have
|
||||
// false positives or false negatives in determining which relocation is currently loaded in which
|
||||
// register at any given point in the function, as control flow is not respected.
|
||||
// There are currently no known examples of this method producing inaccurate results in reality, but
|
||||
// if examples are found, it may be possible to update this method to also follow all branches so
|
||||
// that it produces more accurate results.
|
||||
// This method tries to follow the function's proper control flow. It keeps track of a queue of
|
||||
// states it hasn't traversed yet, where each state holds an instruction address and a HashMap of
|
||||
// which registers hold which pool relocations at that point.
|
||||
// When a conditional or unconditional branch is encountered, the destination of the branch is added
|
||||
// to the queue. Conditional branches will traverse both the path where the branch is taken and the
|
||||
// one where it's not. Unconditional branches only follow the branch, ignoring any code immediately
|
||||
// after the branch instruction.
|
||||
// Limitations: This method cannot read jump tables. This is because the jump tables are located in
|
||||
// the .data section, but ObjArch.process_code only has access to the .text section. In order to
|
||||
// work around this limitation and avoid completely missing most code inside switch statements that
|
||||
// use jump tables, we instead guess that any parts of a function we missed were switch cases, and
|
||||
// traverse them as if the last `bctr` before that address had branched there. This should be fairly
|
||||
// accurate in practice - in testing the only instructions it seems to miss are double branches that
|
||||
// the compiler generates in error which can never be reached during normal execution anyway.
|
||||
fn generate_fake_pool_reloc_for_addr_mapping(
|
||||
address: u64,
|
||||
func_address: u64,
|
||||
code: &[u8],
|
||||
relocations: &[ObjReloc],
|
||||
) -> HashMap<u32, ObjReloc> {
|
||||
let mut active_pool_relocs = HashMap::new();
|
||||
let mut visited_ins_addrs = HashSet::new();
|
||||
let mut pool_reloc_for_addr = HashMap::new();
|
||||
for (cur_addr, ins) in InsIter::new(code, address as u32) {
|
||||
let simplified = ins.simplified();
|
||||
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
|
||||
let mut ins_iters_with_gpr_state =
|
||||
vec![(InsIter::new(code, func_address as u32), HashMap::new())];
|
||||
let mut gpr_state_at_bctr = BTreeMap::new();
|
||||
while let Some((ins_iter, mut gpr_pool_relocs)) = ins_iters_with_gpr_state.pop() {
|
||||
for (cur_addr, ins) in ins_iter {
|
||||
if visited_ins_addrs.contains(&cur_addr) {
|
||||
// Avoid getting stuck in an infinite loop when following looping branches.
|
||||
break;
|
||||
}
|
||||
visited_ins_addrs.insert(cur_addr);
|
||||
|
||||
if let Some(reloc) = reloc {
|
||||
// This instruction has a real relocation, so it may be a pool load we want to keep
|
||||
// track of.
|
||||
let args = &simplified.args;
|
||||
match (ins.op, args[0], args[1], args[2]) {
|
||||
(
|
||||
Opcode::Addi,
|
||||
Argument::GPR(addr_dst_gpr),
|
||||
Argument::GPR(_addr_src_gpr),
|
||||
Argument::Simm(_simm),
|
||||
) => {
|
||||
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `addi`
|
||||
let simplified = ins.simplified();
|
||||
|
||||
// First handle traversing the function's control flow.
|
||||
let mut branch_dest = None;
|
||||
for arg in simplified.args_iter() {
|
||||
if let Argument::BranchDest(dest) = arg {
|
||||
let dest = cur_addr.wrapping_add_signed(dest.0);
|
||||
branch_dest = Some(dest);
|
||||
break;
|
||||
}
|
||||
(
|
||||
Opcode::Ori,
|
||||
Argument::GPR(addr_dst_gpr),
|
||||
Argument::GPR(_addr_src_gpr),
|
||||
Argument::Uimm(_uimm),
|
||||
) => {
|
||||
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `ori`
|
||||
}
|
||||
(Opcode::B, _, _, _) => {
|
||||
if simplified.mnemonic == "bl" {
|
||||
// When encountering a function call, clear any active pool relocations from
|
||||
// the volatile registers (r0, r3-r12), but not the nonvolatile registers.
|
||||
active_pool_relocs.remove(&0);
|
||||
for gpr in 3..12 {
|
||||
active_pool_relocs.remove(&gpr);
|
||||
}
|
||||
if let Some(branch_dest) = branch_dest {
|
||||
if branch_dest >= func_address as u32
|
||||
&& (branch_dest - func_address as u32) < code.len() as u32
|
||||
{
|
||||
let dest_offset_into_func = branch_dest - func_address as u32;
|
||||
let dest_code_slice = &code[dest_offset_into_func as usize..];
|
||||
match ins.op {
|
||||
Opcode::Bc => {
|
||||
// Conditional branch.
|
||||
// Add the branch destination to the queue to do later.
|
||||
ins_iters_with_gpr_state.push((
|
||||
InsIter::new(dest_code_slice, branch_dest),
|
||||
gpr_pool_relocs.clone(),
|
||||
));
|
||||
// Then continue on with the current iterator.
|
||||
}
|
||||
Opcode::B => {
|
||||
if simplified.mnemonic != "bl" {
|
||||
// Unconditional branch.
|
||||
// Add the branch destination to the queue.
|
||||
ins_iters_with_gpr_state.push((
|
||||
InsIter::new(dest_code_slice, branch_dest),
|
||||
gpr_pool_relocs.clone(),
|
||||
));
|
||||
// Break out of the current iterator so we can do the newly added one.
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else if let Some((offset, addr_src_gpr, addr_dst_gpr)) =
|
||||
get_offset_and_addr_gpr_for_possible_pool_reference(ins.op, &simplified)
|
||||
{
|
||||
// This instruction doesn't have a real relocation, so it may be a reference to one of
|
||||
// the already-loaded pools.
|
||||
if let Some(pool_reloc) = active_pool_relocs.get(&addr_src_gpr.0) {
|
||||
if let Some(fake_pool_reloc) = make_fake_pool_reloc(offset, cur_addr, pool_reloc) {
|
||||
pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
|
||||
if let Opcode::Bcctr = ins.op {
|
||||
if simplified.mnemonic == "bctr" {
|
||||
// Unconditional branch to count register.
|
||||
// Likely a jump table.
|
||||
gpr_state_at_bctr.insert(cur_addr, gpr_pool_relocs.clone());
|
||||
}
|
||||
if let Some(addr_dst_gpr) = addr_dst_gpr {
|
||||
// If the address of the pool relocation got copied into another register, we
|
||||
// need to keep track of it in that register too as future instructions may
|
||||
// reference the symbol indirectly via this new register, instead of the
|
||||
// register the symbol's address was originally loaded into.
|
||||
// For example, the start of the function might `lis` + `addi` the start of the
|
||||
// ...data pool into r25, and then later the start of a loop will `addi` r25
|
||||
// with the offset within the .data section of an array variable into r21.
|
||||
// Then the body of the loop will `lwzx` one of the array elements from r21.
|
||||
let mut new_reloc = pool_reloc.clone();
|
||||
new_reloc.addend += offset as i64;
|
||||
active_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
|
||||
}
|
||||
|
||||
// Then handle keeping track of which GPR contains which pool relocation.
|
||||
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
|
||||
if let Some(reloc) = reloc {
|
||||
// This instruction has a real relocation, so it may be a pool load we want to keep
|
||||
// track of.
|
||||
let args = &simplified.args;
|
||||
match (ins.op, args[0], args[1], args[2]) {
|
||||
(
|
||||
// `lis` + `addi`
|
||||
Opcode::Addi,
|
||||
Argument::GPR(addr_dst_gpr),
|
||||
Argument::GPR(_addr_src_gpr),
|
||||
Argument::Simm(_simm),
|
||||
) => {
|
||||
gpr_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
|
||||
}
|
||||
(
|
||||
// `lis` + `ori`
|
||||
Opcode::Ori,
|
||||
Argument::GPR(addr_dst_gpr),
|
||||
Argument::GPR(_addr_src_gpr),
|
||||
Argument::Uimm(_uimm),
|
||||
) => {
|
||||
gpr_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
|
||||
}
|
||||
(Opcode::B, _, _, _) => {
|
||||
if simplified.mnemonic == "bl" {
|
||||
// When encountering a function call, clear any active pool relocations from
|
||||
// the volatile registers (r0, r3-r12), but not the nonvolatile registers.
|
||||
gpr_pool_relocs.remove(&0);
|
||||
for gpr in 3..12 {
|
||||
gpr_pool_relocs.remove(&gpr);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||
}
|
||||
}
|
||||
} else if let Some((offset, addr_src_gpr, addr_dst_gpr)) =
|
||||
get_offset_and_addr_gpr_for_possible_pool_reference(ins.op, &simplified)
|
||||
{
|
||||
// This instruction doesn't have a real relocation, so it may be a reference to one of
|
||||
// the already-loaded pools.
|
||||
if let Some(pool_reloc) = gpr_pool_relocs.get(&addr_src_gpr.0) {
|
||||
if let Some(fake_pool_reloc) =
|
||||
make_fake_pool_reloc(offset, cur_addr, pool_reloc)
|
||||
{
|
||||
pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
|
||||
}
|
||||
if let Some(addr_dst_gpr) = addr_dst_gpr {
|
||||
// If the address of the pool relocation got copied into another register, we
|
||||
// need to keep track of it in that register too as future instructions may
|
||||
// reference the symbol indirectly via this new register, instead of the
|
||||
// register the symbol's address was originally loaded into.
|
||||
// For example, the start of the function might `lis` + `addi` the start of the
|
||||
// ...data pool into r25, and then later the start of a loop will `addi` r25
|
||||
// with the offset within the .data section of an array variable into r21.
|
||||
// Then the body of the loop will `lwzx` one of the array elements from r21.
|
||||
let mut new_reloc = pool_reloc.clone();
|
||||
new_reloc.addend += offset as i64;
|
||||
gpr_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
|
||||
} else {
|
||||
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||
}
|
||||
} else {
|
||||
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||
}
|
||||
} else {
|
||||
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, if we're about to finish the outer loop and don't have any more control flow to
|
||||
// follow, we check if there are any instruction addresses in this function that we missed.
|
||||
// If so, and if there were any `bctr` instructions before those points in this function,
|
||||
// then we try to traverse those missing spots as switch cases.
|
||||
if ins_iters_with_gpr_state.is_empty() {
|
||||
let unseen_addrs = (func_address as u32..func_address as u32 + code.len() as u32)
|
||||
.step_by(4)
|
||||
.filter(|addr| !visited_ins_addrs.contains(addr));
|
||||
for unseen_addr in unseen_addrs {
|
||||
let prev_bctr_gpr_state = gpr_state_at_bctr
|
||||
.iter()
|
||||
.filter(|(&addr, _)| addr < unseen_addr)
|
||||
.min_by_key(|(&addr, _)| addr)
|
||||
.map(|(_, gpr_state)| gpr_state);
|
||||
if let Some(gpr_pool_relocs) = prev_bctr_gpr_state {
|
||||
let dest_offset_into_func = unseen_addr - func_address as u32;
|
||||
let dest_code_slice = &code[dest_offset_into_func as usize..];
|
||||
ins_iters_with_gpr_state.push((
|
||||
InsIter::new(dest_code_slice, unseen_addr),
|
||||
gpr_pool_relocs.clone(),
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,19 @@ impl ObjArch for ObjArchX86 {
|
||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
|
||||
match flags {
|
||||
RelocationFlags::Coff { typ } => match typ {
|
||||
pe::IMAGE_REL_I386_DIR16 => 2,
|
||||
pe::IMAGE_REL_I386_REL16 => 2,
|
||||
pe::IMAGE_REL_I386_DIR32 => 4,
|
||||
pe::IMAGE_REL_I386_REL32 => 4,
|
||||
_ => 1,
|
||||
},
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_arg(
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::{
|
||||
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
|
||||
ObjInsDiff, ObjInsDiffKind, ObjSectionDiff, ObjSymbolDiff,
|
||||
},
|
||||
obj,
|
||||
obj::{
|
||||
ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSectionKind, ObjSymbol,
|
||||
ObjSymbolFlagSet, ObjSymbolFlags,
|
||||
@@ -39,14 +40,14 @@ impl ObjectDiff {
|
||||
impl SectionDiff {
|
||||
pub fn new(obj: &ObjInfo, section_index: usize, section_diff: &ObjSectionDiff) -> Self {
|
||||
let section = &obj.sections[section_index];
|
||||
let functions = section_diff.symbols.iter().map(|d| FunctionDiff::new(obj, d)).collect();
|
||||
let symbols = section_diff.symbols.iter().map(|d| SymbolDiff::new(obj, d)).collect();
|
||||
let data = section_diff.data_diff.iter().map(|d| DataDiff::new(obj, d)).collect();
|
||||
Self {
|
||||
name: section.name.to_string(),
|
||||
kind: SectionKind::from(section.kind) as i32,
|
||||
size: section.size,
|
||||
address: section.address,
|
||||
functions,
|
||||
symbols,
|
||||
data,
|
||||
match_percent: section_diff.match_percent,
|
||||
}
|
||||
@@ -64,13 +65,22 @@ impl From<ObjSectionKind> for SectionKind {
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionDiff {
|
||||
impl From<obj::SymbolRef> for SymbolRef {
|
||||
fn from(value: obj::SymbolRef) -> Self {
|
||||
Self {
|
||||
section_index: if value.section_idx == obj::SECTION_COMMON {
|
||||
None
|
||||
} else {
|
||||
Some(value.section_idx as u32)
|
||||
},
|
||||
symbol_index: value.symbol_idx as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolDiff {
|
||||
pub fn new(object: &ObjInfo, symbol_diff: &ObjSymbolDiff) -> Self {
|
||||
let (_section, symbol) = object.section_symbol(symbol_diff.symbol_ref);
|
||||
// let diff_symbol = symbol_diff.diff_symbol.map(|symbol_ref| {
|
||||
// let (_section, symbol) = object.section_symbol(symbol_ref);
|
||||
// Symbol::from(symbol)
|
||||
// });
|
||||
let instructions = symbol_diff
|
||||
.instructions
|
||||
.iter()
|
||||
@@ -78,9 +88,9 @@ impl FunctionDiff {
|
||||
.collect();
|
||||
Self {
|
||||
symbol: Some(Symbol::new(symbol)),
|
||||
// diff_symbol,
|
||||
instructions,
|
||||
match_percent: symbol_diff.match_percent,
|
||||
target: symbol_diff.target_symbol.map(SymbolRef::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +120,7 @@ impl Symbol {
|
||||
fn symbol_flags(value: ObjSymbolFlagSet) -> u32 {
|
||||
let mut flags = 0u32;
|
||||
if value.0.contains(ObjSymbolFlags::Global) {
|
||||
flags |= SymbolFlag::SymbolNone as u32;
|
||||
flags |= SymbolFlag::SymbolGlobal as u32;
|
||||
}
|
||||
if value.0.contains(ObjSymbolFlags::Local) {
|
||||
flags |= SymbolFlag::SymbolLocal as u32;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user