tools: Add Rust DWARF reconstruction tool

This commit is contained in:
lief-integration-bot
2026-10-03 05:34:52 +02:00
parent f002145a99
commit c638f004a8
8 changed files with 339 additions and 1 deletions
+1
View File
@@ -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">&nbsp;</i>Tools
:maxdepth: 1
tools/lief-dwarfdump/index
tools/lief-patchelf/index
```
+128
View File
@@ -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}
```
+1 -1
View File
@@ -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]
+13
View File
@@ -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"
+1
View File
@@ -10,6 +10,7 @@ repository = "https://github.com/lief-project/LIEF"
[workspace]
members = [
"common",
"lief-dwarfdump",
"lief-patchelf",
]
resolver = "3"
+19
View File
@@ -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 }
+3
View File
@@ -0,0 +1,3 @@
# `lief-dwarfdump`
See `doc/sphinx/lief-dwarfdump/index.md`
+173
View File
@@ -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(())
}