addr2line 0.2.0

A cross-platform `addr2line` clone written in Rust, using `gimli`
Documentation

addr2line

Build Status Coverage Status

A cross-platform library for retrieving per-address debug information executables with DWARF debug symbols.

addr2line uses gimli to parse the debug symbols of an executable, and exposes an interface for finding the source file, line number, and wrapping function for instruction addresses within the target program. These lookups can either be performed programmatically through Mapping::locate, or via the included binary, addr2line (named and modelled after the equivalent utility from GNU binutils).

Quickstart

Usage example

The library is primarily meant to be used to investigate other binaries, but it can also be used for introspection. This provides a neat, self-contained example that approximates the functionality of the provided addr2line binary:

extern crate addr2line;

fn main() {
    use std::env;
    let us = env::current_exe().expect("not running as an executable");
    let map = addr2line::Mapping::new(&us).expect("debug symbols not found");
    let addr = env::args().skip(1).next().expect("no address passed");
    let addr = u64::from_str_radix(&addr[2..], 16).expect("address not valid");
    let loc = map.locate(addr as u64).expect("invalid debug symbols found");
    if let Some((file, line, func)) = loc {
        print!("0x{:08x} is in {}", addr, file.display());
        if let Some(line) = line {
            print!(" on line {}", line);
        }
        if let Some(func) = func {
            print!(" in function {}", func);
        }
        println!("");
    } else {
        println!("could not locate debug information for 0x{:08x}", addr);
    }
}

Let's find an interesting address to look up using objdump:

$ cargo build
   Compiling addr2line v0.2.0 (file:///home/jon/dev/minor/addr2line)
   Compiling addr2line-ex v0.1.0 (file:///home/jon/tmp/addr2line-ex)
    Finished debug [unoptimized + debuginfo] target(s) in 2.50 secs
$ objdump -d target/debug/addr2line-ex  | grep -E 'main.*:'
000000000000cd80 <_ZN12addr2line_ex4main17h4c6c4a79f3ebd4beE>:
000000000000d930 <main>:

The first result is the fn main() we defined, the second is the program entry point, which does little more than just jump to the Rust main(). Let's confirm that this is indeed the case:

$ cargo run 0xcd80
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/addr2line-ex 0x000000000000cd80`
0x0000cd80 is in /home/jon/tmp/addr2line-ex/src/main.rs on line 3

Exactly where we thought it would be! Unfortunately, we don't also see the expected "in function main" message. This is because Mapping::new does not extract and expose function-level debug information. To get that, you need to use Mapping::with_functions instead.

$ sed -i 's/Mapping::new/Mapping::with_functions/' src/main.rs
$ cargo run 0xcd80
   Compiling addr2line-ex v0.1.0 (file:///home/jon/tmp/addr2line-ex)
    Finished debug [unoptimized + debuginfo] target(s) in 0.41 secs
     Running `target/debug/addr2line-ex 0xcd80`
0x0000cd80 is in /home/jon/tmp/addr2line-ex/src/main.rs on line 3 in function _ZN12addr2line_ex4mainE

Perfect! Let's see if it also works for an address inside main. What about the call to from_str_radix?

$ objdump -d target/debug/addr2line-ex  | grep -E 'call.*from_str_radix'
    cf79:	e8 d2 c3 0d 00       	callq  e9350 <_ZN4core3num21_$LT$impl$u20$u64$GT$14from_str_radix17h6e264457c3d6c0cfE>
   e9354:	e8 37 da ff ff       	callq  e6d90 <_ZN4core3num14from_str_radix17he4ded7f141af381aE>
   e9369:	e8 02 d9 ff ff       	callq  e6c70 <_ZN4core3num14from_str_radix17h7674eac951972f07E>

The first one is the one in our main (look at the addresses in the left-most column). Let's check:

$ cargo run 0xcf79
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/addr2line-ex 0xcf79`
0x0000cf79 is in /home/jon/tmp/addr2line-ex/src/main.rs on line 8 in function _ZN12addr2line_ex4mainE

How about that? Quite neat!

Using the provided binary

The library ships with a binary that is written to emulate the addr2line included with GNU binutils. We can use this instead of our little program above to get the same information:

$ cargo install addr2line
$ ~/.cargo/bin/addr2line -e target/debug/addr2line-ex -f 0xcd80
_ZN12addr2line_ex4mainE
/home/jon/tmp/addr2line-ex/src/main.rs:3
$ addr2line -e target/debug/addr2line-ex -f 0xcd80
_ZN12addr2line_ex4mainE
/home/jon/tmp/addr2line-ex/src/main.rs:3

Performance

The library aims to perform similarly to equivalent existing tools such as addr2line from binutils, eu-addr2line from elfutils, and llvm-symbolize from the llvm project. We haven't done extensive benchmarking (yet), but the runtime and memory use results we observe for one relatively large Rust application around are quite promising:

addr2line runtime addr2line memory