Search code examples
linuxdebuggingelfdwarf

Getting symbol name at address in Linux DWARF executable


In an application built with debug symbols I need a function that given an address returns name of the symbol (if exists) at that address.

I'm aware of dladdr() but it only works for symbols in shared libraries, not for symbols in the executable or static libraries.

As an example of how it should work, in gdb I can do info symbol 0x... and it gives me a name. For the same address I should be able to do symbolName(0x...) and I should get back the same name.

I'm guessing that there isn't an readily available solution for this so I'm happy to implement this myself, but I'm not sure where to start. I can parse DWARF info of the executable, but then I'm not sure where to look at in the DWARF.

Thanks


Solution

  • One solution is to use ELF for symbols in the current executable and dladdr() for the symbols in dynamically loaded libraries.

    Here's the full code, in Rust (using goblin crate for reading ELF):

    use std::collections::HashMap;
    use std::ffi::CString;
    use goblin::elf::Elf;
    
    /// Returns a map from addresses to symbols in the current executable.
    fn addr_to_sym() -> HashMap<u64, String> {
        let exec_path = std::fs::read_link("/proc/self/exe");
        println!("exec_path: {:#?}", exec_path);
    
        let mut addr_to_sym: HashMap<u64, String> = HashMap::new();
    
        if let Ok(exec_path) = exec_path {
            if let Ok(exec_bytes) = std::fs::read(exec_path) {
                if let Ok(elf) = Elf::parse(&exec_bytes) {
                    for sym in elf.syms.iter() {
                        if let Some(Ok(str)) = elf.strtab.get(sym.st_name) {
                            if sym.st_value == 0 {
                                // Can't find address of the symbol in the current binary, try dlsym
                                let dyn_addr = unsafe {
                                    libc::dlsym(libc::RTLD_DEFAULT, CString::new(str).unwrap().as_ptr())
                                };
                                if !dyn_addr.is_null() {
                                    addr_to_sym.insert(dyn_addr as u64, str.to_owned());
                                }
                            } else {
                                addr_to_sym.insert(sym.st_value, str.to_owned());
                            }
                        }
                    }
                }
            }
        }
    
        addr_to_sym
    }
    

    Using this map I can find symbols in both current executable (which includes statically linked dependencies) and in the dynamic dependencies.