2026-07-25
The asker is building their own debugger. They've parsed DWARF4 line-number information out of an ELF binary and produced a table of (address, source line) pairs. When they compare that table against gdb's disassembly, the addresses line up perfectly — but only when the binary is examined statically. As soon as the process is running, the "real" addresses inside the traced program don't match what their line table says, and they need to know how to bridge that gap.
This is the classic load bias problem, and it's more subtle than it looks. Modern Linux toolchains default to -fPIE -pie, which produces an ET_DYN ELF (a shared-object-flavored executable). The addresses baked into DWARF are relative to the file's own virtual address space — typically starting at 0 for PIE binaries — but the kernel's loader (or ld-linux.so) picks a randomized base address at exec time thanks to ASLR. So a DWARF entry that says "line 42 is at 0x1149" actually lives at base + 0x1149 in the running process.
For non-PIE ET_EXEC binaries the load address is fixed (usually 0x400000 on x86_64), which is why static inspection "just works" and why the discrepancy only bites once you attach to a live process.
e_type from the ELF header. ET_EXEC → no load bias. ET_DYN → you must compute one./proc/<pid>/maps and locate the first executable mapping backed by the binary. Subtract the lowest p_vaddr of any PT_LOAD segment (from the program headers) from the mapping's start address. That difference is the bias.ptrace, subtract the bias before looking it up in your line table..so has its own bias; the dynamic linker's r_debug / _dl_debug_state rendezvous protocol is how gdb discovers them.DW_AT_low_pc), not always the file base. The line program's initial state uses the CU's low_pc..debug_info may reference addresses through .debug_addr (DWARF5) or a DW_AT_ranges list — plan for both..gnu_debuglink or a build-id-indexed file under /usr/lib/debug), you still compute bias against the loaded ELF, not the debug file.PT_LOAD p_vaddr is 0.