mirror of
https://github.com/lief-project/LIEF.git
synced 2026-10-11 22:09:53 +00:00
tools: Add Rust DWARF reconstruction tool
This commit is contained in:
@@ -72,6 +72,7 @@ ELF, PE, Mach-O, COFF, Android formats, and more in C++, Python, and Rust.
|
||||
:caption: <i class="fa-solid fa-toolbox"> </i>Tools
|
||||
:maxdepth: 1
|
||||
|
||||
tools/lief-dwarfdump/index
|
||||
tools/lief-patchelf/index
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
(tools-lief-dwarfdump)=
|
||||
|
||||
# {fa}`solid fa-bars-staggered` lief-dwarfdump
|
||||
|
||||
`lief-dwarfdump` reconstructs C/C++ declarations from the DWARF debug
|
||||
information of a binary. It is a Rust command-line front end for the
|
||||
{ref}`declaration <extended-dwarf-to-decl>` API.
|
||||
|
||||
:::{admonition} LIEF Extended
|
||||
:class: warning
|
||||
|
||||
DWARF support is an {ref}`extended <extended-intro>` feature.
|
||||
:::
|
||||
|
||||
## Usage
|
||||
|
||||
The tool takes the binary or the DWARF file to read, and generates the C/C++
|
||||
declaration.
|
||||
|
||||
### Complete Dump
|
||||
|
||||
Without any options, `lief-dwarfdump` prints every compilation unit:
|
||||
|
||||
```bash
|
||||
$ lief-dwarfdump liblinker
|
||||
/*
|
||||
* Producer: Binary Ninja DWARF Export Plugin
|
||||
*/
|
||||
|
||||
/*
|
||||
* Addr: 0x0000
|
||||
* size: 0x0040
|
||||
*/
|
||||
static Elf64_Header __elf_header;
|
||||
|
||||
[...]
|
||||
|
||||
/*
|
||||
* Address: 0xec50
|
||||
*/
|
||||
void _exit(int status);
|
||||
```
|
||||
|
||||
By default, only functions and variables are rendered. `--show-types` can be
|
||||
used to emit the definition of the types they reference:
|
||||
|
||||
```bash
|
||||
$ lief-dwarfdump --show-types liblinker
|
||||
```
|
||||
|
||||
### By Address
|
||||
|
||||
`--address` selects a single function by its virtual address, along with its
|
||||
stack variables.
|
||||
|
||||
```bash
|
||||
$ lief-dwarfdump --address 0x1ed4 libdexprotector.so.dwarf
|
||||
/*
|
||||
* Address: 0x1ed4
|
||||
*/
|
||||
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
/* Start: 0x001eec */ {
|
||||
/* Start: 0x001ef8 */ {
|
||||
Call the JNI_OnLoad from the payload loaded by the first DT_INIT_ARRAY constructor
|
||||
} /* End: 0x001efc */
|
||||
} /* End: 0x001f10 */
|
||||
}
|
||||
```
|
||||
|
||||
### By Name
|
||||
|
||||
`--function` selects a single function by its (demangled) name:
|
||||
|
||||
```bash
|
||||
$ lief-dwarfdump --function dp_mmap_payload libdexprotector.so.dwarf
|
||||
/*
|
||||
* Address: 0x0c44
|
||||
*/
|
||||
bool dp_mmap_payload(void *start, unsigned long long size, mmap_info_t *info) {
|
||||
/*
|
||||
* Stack addr: -0x00f8
|
||||
* size: 0x0038
|
||||
*/
|
||||
cipher_context_t cipher_ctx;
|
||||
[...]
|
||||
}
|
||||
```
|
||||
|
||||
### By Type
|
||||
|
||||
`--type` selects a single type by its short name:
|
||||
|
||||
```bash
|
||||
$ lief-dwarfdump --type key_t libdexprotector.so.dwarf
|
||||
unsigned char[32]
|
||||
```
|
||||
|
||||
## Compilation
|
||||
|
||||
`lief-dwarfdump` links against the {ref}`Extended Rust SDK <extended-intro>`.
|
||||
Set {ref}`LIEF_RUST_PRECOMPILED <lief-rust-precompiled>` at the extracted SDK,
|
||||
then build the tool with `cargo` from the `tools/lief-dwarfdump` directory:
|
||||
|
||||
```bash
|
||||
$ export LIEF_RUST_PRECOMPILED=$(pwd)/LIEF-extended-rust-0.16.0.2378-Linux-x86_64
|
||||
$ cargo build [--release]
|
||||
$ ./target/{release,debug}/lief-dwarfdump --version
|
||||
```
|
||||
|
||||
## Man Page
|
||||
|
||||
Given the `lief-dwarfdump` binary, you can generate a man page using the
|
||||
following command:
|
||||
|
||||
```bash
|
||||
$ lief-dwarfdump --generate-manpage ./lief-dwarfdump.1
|
||||
```
|
||||
|
||||
This functionality is provided by [clap_mangen](https://crates.io/crates/clap_mangen).
|
||||
|
||||
## Shell Completion
|
||||
|
||||
Thanks to [clap](https://github.com/clap-rs/clap) and its `clap_complete`
|
||||
extension, you can generate auto-completion for various shells:
|
||||
|
||||
```bash
|
||||
$ ./lief-dwarfdump --generate {bash, elvish, fish, powershell, zsh}
|
||||
```
|
||||
@@ -19,7 +19,7 @@ the two versions.
|
||||
## Compilation
|
||||
|
||||
You can build `lief-patchelf` using `cargo` from the
|
||||
`src/tools/lief-patchelf` directory with the following commands:
|
||||
`tools/lief-patchelf` directory with the following commands:
|
||||
|
||||
```bash
|
||||
$ cargo build [--release]
|
||||
|
||||
Generated
+13
@@ -904,6 +904,19 @@ dependencies = [
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lief-dwarfdump"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"clap_mangen",
|
||||
"common",
|
||||
"indoc",
|
||||
"lief",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lief-ffi"
|
||||
version = "2.0.0"
|
||||
|
||||
@@ -10,6 +10,7 @@ repository = "https://github.com/lief-project/LIEF"
|
||||
[workspace]
|
||||
members = [
|
||||
"common",
|
||||
"lief-dwarfdump",
|
||||
"lief-patchelf",
|
||||
]
|
||||
resolver = "3"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "lief-dwarfdump"
|
||||
description = "Reconstruct C/C++ declarations from DWARF"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
common = { workspace = true }
|
||||
lief = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
clap_complete = { workspace = true }
|
||||
clap_mangen = { workspace = true }
|
||||
indoc = { workspace = true }
|
||||
@@ -0,0 +1,3 @@
|
||||
# `lief-dwarfdump`
|
||||
|
||||
See `doc/sphinx/lief-dwarfdump/index.md`
|
||||
@@ -0,0 +1,173 @@
|
||||
use anyhow::{Context, Result, ensure};
|
||||
use clap::{Arg, ArgAction, Command, value_parser};
|
||||
use clap_complete::aot::{Generator, Shell, generate};
|
||||
use common::version::get_lief_version;
|
||||
use indoc::indoc;
|
||||
use lief::DeclOpt;
|
||||
use lief::dwarf::types::DwarfType;
|
||||
use lief::logging::{self, Level};
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn parse_address(value: &str) -> Result<u64, String> {
|
||||
let (digits, radix) = match value.as_bytes() {
|
||||
[b'0', b'x' | b'X', ..] => (&value[2..], 16),
|
||||
[b'0', b'b' | b'B', ..] => (&value[2..], 2),
|
||||
[b'0', b'o' | b'O', ..] => (&value[2..], 8),
|
||||
[b'0', _, ..] => (&value[1..], 8),
|
||||
_ => (value, 10),
|
||||
};
|
||||
if digits.starts_with('+') {
|
||||
return Err(format!("Invalid address: {value}"));
|
||||
}
|
||||
u64::from_str_radix(digits, radix).map_err(|e| format!("Invalid address {value}: {e}"))
|
||||
}
|
||||
|
||||
fn print_completions<G: Generator>(generator: G, cmd: &mut Command) {
|
||||
generate(
|
||||
generator,
|
||||
cmd,
|
||||
cmd.get_name().to_string(),
|
||||
&mut io::stdout(),
|
||||
);
|
||||
}
|
||||
|
||||
fn build_cli() -> Command {
|
||||
Command::new("lief-dwarfdump")
|
||||
.about("Reconstruct C++ declarations from DWARF")
|
||||
.long_about(indoc! {"
|
||||
Reconstruct C++ declarations from the DWARF debug info of a binary.
|
||||
|
||||
Without a selector, lief-dwarfdump prints the declarations of every
|
||||
compilation unit. --type, --function and --address each select a single
|
||||
declaration instead. An empty name and the address 0 do not select anything.
|
||||
|
||||
This tool requires LIEF extended.
|
||||
"})
|
||||
.version(get_lief_version())
|
||||
.arg_required_else_help(true)
|
||||
.arg(
|
||||
Arg::new("input")
|
||||
.help("Binary or DWARF file holding the debug info")
|
||||
.required_unless_present_any(["generator", "generate-manpage"]),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("debug-log")
|
||||
.long("debug-log")
|
||||
.help("Enable debug logging")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("show-types")
|
||||
.long("show-types")
|
||||
.help("Show type definitions")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("type")
|
||||
.long("type")
|
||||
.value_name("NAME")
|
||||
.help("Reconstruct a single type by its short name"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("function")
|
||||
.long("function")
|
||||
.value_name("NAME")
|
||||
.help("Reconstruct a single function by demangled name with its stack variables"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("address")
|
||||
.long("address")
|
||||
.value_name("ADDRESS")
|
||||
.value_parser(parse_address)
|
||||
.help(
|
||||
"Reconstruct a single function by its address (decimal, hex, octal or binary)",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("generator")
|
||||
.long("generate")
|
||||
.hide(true)
|
||||
.action(ArgAction::Set)
|
||||
.value_parser(value_parser!(Shell)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("generate-manpage")
|
||||
.long("generate-manpage")
|
||||
.hide(true)
|
||||
.action(ArgAction::Set)
|
||||
.value_parser(value_parser!(PathBuf)),
|
||||
)
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let matches = build_cli().get_matches();
|
||||
|
||||
if let Some(generator) = matches.get_one::<Shell>("generator").copied() {
|
||||
let mut cmd = build_cli();
|
||||
print_completions(generator, &mut cmd);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(man_path) = matches.get_one::<PathBuf>("generate-manpage") {
|
||||
let cmd = build_cli();
|
||||
let man = clap_mangen::Man::new(cmd);
|
||||
let mut buffer: Vec<u8> = Default::default();
|
||||
man.render(&mut buffer)?;
|
||||
|
||||
std::fs::write(man_path, buffer)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ensure!(lief::is_extended(), "This tool requires LIEF extended");
|
||||
|
||||
let input = matches.get_one::<String>("input").unwrap();
|
||||
let dwarf = lief::dwarf::load(input).with_context(|| format!("Can't parse: {input}"))?;
|
||||
|
||||
logging::set_level(if matches.get_flag("debug-log") {
|
||||
Level::Debug
|
||||
} else {
|
||||
Level::Info
|
||||
});
|
||||
|
||||
let mut options = DeclOpt {
|
||||
is_cpp: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(name) = matches.get_one::<String>("type").filter(|s| !s.is_empty()) {
|
||||
let ty = dwarf
|
||||
.type_by_name(name)
|
||||
.with_context(|| format!("Can't find type: {name}"))?;
|
||||
logging::log(Level::Info, &ty.to_decl_with_opt(&options));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
options.include_locals = true;
|
||||
if let Some(name) = matches
|
||||
.get_one::<String>("function")
|
||||
.filter(|s| !s.is_empty())
|
||||
{
|
||||
let function = dwarf
|
||||
.function_by_name(name)
|
||||
.with_context(|| format!("Can't find function: {name}"))?;
|
||||
logging::log(Level::Info, &function.to_decl_with_opt(&options));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(&address) = matches.get_one::<u64>("address").filter(|&&addr| addr != 0) {
|
||||
let function = dwarf
|
||||
.function_by_addr(address)
|
||||
.with_context(|| format!("Can't find function at: {address:#x}"))?;
|
||||
logging::log(Level::Info, &function.to_decl_with_opt(&options));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
options.include_locals = false;
|
||||
options.include_types = matches.get_flag("show-types");
|
||||
for unit in dwarf.compilation_units() {
|
||||
logging::log(Level::Info, &unit.to_decl_with_opt(&options));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user