| author | |
| committer | |
| log | 5d241a14789feac397115b7204528212bfe15850 |
| tree | a5fc45ac910fceaf96c7478a977c6a6ae2e599b8 |
| parent | 8bf4b3c611b39fc39bad2a6c657541fa13973dea |
```zig
const std = @import("std");
pub fn main() !void {
	var addr: *u8 = @ptrFromInt(0xaaaaaaaaaaaaaaaa);
	addr.* = 1;
}
```
On x86_64-linux:
Before:
```
$ zig run x.zig
Segmentation fault at address 0x0
/home/wooster/Desktop/zig/x.zig:5:5: 0x21d887 in main (x)
addr.* = 1;
^
/home/wooster/Desktop/zig-linux-x86_64/lib/std/start.zig:583:37: 0x21d847 in posixCallMainAndExit (x)
const result = root.main() catch |err| {
^
/home/wooster/Desktop/zig-linux-x86_64/lib/std/start.zig:251:5: 0x21d371 in _start (x)
asm volatile (switch (native_arch) {
^
???:?:?: 0x0 in ??? (???)
Aborted (core dumped)
```
After:
```
$ zig run x.zig --zig-lib-dir lib
General protection exception
/home/wooster/Desktop/zig/x.zig:5:5: 0x21d907 in main (x)
addr.* = 1;
^
/home/wooster/Desktop/zig/lib/std/start.zig:583:37: 0x21d8c7 in posixCallMainAndExit (x)
const result = root.main() catch |err| {
^
/home/wooster/Desktop/zig/lib/std/start.zig:251:5: 0x21d3f1 in _start (x)
asm volatile (switch (native_arch) {
^
???:?:?: 0x0 in ??? (???)
Aborted (core dumped)
```
As @IntegratedQuantum pointed out in <https://github.com/ziglang/zig/issues/17745#issuecomment-1783815386>,
it seems that if `code` of the `siginfo_t` instance is a certain value (128), you are able to distinguish between
a general protection exception and a segmentation fault.
This does not seem to be documented on `man sigaction`:
```
The following values can be placed in si_code for a SIGSEGV signal:
SEGV_MAPERR
Address not mapped to object.
SEGV_ACCERR
Invalid permissions for mapped object.
SEGV_BNDERR (since Linux 3.19)
Failed address bound checks.
SEGV_PKUERR (since Linux 4.6)
Access was denied by memory protection keys. See pkeys(7). The protection key which applied to this access is available via si_pkey.
```
(those constants are 1, 2, 3, and 4; none of them are the 128)
I can't find a lot of documentation about this but it seems to work consistently for me on x86_64-linux.
Here is a gist which provides additional evidence that this is a reliable way of checking for a general protection fault:
https://gist.github.com/ytoshima/5682393 (read comment in first line)
See also: https://stackoverflow.com/questions/64309366/why-is-the-segfault-address-null-when-accessing-memory-that-has-any-of-the-16-mo
This only seems to affect x86_64 and on 32-bit x86 this does not seem to be a problem.
Helps with #17745 but doesn't close it because the issue still exists on Windows and other POSIX OSs.
I also limited this to x86_64-linux for now because that's the only platform where I tested it. Might work on more POSIX OSs.1 files changed, 14 insertions(+), 4 deletions(-)
lib/std/debug.zig+14-4| ... | @@ -2401,14 +2401,14 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any | ... | @@ -2401,14 +2401,14 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any |
| 2401 | panic_mutex.lock(); | 2401 | panic_mutex.lock(); |
| 2402 | defer panic_mutex.unlock(); | 2402 | defer panic_mutex.unlock(); |
| 2403 | 2403 | ||
| 2404 | dumpSegfaultInfoPosix(sig, addr, ctx_ptr); | 2404 | dumpSegfaultInfoPosix(sig, info.code, addr, ctx_ptr); |
| 2405 | } | 2405 | } |
| 2406 | 2406 | ||
| 2407 | waitForOtherThreadToFinishPanicking(); | 2407 | waitForOtherThreadToFinishPanicking(); |
| 2408 | }, | 2408 | }, |
| 2409 | else => { | 2409 | else => { |
| 2410 | // panic mutex already locked | 2410 | // panic mutex already locked |
| 2411 | dumpSegfaultInfoPosix(sig, addr, ctx_ptr); | 2411 | dumpSegfaultInfoPosix(sig, info.code, addr, ctx_ptr); |
| 2412 | }, | 2412 | }, |
| 2413 | }; | 2413 | }; |
| 2414 | 2414 | ||
| ... | @@ -2418,10 +2418,20 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any | ... | @@ -2418,10 +2418,20 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any |
| 2418 | os.abort(); | 2418 | os.abort(); |
| 2419 | } | 2419 | } |
| 2420 | 2420 | ||
| 2421 | fn dumpSegfaultInfoPosix(sig: i32, addr: usize, ctx_ptr: ?*const anyopaque) void { | 2421 | fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*const anyopaque) void { |
| 2422 | const stderr = io.getStdErr().writer(); | 2422 | const stderr = io.getStdErr().writer(); |
| 2423 | _ = switch (sig) { | 2423 | _ = switch (sig) { |
| 2424 | os.SIG.SEGV => stderr.print("Segmentation fault at address 0x{x}\n", .{addr}), | 2424 | os.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL |
| 2425 | // x86_64 doesn't have a full 64-bit virtual address space. | ||
| 2426 | // Addresses outside of that address space are non-canonical | ||
| 2427 | // and the CPU won't provide the faulting address to us. | ||
| 2428 | // This happens when accessing memory addresses such as 0xaaaaaaaaaaaaaaaa | ||
| 2429 | // but can also happen when no addressable memory is involved; | ||
| 2430 | // for example when reading/writing model-specific registers | ||
| 2431 | // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode). | ||
| 2432 | stderr.print("General protection exception (no address available)\n", .{}) | ||
| 2433 | else | ||
| 2434 | stderr.print("Segmentation fault at address 0x{x}\n", .{addr}), | ||
| 2425 | os.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}), | 2435 | os.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}), |
| 2426 | os.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}), | 2436 | os.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}), |
| 2427 | os.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}), | 2437 | os.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}), |