Commit graph

35218 commits

Author SHA1 Message Date
Alex Rønne Petersen
01a2989fcc
ci: run test-error-traces on riscv64-linux 2025-10-01 01:06:15 +02:00
Alex Rønne Petersen
d97954a8ea
test: remove stack_iterator standalone test
Our new stack trace tests cover all the important parts of this.
2025-10-01 01:06:13 +02:00
Alex Rønne Petersen
5a71e15f1f
test: don't run error/stack trace tests on self-hosted on BSDs
See: 0700ec35bd
2025-10-01 01:06:10 +02:00
Alex Rønne Petersen
43806cf0bd
test: disable an error trace test on optimized x86_64-freebsd and loongarch64-linux 2025-10-01 01:06:07 +02:00
Matthew Lugg
b64535e3c8
Merge pull request #25227 from mlugg/capture-stack
The Great `std.debug` Refactor
2025-09-30 20:24:58 +01: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
3f84b6c80e
cbe: workaround GCC miscompilation
This was causing a zig2 miscomp, which emitted slightly broken debug
information, which caused extremely slow stack unwinding. We're working
on fixing or reporting this upstream, but we can use this workaround for
now, because GCC guarantees arithmetic signed shift.
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
b0f222777c
std.debug: cap total stack trace frames
...just in case there is broken debug info and/or bad values on the
stack, either of which could cause stack unwinding to potentially loop
forever.
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
dae703d3c0
std.posix.abort: only trigger breakpoint on Windows if being debugged
Processes should reasonably be able to expect their children to abort
with typical exit codes, rather than a debugger breakpoint signal. This
flag in the PEB is what would be checked by `IsDebuggerPresent` in
kernel32, which is the function you would typically use for this
purpose.

This fixes `test-stack-trace` failures on Windows, as these tests were
expecting exit code 3 to indicate abort.
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
3a9c680ad7
std: allow disabling stack tracing
This option disables both capturing and printing stack traces. The
default is to disable if debug info is stripped.
2025-09-30 13:44:55 +01:00
mlugg
abb2b1e2da
std.debug: update support checks 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
Jacob Young
b578cca022
link.Dwarf: i just fixed error union values, s'nothin' else to it 2025-09-30 13:44:54 +01:00
mlugg
604fb3001d
std.start: also don't print error trace targeting .other
This only matters if `callMain` is called by a user, since `std.start`
will never itself call `callMain` when `target.os.tag == .other`.
However, it *is* a valid use case for a user to call
`std.start.callMain` in their own startup logic, so this makes sense.
2025-09-30 13:44:54 +01:00
mlugg
e9c0d43c5b
test-error-traces: skip some more optimized traces 2025-09-30 13:44:54 +01:00
mlugg
2fefe0e4b3
tests: fix 32-bit compatible arch selection 2025-09-30 13:44:54 +01:00
mlugg
f5c8d80e08
windows_bat_args: fix path handling
The input path could be cwd-relative, in which case it must be modified
before it is written into the batch script.

Also, remove usage of deprecated `GeneralPurposeAllocator` alias, rename
`allocator` to `gpa`, use unmanaged `ArrayList`.
2025-09-30 13:44:54 +01:00
mlugg
d289667856
std.debug.Pdb: fix leak 2025-09-30 13:44:54 +01:00
mlugg
4cb84f8e48
test-standalone: update for std.debug changes 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
5f00738969
test-stack-traces: fix on x86-windows 2025-09-30 13:44:53 +01:00
mlugg
344ab62b3f
std.debug: don't attempt SelfInfo unwinding when unsupported 2025-09-30 13:44:53 +01:00
mlugg
cf13b40946
test-stack-traces: don't try to strip unwind tables on x86-windows 2025-09-30 13:44:53 +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
a12ce28224
std: fix os.linux.x86.syscall6
It was possible for `arg6` to be passed as an operand relative to esp.
In that case, the `push` at the top clobbered esp and hence made the
reference to arg6 invalid. This was manifesting in this branch as broken
stack traces on x86-linux due to an `mmap2` syscall accidentally passing
the page offset as non-zero!

This commit fixes a bug introduced in cb0e6d8aa.
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
02a0ade138
std.debug: never attempt FP unwind under fomit-frame-pointer 2025-09-30 13:44:53 +01:00
mlugg
4e45362529
link.Elf: fix static PIE
We mustn't emit the DT_PLTGOT entry in `.dynamic` in a statically-linked
PIE, because there's no dl to relocate it (and `std.pie.relocate`, or
the PIE relocator in libc, won't touch it). In that case, there cannot
be any PLT entries, so there's no point emitting the `.got.plt` section
at all. If we just don't create that section, `link.Elf` already knows
not to add the DT_PLTGOT entry to `.dynamic`.

Co-authored-by: Jacob Young <jacobly0@users.noreply.github.com>
2025-09-30 13:44:53 +01:00
mlugg
1123741fd5
Dwarf: use 'gpa' terminology 2025-09-30 13:44:52 +01:00
mlugg
bfbbda7751
compiler: fix new panic handler in release builds 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