-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add DWARF unwinding, and an external debug info loader for ELF #15823
Conversation
81f9ece
to
c99a436
Compare
4cc7a33
to
6d4b19b
Compare
bdf575a
to
8505ff0
Compare
7d3a510
to
d9a6fdf
Compare
Renamed dwarf_unwinding -> stack_iterator to better reflect that it's not just DWARF unwinding. Added a test for unwinding with a frame pointer.
debug: handle the possibility of eh_frame / debug_frame being mapped in memory or loaded from disk
…m the __unwind_info directly
… itself is updated by a column rule
dwarf: handle signal frame CIE flag
dwarf: fixup unchecked .eh_frame CIE offset subtraction
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you share a performance measurements of how long the compiler takes to build "hello, world" before and after this change?
pub usingnamespace if (builtin.os.tag == .linux and builtin.target.isMusl()) struct { | ||
// musl does not implement getcontext | ||
pub const getcontext = std.os.linux.getcontext; | ||
} else struct { | ||
pub extern "c" fn getcontext(ucp: *std.os.ucontext_t) c_int; | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for this usingnamespace. Just put the definition here and let the linker tell the user that the symbol does not exist. The user should do this logic themselves if they want to reach into Linux directly.
This is the std.c
namespace after all, it should not pretend to be libc and then do something else.
I did a small analysis. It seems that the binary size of an empty program grew by over 400 kB. Expand
|
@IntegratedQuantum thanks! Indeed, reducing those cases does improve the compile time, working on those fixes here: https://github.com/kcbanner/zig/tree/fixup_unwind_perf_regression |
This supercedes (and includes the changes from) #15531.
These changes were developed in tandem with this PR, which might be a good reference for reviewers: kubkon/zig-dwarfdump#1
Closes #13114
Stack unwinding using DWARF unwind info
This change allows stacks to be unwound (and thus stack traces printed) for code that is compiled without
-fomit-frame-pointer
. This is accomplished by utilizing the DWARF debug info that is written into the.eh_frame
section (or.debug_frame
section). Conceptually, those sections contain a large lookup table mapping program addresses to a set of rules per-register that allow you to recover the value of that register in the previous frame - this allows you to recover the return address and previous stack frame values. Instead of actually storing the full lookup table (which would be large), it's stored as a set of instructions (not native code, but a special DWARF CFI bytecode), that allow you to build this table as needed. This system is howlibunwind
and debuggers themselves can unwind stacks.This system is described in section 6.4.1 of this document: https://dwarfstd.org/doc/DWARF5.pdf
The overall motivation for this PR is to have zig's stack trace output be at least as good as
gdb
.Changes:
.eh_frame
,.eh_frame_hdr
, and.debug_frame
. I've utilizedzig-dwarfdump
as a testbed for the parsers (see linked PR above), and it's able to output an exact match ofllvm-dwarfdump
for Ubuntu's libc currently.dwarf.call_frame.VirtualMachine
which can run the CFI instructions and build the aforementioned register-rule lookup table.debug.StackIterator
to utilize the DWARF debug info to unwind the stack, if it is available. It falls back to standard FP-based unwinding if it encounters any errors or missing info. I've tried to have the unwinder fail gracefully as much as possible. For example, a bad subtraction or overflow while running the DWARF opcodes (potentially due to malformed debug info) results in an error and fallback to FP unwinding instead of panicing while panicing.DW_AT_ranges
in subprograms. Some DWARF5 subprograms have non-contiguous instruction ranges. An example of such a function isputs
in Ubuntu's libc. Stack traces now include the names of these functions.getcontext
implementations forx86-linux
andx86_64-linux
. If linkinglibc
, then the system provided implementation is used, with the exception of-musl
which doesn't providegetcontext
. These are used to get a register context to unwind stacks via a@panic
(vs a segfault handler, where the OS provides the context). Support for additional platforms is straightforward to add and once this is merged I want to open contributor-friendly issues to add support for the other major platforms that I don't have access to.An example of how this improves the output, in this case calling
puts
with a bad pointer:Before:
After:
Output when unwinding stack beginning in a
libc
without frame pointers, on a system that doesn't have debug info installed for libc (in this example, running an x86 program when there is only x86_64 libc symbols available). Note that the unwound stack skips several frames (due to missing unwind info), but the user is notified of this:Stack unwinding on Darwin using
__unwind_info
Apple has their own proprietary unwind info format: https://faultlore.com/blah/compact-unwinding/. It works in a similar way to the DWARF unwind tables, but it's a bit less general.
StackIterator
will attempt to use this unwind info on macos, if it's available. The format supports falling back to DWARF unwinding, which is also supported in this implementation.If only DWARF information is available then normal FP stack unwinding is used, because in this case I observed that the DWARF unwind info for some functions was incorrect. Specifically, certain FDEs would have instruction ranges that spanned functions that they shouldn't have.
On
aarch64
, Apple requires that the frame pointer be used. Onx86_64
, this isn't the case.Now that we support unwind tables, I've updated
target.needUnwindTables
to return true forofmt = .macho
.External debug info
Some distributions (ie. Ubuntu) have their libc debug info in separate files. This scheme allows for shipping debug info separately, which, can save space if the user doesn't actually need to debug the program / library. I've added support for reading external debug info:
.gnu_debuglink
section to look up external debug infoAs an example, in the stack traces above, their debug info actually comes from
/usr/lib/debug/.build-id/69/389d485a9793dbe873f0ea2c93e02efaa9aa3d.debug
.Other changes
-gdwarf
). I added a test to cover this scenario.Remaining Work
This is a work in progress, but it's at the point where I'd like to get it running against the CIs for arches / OSes I don't have access to, so this will be in draft status until these items are complete:
__unwind_info
support)Support aarch64-macos (add. Deferring this to a future PR. Since Apple mandates that the FP be used on this platform, most traces will unwind fine with the FP-based StackIterator. I ran into what I suspect is a bug in the CFI generation on this platform, but haven't confirmed this yet.__unwind_info
support).eh_frame_hdr
section. This is an optimization that will speed up unwinding - it contains a sorted lookup table for the information in.eh_frame
, which is binary searchable.eh_frame
andeh_frame_hdr
are already mapped into the program's address space). Support using these already-mapped sections directly. This is particularly important for things that just measure stack traces and don't need to write them yet (ie. GPA capturing backtraces - with this improvement it could support-fomit-frame-pointer
traces without touching the disk)..debug_frame
. This is a very similar section to.eh_frame
with some slight changes.Update all uses in std of. I'm going to address this in a future PR. I've addressed all uses except forStackIterator
to use the DWARF unwinding version, if availablecaptureStackTrace
, which doesn't have access to an allocator, which is currently required for the unwinder.Thanks to @jacobly0 for the help debugging aarch64-macos, and @kubkon for answering all my linker questions!