cmd/link: DWARF5 implementation breaks debugedit
Go's DWARF5 line tables use DW_FORM_string for directory and file paths, which causes debugedit and other tools to fail.
It can be easily reproduced using debugedit, for example, in Delve:
$ gotip build -o dlv-upstream cmd/dlv/main.go
$ debugedit -b $(pwd) -d /usr/src/debug -i ./dlv-upstream
[...]
debugedit: ./dlv-upstream: Unsupported .debug_line directory 0 path DW_FORM_0x8
debugedit: ./dlv-upstream: Unsupported .debug_line directory 0 path DW_FORM_0x8
debugedit: ./dlv-upstream: Unsupported .debug_line directory 0 path DW_FORM_0x8
debugedit: ./dlv-upstream: Unsupported .debug_line directory 0 path DW_FORM_0x8
debugedit: ./dlv-upstream: Unsupported .debug_line directory 0 path DW_FORM_0x8
debugedit: ./dlv-upstream: Unsupported .debug_line directory 0 path DW_FORM_0x8
debugedit: ./dlv-upstream: Unsupported .debug_line directory 0 path DW_FORM_0x8
5e781272b49e753bcddccf567b746e1edd677f9a
This behavior was detected during the previous Fedora mass rebuild when Go 1.25 made DWARF5 the default implementation. We currently have DWARF5 disabled in Fedora, CentOS Stream, and RHEL. Other distributions that use debugedit in their build pipelines might also be affected.
There is a tracking issue since the mass rebuild on the sourceware side: https://sourceware.org/bugzilla/show_bug.cgi?id=33204
These tools expect the .debug_line_str section instead.
I created a PR, but it's worth noting that this is not a bug per se, more like a misalignment of what is expected from each other's projects. My suggested change might not be useful if it is fixed in the tooling.
https://go-review.googlesource.com/c/go/+/725160
Related Issues
- Disable DWARF 5 in Go 1.25 #75079 (closed)
- cmd/compile: consider using DWARF 5 #26379 (closed)
- cmd/compile: bad inlining informations in debug_info with DWARFv5 on tip #72821 (closed)
- debug/dwarf: r.Next() returns wrong ent when DWARF5 used. #57046 (closed)
Related Code Changes
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
Why can't we fix debugedit? It knows how to handle DW_FORM_string for DW_AT_comp_dir, it doesn't seem unreasonable that it should handle it for DW_LCNT_path.
@alexsaezm Could we possibly contact and involve a debugedit owner/maintainer? (Are you that person?)
@alexsaezm Could we possibly contact and involve a debugedit owner/maintainer? (Are you that person?)
I just emailed Mark Wielaard with this conversation. I am not sure if he has a GitHub account. Hope he can jump in.
So Mark answered me back. He doesn't have a GitHub account, so I will try to answer everything here myself and bother him with the questions. :) Sorry in advance for destroying his takes.
Regarding this:
Why can't we fix debugedit? It knows how to handle
DW_FORM_stringforDW_AT_comp_dir, it doesn't seem unreasonable that it should handle it forDW_LCNT_path.
He said:
Yes, it is reasonable for debugedit to try to handle DW_FORM_string in DWARF5 .debug_line. But it is a bit of work because rewriting embedded strings causes the section to change size so all references from other sections like .debug_info need to be rewritten, so we need to do it in multiple passes. That is debugedit bug https://sourceware.org/bugzilla/show_bug.cgi?id=33204
But I believe this patch for the Go DWARF5 producer is useful on its own. No other DWARF5 producer uses embedded DW_FORM_strings in .debug_line. They all store the strings together in a mergable string pool. Using DW_FORM_line_strp here also prepares for storing other identical file/directory strings from other debug sections, like e.g. the compilation file name and DW_AT_comp_dir in the same pool.
P.S. That does mean the .debug_line_strp should be created with SHF_STRINGS + SHF_MERGE for the linker to do the string merging. It isn't immediately clear to me that is done by this patch with lineStrSym = mkSecSym(".debug_line_str")
Just to note it, Arch Linux also has DWARF5 disabled for its Go distribution for the same reason: https://gitlab.archlinux.org/archlinux/packaging/packages/go/-/commit/a2a4a14d45c78ade2565be537d20cb3b2605fbc9
@alexsaezm thanks for sending this tracking issue.
I mentioned this in the Gerrit CL comments but I want to echo here as well -- in addition to the debugedit errors, the original binutils issue also indicates that dwz (run alongside or in combination with debugedit) is issuing this error for Go binaries:
DWARF-compressing 1 files
dwz: ./usr/bin/docker-compose-1.0.5-3.fc43.x86_64.debug: Unknown debugging section .debug_addr
Looking at the source code for dwz, it appears to not recognize this DWARF-5 specific section. Browsing the git logs, the commit comment for 90a85c884556df07dd7b00c5f692bdb68bb3dc31 seem to imply that the authors expected .debug_addr to be used only by GCC with split-dwarf mode. While that is currently the case, other compilers (ex: clang) don't share that strategy, and do employ .debug_addr for regular non-split-DWARF builds. Example:
$ clang-16 -g himom.c && llvm-objdump-16 -x a.out | fgrep .debug
28 .debug_info 00000172 0000000000000000 DEBUG
29 .debug_abbrev 0000011a 0000000000000000 DEBUG
30 .debug_line 00000164 0000000000000000 DEBUG
31 .debug_str 00000087 0000000000000000 DEBUG
32 .debug_addr 00000058 0000000000000000 DEBUG
33 .debug_line_str 00000018 0000000000000000 DEBUG
34 .debug_str_offsets 0000006c 0000000000000000 DEBUG
$
Hence even if we move ahead with this patch for .debug_line_str support, the dwz problem would still be there. Go's DWARF5 generation strategy relies heavily on .debug_addr, there isn't any easy way to work around that I would think.
Regarding the comment passed on from Mark Wielaard, e.g.
But I believe this patch for the Go DWARF5 producer is useful on its own. No
other DWARF5 producer uses embedded DW_FORM_strings in .debug_line. They all
store the strings together in a mergable string pool. Using DW_FORM_line_strp
here also prepares for storing other identical file/directory strings from
other debug sections, like e.g. the compilation file name and DW_AT_comp_dir
in the same pool.
I am leery of making changes to Go's DWARF generation that effectively nudge it in the direction of generating more "C-like" DWARF (or an even more restrictive "GCC-like DWARF") just because that makes it slightly simpler to write tools that read DWARF. Go should be free to use DWARF (including DWARF 5) in whatever ways it needs to produce the most compact and expressive info, as opposed to mimicing however C compilers do it. The reason that Go has fantastically speedy compile and build times is in a good part due to the willingness of Go's designers to do things differently from C compilers.