mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
11 Commits
v3.2.1
...
mips-gprel32
| Author | SHA1 | Date | |
|---|---|---|---|
| 9123928b76 | |||
| b5846cc1c8 | |||
| c3ab203ec1 | |||
| 572afa8551 | |||
| 90e81fad7e | |||
| 7a8efb4c88 | |||
| 56dac46280 | |||
| 1866158092 | |||
| fe8e7029f6 | |||
| e2c70342c9 | |||
| e6035b00df |
@@ -0,0 +1,44 @@
|
|||||||
|
# Repository Guidelines
|
||||||
|
|
||||||
|
## Project Structure & Module Organization
|
||||||
|
|
||||||
|
- `objdiff-core`: core library with diffing logic and shared components
|
||||||
|
- `objdiff-cli`: CLI/TUI with `ratatui`
|
||||||
|
- `objdiff-gui`: GUI with `eframe`/`egui`
|
||||||
|
- `objdiff-wasm`: WebAssembly bindings
|
||||||
|
|
||||||
|
objdiff has three main frontends: GUI, CLI/TUI, and [web](https://github.com/encounter/objdiff-web) (utilizing `objdiff-wasm`).
|
||||||
|
|
||||||
|
`objdiff-gui` is the most fully-featured and is the primary target for new features.
|
||||||
|
|
||||||
|
`objdiff-cli` has an interactive TUI `diff` mode (powered by `ratatui`) and a non-interactive `report` mode to generate detailed progress reports (e.g. for <https://decomp.dev>).
|
||||||
|
|
||||||
|
`objdiff-wasm` is compiled into a [WebAssembly Component](https://component-model.bytecodealliance.org/). The [API](objdiff-wasm/wit/objdiff.wit) is defined using [WIT](https://component-model.bytecodealliance.org/design/wit.html). The web interface is separate, but mirrors the GUI in functionality.
|
||||||
|
|
||||||
|
## Build, Test, and Development Commands
|
||||||
|
|
||||||
|
Run `cargo build` for a debug build and `cargo build --release` for an optimized build.
|
||||||
|
The CLI can be exercised with `cargo run --release --bin objdiff-cli -- --help`.
|
||||||
|
|
||||||
|
The WASM build is not included in the workspace by default due to differences in toolchain and target; build it with `npm -C objdiff-wasm run build`. (nightly required)
|
||||||
|
In general, do NOT use `--workspace`, as it will include the WASM crate and likely fail. Formatting and linting commands are exempt.
|
||||||
|
|
||||||
|
Pre-commit tasks:
|
||||||
|
|
||||||
|
- `cargo test` to run the test suite
|
||||||
|
- `cargo +nightly fmt --all` to format code (nightly required)
|
||||||
|
- `cargo +nightly clippy --all-targets --all-features --workspace -- -D warnings` to lint code (nightly required)
|
||||||
|
- `cargo deny check` (`cargo install --locked cargo-deny` if needed) if dependencies change
|
||||||
|
|
||||||
|
## Testing Guidelines
|
||||||
|
|
||||||
|
Favor focused unit tests adjacent to the code, and integration scenarios in `objdiff-core/tests`.
|
||||||
|
Integration tests utilize snapshots with the `insta` crate; run `curl -LsSf https://insta.rs/install.sh | sh` if needed.
|
||||||
|
To generate updated snapshots, use `cargo insta test`, review diffs manually, then accept changes with `cargo insta accept` (or simply `mv` to accept specific snapshots).
|
||||||
|
|
||||||
|
## Configuration Tips
|
||||||
|
|
||||||
|
- `config.schema.json`: JSON schema for the user-facing project config file (`objdiff.json`)
|
||||||
|
- `objdiff-core/config-schema.json`: Internal options schema to generate `DiffObjConfig` (see `objdiff-core/config_gen.rs`)
|
||||||
|
|
||||||
|
The project configuration (`objdiff.json`) is intended to be generated by the projects' build system. It includes paths to the target (expected) and base (current) object files, along with build commands and file watch patterns. See `README.md` for a summary of key options.
|
||||||
Generated
+421
-304
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -14,7 +14,7 @@ default-members = [
|
|||||||
resolver = "3"
|
resolver = "3"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "3.2.1"
|
version = "3.3.0"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|||||||
@@ -111,6 +111,25 @@
|
|||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/$defs/progress_category"
|
"$ref": "#/$defs/progress_category"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Diff configuration options that should be applied automatically when the project is loaded.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"demangler": "gnu_legacy"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"$defs": {
|
"$defs": {
|
||||||
@@ -156,6 +175,20 @@
|
|||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Diff configuration options that should be applied when this unit is active.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+39
-21
@@ -24,7 +24,8 @@ use objdiff_core::{
|
|||||||
watcher::{Watcher, create_watcher},
|
watcher::{Watcher, create_watcher},
|
||||||
},
|
},
|
||||||
config::{
|
config::{
|
||||||
ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset,
|
ProjectConfig, ProjectObject, ProjectObjectMetadata, ProjectOptions, apply_project_options,
|
||||||
|
build_globset,
|
||||||
path::{check_path_buf, platform_path, platform_path_serde_option},
|
path::{check_path_buf, platform_path, platform_path_serde_option},
|
||||||
},
|
},
|
||||||
diff::{DiffObjConfig, MappingConfig, ObjectDiff},
|
diff::{DiffObjConfig, MappingConfig, ObjectDiff},
|
||||||
@@ -77,11 +78,11 @@ pub struct Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(args: Args) -> Result<()> {
|
pub fn run(args: Args) -> Result<()> {
|
||||||
let (target_path, base_path, project_config) =
|
let (target_path, base_path, project_config, unit_options) =
|
||||||
match (&args.target, &args.base, &args.project, &args.unit) {
|
match (&args.target, &args.base, &args.project, &args.unit) {
|
||||||
(Some(_), Some(_), None, None)
|
(Some(_), Some(_), None, None)
|
||||||
| (Some(_), None, None, None)
|
| (Some(_), None, None, None)
|
||||||
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None),
|
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None, None),
|
||||||
(None, None, p, u) => {
|
(None, None, p, u) => {
|
||||||
let project = match p {
|
let project = match p {
|
||||||
Some(project) => project.clone(),
|
Some(project) => project.clone(),
|
||||||
@@ -106,28 +107,32 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
.base_dir
|
.base_dir
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| project.join(p.with_platform_encoding()));
|
.map(|p| project.join(p.with_platform_encoding()));
|
||||||
let objects = project_config
|
let units = project_config.units.as_deref().unwrap_or_default();
|
||||||
.units
|
let objects = units
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.enumerate()
|
||||||
.map(|o| {
|
.map(|(idx, o)| {
|
||||||
ObjectConfig::new(
|
(
|
||||||
o,
|
ObjectConfig::new(
|
||||||
&project,
|
o,
|
||||||
target_obj_dir.as_deref(),
|
&project,
|
||||||
base_obj_dir.as_deref(),
|
target_obj_dir.as_deref(),
|
||||||
|
base_obj_dir.as_deref(),
|
||||||
|
),
|
||||||
|
idx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let object = if let Some(u) = u {
|
let (object, unit_idx) = if let Some(u) = u {
|
||||||
objects
|
objects
|
||||||
.iter()
|
.iter()
|
||||||
.find(|obj| obj.name == *u)
|
.find(|(obj, _)| obj.name == *u)
|
||||||
|
.map(|(obj, idx)| (obj, *idx))
|
||||||
.ok_or_else(|| anyhow!("Unit not found: {}", u))?
|
.ok_or_else(|| anyhow!("Unit not found: {}", u))?
|
||||||
} else if let Some(symbol_name) = &args.symbol {
|
} else if let Some(symbol_name) = &args.symbol {
|
||||||
let mut idx = None;
|
let mut idx = None;
|
||||||
let mut count = 0usize;
|
let mut count = 0usize;
|
||||||
for (i, obj) in objects.iter().enumerate() {
|
for (i, (obj, unit_idx)) in objects.iter().enumerate() {
|
||||||
if obj
|
if obj
|
||||||
.target_path
|
.target_path
|
||||||
.as_deref()
|
.as_deref()
|
||||||
@@ -135,7 +140,7 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
idx = Some(i);
|
idx = Some((i, *unit_idx));
|
||||||
count += 1;
|
count += 1;
|
||||||
if count > 1 {
|
if count > 1 {
|
||||||
break;
|
break;
|
||||||
@@ -144,7 +149,7 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
}
|
}
|
||||||
match (count, idx) {
|
match (count, idx) {
|
||||||
(0, None) => bail!("Symbol not found: {}", symbol_name),
|
(0, None) => bail!("Symbol not found: {}", symbol_name),
|
||||||
(1, Some(i)) => &objects[i],
|
(1, Some((i, unit_idx))) => (&objects[i].0, unit_idx),
|
||||||
(2.., Some(_)) => bail!(
|
(2.., Some(_)) => bail!(
|
||||||
"Multiple instances of {} were found, try specifying a unit",
|
"Multiple instances of {} were found, try specifying a unit",
|
||||||
symbol_name
|
symbol_name
|
||||||
@@ -154,18 +159,29 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
bail!("Must specify one of: symbol, project and unit, target and base objects")
|
bail!("Must specify one of: symbol, project and unit, target and base objects")
|
||||||
};
|
};
|
||||||
|
let unit_options = units.get(unit_idx).and_then(|u| u.options().cloned());
|
||||||
let target_path = object.target_path.clone();
|
let target_path = object.target_path.clone();
|
||||||
let base_path = object.base_path.clone();
|
let base_path = object.base_path.clone();
|
||||||
(target_path, base_path, Some(project_config))
|
(target_path, base_path, Some(project_config), unit_options)
|
||||||
}
|
}
|
||||||
_ => bail!("Either target and base or project and unit must be specified"),
|
_ => bail!("Either target and base or project and unit must be specified"),
|
||||||
};
|
};
|
||||||
|
|
||||||
run_interactive(args, target_path, base_path, project_config)
|
run_interactive(args, target_path, base_path, project_config, unit_options)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> {
|
fn build_config_from_args(
|
||||||
|
args: &Args,
|
||||||
|
project_config: Option<&ProjectConfig>,
|
||||||
|
unit_options: Option<&ProjectOptions>,
|
||||||
|
) -> Result<(DiffObjConfig, MappingConfig)> {
|
||||||
let mut diff_config = DiffObjConfig::default();
|
let mut diff_config = DiffObjConfig::default();
|
||||||
|
if let Some(options) = project_config.and_then(|config| config.options.as_ref()) {
|
||||||
|
apply_project_options(&mut diff_config, options)?;
|
||||||
|
}
|
||||||
|
if let Some(options) = unit_options {
|
||||||
|
apply_project_options(&mut diff_config, options)?;
|
||||||
|
}
|
||||||
apply_config_args(&mut diff_config, &args.config)?;
|
apply_config_args(&mut diff_config, &args.config)?;
|
||||||
let mut mapping_config = MappingConfig {
|
let mut mapping_config = MappingConfig {
|
||||||
mappings: Default::default(),
|
mappings: Default::default(),
|
||||||
@@ -316,11 +332,13 @@ fn run_interactive(
|
|||||||
target_path: Option<Utf8PlatformPathBuf>,
|
target_path: Option<Utf8PlatformPathBuf>,
|
||||||
base_path: Option<Utf8PlatformPathBuf>,
|
base_path: Option<Utf8PlatformPathBuf>,
|
||||||
project_config: Option<ProjectConfig>,
|
project_config: Option<ProjectConfig>,
|
||||||
|
unit_options: Option<ProjectOptions>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
|
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]")
|
let time_format = time::format_description::parse_borrowed::<2>("[hour]:[minute]:[second]")
|
||||||
.context("Failed to parse time format")?;
|
.context("Failed to parse time format")?;
|
||||||
let (diff_obj_config, mapping_config) = build_config_from_args(&args)?;
|
let (diff_obj_config, mapping_config) =
|
||||||
|
build_config_from_args(&args, project_config.as_ref(), unit_options.as_ref())?;
|
||||||
let mut state = AppState {
|
let mut state = AppState {
|
||||||
jobs: Default::default(),
|
jobs: Default::default(),
|
||||||
waker: Default::default(),
|
waker: Default::default(),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use objdiff_core::{
|
|||||||
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION,
|
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION,
|
||||||
Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
||||||
},
|
},
|
||||||
config::path::platform_path,
|
config::{ProjectObject, ProjectOptions, apply_project_options, path::platform_path},
|
||||||
diff,
|
diff,
|
||||||
obj::{self, SectionKind, SymbolFlag, SymbolKind},
|
obj::{self, SectionKind, SymbolFlag, SymbolKind},
|
||||||
};
|
};
|
||||||
@@ -83,14 +83,13 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn generate(args: GenerateArgs) -> Result<()> {
|
fn generate(args: GenerateArgs) -> Result<()> {
|
||||||
let mut diff_config = diff::DiffObjConfig {
|
let base_diff_config = diff::DiffObjConfig {
|
||||||
function_reloc_diffs: diff::FunctionRelocDiffs::None,
|
function_reloc_diffs: diff::FunctionRelocDiffs::None,
|
||||||
combine_data_sections: true,
|
combine_data_sections: true,
|
||||||
combine_text_sections: true,
|
combine_text_sections: true,
|
||||||
ppc_calculate_pool_relocations: false,
|
ppc_calculate_pool_relocations: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
apply_config_args(&mut diff_config, &args.config)?;
|
|
||||||
|
|
||||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||||
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
|
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
|
||||||
@@ -101,31 +100,44 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
|
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
|
||||||
None => bail!("No project configuration found"),
|
None => bail!("No project configuration found"),
|
||||||
};
|
};
|
||||||
info!(
|
|
||||||
"Generating report for {} units (using {} threads)",
|
|
||||||
project.units().len(),
|
|
||||||
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
|
||||||
);
|
|
||||||
|
|
||||||
let target_obj_dir =
|
let target_obj_dir =
|
||||||
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
let base_obj_dir =
|
let base_obj_dir =
|
||||||
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
let objects = project
|
let project_units = project.units.as_deref().unwrap_or_default();
|
||||||
.units
|
let objects = project_units
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.enumerate()
|
||||||
.map(|o| {
|
.map(|(idx, o)| {
|
||||||
ObjectConfig::new(o, project_dir, target_obj_dir.as_deref(), base_obj_dir.as_deref())
|
(
|
||||||
|
ObjectConfig::new(
|
||||||
|
o,
|
||||||
|
project_dir,
|
||||||
|
target_obj_dir.as_deref(),
|
||||||
|
base_obj_dir.as_deref(),
|
||||||
|
),
|
||||||
|
idx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
info!(
|
||||||
|
"Generating report for {} units (using {} threads)",
|
||||||
|
objects.len(),
|
||||||
|
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
||||||
|
);
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut units = vec![];
|
let mut units = vec![];
|
||||||
let mut existing_functions: HashSet<String> = HashSet::new();
|
let mut existing_functions: HashSet<String> = HashSet::new();
|
||||||
if args.deduplicate {
|
if args.deduplicate {
|
||||||
// If deduplicating, we need to run single-threaded
|
// If deduplicating, we need to run single-threaded
|
||||||
for object in &objects {
|
for (object, unit_idx) in &objects {
|
||||||
|
let diff_config = build_unit_diff_config(
|
||||||
|
&base_diff_config,
|
||||||
|
project.options.as_ref(),
|
||||||
|
project_units.get(*unit_idx).and_then(ProjectObject::options),
|
||||||
|
&args.config,
|
||||||
|
)?;
|
||||||
if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))?
|
if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))?
|
||||||
{
|
{
|
||||||
units.push(unit);
|
units.push(unit);
|
||||||
@@ -134,7 +146,15 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
let vec = objects
|
let vec = objects
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.map(|object| report_object(object, &diff_config, None))
|
.map(|(object, unit_idx)| {
|
||||||
|
let diff_config = build_unit_diff_config(
|
||||||
|
&base_diff_config,
|
||||||
|
project.options.as_ref(),
|
||||||
|
project_units.get(*unit_idx).and_then(ProjectObject::options),
|
||||||
|
&args.config,
|
||||||
|
)?;
|
||||||
|
report_object(object, &diff_config, None)
|
||||||
|
})
|
||||||
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
||||||
units = vec.into_iter().flatten().collect();
|
units = vec.into_iter().flatten().collect();
|
||||||
}
|
}
|
||||||
@@ -156,6 +176,24 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_unit_diff_config(
|
||||||
|
base: &diff::DiffObjConfig,
|
||||||
|
project_options: Option<&ProjectOptions>,
|
||||||
|
unit_options: Option<&ProjectOptions>,
|
||||||
|
cli_args: &[String],
|
||||||
|
) -> Result<diff::DiffObjConfig> {
|
||||||
|
let mut diff_config = base.clone();
|
||||||
|
if let Some(options) = project_options {
|
||||||
|
apply_project_options(&mut diff_config, options)?;
|
||||||
|
}
|
||||||
|
if let Some(options) = unit_options {
|
||||||
|
apply_project_options(&mut diff_config, options)?;
|
||||||
|
}
|
||||||
|
// CLI args override project and unit options
|
||||||
|
apply_config_args(&mut diff_config, cli_args)?;
|
||||||
|
Ok(diff_config)
|
||||||
|
}
|
||||||
|
|
||||||
fn report_object(
|
fn report_object(
|
||||||
object: &ObjectConfig,
|
object: &ObjectConfig,
|
||||||
diff_config: &diff::DiffObjConfig,
|
diff_config: &diff::DiffObjConfig,
|
||||||
|
|||||||
+14
-11
@@ -41,7 +41,8 @@ any-arch = [
|
|||||||
"dep:regex",
|
"dep:regex",
|
||||||
"dep:similar",
|
"dep:similar",
|
||||||
"dep:syn",
|
"dep:syn",
|
||||||
"dep:encoding_rs"
|
"dep:encoding_rs",
|
||||||
|
"demangler",
|
||||||
]
|
]
|
||||||
bindings = [
|
bindings = [
|
||||||
"dep:prost",
|
"dep:prost",
|
||||||
@@ -88,38 +89,37 @@ std = [
|
|||||||
]
|
]
|
||||||
mips = [
|
mips = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
"dep:cpp_demangle",
|
|
||||||
"dep:cwdemangle",
|
|
||||||
"dep:rabbitizer",
|
"dep:rabbitizer",
|
||||||
]
|
]
|
||||||
ppc = [
|
ppc = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
"dep:cwdemangle",
|
|
||||||
"dep:cwextab",
|
"dep:cwextab",
|
||||||
"dep:powerpc",
|
"dep:powerpc",
|
||||||
"dep:rlwinmdec",
|
"dep:rlwinmdec",
|
||||||
]
|
]
|
||||||
x86 = [
|
x86 = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
"dep:cpp_demangle",
|
|
||||||
"dep:iced-x86",
|
"dep:iced-x86",
|
||||||
"dep:msvc-demangler",
|
|
||||||
]
|
]
|
||||||
arm = [
|
arm = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
"dep:arm-attr",
|
"dep:arm-attr",
|
||||||
"dep:cpp_demangle",
|
|
||||||
"dep:unarm",
|
"dep:unarm",
|
||||||
]
|
]
|
||||||
arm64 = [
|
arm64 = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
"dep:cpp_demangle",
|
|
||||||
"dep:yaxpeax-arch",
|
"dep:yaxpeax-arch",
|
||||||
"dep:yaxpeax-arm",
|
"dep:yaxpeax-arm",
|
||||||
]
|
]
|
||||||
superh = [
|
superh = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
]
|
]
|
||||||
|
demangler = [
|
||||||
|
"dep:cpp_demangle",
|
||||||
|
"dep:cwdemangle",
|
||||||
|
"dep:gnuv2_demangle",
|
||||||
|
"dep:msvc-demangler",
|
||||||
|
]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["all"]
|
features = ["all"]
|
||||||
@@ -150,7 +150,6 @@ gimli = { git = "https://github.com/gimli-rs/gimli", rev = "7335f00e7c39fd501511
|
|||||||
typed-arena = { version = "2.0", default-features = false, optional = true }
|
typed-arena = { version = "2.0", default-features = false, optional = true }
|
||||||
|
|
||||||
# ppc
|
# ppc
|
||||||
cwdemangle = { version = "1.0", optional = true }
|
|
||||||
cwextab = { version = "1.1", optional = true }
|
cwextab = { version = "1.1", optional = true }
|
||||||
powerpc = { version = "0.4", optional = true }
|
powerpc = { version = "0.4", optional = true }
|
||||||
rlwinmdec = { version = "1.1", optional = true }
|
rlwinmdec = { version = "1.1", optional = true }
|
||||||
@@ -159,9 +158,7 @@ rlwinmdec = { version = "1.1", optional = true }
|
|||||||
rabbitizer = { version = "2.0.0-alpha.4", default-features = false, features = ["all_extensions"], optional = true }
|
rabbitizer = { version = "2.0.0-alpha.4", default-features = false, features = ["all_extensions"], optional = true }
|
||||||
|
|
||||||
# x86
|
# x86
|
||||||
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
|
|
||||||
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
|
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
|
||||||
msvc-demangler = { version = "0.11", optional = true }
|
|
||||||
|
|
||||||
# arm
|
# arm
|
||||||
unarm = { version = "1.9", optional = true }
|
unarm = { version = "1.9", optional = true }
|
||||||
@@ -179,6 +176,12 @@ tempfile = { version = "3.20", optional = true }
|
|||||||
time = { version = "0.3", optional = true }
|
time = { version = "0.3", optional = true }
|
||||||
encoding_rs = { version = "0.8.35", optional = true }
|
encoding_rs = { version = "0.8.35", optional = true }
|
||||||
|
|
||||||
|
# demangler
|
||||||
|
cpp_demangle = { version = "0.4", optional = true, default-features = false, features = ["alloc"] }
|
||||||
|
cwdemangle = { version = "1.0", optional = true }
|
||||||
|
gnuv2_demangle = { version = "0.1.0", optional = true }
|
||||||
|
msvc-demangler = { version = "0.11", optional = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = { version = "0.3", optional = true, features = ["winbase"] }
|
winapi = { version = "0.3", optional = true, features = ["winbase"] }
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,42 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "demangler",
|
||||||
|
"type": "choice",
|
||||||
|
"default": "auto",
|
||||||
|
"name": "Demangler",
|
||||||
|
"description": "Which demangler should be used to demangle each symbol.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "auto",
|
||||||
|
"name": "Auto",
|
||||||
|
"description": "Try to automatically guess the mangling format."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "none",
|
||||||
|
"name": "None",
|
||||||
|
"description": "Disable demangling."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "codewarrior",
|
||||||
|
"name": "CodeWarrior"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "itanium",
|
||||||
|
"name": "Itanium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "msvc",
|
||||||
|
"name": "MSVC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "gnu_legacy",
|
||||||
|
"name": "GNU g++ (Legacy)",
|
||||||
|
"description": "Use the old GNU mangling ABI. Used up to g++ 2.9.x"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "analyzeDataFlow",
|
"id": "analyzeDataFlow",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -158,6 +194,10 @@
|
|||||||
"value": "o32",
|
"value": "o32",
|
||||||
"name": "O32"
|
"name": "O32"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"value": "o64",
|
||||||
|
"name": "O64"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"value": "n32",
|
"value": "n32",
|
||||||
"name": "N32"
|
"name": "N32"
|
||||||
@@ -165,6 +205,14 @@
|
|||||||
{
|
{
|
||||||
"value": "n64",
|
"value": "n64",
|
||||||
"name": "N64"
|
"name": "N64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "eabi32",
|
||||||
|
"name": "eabi32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "eabi64",
|
||||||
|
"name": "eabi64"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -247,6 +295,7 @@
|
|||||||
"name": "General",
|
"name": "General",
|
||||||
"properties": [
|
"properties": [
|
||||||
"functionRelocDiffs",
|
"functionRelocDiffs",
|
||||||
|
"demangler",
|
||||||
"spaceBetweenArgs",
|
"spaceBetweenArgs",
|
||||||
"combineDataSections",
|
"combineDataSections",
|
||||||
"combineTextSections"
|
"combineTextSections"
|
||||||
@@ -268,26 +317,17 @@
|
|||||||
{
|
{
|
||||||
"id": "mips",
|
"id": "mips",
|
||||||
"name": "MIPS",
|
"name": "MIPS",
|
||||||
"properties": [
|
"properties": ["mips.abi", "mips.instrCategory", "mips.registerPrefix"]
|
||||||
"mips.abi",
|
|
||||||
"mips.instrCategory",
|
|
||||||
"mips.registerPrefix"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "ppc",
|
"id": "ppc",
|
||||||
"name": "PowerPC",
|
"name": "PowerPC",
|
||||||
"properties": [
|
"properties": ["ppc.calculatePoolRelocations", "analyzeDataFlow"]
|
||||||
"ppc.calculatePoolRelocations",
|
|
||||||
"analyzeDataFlow"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "x86",
|
"id": "x86",
|
||||||
"name": "x86",
|
"name": "x86",
|
||||||
"properties": [
|
"properties": ["x86.formatter"]
|
||||||
"x86.formatter"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
use alloc::{
|
use alloc::{collections::BTreeMap, format, string::ToString, vec::Vec};
|
||||||
collections::BTreeMap,
|
|
||||||
format,
|
|
||||||
string::{String, ToString},
|
|
||||||
vec::Vec,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use arm_attr::{BuildAttrs, enums::CpuArch, tag::Tag};
|
use arm_attr::{BuildAttrs, enums::CpuArch, tag::Tag};
|
||||||
@@ -11,7 +6,7 @@ use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, el
|
|||||||
use unarm::{args, arm, thumb};
|
use unarm::{args, arm, thumb};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::{Arch, RelocationOverride, RelocationOverrideTarget},
|
arch::{Arch, OPCODE_DATA, OPCODE_INVALID, RelocationOverride, RelocationOverrideTarget},
|
||||||
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, display::InstructionPart},
|
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, display::InstructionPart},
|
||||||
obj::{
|
obj::{
|
||||||
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
|
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
|
||||||
@@ -164,7 +159,7 @@ impl ArchArm {
|
|||||||
}
|
}
|
||||||
_ => bail!("Invalid instruction size {}", ins_ref.size),
|
_ => bail!("Invalid instruction size {}", ins_ref.size),
|
||||||
};
|
};
|
||||||
let (ins, parsed_ins) = if ins_ref.opcode == u16::MAX {
|
let (ins, parsed_ins) = if ins_ref.opcode == OPCODE_DATA {
|
||||||
let mut args = args::Arguments::default();
|
let mut args = args::Arguments::default();
|
||||||
args[0] = args::Argument::UImm(code);
|
args[0] = args::Argument::UImm(code);
|
||||||
let mnemonic = if ins_ref.size == 4 { ".word" } else { ".hword" };
|
let mnemonic = if ins_ref.size == 4 { ".word" } else { ".hword" };
|
||||||
@@ -238,7 +233,7 @@ impl Arch for ArchArm {
|
|||||||
ops.push(InstructionRef {
|
ops.push(InstructionRef {
|
||||||
address: address as u64,
|
address: address as u64,
|
||||||
size: data.len() as u8,
|
size: data.len() as u8,
|
||||||
opcode: u16::MAX,
|
opcode: OPCODE_DATA,
|
||||||
branch_dest: None,
|
branch_dest: None,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -257,7 +252,7 @@ impl Arch for ArchArm {
|
|||||||
ops.push(InstructionRef {
|
ops.push(InstructionRef {
|
||||||
address: address as u64,
|
address: address as u64,
|
||||||
size: ins_size as u8,
|
size: ins_size as u8,
|
||||||
opcode: u16::MAX,
|
opcode: OPCODE_INVALID,
|
||||||
branch_dest: None,
|
branch_dest: None,
|
||||||
});
|
});
|
||||||
address += ins_size as u32;
|
address += ins_size as u32;
|
||||||
@@ -318,7 +313,7 @@ impl Arch for ArchArm {
|
|||||||
};
|
};
|
||||||
(opcode, branch_dest)
|
(opcode, branch_dest)
|
||||||
}
|
}
|
||||||
unarm::ParseMode::Data => (u16::MAX, None),
|
unarm::ParseMode::Data => (OPCODE_DATA, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
ops.push(InstructionRef {
|
ops.push(InstructionRef {
|
||||||
@@ -409,12 +404,6 @@ impl Arch for ArchArm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn demangle(&self, name: &str) -> Option<String> {
|
|
||||||
cpp_demangle::Symbol::new(name)
|
|
||||||
.ok()
|
|
||||||
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||||
match flags {
|
match flags {
|
||||||
RelocationFlags::Elf(r_type) => match r_type {
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
@@ -471,12 +460,16 @@ impl Arch for ArchArm {
|
|||||||
section: &Section,
|
section: &Section,
|
||||||
mut next_address: u64,
|
mut next_address: u64,
|
||||||
) -> Result<u64> {
|
) -> Result<u64> {
|
||||||
// Trim any trailing 4-byte zeroes from the end (padding)
|
// TODO: This should probably check the disasm mode and trim accordingly,
|
||||||
while next_address >= symbol.address + 4
|
// but self.disasm_modes isn't populated until post_init, so it needs a refactor.
|
||||||
&& let Some(data) = section.data_range(next_address - 4, 4)
|
|
||||||
&& data == [0u8; 4]
|
// Trim any trailing 2-byte zeroes from the end (padding)
|
||||||
|
while next_address >= symbol.address + 2
|
||||||
|
&& let Some(data) = section.data_range(next_address - 2, 2)
|
||||||
|
&& data == [0u8; 2]
|
||||||
|
&& section.relocation_at(next_address - 2, 2).is_none()
|
||||||
{
|
{
|
||||||
next_address -= 4;
|
next_address -= 2;
|
||||||
}
|
}
|
||||||
Ok(next_address.saturating_sub(symbol.address))
|
Ok(next_address.saturating_sub(symbol.address))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
use alloc::{
|
use alloc::{format, string::ToString, vec::Vec};
|
||||||
format,
|
|
||||||
string::{String, ToString},
|
|
||||||
vec::Vec,
|
|
||||||
};
|
|
||||||
use core::cmp::Ordering;
|
use core::cmp::Ordering;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -14,7 +10,7 @@ use yaxpeax_arm::armv8::a64::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::Arch,
|
arch::{Arch, OPCODE_INVALID},
|
||||||
diff::{DiffObjConfig, display::InstructionPart},
|
diff::{DiffObjConfig, display::InstructionPart},
|
||||||
obj::{
|
obj::{
|
||||||
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
|
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
|
||||||
@@ -60,7 +56,7 @@ impl Arch for ArchArm64 {
|
|||||||
ops.push(InstructionRef {
|
ops.push(InstructionRef {
|
||||||
address,
|
address,
|
||||||
size: 4,
|
size: 4,
|
||||||
opcode: u16::MAX,
|
opcode: OPCODE_INVALID,
|
||||||
branch_dest: None,
|
branch_dest: None,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
@@ -87,7 +83,7 @@ impl Arch for ArchArm64 {
|
|||||||
let decoder = InstDecoder::default();
|
let decoder = InstDecoder::default();
|
||||||
let mut ins = Instruction::default();
|
let mut ins = Instruction::default();
|
||||||
if decoder.decode_into(&mut ins, &mut reader).is_err() {
|
if decoder.decode_into(&mut ins, &mut reader).is_err() {
|
||||||
cb(InstructionPart::opcode("<invalid>", u16::MAX))?;
|
cb(InstructionPart::opcode("<invalid>", OPCODE_INVALID))?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,12 +104,6 @@ impl Arch for ArchArm64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn demangle(&self, name: &str) -> Option<String> {
|
|
||||||
cpp_demangle::Symbol::new(name)
|
|
||||||
.ok()
|
|
||||||
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||||
match flags {
|
match flags {
|
||||||
RelocationFlags::Elf(r_type) => match r_type {
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
@@ -2295,7 +2285,7 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
// Opcode is #[repr(u16)], but the tuple variants negate that, so we have to do this instead.
|
// Opcode is #[repr(u16)], but the tuple variants negate that, so we have to do this instead.
|
||||||
const fn opcode_to_u16(opcode: Opcode) -> u16 {
|
const fn opcode_to_u16(opcode: Opcode) -> u16 {
|
||||||
match opcode {
|
match opcode {
|
||||||
Opcode::Invalid => u16::MAX,
|
Opcode::Invalid => OPCODE_INVALID,
|
||||||
Opcode::UDF => 0,
|
Opcode::UDF => 0,
|
||||||
Opcode::MOVN => 1,
|
Opcode::MOVN => 1,
|
||||||
Opcode::MOVK => 2,
|
Opcode::MOVK => 2,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use alloc::{
|
use alloc::{
|
||||||
collections::{BTreeMap, BTreeSet},
|
collections::{BTreeMap, BTreeSet},
|
||||||
string::{String, ToString},
|
string::ToString,
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,8 +46,11 @@ impl ArchMips {
|
|||||||
object::FileFlags::None => {}
|
object::FileFlags::None => {}
|
||||||
object::FileFlags::Elf { e_flags, .. } => {
|
object::FileFlags::Elf { e_flags, .. } => {
|
||||||
abi = match e_flags & EF_MIPS_ABI {
|
abi = match e_flags & EF_MIPS_ABI {
|
||||||
elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32,
|
elf::EF_MIPS_ABI_O32 => Abi::O32,
|
||||||
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32,
|
elf::EF_MIPS_ABI_O64 if e_flags & elf::EF_MIPS_ABI2 != 0 => Abi::N64,
|
||||||
|
elf::EF_MIPS_ABI_O64 => Abi::O64,
|
||||||
|
elf::EF_MIPS_ABI_EABI32 => Abi::EABI32,
|
||||||
|
elf::EF_MIPS_ABI_EABI64 => Abi::EABI64,
|
||||||
_ => {
|
_ => {
|
||||||
if e_flags & elf::EF_MIPS_ABI2 != 0 {
|
if e_flags & elf::EF_MIPS_ABI2 != 0 {
|
||||||
Abi::N32
|
Abi::N32
|
||||||
@@ -170,8 +173,11 @@ impl ArchMips {
|
|||||||
.with_abi(match diff_config.mips_abi {
|
.with_abi(match diff_config.mips_abi {
|
||||||
MipsAbi::Auto => self.abi,
|
MipsAbi::Auto => self.abi,
|
||||||
MipsAbi::O32 => Abi::O32,
|
MipsAbi::O32 => Abi::O32,
|
||||||
|
MipsAbi::O64 => Abi::O64,
|
||||||
MipsAbi::N32 => Abi::N32,
|
MipsAbi::N32 => Abi::N32,
|
||||||
MipsAbi::N64 => Abi::N64,
|
MipsAbi::N64 => Abi::N64,
|
||||||
|
MipsAbi::Eabi32 => Abi::EABI32,
|
||||||
|
MipsAbi::Eabi64 => Abi::EABI64,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,6 +293,7 @@ impl Arch for ArchMips {
|
|||||||
}
|
}
|
||||||
elf::R_MIPS_PC16 => 0, // PC-relative relocation
|
elf::R_MIPS_PC16 => 0, // PC-relative relocation
|
||||||
R_MIPS15_S3 => ((code & 0x001FFFC0) >> 3) as i64,
|
R_MIPS15_S3 => ((code & 0x001FFFC0) >> 3) as i64,
|
||||||
|
elf::R_MIPS_GPREL32 => (code as i32 as i64) + self.ri_gp_value as i64,
|
||||||
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
|
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
|
||||||
};
|
};
|
||||||
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
|
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
|
||||||
@@ -298,13 +305,6 @@ impl Arch for ArchMips {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn demangle(&self, name: &str) -> Option<String> {
|
|
||||||
cpp_demangle::Symbol::new(name)
|
|
||||||
.ok()
|
|
||||||
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
|
||||||
.or_else(|| cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||||
match flags {
|
match flags {
|
||||||
RelocationFlags::Elf(r_type) => match r_type {
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
@@ -319,6 +319,7 @@ impl Arch for ArchMips {
|
|||||||
elf::R_MIPS_GOT16 => Some("R_MIPS_GOT16"),
|
elf::R_MIPS_GOT16 => Some("R_MIPS_GOT16"),
|
||||||
elf::R_MIPS_PC16 => Some("R_MIPS_PC16"),
|
elf::R_MIPS_PC16 => Some("R_MIPS_PC16"),
|
||||||
elf::R_MIPS_CALL16 => Some("R_MIPS_CALL16"),
|
elf::R_MIPS_CALL16 => Some("R_MIPS_CALL16"),
|
||||||
|
elf::R_MIPS_GPREL32 => Some("R_MIPS_GPREL32"),
|
||||||
R_MIPS15_S3 => Some("R_MIPS15_S3"),
|
R_MIPS15_S3 => Some("R_MIPS15_S3"),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
@@ -356,6 +357,7 @@ impl Arch for ArchMips {
|
|||||||
while new_address >= symbol.address + 4
|
while new_address >= symbol.address + 4
|
||||||
&& let Some(data) = section.data_range(new_address - 4, 4)
|
&& let Some(data) = section.data_range(new_address - 4, 4)
|
||||||
&& data == [0u8; 4]
|
&& data == [0u8; 4]
|
||||||
|
&& section.relocation_at(next_address - 4, 4).is_none()
|
||||||
{
|
{
|
||||||
new_address -= 4;
|
new_address -= 4;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ pub mod superh;
|
|||||||
#[cfg(feature = "x86")]
|
#[cfg(feature = "x86")]
|
||||||
pub mod x86;
|
pub mod x86;
|
||||||
|
|
||||||
|
pub const OPCODE_INVALID: u16 = u16::MAX;
|
||||||
|
pub const OPCODE_DATA: u16 = u16::MAX - 1;
|
||||||
|
|
||||||
/// Represents the type of data associated with an instruction
|
/// Represents the type of data associated with an instruction
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub enum DataType {
|
pub enum DataType {
|
||||||
@@ -368,8 +371,6 @@ pub trait Arch: Any + Debug + Send + Sync {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn demangle(&self, _name: &str) -> Option<String> { None }
|
|
||||||
|
|
||||||
fn reloc_name(&self, _flags: RelocationFlags) -> Option<&'static str> { None }
|
fn reloc_name(&self, _flags: RelocationFlags) -> Option<&'static str> { None }
|
||||||
|
|
||||||
fn data_reloc_size(&self, flags: RelocationFlags) -> usize;
|
fn data_reloc_size(&self, flags: RelocationFlags) -> usize;
|
||||||
|
|||||||
@@ -308,18 +308,6 @@ impl Arch for ArchPpc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn demangle(&self, mut name: &str) -> Option<String> {
|
|
||||||
if name.starts_with('?') {
|
|
||||||
msvc_demangler::demangle(name, msvc_demangler::DemangleFlags::llvm()).ok()
|
|
||||||
} else {
|
|
||||||
name = name.trim_start_matches('.');
|
|
||||||
cpp_demangle::Symbol::new(name)
|
|
||||||
.ok()
|
|
||||||
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
|
||||||
.or_else(|| cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||||
match flags {
|
match flags {
|
||||||
RelocationFlags::Elf(r_type) => match r_type {
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
@@ -469,6 +457,7 @@ impl Arch for ArchPpc {
|
|||||||
while next_address >= symbol.address + 4
|
while next_address >= symbol.address + 4
|
||||||
&& let Some(data) = section.data_range(next_address - 4, 4)
|
&& let Some(data) = section.data_range(next_address - 4, 4)
|
||||||
&& data == [0u8; 4]
|
&& data == [0u8; 4]
|
||||||
|
&& section.relocation_at(next_address - 4, 4).is_none()
|
||||||
{
|
{
|
||||||
next_address -= 4;
|
next_address -= 4;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use alloc::{collections::BTreeMap, format, string::String, vec, vec::Vec};
|
use alloc::{collections::BTreeMap, format, vec, vec::Vec};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use object::elf;
|
use object::elf;
|
||||||
@@ -132,12 +132,6 @@ impl Arch for ArchSuperH {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn demangle(&self, name: &str) -> Option<String> {
|
|
||||||
cpp_demangle::Symbol::new(name)
|
|
||||||
.ok()
|
|
||||||
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||||
match flags {
|
match flags {
|
||||||
RelocationFlags::Elf(r_type) => match r_type {
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use alloc::{boxed::Box, format, string::String, vec::Vec};
|
use alloc::{boxed::Box, format, vec::Vec};
|
||||||
use core::cmp::Ordering;
|
use core::cmp::Ordering;
|
||||||
|
|
||||||
use anyhow::{Context, Result, anyhow, bail};
|
use anyhow::{Context, Result, anyhow, bail};
|
||||||
@@ -9,7 +9,7 @@ use iced_x86::{
|
|||||||
use object::{Endian as _, Object as _, ObjectSection as _, elf, pe};
|
use object::{Endian as _, Object as _, ObjectSection as _, elf, pe};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::{Arch, RelocationOverride, RelocationOverrideTarget},
|
arch::{Arch, OPCODE_DATA, RelocationOverride, RelocationOverrideTarget},
|
||||||
diff::{DiffObjConfig, X86Formatter, display::InstructionPart},
|
diff::{DiffObjConfig, X86Formatter, display::InstructionPart},
|
||||||
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, Section, Symbol},
|
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, Section, Symbol},
|
||||||
};
|
};
|
||||||
@@ -89,8 +89,6 @@ impl ArchX86 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DATA_OPCODE: u16 = u16::MAX - 1;
|
|
||||||
|
|
||||||
impl Arch for ArchX86 {
|
impl Arch for ArchX86 {
|
||||||
fn scan_instructions_internal(
|
fn scan_instructions_internal(
|
||||||
&self,
|
&self,
|
||||||
@@ -121,7 +119,7 @@ impl Arch for ArchX86 {
|
|||||||
out.push(InstructionRef {
|
out.push(InstructionRef {
|
||||||
address,
|
address,
|
||||||
size: size as u8,
|
size: size as u8,
|
||||||
opcode: DATA_OPCODE,
|
opcode: OPCODE_DATA,
|
||||||
branch_dest: None,
|
branch_dest: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -148,7 +146,7 @@ impl Arch for ArchX86 {
|
|||||||
out.push(InstructionRef {
|
out.push(InstructionRef {
|
||||||
address: indirect_array_address + i as u64,
|
address: indirect_array_address + i as u64,
|
||||||
size: 1,
|
size: 1,
|
||||||
opcode: DATA_OPCODE,
|
opcode: OPCODE_DATA,
|
||||||
branch_dest: None,
|
branch_dest: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -187,14 +185,14 @@ impl Arch for ArchX86 {
|
|||||||
diff_config: &DiffObjConfig,
|
diff_config: &DiffObjConfig,
|
||||||
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if resolved.ins_ref.opcode == DATA_OPCODE {
|
if resolved.ins_ref.opcode == OPCODE_DATA {
|
||||||
let (mnemonic, imm) = match resolved.ins_ref.size {
|
let (mnemonic, imm) = match resolved.ins_ref.size {
|
||||||
1 => (".byte", resolved.code[0] as u64),
|
1 => (".byte", resolved.code[0] as u64),
|
||||||
2 => (".word", self.endianness.read_u16_bytes(resolved.code.try_into()?) as u64),
|
2 => (".word", self.endianness.read_u16_bytes(resolved.code.try_into()?) as u64),
|
||||||
4 => (".dword", self.endianness.read_u32_bytes(resolved.code.try_into()?) as u64),
|
4 => (".dword", self.endianness.read_u32_bytes(resolved.code.try_into()?) as u64),
|
||||||
_ => bail!("Unsupported x86 inline data size {}", resolved.ins_ref.size),
|
_ => bail!("Unsupported x86 inline data size {}", resolved.ins_ref.size),
|
||||||
};
|
};
|
||||||
cb(InstructionPart::opcode(mnemonic, DATA_OPCODE))?;
|
cb(InstructionPart::opcode(mnemonic, OPCODE_DATA))?;
|
||||||
if resolved.relocation.is_some() {
|
if resolved.relocation.is_some() {
|
||||||
cb(InstructionPart::reloc())?;
|
cb(InstructionPart::reloc())?;
|
||||||
} else {
|
} else {
|
||||||
@@ -302,16 +300,6 @@ impl Arch for ArchX86 {
|
|||||||
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
|
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn demangle(&self, name: &str) -> Option<String> {
|
|
||||||
if name.starts_with('?') {
|
|
||||||
msvc_demangler::demangle(name, msvc_demangler::DemangleFlags::llvm()).ok()
|
|
||||||
} else {
|
|
||||||
cpp_demangle::Symbol::new(name)
|
|
||||||
.ok()
|
|
||||||
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||||
match self.arch {
|
match self.arch {
|
||||||
Architecture::X86 => match flags {
|
Architecture::X86 => match flags {
|
||||||
@@ -836,7 +824,7 @@ mod test {
|
|||||||
ins_ref: InstructionRef {
|
ins_ref: InstructionRef {
|
||||||
address: 0x1234,
|
address: 0x1234,
|
||||||
size: 1,
|
size: 1,
|
||||||
opcode: DATA_OPCODE,
|
opcode: OPCODE_DATA,
|
||||||
branch_dest: None,
|
branch_dest: None,
|
||||||
},
|
},
|
||||||
code: &code,
|
code: &code,
|
||||||
@@ -850,7 +838,7 @@ mod test {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(parts, &[
|
assert_eq!(parts, &[
|
||||||
InstructionPart::opcode(".byte", DATA_OPCODE),
|
InstructionPart::opcode(".byte", OPCODE_DATA),
|
||||||
InstructionPart::unsigned(0xABu64),
|
InstructionPart::unsigned(0xABu64),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod path;
|
pub mod path;
|
||||||
|
|
||||||
use alloc::{
|
use alloc::{
|
||||||
|
borrow::Cow,
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
@@ -45,6 +46,8 @@ pub struct ProjectConfig {
|
|||||||
pub units: Option<Vec<ProjectObject>>,
|
pub units: Option<Vec<ProjectObject>>,
|
||||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub progress_categories: Option<Vec<ProjectProgressCategory>>,
|
pub progress_categories: Option<Vec<ProjectProgressCategory>>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub options: Option<ProjectOptions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectConfig {
|
impl ProjectConfig {
|
||||||
@@ -116,6 +119,8 @@ pub struct ProjectObject {
|
|||||||
pub metadata: Option<ProjectObjectMetadata>,
|
pub metadata: Option<ProjectObjectMetadata>,
|
||||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub symbol_mappings: Option<BTreeMap<String, String>>,
|
pub symbol_mappings: Option<BTreeMap<String, String>>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub options: Option<ProjectOptions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
@@ -143,6 +148,15 @@ pub struct ProjectProgressCategory {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type ProjectOptions = BTreeMap<String, ProjectOptionValue>;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(untagged))]
|
||||||
|
pub enum ProjectOptionValue {
|
||||||
|
Bool(bool),
|
||||||
|
String(String),
|
||||||
|
}
|
||||||
|
|
||||||
impl ProjectObject {
|
impl ProjectObject {
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
if let Some(name) = &self.name {
|
if let Some(name) = &self.name {
|
||||||
@@ -179,6 +193,8 @@ impl ProjectObject {
|
|||||||
pub fn auto_generated(&self) -> Option<bool> {
|
pub fn auto_generated(&self) -> Option<bool> {
|
||||||
self.metadata.as_ref().and_then(|m| m.auto_generated)
|
self.metadata.as_ref().and_then(|m| m.auto_generated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn options(&self) -> Option<&ProjectOptions> { self.options.as_ref() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Eq, PartialEq)]
|
#[derive(Default, Clone, Eq, PartialEq)]
|
||||||
@@ -310,3 +326,47 @@ pub fn build_globset(vec: &[Glob]) -> Result<GlobSet, globset::Error> {
|
|||||||
}
|
}
|
||||||
builder.build()
|
builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "any-arch")]
|
||||||
|
pub fn apply_project_options(
|
||||||
|
diff_config: &mut crate::diff::DiffObjConfig,
|
||||||
|
options: &ProjectOptions,
|
||||||
|
) -> Result<()> {
|
||||||
|
use core::str::FromStr;
|
||||||
|
|
||||||
|
use crate::diff::{ConfigEnum, ConfigPropertyId, ConfigPropertyKind};
|
||||||
|
|
||||||
|
let mut result = Ok(());
|
||||||
|
for (key, value) in options.iter() {
|
||||||
|
let property_id = ConfigPropertyId::from_str(key)
|
||||||
|
.map_err(|()| anyhow!("Invalid configuration property: {key}"))?;
|
||||||
|
let value = match value {
|
||||||
|
ProjectOptionValue::Bool(value) => Cow::Borrowed(if *value { "true" } else { "false" }),
|
||||||
|
ProjectOptionValue::String(value) => Cow::Borrowed(value.as_str()),
|
||||||
|
};
|
||||||
|
if diff_config.set_property_value_str(property_id, &value).is_err() {
|
||||||
|
if result.is_err() {
|
||||||
|
// Already returning an error, skip further errors
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut expected = String::new();
|
||||||
|
match property_id.kind() {
|
||||||
|
ConfigPropertyKind::Boolean => expected.push_str("true, false"),
|
||||||
|
ConfigPropertyKind::Choice(variants) => {
|
||||||
|
for (idx, variant) in variants.iter().enumerate() {
|
||||||
|
if idx > 0 {
|
||||||
|
expected.push_str(", ");
|
||||||
|
}
|
||||||
|
expected.push_str(variant.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = Err(anyhow!(
|
||||||
|
"Invalid value for {}. Expected one of: {}",
|
||||||
|
property_id.name(),
|
||||||
|
expected
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use alloc::string::String;
|
||||||
|
|
||||||
|
use crate::diff::Demangler;
|
||||||
|
|
||||||
|
#[cfg(feature = "demangler")]
|
||||||
|
impl Demangler {
|
||||||
|
pub fn demangle(&self, name: &str) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
Demangler::None => None,
|
||||||
|
Demangler::Codewarrior => Self::demangle_codewarrior(name),
|
||||||
|
Demangler::Msvc => Self::demangle_msvc(name),
|
||||||
|
Demangler::Itanium => Self::demangle_itanium(name),
|
||||||
|
Demangler::GnuLegacy => Self::demangle_gnu_legacy(name),
|
||||||
|
Demangler::Auto => {
|
||||||
|
// Try to guess
|
||||||
|
if name.starts_with('?') {
|
||||||
|
Self::demangle_msvc(name)
|
||||||
|
} else {
|
||||||
|
Self::demangle_codewarrior(name)
|
||||||
|
.or_else(|| Self::demangle_gnu_legacy(name))
|
||||||
|
.or_else(|| Self::demangle_itanium(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn demangle_codewarrior(name: &str) -> Option<String> {
|
||||||
|
cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn demangle_msvc(name: &str) -> Option<String> {
|
||||||
|
msvc_demangler::demangle(name, msvc_demangler::DemangleFlags::llvm()).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn demangle_itanium(name: &str) -> Option<String> {
|
||||||
|
let name = name.trim_start_matches('.');
|
||||||
|
cpp_demangle::Symbol::new(name)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn demangle_gnu_legacy(name: &str) -> Option<String> {
|
||||||
|
let name = name.trim_start_matches('.');
|
||||||
|
gnuv2_demangle::demangle(name, &gnuv2_demangle::DemangleConfig::new_no_cfilt_mimics()).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "demangler"))]
|
||||||
|
impl Demangler {
|
||||||
|
pub fn demangle(&self, _name: &str) -> Option<String> { None }
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ use crate::{
|
|||||||
|
|
||||||
pub mod code;
|
pub mod code;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
|
pub mod demangler;
|
||||||
pub mod display;
|
pub mod display;
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/config.gen.rs"));
|
include!(concat!(env!("OUT_DIR"), "/config.gen.rs"));
|
||||||
|
|||||||
@@ -0,0 +1,401 @@
|
|||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result, bail, ensure};
|
||||||
|
use object::{Endianness, Object, ObjectSection};
|
||||||
|
|
||||||
|
use super::{Section, SectionKind};
|
||||||
|
|
||||||
|
const HDRR_SIZE: usize = 0x60;
|
||||||
|
const FDR_SIZE: usize = 0x48;
|
||||||
|
const PDR_SIZE: usize = 0x34;
|
||||||
|
const SYMR_SIZE: usize = 0x0c;
|
||||||
|
|
||||||
|
const ST_PROC: u8 = 6;
|
||||||
|
const ST_STATICPROC: u8 = 14;
|
||||||
|
const ST_END: u8 = 8;
|
||||||
|
|
||||||
|
pub(super) fn parse_line_info_mdebug(
|
||||||
|
obj_file: &object::File,
|
||||||
|
sections: &mut [Section],
|
||||||
|
) -> Result<()> {
|
||||||
|
let Some(section) = obj_file.section_by_name(".mdebug") else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = section.data().context("failed to read .mdebug contents")?;
|
||||||
|
if data.len() < HDRR_SIZE {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let section_file_offset = section.file_range().map(|(offset, _)| offset as usize);
|
||||||
|
|
||||||
|
let endianness = obj_file.endianness();
|
||||||
|
let header = Header::parse(data, endianness)?;
|
||||||
|
|
||||||
|
let symbols_data = slice_at(
|
||||||
|
data,
|
||||||
|
header.cb_sym_offset,
|
||||||
|
header.isym_max.checked_mul(SYMR_SIZE as u32).context("symbol table size overflow")?,
|
||||||
|
section_file_offset,
|
||||||
|
)?;
|
||||||
|
let symbols = parse_symbols(symbols_data, endianness)?;
|
||||||
|
|
||||||
|
let fdr_data = slice_at(
|
||||||
|
data,
|
||||||
|
header.cb_fd_offset,
|
||||||
|
header
|
||||||
|
.ifd_max
|
||||||
|
.checked_mul(FDR_SIZE as u32)
|
||||||
|
.context("file descriptor table size overflow")?,
|
||||||
|
section_file_offset,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
for fdr_index in 0..header.ifd_max as usize {
|
||||||
|
let fdr_offset = fdr_index * FDR_SIZE;
|
||||||
|
let fdr = FileDescriptor::parse(&fdr_data[fdr_offset..fdr_offset + FDR_SIZE], endianness)?;
|
||||||
|
if fdr.cpd == 0 || fdr.csym == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sym_base = fdr.isym_base as usize;
|
||||||
|
let sym_end = sym_base + fdr.csym as usize;
|
||||||
|
if sym_end > symbols.len() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(line_file_offset) = header.cb_line_offset.checked_add(fdr.cb_line_offset) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(line_file_base) =
|
||||||
|
resolve_offset(line_file_offset, data.len(), section_file_offset)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(line_file_end) = line_file_base.checked_add(fdr.cb_line as usize) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if line_file_end > data.len() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for local_proc_index in 0..fdr.cpd as usize {
|
||||||
|
let pdr_index = fdr.ipd_first as usize + local_proc_index;
|
||||||
|
let pdr_offset = header
|
||||||
|
.cb_pd_offset
|
||||||
|
.checked_add((pdr_index as u32) * PDR_SIZE as u32)
|
||||||
|
.context("procedure descriptor offset overflow")?;
|
||||||
|
let pdr_data = match slice_at(data, pdr_offset, PDR_SIZE as u32, section_file_offset) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
let pdr = ProcDescriptor::parse(pdr_data, endianness)?;
|
||||||
|
if pdr.isym as usize >= fdr.csym as usize {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let global_sym_index = sym_base + pdr.isym as usize;
|
||||||
|
let Some(start_symbol) = symbols.get(global_sym_index) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if start_symbol.st != ST_PROC && start_symbol.st != ST_STATICPROC {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let local_index = pdr.isym as u32;
|
||||||
|
let mut end_address = None;
|
||||||
|
for sym in &symbols[global_sym_index..sym_end] {
|
||||||
|
if sym.st == ST_END && sym.index == local_index {
|
||||||
|
end_address = Some(sym.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let Some(end_address) = end_address else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(size) = end_address.checked_sub(start_symbol.value) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if size == 0 || size % 4 != 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let word_count = (size / 4) as usize;
|
||||||
|
if word_count == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(mut cursor) = line_file_base.checked_add(pdr.cb_line_offset as usize) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if cursor >= line_file_end {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut line_number = pdr.ln_low as i32;
|
||||||
|
let mut lines = Vec::with_capacity(word_count);
|
||||||
|
while lines.len() < word_count && cursor < line_file_end {
|
||||||
|
let b0 = data[cursor];
|
||||||
|
cursor += 1;
|
||||||
|
let count = (b0 & 0x0f) as usize + 1;
|
||||||
|
let delta = decode_delta(endianness, b0 >> 4, data, &mut cursor, line_file_end)?;
|
||||||
|
line_number = line_number.wrapping_add(delta as i32);
|
||||||
|
for _ in 0..count {
|
||||||
|
if lines.len() == word_count {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lines.push(line_number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if lines.len() != word_count {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
assign_lines(sections, fdr.adr as u64 + pdr.addr as u64, &lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assign_lines(sections: &mut [Section], base_address: u64, lines: &[i32]) {
|
||||||
|
let mut address = base_address;
|
||||||
|
for &line in lines {
|
||||||
|
if line >= 0
|
||||||
|
&& let Some(section) = find_code_section(sections, address)
|
||||||
|
{
|
||||||
|
section.line_info.insert(address, line as u32);
|
||||||
|
}
|
||||||
|
address = address.wrapping_add(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_code_section(sections: &mut [Section], address: u64) -> Option<&mut Section> {
|
||||||
|
sections.iter_mut().find(|section| {
|
||||||
|
section.kind == SectionKind::Code
|
||||||
|
&& address >= section.address
|
||||||
|
&& address < section.address + section.size
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_delta(
|
||||||
|
endianness: Endianness,
|
||||||
|
nibble: u8,
|
||||||
|
data: &[u8],
|
||||||
|
cursor: &mut usize,
|
||||||
|
end: usize,
|
||||||
|
) -> Result<i32> {
|
||||||
|
if nibble == 8 {
|
||||||
|
ensure!(*cursor + 2 <= end, "extended delta out of range");
|
||||||
|
let bytes: [u8; 2] = data[*cursor..*cursor + 2].try_into().unwrap();
|
||||||
|
*cursor += 2;
|
||||||
|
Ok(match endianness {
|
||||||
|
Endianness::Big => i16::from_be_bytes(bytes) as i32,
|
||||||
|
Endianness::Little => i16::from_le_bytes(bytes) as i32,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let mut value = (nibble & 0x0f) as i32;
|
||||||
|
if value & 0x8 != 0 {
|
||||||
|
value -= 0x10;
|
||||||
|
}
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slice_at(
|
||||||
|
data: &[u8],
|
||||||
|
offset: u32,
|
||||||
|
size: u32,
|
||||||
|
section_file_offset: Option<usize>,
|
||||||
|
) -> Result<&[u8]> {
|
||||||
|
let size = size as usize;
|
||||||
|
if size == 0 {
|
||||||
|
ensure!(
|
||||||
|
resolve_offset(offset, data.len(), section_file_offset).is_some(),
|
||||||
|
"offset outside of .mdebug section"
|
||||||
|
);
|
||||||
|
return Ok(&data[0..0]);
|
||||||
|
}
|
||||||
|
let Some(offset) = resolve_offset(offset, data.len(), section_file_offset) else {
|
||||||
|
bail!("offset outside of .mdebug section");
|
||||||
|
};
|
||||||
|
let end = offset.checked_add(size).context("range overflow")?;
|
||||||
|
ensure!(end <= data.len(), "range exceeds .mdebug size");
|
||||||
|
Ok(&data[offset..end])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_offset(
|
||||||
|
offset: u32,
|
||||||
|
data_len: usize,
|
||||||
|
section_file_offset: Option<usize>,
|
||||||
|
) -> Option<usize> {
|
||||||
|
let offset = offset as usize;
|
||||||
|
if offset <= data_len {
|
||||||
|
Some(offset)
|
||||||
|
} else if let Some(file_offset) = section_file_offset {
|
||||||
|
offset.checked_sub(file_offset).filter(|rel| *rel <= data_len)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Header {
|
||||||
|
cb_line_offset: u32,
|
||||||
|
cb_pd_offset: u32,
|
||||||
|
cb_sym_offset: u32,
|
||||||
|
cb_fd_offset: u32,
|
||||||
|
isym_max: u32,
|
||||||
|
ifd_max: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header {
|
||||||
|
fn parse(data: &[u8], endianness: Endianness) -> Result<Self> {
|
||||||
|
ensure!(HDRR_SIZE <= data.len(), ".mdebug header truncated");
|
||||||
|
let mut cursor = 0;
|
||||||
|
let magic = read_u16(data, &mut cursor, endianness)?;
|
||||||
|
let _vstamp = read_u16(data, &mut cursor, endianness)?;
|
||||||
|
ensure!(magic == 0x7009, "unexpected .mdebug magic: {magic:#x}");
|
||||||
|
let _iline_max = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _cb_line = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let cb_line_offset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _idn_max = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _cb_dn_offset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _ipd_max = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let cb_pd_offset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let isym_max = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let cb_sym_offset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _iopt_max = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _cb_opt_offset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _iaux_max = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _cb_aux_offset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _iss_max = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _cb_ss_offset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _iss_ext_max = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _cb_ss_ext_offset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let ifd_max = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let cb_fd_offset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _crfd = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _cb_rfd_offset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _iext_max = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _cb_ext_offset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
|
||||||
|
Ok(Header { cb_line_offset, cb_pd_offset, cb_sym_offset, cb_fd_offset, isym_max, ifd_max })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct FileDescriptor {
|
||||||
|
adr: u32,
|
||||||
|
isym_base: u32,
|
||||||
|
csym: u32,
|
||||||
|
ipd_first: u16,
|
||||||
|
cpd: u16,
|
||||||
|
cb_line_offset: u32,
|
||||||
|
cb_line: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileDescriptor {
|
||||||
|
fn parse(data: &[u8], endianness: Endianness) -> Result<Self> {
|
||||||
|
ensure!(data.len() >= FDR_SIZE, "FDR truncated");
|
||||||
|
let mut cursor = 0;
|
||||||
|
let adr = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _rss = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _iss_base = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _cb_ss = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let isym_base = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let csym = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _iline_base = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _cline = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _iopt_base = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _copt = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let ipd_first = read_u16(data, &mut cursor, endianness)?;
|
||||||
|
let cpd = read_u16(data, &mut cursor, endianness)?;
|
||||||
|
let _iaux_base = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _caux = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _rfd_base = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _crfd = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _bits = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let cb_line_offset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let cb_line = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
|
||||||
|
Ok(FileDescriptor { adr, isym_base, csym, ipd_first, cpd, cb_line_offset, cb_line })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct ProcDescriptor {
|
||||||
|
addr: u32,
|
||||||
|
isym: u32,
|
||||||
|
ln_low: i32,
|
||||||
|
cb_line_offset: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcDescriptor {
|
||||||
|
fn parse(data: &[u8], endianness: Endianness) -> Result<Self> {
|
||||||
|
ensure!(data.len() >= PDR_SIZE, "PDR truncated");
|
||||||
|
let mut cursor = 0;
|
||||||
|
let addr = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let isym = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _iline = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _regmask = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _regoffset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _iopt = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _fregmask = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _fregoffset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _frameoffset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let _framereg = read_u16(data, &mut cursor, endianness)?;
|
||||||
|
let _pcreg = read_u16(data, &mut cursor, endianness)?;
|
||||||
|
let ln_low = read_i32(data, &mut cursor, endianness)?;
|
||||||
|
let _ln_high = read_i32(data, &mut cursor, endianness)?;
|
||||||
|
let cb_line_offset = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
|
||||||
|
Ok(ProcDescriptor { addr, isym, ln_low, cb_line_offset })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct SymbolEntry {
|
||||||
|
value: u32,
|
||||||
|
st: u8,
|
||||||
|
index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_symbols(data: &[u8], endianness: Endianness) -> Result<Vec<SymbolEntry>> {
|
||||||
|
ensure!(data.len().is_multiple_of(SYMR_SIZE), "symbol table misaligned");
|
||||||
|
let mut symbols = Vec::with_capacity(data.len() / SYMR_SIZE);
|
||||||
|
let mut cursor = 0;
|
||||||
|
while cursor + SYMR_SIZE <= data.len() {
|
||||||
|
let _iss = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let value = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let bits = read_u32(data, &mut cursor, endianness)?;
|
||||||
|
let (st, index) = match endianness {
|
||||||
|
Endianness::Big => (((bits >> 26) & 0x3f) as u8, bits & 0x000f_ffff),
|
||||||
|
Endianness::Little => (((bits & 0x3f) as u8), (bits >> 12) & 0x000f_ffff),
|
||||||
|
};
|
||||||
|
symbols.push(SymbolEntry { value, st, index });
|
||||||
|
}
|
||||||
|
Ok(symbols)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u16(data: &[u8], cursor: &mut usize, endianness: Endianness) -> Result<u16> {
|
||||||
|
ensure!(*cursor + 2 <= data.len(), "unexpected EOF while reading u16");
|
||||||
|
let bytes: [u8; 2] = data[*cursor..*cursor + 2].try_into().unwrap();
|
||||||
|
*cursor += 2;
|
||||||
|
Ok(match endianness {
|
||||||
|
Endianness::Big => u16::from_be_bytes(bytes),
|
||||||
|
Endianness::Little => u16::from_le_bytes(bytes),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u32(data: &[u8], cursor: &mut usize, endianness: Endianness) -> Result<u32> {
|
||||||
|
ensure!(*cursor + 4 <= data.len(), "unexpected EOF while reading u32");
|
||||||
|
let bytes: [u8; 4] = data[*cursor..*cursor + 4].try_into().unwrap();
|
||||||
|
*cursor += 4;
|
||||||
|
Ok(match endianness {
|
||||||
|
Endianness::Big => u32::from_be_bytes(bytes),
|
||||||
|
Endianness::Little => u32::from_le_bytes(bytes),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_i32(data: &[u8], cursor: &mut usize, endianness: Endianness) -> Result<i32> {
|
||||||
|
Ok(read_u32(data, cursor, endianness)? as i32)
|
||||||
|
}
|
||||||
+16
-14
@@ -1,5 +1,6 @@
|
|||||||
#[cfg(feature = "dwarf")]
|
#[cfg(feature = "dwarf")]
|
||||||
mod dwarf2;
|
mod dwarf2;
|
||||||
|
mod mdebug;
|
||||||
pub mod read;
|
pub mod read;
|
||||||
pub mod split_meta;
|
pub mod split_meta;
|
||||||
|
|
||||||
@@ -107,32 +108,33 @@ impl Section {
|
|||||||
// The alignment to use when "Combine data/text sections" is enabled.
|
// The alignment to use when "Combine data/text sections" is enabled.
|
||||||
pub fn combined_alignment(&self) -> u64 {
|
pub fn combined_alignment(&self) -> u64 {
|
||||||
const MIN_ALIGNMENT: u64 = 4;
|
const MIN_ALIGNMENT: u64 = 4;
|
||||||
self.align.map(|align| align.get().max(MIN_ALIGNMENT)).unwrap_or(MIN_ALIGNMENT)
|
self.align.map_or(MIN_ALIGNMENT, |align| align.get().max(MIN_ALIGNMENT))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn relocation_at<'obj>(
|
pub fn relocation_at(&self, address: u64, size: u8) -> Option<&Relocation> {
|
||||||
&'obj self,
|
match self.relocations.binary_search_by_key(&address, |r| r.address) {
|
||||||
obj: &'obj Object,
|
|
||||||
ins_ref: InstructionRef,
|
|
||||||
) -> Option<ResolvedRelocation<'obj>> {
|
|
||||||
match self.relocations.binary_search_by_key(&ins_ref.address, |r| r.address) {
|
|
||||||
Ok(mut i) => {
|
Ok(mut i) => {
|
||||||
// Find the first relocation at the address
|
// Find the first relocation at the address
|
||||||
while i
|
while i
|
||||||
.checked_sub(1)
|
.checked_sub(1)
|
||||||
.and_then(|n| self.relocations.get(n))
|
.and_then(|n| self.relocations.get(n))
|
||||||
.is_some_and(|r| r.address == ins_ref.address)
|
.is_some_and(|r| r.address == address)
|
||||||
{
|
{
|
||||||
i -= 1;
|
i -= 1;
|
||||||
}
|
}
|
||||||
self.relocations.get(i)
|
self.relocations.get(i)
|
||||||
}
|
}
|
||||||
Err(i) => self
|
Err(i) => self.relocations.get(i).filter(|r| r.address < address + size as u64),
|
||||||
.relocations
|
|
||||||
.get(i)
|
|
||||||
.filter(|r| r.address < ins_ref.address + ins_ref.size as u64),
|
|
||||||
}
|
}
|
||||||
.and_then(|relocation| {
|
}
|
||||||
|
|
||||||
|
pub fn resolve_relocation_at<'obj>(
|
||||||
|
&'obj self,
|
||||||
|
obj: &'obj Object,
|
||||||
|
address: u64,
|
||||||
|
size: u8,
|
||||||
|
) -> Option<ResolvedRelocation<'obj>> {
|
||||||
|
self.relocation_at(address, size).and_then(|relocation| {
|
||||||
let symbol = obj.symbols.get(relocation.target_symbol)?;
|
let symbol = obj.symbols.get(relocation.target_symbol)?;
|
||||||
Some(ResolvedRelocation { relocation, symbol })
|
Some(ResolvedRelocation { relocation, symbol })
|
||||||
})
|
})
|
||||||
@@ -316,7 +318,7 @@ impl Object {
|
|||||||
let section = self.sections.get(section_index)?;
|
let section = self.sections.get(section_index)?;
|
||||||
let offset = ins_ref.address.checked_sub(section.address)?;
|
let offset = ins_ref.address.checked_sub(section.address)?;
|
||||||
let code = section.data.get(offset as usize..offset as usize + ins_ref.size as usize)?;
|
let code = section.data.get(offset as usize..offset as usize + ins_ref.size as usize)?;
|
||||||
let relocation = section.relocation_at(self, ins_ref);
|
let relocation = section.resolve_relocation_at(self, ins_ref.address, ins_ref.size);
|
||||||
Some(ResolvedInstructionRef {
|
Some(ResolvedInstructionRef {
|
||||||
ins_ref,
|
ins_ref,
|
||||||
symbol_index,
|
symbol_index,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user