Join GitHub today
cmd/link: dsymutil also mangles __debug_info sometimes #22068
Found while investigating this: #21945
When calling dsymutil with the verbose option a number of warnings like this are emitted:
the corresponding DIE is fine in go.o but mangled (missing the DW_AT_Type attribute) in the final binary:
I built dsymutil from source and debugged it today. I didn't get all the way through but wanted to write down findings so far.
The problem occurs when dsymutil applies relocations here:
.debug_info contains relocations to multiple sections. In the instance I was looking at, the DW_AT_type that was getting lost relocated to some offset X within .debug_info. It so happened that a prior function DIE's DW_AT_high_pc had the same offset X, but that's a relocation into .text. Something somewhere conflated the two relocations, and it ended up using the function's address where it should have used the type DIE's.
Right now I think there are two possibilities: either dsymutil is busted somewhere, or the Go toolchain is producing a bad debug map. (I don't know exactly what a debug map is, but it seems to contain relocations, and it's what's fed to the RelocMgr above.) I'll try to figure out which at some point.
Okay. I don't like it, but I think I understand what's happening now.
In a normal .debug_info, you can get away without a relocation within the same CU using the CU-relative forms of reference (DW_FORM_refN) but for references to a different CU or a different section (.debug_loc, .debug_ranges) you need a DW_FORM_ref_addr and a relocation. This allows the linker to adjust the pointers as it merges the debug information from all the object files.
Mach-O C++ object files don't seem to have any relocations to their DWARF at all. Instead, they rely completely on the existence of dsymutil to do a content-aware linking step without the benefit of any relocations. It walks over all the DIEs in the input and emits referenced ranges and location lists, updating the references as it goes. (https://github.com/llvm-mirror/llvm/blob/f1ff67e5f1d9bba6d4a95deef0a72ab888158c60/tools/dsymutil/DwarfLinker.cpp#L2871, https://github.com/llvm-mirror/llvm/blob/f1ff67e5f1d9bba6d4a95deef0a72ab888158c60/tools/dsymutil/DwarfLinker.cpp#L838).
Dropping the relocations from the output seems to work. I'll mail a WIP CL.
Some notes for posterity. There's still a bunch I don't understand here though.
The crux of the problem is the way that dsymutil finds entries in the debug map. It assumes that all relocations are absolute addresses, and reads the addend of the relocation (which in Mach-O is in the instruction text, see https://github.com/llvm-mirror/llvm/blob/f1ff67e5f1d9bba6d4a95deef0a72ab888158c60/tools/dsymutil/DwarfLinker.cpp#L1936). It uses that to get the final address and replace the relocation with it. This breaks with our R_DWARFREF relocations, which are section-relative, not absolute addresses. When they happen to collide with an absolute address in the debug map, we get this issue.
There's a debug map for each object file, formed from two pieces of information: STAB entries in the main binary, which map a symbol name to its final value, and the object's symbol table, which maps that to the addend that dsymutil can recognize -- its location in the object file.
It's not clear to me how the Go linker produces an object whose relocations' addends match the position they end up in the object file. @crawshaw suggests that this might be because the linker mostly thinks it's linking the final binary, and so it thinks it knows the load address of the object. I'm mostly just confused.