sa-resethand.dhover×111all
#!/usr/bin/env dub
/+ dub.sdl:
    name "sa_resethand_probe"
+/
/**
Does this kernel honour `SA_RESETHAND`?

A child installs a `SIGSEGV` handler with `SA_RESETHAND`, faults, and the
handler writes one byte to a pipe and returns. Returning re-executes the
faulting instruction. With `SA_RESETHAND` honoured the disposition is now
`SIG_DFL`, so the second delivery kills the child: the parent counts
exactly one byte and a `SIGSEGV` death. Where the reset is ignored the
handler runs again, and again — the parent caps the count and kills the
child itself.

This is how druntime's `runModuleUnitTests` installs its own segfault
handler (`SA_SIGINFO | SA_RESETHAND`, prints a backtrace, returns), which
every forked test child inherits. `sparkles:event-horizon`'s
`forkserver.crashIsOneLostRequestNotTheHost` therefore loops for ever on a
kernel that ignores the flag — observed under Apple `container --rosetta`
(`linux/amd64` on Apple silicon), not on native aarch64-linux, not on CI.

See: docs/research/linux-on-macos/sparkles-baseline.md
*/
module 
(module) sa_resethand_probe

Does this kernel honour SA_RESETHAND?

A child installs a SIGSEGV handler with SA_RESETHAND, faults, and the handler writes one byte to a pipe and returns. Returning re-executes the faulting instruction. With SA_RESETHAND honoured the disposition is now SIG_DFL, so the second delivery kills the child: the parent counts exactly one byte and a SIGSEGV death. Where the reset is ignored the handler runs again, and again — the parent caps the count and kills the child itself.

This is how druntime's runModuleUnitTests installs its own segfault handler (SA_SIGINFO | SA_RESETHAND, prints a backtrace, returns), which every forked test child inherits. sparkles:event-horizon's forkserver.crashIsOneLostRequestNotTheHost therefore loops for ever on a kernel that ignores the flag — observed under Apple container --rosetta (linux/amd64 on Apple silicon), not on native aarch64-linux, not on CI.

See

docs/research/linux-on-macos/sparkles-baseline.md

sa_resethand_probe
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.posix
posix
.
(module) core.sys.posix.signal

D header file for POSIX.

Source

core/sys/posix/signal.d

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseBoost License 1.0@authorsSean Kelly, Alex Rønne Petersen@standardsThe Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition
signal
:
(alias) sa_resethand_probe.kill = int core.sys.posix.signal.kill(int, int) nothrow @nogc
kill
,
(alias constant) sa_resethand_probe.SA_RESETHAND = uint core.sys.posix.signal.SA_RESETHAND = 2147483648u
SA_RESETHAND
,
(alias constant) sa_resethand_probe.SA_SIGINFO = int core.sys.posix.signal.SA_SIGINFO = 4
SA_SIGINFO
,
(alias) sa_resethand_probe.sigaction = int core.sys.posix.signal.sigaction(int, scope const(core.sys.posix.signal.sigaction_t*), core.sys.posix.signal.sigaction_t*) nothrow @nogc
sigaction
,
(struct) core.sys.posix.signal.sigaction_t
sigaction_t
,
(alias) sa_resethand_probe.sigemptyset = int core.sys.posix.signal.sigemptyset(core.sys.posix.signal.sigset_t*) nothrow @nogc
sigemptyset
,
(struct) core.sys.posix.signal.siginfo_t
siginfo_t
,
(alias constant) sa_resethand_probe.SIGKILL = int core.sys.posix.signal.SIGKILL = 9
SIGKILL
,
(alias constant) sa_resethand_probe.SIGSEGV = int core.stdc.signal.SIGSEGV = 11
SIGSEGV
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.posix
posix
.
(package) core.sys.posix.sys
sys
.
(module) core.sys.posix.sys.wait

D header file for POSIX.

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseBoost License 1.0.@authorsSean Kelly, Alex Rønne Petersen@standardsThe Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition
wait
:
(alias) sa_resethand_probe.waitpid = int core.sys.posix.sys.wait.waitpid(int, int*, int) nothrow @nogc
waitpid
,
(alias template) sa_resethand_probe.WIFSIGNALED = core.sys.posix.sys.wait.WIFSIGNALED()(int status)
WIFSIGNALED
,
(alias template) sa_resethand_probe.WTERMSIG = core.sys.posix.sys.wait.WTERMSIG()(int status)
WTERMSIG
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.posix
posix
.
(module) core.sys.posix.unistd

D header file for POSIX.

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseBoost License 1.0.@authorsSean Kelly@standardsThe Open Group Base Specifications Issue 8, IEEE Std 1003.1, 2024 Edition
unistd
:
(alias) sa_resethand_probe._exit = noreturn core.sys.posix.unistd._exit(int) nothrow @nogc @trusted
_exit
,
(alias) sa_resethand_probe.close = int core.sys.posix.unistd.close(int) nothrow @nogc @trusted
close
,
(alias) sa_resethand_probe.fork = int core.sys.posix.unistd.fork() nothrow @nogc @trusted
fork
,
(alias) sa_resethand_probe.pipe = int core.sys.posix.unistd.pipe(ref int[2]) nothrow @nogc @trusted
pipe
,
(alias) sa_resethand_probe.read = long core.sys.posix.unistd.read(int, void*, ulong) nothrow @nogc
read
,
(alias) sa_resethand_probe.write = long core.sys.posix.unistd.write(int, scope const(void*), ulong) nothrow @nogc
write
;
import
(package) core
core
.
(module) core.volatile

This module declares intrinsics for volatile operations.

Source

core/volatile.d

@copyrightCopyright © 2019, The D Language Foundation@licenseBoost License 1.0@authorsWalter Bright, Emily Castellotti
volatile
:
(alias) sa_resethand_probe.volatileStore = void core.volatile.volatileStore(ubyte* ptr, ubyte value) nothrow @nogc @safe

Read/write value from/to the memory location indicated by ptr.

These functions are recognized by the compiler, and calls to them are guaranteed to not be removed (as dead assignment elimination or presumed to have no effect) or reordered in the same thread.

These reordering guarantees are only made with regards to other operations done through these functions; the compiler is free to reorder regular loads/stores with regards to loads/stores done through these functions.

This is useful when dealing with memory-mapped I/O (MMIO) where a store can have an effect other than just writing a value, or where sequential loads with no intervening stores can retrieve different values from the same location due to external stores to the location.

These functions will, when possible, do the load/store as a single operation. In general, this is possible when the size of the operation is less than or equal to (void*).sizeof, although some targets may support larger operations. If the load/store cannot be done as a single operation, multiple smaller operations will be used.

These are not to be conflated with atomic operations. They do not guarantee any atomicity. This may be provided by coincidence as a result of the instructions used on the target, but this should not be relied on for portable programs. Further, no memory fences are implied by these functions. They should not be used for communication between threads. They may be used to guarantee a write or read cycle occurs at a specified address.

volatileStore
;
import
(package) std
std
.
(module) std.stdio
Category Symbols
File handles _popen File isFileHandle openNetwork stderr stdin stdout
Reading chunks lines readf readfln readln
Writing toFile write writef writefln writeln
Misc KeepTerminator LockType StdioException

Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio is publically imported when importing std.stdio.

There are three layers of I/O:

  1. The lowest layer is the operating system layer. The two main schemes are Windows and Posix.

  2. C's stdio.h which unifies the two operating system schemes.

  3. std.stdio, this module, unifies the various stdio.h implementations into a high level package for D programs.

Source

std/stdio.d

@copyrightCopyright The D Language Foundation 2007-.@licenseBoost License 1.0.@authorsWalter Bright, Andrei Alexandrescu, Alex Rønne Petersen
stdio
:
(alias template) sa_resethand_probe.writeln = std.stdio.writeln(T...)(T args)

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
;
__gshared int
(__gshared global) int sa_resethand_probe.reportFd
reportFd
;
extern (C) void
void sa_resethand_probe.onSegv(int __param_0, core.sys.posix.signal.siginfo_t* __param_1, void* __param_2) nothrow @nogc
onSegv
(int,
(struct) core.sys.posix.signal.siginfo_t
siginfo_t
*, void*) nothrow @nogc
{ ubyte
(local variable) ubyte one
one
= 1;
long core.sys.posix.unistd.write(int, scope const(void*), ulong) nothrow @nogc
write
(
(__gshared global) int sa_resethand_probe.reportFd
reportFd
, &
(local variable) ubyte one
one
, 1);
} int
int D main()
main
()
{ int[2]
(local variable) int[2] fds
fds
;
if (
int core.sys.posix.unistd.pipe(ref int[2]) nothrow @nogc @trusted
pipe
(
(local variable) int[2] fds
fds
) != 0)
return 1; const
(local variable) const(int) pid
pid
=
int core.sys.posix.unistd.fork() nothrow @nogc @trusted
fork
();
if (
(local variable) const(int) pid
pid
< 0)
return 1; if (
(local variable) const(int) pid
pid
== 0)
{
int core.sys.posix.unistd.close(int) nothrow @nogc @trusted
close
(
(local variable) int[2] fds
fds
[0]);
(__gshared global) int sa_resethand_probe.reportFd
reportFd
=
(local variable) int[2] fds
fds
[1];
(struct) core.sys.posix.signal.sigaction_t
sigaction_t
(local variable) core.sys.posix.signal.sigaction_t sa
sa
;
(local variable) core.sys.posix.signal.sigaction_t sa
sa
.
(field) extern (C) void function(int, core.sys.posix.signal.siginfo_t*, void*) core.sys.posix.signal.sigaction_t.sa_sigaction
sa_sigaction
= &
void sa_resethand_probe.onSegv(int __param_0, core.sys.posix.signal.siginfo_t* __param_1, void* __param_2) nothrow @nogc
onSegv
;
(local variable) core.sys.posix.signal.sigaction_t sa
sa
.
(field) int core.sys.posix.signal.sigaction_t.sa_flags
sa_flags
=
(constant) int core.sys.posix.signal.SA_SIGINFO = 4
SA_SIGINFO
|
(constant) uint core.sys.posix.signal.SA_RESETHAND = 2147483648u
SA_RESETHAND
;
int core.sys.posix.signal.sigemptyset(core.sys.posix.signal.sigset_t*) nothrow @nogc
sigemptyset
(&
(local variable) core.sys.posix.signal.sigaction_t sa
sa
.
(field) core.sys.posix.signal.sigset_t core.sys.posix.signal.sigaction_t.sa_mask
sa_mask
);
int core.sys.posix.signal.sigaction(int, scope const(core.sys.posix.signal.sigaction_t*), core.sys.posix.signal.sigaction_t*) nothrow @nogc
sigaction
(
(constant) int core.stdc.signal.SIGSEGV = 11
SIGSEGV
, &
(local variable) core.sys.posix.signal.sigaction_t sa
sa
, null);
void core.volatile.volatileStore(uint* ptr, uint value) nothrow @nogc @safe

Read/write value from/to the memory location indicated by ptr.

These functions are recognized by the compiler, and calls to them are guaranteed to not be removed (as dead assignment elimination or presumed to have no effect) or reordered in the same thread.

These reordering guarantees are only made with regards to other operations done through these functions; the compiler is free to reorder regular loads/stores with regards to loads/stores done through these functions.

This is useful when dealing with memory-mapped I/O (MMIO) where a store can have an effect other than just writing a value, or where sequential loads with no intervening stores can retrieve different values from the same location due to external stores to the location.

These functions will, when possible, do the load/store as a single operation. In general, this is possible when the size of the operation is less than or equal to (void*).sizeof, although some targets may support larger operations. If the load/store cannot be done as a single operation, multiple smaller operations will be used.

These are not to be conflated with atomic operations. They do not guarantee any atomicity. This may be provided by coincidence as a result of the instructions used on the target, but this should not be relied on for portable programs. Further, no memory fences are implied by these functions. They should not be used for communication between threads. They may be used to guarantee a write or read cycle occurs at a specified address.

volatileStore
(cast(uint*) 8, 1u); // the fault
noreturn core.sys.posix.unistd._exit(int) nothrow @nogc @trusted
_exit
(0);
}
int core.sys.posix.unistd.close(int) nothrow @nogc @trusted
close
(
(local variable) int[2] fds
fds
[1]);
enum
(constant) int sa_resethand_probe.main.cap = 100
cap
= 100;
int
(local variable) int deliveries
deliveries
;
ubyte
(local variable) ubyte b
b
;
while (
(local variable) int deliveries
deliveries
<
(constant) int sa_resethand_probe.main.cap = 100
cap
&&
long core.sys.posix.unistd.read(int, void*, ulong) nothrow @nogc
read
(
(local variable) int[2] fds
fds
[0], &
(local variable) ubyte b
b
, 1) == 1)
++
(local variable) int deliveries
deliveries
;
const
(local variable) const(bool) capped
capped
=
(local variable) int deliveries
deliveries
>=
(constant) int sa_resethand_probe.main.cap = 100
cap
;
if (
(local variable) const(bool) capped
capped
)
int core.sys.posix.signal.kill(int, int) nothrow @nogc
kill
(
(local variable) const(int) pid
pid
,
(constant) int core.sys.posix.signal.SIGKILL = 9
SIGKILL
);
int
(local variable) int status
status
;
int core.sys.posix.sys.wait.waitpid(int, int*, int) nothrow @nogc
waitpid
(
(local variable) const(int) pid
pid
, &
(local variable) int status
status
, 0);
const
(local variable) const(int) signalled
signalled
=
bool core.sys.posix.sys.wait.WIFSIGNALED!()(int status) pure nothrow @nogc @safe
WIFSIGNALED
(
(local variable) int status
status
) ?
int core.sys.posix.sys.wait.WTERMSIG!()(int status) pure nothrow @nogc @safe
WTERMSIG
(
(local variable) int status
status
) : 0;
if (!
(local variable) const(bool) capped
capped
&&
(local variable) int deliveries
deliveries
== 1 &&
(local variable) const(int) signalled
signalled
==
(constant) int core.stdc.signal.SIGSEGV = 11
SIGSEGV
)
void std.stdio.writeln!string(string __param_0) @safe

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
("SA_RESETHAND honoured: 1 delivery, child died of SIGSEGV");
else
void std.stdio.writeln!(string, int, string, string, string, string, const(int), string)(string __param_0, int __param_1, string __param_2, string __param_3, string __param_4, string __param_5, const(int) __param_6, string __param_7) @safe

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
("SA_RESETHAND IGNORED: ",
(local variable) int deliveries
deliveries
,
(local variable) const(bool) capped
capped
? "+" : "",
" deliveries, child ",
(local variable) const(bool) capped
capped
? "killed by the parent" : "exited otherwise",
" (signal ",
(local variable) const(int) signalled
signalled
, ")");
return 0; }