Repository navigation
DWARF import: data variable addresses are not rebased in PIE or rebased views #8653
Description
Version and Platform (required):
- Binary Ninja Version: 6.1.10874-dev
- Edition: Ultimate
- OS: Ubuntu Linux
- OS Version: 26.04
- CPU Architecture: x64
Bug Description:
When the view's base differs from the file's original image base, the DWARF importer adds every data variable at its unrebased address. This affects PIE binaries, which Binary Ninja loads at 0x400000, and any binary opened with a non-default loader.imageBase. Functions are rebased correctly in the same views, so only data variables are affected.
The imported data variables and their symbols end up at addresses that are not mapped in the view, and the real ELF symbols at the correct addresses get no DWARF type.
Steps To Reproduce:
-
Save this as
globals.c:int counter = 42; int zeroed; const int answer = 7; char message[] = "hello"; int main(void) { return counter + zeroed + answer + message[0]; }
-
Build it as a PIE and as a non-PIE (gcc 15.2.0):
gcc -g -O0 -fPIE -pie -o globals_pie globals.c gcc -g -O0 -fno-pie -no-pie -o globals_nopie globals.c
-
Load it with the stock DWARF importer and compare each DWARF data variable with its ELF symbol:
import binaryninja as bn with bn.load("globals_pie") as bv: # or: bn.load("globals_nopie", options={"loader.imageBase": 0x800000}) print(bn.core_version(), hex(bv.start)) for v in bv.debug_info.data_variables: if v.name in ("counter", "zeroed", "answer", "message"): elf = [hex(s.address) for s in bv.get_symbols_by_name(v.name) if s.address != v.address] print(f"{v.name:8} dwarf={v.address:#x} mapped={bv.is_valid_offset(v.address)} elf_symbol={elf}")
Output for
globals_pie:6.1.10874-dev Ultimate 0x400000 answer dwarf=0x2004 mapped=False elf_symbol=['0x402004'] message dwarf=0x4014 mapped=False elf_symbol=['0x404014'] counter dwarf=0x4010 mapped=False elf_symbol=['0x404010'] zeroed dwarf=0x4020 mapped=False elf_symbol=['0x404020']
Expected Behavior:
Each data variable should be at its ELF symbol's address: the nm address plus the rebase amount (view start minus the original image base). Instead, every one is off by exactly the rebase amount, while the function main is placed correctly.
globals_pie (view start 0x400000, original image base 0):
| Name | Expected (ELF symbol) | DWARF data variable | Difference |
|---|---|---|---|
counter |
0x404010 | 0x4010 | 0x400000 |
zeroed |
0x404020 | 0x4020 | 0x400000 |
answer |
0x402004 | 0x2004 | 0x400000 |
message |
0x404014 | 0x4014 | 0x400000 |
main (function) |
0x401129 | 0x401129 | 0 |
globals_nopie with loader.imageBase = 0x800000 (original image base 0x400000):
| Name | Expected (ELF symbol) | DWARF data variable | Difference |
|---|---|---|---|
counter |
0x804010 | 0x404010 | 0x400000 |
zeroed |
0x804020 | 0x404020 | 0x400000 |
answer |
0x802004 | 0x402004 | 0x400000 |
message |
0x804014 | 0x404014 | 0x400000 |
main (function) |
0x801106 | 0x801106 | 0 |
globals_nopie loaded at its default base 0x400000 is correct.
Binary:
The reproducer above builds the binaries in seconds.
Additional Information:
post_process rebases function addresses by bv.start() - bv.original_image_base():
binaryninja-api/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs
Lines 869 to 872 in f15f46d
| if let Some(address) = func.address.as_mut() { | |
| let (diff, overflowed) = bv.start().overflowing_sub(bv.original_image_base()); | |
| if !overflowed { | |
| *address = (*address).overflowing_add(diff).0; // rebase the address |
It does not adjust self.data_variables. Those addresses are taken directly from DW_OP_addr/DW_OP_addrx:
binaryninja-api/plugins/dwarf/dwarf_import/src/types.rs
Lines 70 to 78 in f15f46d
| Ok(Operation::Address { address }) => { | |
| if let Some(uid) = type_uid { | |
| debug_info_builder.add_data_variable(address, full_name, uid) | |
| } | |
| } | |
| Ok(Operation::AddressIndex { index }) => { | |
| if let Some(uid) = type_uid { | |
| if let Ok(address) = dwarf.address(unit, index) { | |
| debug_info_builder.add_data_variable(address, full_name, uid) |
commit_data_variables then adds them unchanged:
binaryninja-api/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs
Lines 769 to 785 in f15f46d
| fn commit_data_variables(&self, debug_info: &mut DebugInfo) { | |
| for (&address, (name, type_uid)) in &self.data_variables { | |
| let data_var_type = match self.get_type(*type_uid) { | |
| Some(x) => x.get_type(), | |
| None => { | |
| tracing::error!("Failed to find type for data variable at {:#x}", address); | |
| continue; | |
| } | |
| }; | |
| assert!(debug_info.add_data_variable( | |
| address, | |
| &data_var_type, | |
| name.as_deref(), | |
| &[] // TODO : Components | |
| )); | |
| } | |
| } |
#7163 (closed), "IDB import for PIC binaries does not handle changed base address", was the same class of bug in the IDB importer.
Activity