addr2line Rust Library

repository·main·Indexed 19 days ago

https://github.com/gimli-rs/addr2line

A cross-platform symbolication library written in Rust using `gimli` to retrieve debug information (file, line, function, and inline call stacks) from DWARF-instrumented files. It provides a high-level `Loader` for file-based lookups, a low-level `Context` for custom memory management, and a CLI tool for translating instruction addresses into source locations.

Tokens
4.7K
Snippets
13
Records
25
Agent score
65%

What's inside addr2line

  1. Overview of addr2line

    main
    addr2line is a cross-platform Rust library used to retrieve per-address debug information from files containing DWARF debug information. It allows you to map a specific address to its associated file name, line number, and function name. Additionally, it can resolve the inline call stack leading to a given address. The crate also includes a CLI wrapper that provides functionality similar to the addr2line tool found in GNU binutils.
  2. How to provide custom file loading and memory management

    main
    While addr2line::Loader is suitable for standard file-based lookups, you should use addr2line::Context if you need to provide your own custom file loading logic or manage memory manually. This is useful for specialized environments where debug information might not be stored in a standard file system format.
  3. Performance characteristics of addr2line

    main
    addr2line is designed to optimize for speed by caching parsed information and using lazy parsing for DWARF data where possible. It aims to match or exceed the performance of existing tools like GNU binutils addr2line, eu-addr2line (elfutils), and llvm-addr2line (LLVM).
  4. Quickstart: Using addr2line with Loader

    main

    To quickly use addr2line for looking up debug information from a file, follow these steps:

    1. Add the addr2line crate to your Cargo.toml.
    2. Initialize a new loader using addr2line::Loader::new(path) with the path to your debug file.
    3. Use addr2line::Loader::find_location to retrieve the file name, line number, and function name for an address, or use addr2line::Loader::find_frames to retrieve the call stack.
  5. Use the `Loader` for easy file-based address lookups

    main

    The Loader is a high-level utility designed to simplify DWARF data loading for a Context. While a Context typically borrows input data (requiring the data to outlive the context), the Loader uses an internal arena to manage the lifetime of the input data, ensuring it lives as long as the Loader itself.

    Key features of Loader include:

    • Automatic loading of the symbol table from the executable.
    • Automatic discovery and loading of Mach-O dSYM files located next to the executable.
    • Locating and loading split DWARF files (DWO and DWP).
    • Handling supplementary object files.
    use addr2line::Loader;
    use std::path::Path;
    
    // Create a loader for an executable file
    let loader = Loader::new("path/to/executable")?;
    
    // Find the source file and line for a specific address
    if let Some(location) = loader.find_location(0x12345)? {
        println!("File: {}, Line: {}", location.file, location.line);
    }
  6. How to handle split DWARF lookups using LookupResult

    main

    When performing address lookups (e.g., using find_frames), the operation may require additional split DWARF data. Instead of returning a final result immediately, the API returns a LookupResult enum.

    To handle this, you must implement a loop that checks if the result is LookupResult::Load. If it is, you use the provided SplitDwarfLoad information to locate and load the required DWARF data, then call continuation.resume(Some(dwarf_data)) to continue the operation. If you cannot or do not want to support split DWARF, you can call skip_all_loads() to get a result based only on the currently available data (though this may be less accurate).

    # use addr2line::*;
    # use std::sync::Arc;
    # use gimli;
    # let ctx: Context<gimli::EndianSlice<gimli::RunTimeEndian>> = todo!();
    # let do_split_dwarf_load = |load: SplitDwarfLoad<gimli::EndianSlice<gimli::RunTimeEndian>>| -> Option<Arc<gimli::Dwarf<gimli::EndianSlice<gimli::RunTimeEndian>>>> { None };
    const ADDRESS: u64 = 0xdeadbeef;
    let mut r = ctx.find_frames(ADDRESS);
    let result = loop {
        match r {
          LookupResult::Output(result) => break result,
          LookupResult::Load { load, continuation } => {
            // 1. Use 'load' to find the DWARF data
            let dwo = do_split_dwarf_load(load);
            // 2. Resume the operation with the loaded data
            r = continuation.resume(dwo);
          }
        }
    };
  7. How Context and Loader work together for address translation

    main

    The addr2line library provides two main ways to perform address-to-line translation:

    1. Context (Low-level): Used when you have already parsed DWARF sections (e.g., using the object crate and gimli). You create a Context to cache parsed information for efficient multiple lookups. It is the core engine for finding locations and frames.
    2. Loader (High-level): A convenience abstraction that handles the heavy lifting. It internally memory maps files, uses the object crate for parsing, and manages a Context. It is ideal for most users as it also provides find_symbol to use the symbol table instead of DWARF information.

    Key Workflow:

    • Use Context::from_dwarf if you already have gimli::Dwarf sections.
    • Use Loader if you want a managed lifecycle that handles file loading and Mach-O dSYM/split DWARF automatically.
  8. Configure addr2line output formats

    main

    You can customize how addr2line presents information using these combinations:

    LLVM Style

    To match the output of llvm-symbolizer, use the --llvm flag. This is useful for scripts expecting that specific format.

    addr2line -e my_exe 0x401234 --llvm

    Pretty Printing

    For more readable output where each location is on its own line, use -p or --pretty-print. This is often used in conjunction with -f (functions) and -i (inlines) to see call stacks clearly.

    addr2line -e my_exe 0x401234 -p -f -i

    Including Addresses

    By default, the address is not printed. To include it, use -a or --addresses.

    addr2line -e my_exe 0x401234 -a
    addr2line -e my_exe 0x401234 -p -f -i
  9. Use the addr2line CLI to translate addresses to source locations

    main

    The addr2line command-line tool translates instruction addresses into file names, line numbers, and function names using debug information (DWARF).

    Input Methods

    • Command-line arguments: Provide hex addresses directly as arguments.
    • Standard Input (stdin): Pipe a list of hex addresses (e.g., from a file or another tool) into the command.
    • All addresses: Use the --all flag to display all addresses that contain line number information within the executable.

    Basic Usage

    # Translate a single address from an executable
    addr2line -e my_executable 0x401234
    
    # Translate addresses from stdin
    echo "0x401234" | addr2line -e my_executable
    
    # Display all addresses with line info
    addr2line -e my_executable --all
    # Example: Basic address translation
    addr2line -e my_executable 0x401234
  10. Get section ranges with `get_section_range`

    main

    You can retrieve the memory range of a specific section by its name using get_section_range.

    use addr2line::Loader;
    
    let loader = Loader::new("path/to/executable")?;
    if let Some(range) = loader.get_section_range(b".text") {
        println!("Section starts at: 0x{:x}, ends at: 0x{:x}", range.begin, range.end);
    }
  11. Handle split DWARF with preload_units

    main

    If your DWARF information is split across multiple files, Context::preload_units allows you to proactively load the necessary data to ensure future lookups for a specific address do not require asynchronous loading.

    preload_units returns an iterator of (SplitDwarfLoad, callback) pairs. You must invoke the provided callback with the loaded DWARF data to complete the process.

    // Example pattern for preloading split DWARF
    ctx.preload_units(ADDRESS).for_each(|(load, callback)| {
        // 1. Load the DWO/split data (implementation specific)
        let dwo = do_split_dwarf_load(load);
        // 2. Pass it to the callback to update the context
        callback(dwo).unwrap();
    });
  12. Initialize a `Loader`

    main

    You can initialize a Loader using one of two methods depending on whether you need to provide a supplementary object file.

    • new(path): Loads DWARF data for the executable at the specified path.
    • new_with_sup(path, sup_path): Loads DWARF data for the executable at path and optionally uses a supplementary object file at sup_path.
    use addr2line::Loader;
    use std::path::Path;
    
    // Basic initialization
    let loader = Loader::new("path/to/executable")?;
    
    // Initialization with a supplementary object file
    let loader = Loader::new_with_sup("path/to/executable", Some("path/to/supplementary"))?;