170 Commits

Author SHA1 Message Date
Alex Rønne Petersen
30f5258fe6 std.debug.SelfInfo.Elf: disable unwinding on mips n32 and x86 x32
The DWARF code can't handle these yet.

ref https://github.com/ziglang/zig/issues/25447
2025-10-05 07:18:50 +02:00
Linus Groh
b0f280f4a4 std.debug: Add unwind support for serenity 2025-10-03 22:59:40 +01:00
Alex Rønne Petersen
95bdb0c1c6
std.debug.Dwarf.SelfUnwinder: default some s390x registers to the same-value rule 2025-10-03 03:45:52 +02:00
Alex Rønne Petersen
8263f55ab2
std.debug: add s390x-linux unwind support 2025-10-03 03:29:20 +02:00
Alex Rønne Petersen
a4f95b1e61
std.debug.Dwarf.Unwind: deal with invalid def_cfa_reg by GNU toolchains 2025-10-02 15:27:35 +02:00
Alex Rønne Petersen
97de46dc16
std.debug: add riscv32-linux and riscv64-linux unwind support 2025-10-01 23:47:47 +02:00
Alex Rønne Petersen
8520e9312e
std.debug: add loongarch64-linux unwind support 2025-10-01 23:47:47 +02:00
Alex Rønne Petersen
b46867848e
std.debug: some adjustments to target handling
* driverkit handling missing in a few places.
* x86-solaris is a dead target.
* aarch64_be does not exist on Darwin, FreeBSD, Windows.
2025-10-01 23:47:47 +02:00
Alex Rønne Petersen
771410cbf2
std.debug.SelfInfo: rename Darwin to MachO 2025-10-01 23:47:47 +02:00
mlugg
1120546f72
std.debug.SelfInfo: remove shared logic
There were only a few dozen lines of common logic, and they frankly
introduced more complexity than they eliminated. Instead, let's accept
that the implementations of `SelfInfo` are all pretty different and want
to track different state. This probably fixes some synchronization and
memory bugs by simplifying a bunch of stuff. It also improves the DWARF
unwind cache, making it around twice as fast in a debug build with the
self-hosted x86_64 backend, because we no longer have to redundantly go
through the hashmap lookup logic to find the module. Unwinding on
Windows will also see a slight performance boost from this change,
because `RtlVirtualUnwind` does not need to know the module whatsoever,
so the old `SelfInfo` implementation was doing redundant work. Lastly,
this makes it even easier to implement `SelfInfo` on freestanding
targets; there is no longer a need to emulate a real module system,
since the user controls the whole implementation!

There are various other small refactors here in the `SelfInfo`
implementations as well as in the DWARF unwinding logic. This change
turned out to make a lot of stuff simpler!
2025-09-30 14:18:26 +01:00
mlugg
12ceb896fa
Dwarf.Unwind: fix typo 2025-09-30 13:44:56 +01:00
mlugg
a90eb50c80
typo 2025-09-30 13:44:56 +01:00
mlugg
8950831d3c
Dwarf.Unwind: handle macOS deviation from standard
Apparently the `__eh_frame` in Mach-O binaries doesn't include the
terminator entry, but in all other respects it acts like `.eh_frame`
rather than `.debug_frame`. I have no idea.
2025-09-30 13:44:56 +01:00
mlugg
156cd8f678
std.debug: significantly speed up capturing stack traces
By my estimation, these changes speed up DWARF unwinding when using the
self-hosted x86_64 backend by around 7x. There are two very significant
enhancements: we no longer iterate frames which don't fit in the stack
trace buffer, and we cache register rules (in a fixed buffer) to avoid
re-parsing and evaluating CFI instructions in most cases. Alongside this
are a bunch of smaller enhancements, such as pre-caching the result of
evaluating the CIE's initial instructions, avoiding re-parsing of CIEs,
and big simplifications to the `Dwarf.Unwind.VirtualMachine` logic.
2025-09-30 13:44:56 +01:00
mlugg
dbda011ae6
std.debug.SelfInfo: mark ARM unwinding as unsupported
We need to parse the `.ARM.exidx` section to be able to reliably unwind
the stack on ARM.
2025-09-30 13:44:56 +01:00
mlugg
950a9d2a10
typo 2025-09-30 13:44:56 +01:00
mlugg
f7e0ff8a5f
std: clarify cpu_context register order rationale 2025-09-30 13:44:56 +01:00
mlugg
c41bf99684
std.debug: don't assume return address register is defined if not specified
This logic was causing some occasional infinite looping on ARM, where
the `.debug_frame` section is often incomplete since the `.exidx`
section is used for unwind information. But the information we're
getting from the compiler is totally *valid*: it's leaving the rule as
the default, which is (as with most architectures) equivalent to
`.undefined`!
2025-09-30 13:44:55 +01:00
mlugg
099a950410
std.debug.SelfInfo: thread safety
This has been a TODO for ages, but in the past it didn't really matter
because stack traces are typically printed to stderr for which a mutex
is held so in practice there was a mutex guarding usage of `SelfInfo`.

However, now that `SelfInfo` is also used for simply capturing traces,
thread safety is needed. Instead of just a single mutex, though, there
are a couple of different mutexes involved; this helps make critical
sections smaller, particularly when unwinding the stack as `unwindFrame`
doesn't typically need to hold any lock at all.
2025-09-30 13:44:55 +01:00
mlugg
9c1821d3bf
ElfModule: fix assertion failure 2025-09-30 13:44:55 +01:00
mlugg
084e92879a
std: don't get CPU context when using CBE targeting MSVC
Calling `current` here causes compilation failures as the C backend
currently does not emit valid MSVC inline assembly. This change means
that when building for MSVC with the self-hosted C backend, only FP
unwinding can be used.
2025-09-30 13:44:55 +01:00
mlugg
2ab650b481
std.debug: go back to storing return addresses instead of call addresses
...and just deal with signal handlers by adding 1 to create a fake
"return address". The system I tried out where the addresses returned by
`StackIterator` were pre-subtracted didn't play nicely with error
traces, which in hindsight, makes perfect sense. This definition also
removes some ugly off-by-one issues in matching `first_address`, so I do
think this is a better approach.
2025-09-30 13:44:55 +01:00
mlugg
9434bab313
std: work around crash parsing LLVM PDB
This crash exists on master, and seems to have existed since 2019; I
think it's just very rare and depends on the exact binary generated. In
theory, a stream block should always be a "data" block rather than a FPM
block; the FPMs use blocks `1, 4097, 8193, ...` and `2, 4097, 8194, ...`
respectively. However, I have observed LLVM emitting an otherwise valid
PDB which maps FPM blocks into streams. This is not a bug in
`std.debug.Pdb`, because `llvm-pdbutil` agrees with our stream indices.
I think this is arguably an LLVM bug; however, we don't really lose
anything from just weakening this check. To be fair, MSF doesn't have an
explicit specification, and LLVM's documentation (which is the closest
thing we have) does not explicitly state that FPM blocks cannot be
mapped into streams, so perhaps this is actually valid.

In the rare case that LLVM emits this, previously, stack traces would
have been completely useless; now, stack traces will work okay.
2025-09-30 13:44:55 +01:00
mlugg
23d6381e8b
std.debug: fix typo 2025-09-30 13:44:55 +01:00
mlugg
0c24b8ec66
update to new std.debug changes 2025-09-30 13:44:55 +01:00
mlugg
dd8d59686a
std.debug: miscellaneous fixes
Mostly on macOS, since Loris showed me a not-great stack trace, and I
spent 8 hours trying to make it better. The dyld shared cache is
designed in a way which makes this really hard to do right, and
documentation is non-existent, but this *seems* to work pretty well.
I'll leave the ruling on whether I did a good job to CI and our users.
2025-09-30 13:44:54 +01:00
mlugg
a18fd41064
std: rework/remove ucontext_t
Our usage of `ucontext_t` in the standard library was kind of
problematic. We unnecessarily mimiced libc-specific structures, and our
`getcontext` implementation was overkill for our use case of stack
tracing.

This commit introduces a new namespace, `std.debug.cpu_context`, which
contains "context" types for various architectures (currently x86,
x86_64, ARM, and AARCH64) containing the general-purpose CPU registers;
the ones needed in practice for stack unwinding. Each implementation has
a function `current` which populates the structure using inline
assembly. The structure is user-overrideable, though that should only be
necessary if the standard library does not have an implementation for
the *architecture*: that is to say, none of this is OS-dependent.

Of course, in POSIX signal handlers, we get a `ucontext_t` from the
kernel. The function `std.debug.cpu_context.fromPosixSignalContext`
converts this to a `std.debug.cpu_context.Native` with a big ol' target
switch.

This functionality is not exposed from `std.c` or `std.posix`, and
neither are `ucontext_t`, `mcontext_t`, or `getcontext`. The rationale
is that these types and functions do not conform to a specific ABI, and
in fact tend to get updated over time based on CPU features and
extensions; in addition, different libcs use different structures which
are "partially compatible" with the kernel structure. Overall, it's a
mess, but all we need is the kernel context, so we can just define a
kernel-compatible structure as long as we don't claim C compatibility by
putting it in `std.c` or `std.posix`.

This change resulted in a few nice `std.debug` simplifications, but
nothing too noteworthy. However, the main benefit of this change is that
DWARF unwinding---sometimes necessary for collecting stack traces
reliably---now requires far less target-specific integration.

Also fix a bug I noticed in `PageAllocator` (I found this due to a bug
in my distro's QEMU distribution; thanks, broken QEMU patch!) and I
think a couple of minor bugs in `std.debug`.

Resolves: #23801
Resolves: #23802
2025-09-30 13:44:54 +01:00
mlugg
d289667856
std.debug.Pdb: fix leak 2025-09-30 13:44:54 +01:00
mlugg
51d08f4b9b
fix compile errors and minor bugs 2025-09-30 13:44:54 +01:00
mlugg
e6eccc3c8f
SelfInfo: remove x86-windows unwinding path
Turns out that RtlCaptureStackBackTrace is actually just doing FP (ebp)
unwinding under the hood, making this logic completely redundant with
our own FP-walking implementation; see added comment for details.
2025-09-30 13:44:53 +01:00
mlugg
1a8a8c610d
tests: split up and enhance stack trace tests
Previously, the `test-stack-traces` step was essentially just testing
error traces, and even there we didn't have much coverage. This commit
solves that by splitting the "stack trace" tests into two separate
harnesses: the "stack trace" tests are for actual stack traces (i.e.
involving stack unwinding), while the "error trace" tests are
specifically for error return traces.

The "stack trace" tests will test different configurations of:

* `-lc`
* `-fPIE`
* `-fomit-frame-pointer`
* `-fllvm`
* unwind tables (currently disabled)
* strip debug info (currently disabled)

The main goal there is to test *stack unwinding* under different
conditions. Meanwhile, the "error trace" tests will test different
configurations of `-O` and `-fllvm`; the main goal here, aside from
checking that error traces themselves do not miscompile, is to check
whether debug info is still working even in optimized builds. Of course,
aggressive optimizations *can* thwart debug info no matter what, so as
before, there is a way to disable cases for specific targets / optimize
modes.

The program which converts stack traces into a more validatable format
by removing things like addresses (previously `check-stack-trace.zig`,
now `convert-stack-trace.zig`) has been rewritten and simplified. Also,
thanks to various fixes in this branch, several workarounds have become
unnecessary: for instance, we don't need to ignore the function name
printed in stack traces in release modes, because `std.debug.Dwarf` now
uses the correct DIE for inlined functions!

Neither `test-stack-traces` nor `test-error-traces` does general foreign
architecture testing, because it seems that (at least for now) external
executors often aren't particularly good at handling stack tracing
correctly (looking at you, Wine). Generally, they just test the native
target (this matches the old behavior of `test-stack-traces`). However,
there is one exception: when on an x86_64 or aarch64 host, we will also
test the 32-bit version (x86 or arm) if the OS supports it, because such
executables can be trivially tested without an external executor.

Oh, also, I wrote a bunch of stack trace tests. Previously there was,
erm, *one* test in `test-stack-traces` which wasn't for error traces.
Now there are a good few!
2025-09-30 13:44:53 +01:00
mlugg
cedd9de64f
std.debug.Dwarf: fix names of inlined functions 2025-09-30 13:44:53 +01:00
mlugg
9901b9389e
std: fix 32-bit build and some unsafe casts 2025-09-30 13:44:53 +01:00
mlugg
7601b397ef
fix bad merge
The API of `std.debug.Pdb` changed.
2025-09-30 13:44:53 +01:00
mlugg
1123741fd5
Dwarf: use 'gpa' terminology 2025-09-30 13:44:52 +01:00
mlugg
c1a30bd0d8
std: replace debug.Dwarf.ElfModule with debug.ElfFile
This abstraction isn't really tied to DWARF at all! Really, we're just
loading some information from an ELF file which is useful for debugging.
That *includes* DWARF, but it also includes other information. For
instance, the other change here:

Now, if DWARF information is missing, `debug.SelfInfo.ElfModule` will
name symbols by finding a matching symtab entry. We actually already do
this on Mach-O, so it makes obvious sense to do the same on ELF! This
change is what motivated the restructuring to begin with.

The symtab work is derived from #22077.

Co-authored-by: geemili <opensource@geemili.xyz>
2025-09-30 13:44:52 +01:00
mlugg
e6adddf80c
small reasonable change 2025-09-30 13:44:52 +01:00
mlugg
229f0a01b8
std.debug: handle ThreadContext slightly better
It's now user-overrideable, and uses `noreturn` types to neatly stop
analysis.
2025-09-30 13:44:52 +01:00
mlugg
1392a7af17
std.debug: unwinding on Windows
...using `RtlVirtualUnwind` on x86_64 and aarch64, and
`RtaCaptureStackBackTrace` on x86.
2025-09-30 13:44:52 +01:00
mlugg
ac4d633ed6
std: fix debug.Info and debug.Coverage 2025-09-30 13:44:52 +01:00
mlugg
3a561da38d
std: doc comments and tweaks 2025-09-30 13:44:51 +01:00
mlugg
0c7b2a7bd5
fix compiler ftbfs from std.macho and std.dwarf changes 2025-09-30 13:44:51 +01:00
mlugg
f1215adeda
SelfInfo.DarwinModule: rename field 2025-09-30 13:44:51 +01:00
mlugg
253fdfce70
SelfInfo: be honest about how general unwinding is
...in that it isn't: it's currently very specialized to DWARF unwinding.

Also, make a type unmanaged.
2025-09-30 13:44:51 +01:00
mlugg
9859440d83
add freestanding support IN THEORY
untested because this branch has errors rn
2025-09-30 13:44:51 +01:00
mlugg
5709369d05
std.debug: improve the APIs and stuff 2025-09-30 13:44:51 +01:00
mlugg
d4f710791f
tweaks 2025-09-30 13:44:51 +01:00
mlugg
ba5d9d5a41
remove redundant test
turns out this actually has coverage in std.debug
2025-09-30 13:44:50 +01:00
mlugg
405075f745
SelfInfo: load eh_frame/debug_frame from ELF file if eh_frame_hdr omitted 2025-09-30 13:44:50 +01:00
mlugg
c895aa7a35
std.debug.SelfInfo: concrete error sets
The downside of this commit is that more precise errors are no longer
propagated up. However, these errors were pretty useless in isolation
due to them having no context; and regardless, we intentionally swallow
most of them in `std.debug` anyway. Therefore, this is better in
practice, because it allows `std.debug` to give slightly more useful
warnings when handling errors. This commit does that for unwind errors,
for instance, which differentiate between the unwind info being corrupt
vs missing vs inaccessible vs unsupported.

A better solution would be to also include more detailed information via
the diagnostics pattern, but this commit is an incremental improvement.
2025-09-30 13:44:50 +01:00