#!/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_probeDoes 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) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.signalD header file for POSIX.
Source
core/sys/posix/signal.d
signal : (alias) sa_resethand_probe.kill = int core.sys.posix.signal.kill(int, int) nothrow @nogckill, (alias constant) sa_resethand_probe.SA_RESETHAND = uint core.sys.posix.signal.SA_RESETHAND = 2147483648uSA_RESETHAND, (alias constant) sa_resethand_probe.SA_SIGINFO = int core.sys.posix.signal.SA_SIGINFO = 4SA_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 @nogcsigaction, (struct) core.sys.posix.signal.sigaction_tsigaction_t,
(alias) sa_resethand_probe.sigemptyset = int core.sys.posix.signal.sigemptyset(core.sys.posix.signal.sigset_t*) nothrow @nogcsigemptyset, (struct) core.sys.posix.signal.siginfo_tsiginfo_t, (alias constant) sa_resethand_probe.SIGKILL = int core.sys.posix.signal.SIGKILL = 9SIGKILL, (alias constant) sa_resethand_probe.SIGSEGV = int core.stdc.signal.SIGSEGV = 11SIGSEGV;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(package) core.sys.posix.syssys.(module) core.sys.posix.sys.waitD header file for POSIX.
wait : (alias) sa_resethand_probe.waitpid = int core.sys.posix.sys.wait.waitpid(int, int*, int) nothrow @nogcwaitpid, (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) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.unistdD header file for POSIX.
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 @trustedclose, (alias) sa_resethand_probe.fork = int core.sys.posix.unistd.fork() nothrow @nogc @trustedfork, (alias) sa_resethand_probe.pipe = int core.sys.posix.unistd.pipe(ref int[2]) nothrow @nogc @trustedpipe, (alias) sa_resethand_probe.read = long core.sys.posix.unistd.read(int, void*, ulong) nothrow @nogcread, (alias) sa_resethand_probe.write = long core.sys.posix.unistd.write(int, scope const(void*), ulong) nothrow @nogcwrite;
import (package) corecore.(module) core.volatileThis module declares intrinsics for volatile operations.
Source
core/volatile.d
volatile : (alias) sa_resethand_probe.volatileStore = void core.volatile.volatileStore(ubyte* ptr, ubyte value) nothrow @nogc @safeRead/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) stdstd.(module) std.stdioCategory 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:
The lowest layer is the operating system layer. The two main schemes are Windows and Posix.
C's stdio.h which unifies the two operating system schemes.
std.stdio, this module, unifies the various stdio.h implementations into
a high level package for D programs.
Source
std/stdio.d
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);
}
}
writeln;
__gshared int (__gshared global) int sa_resethand_probe.reportFdreportFd;
extern (C) void void sa_resethand_probe.onSegv(int __param_0, core.sys.posix.signal.siginfo_t* __param_1, void* __param_2) nothrow @nogconSegv(int, (struct) core.sys.posix.signal.siginfo_tsiginfo_t*, void*) nothrow @nogc
{
ubyte (local variable) ubyte oneone = 1;
long core.sys.posix.unistd.write(int, scope const(void*), ulong) nothrow @nogcwrite((__gshared global) int sa_resethand_probe.reportFdreportFd, &(local variable) ubyte oneone, 1);
}
int int D main()main()
{
int[2] (local variable) int[2] fdsfds;
if (int core.sys.posix.unistd.pipe(ref int[2]) nothrow @nogc @trustedpipe((local variable) int[2] fdsfds) != 0)
return 1;
const (local variable) const(int) pidpid = int core.sys.posix.unistd.fork() nothrow @nogc @trustedfork();
if ((local variable) const(int) pidpid < 0)
return 1;
if ((local variable) const(int) pidpid == 0)
{
int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose((local variable) int[2] fdsfds[0]);
(__gshared global) int sa_resethand_probe.reportFdreportFd = (local variable) int[2] fdsfds[1];
(struct) core.sys.posix.signal.sigaction_tsigaction_t (local variable) core.sys.posix.signal.sigaction_t sasa;
(local variable) core.sys.posix.signal.sigaction_t sasa.(field) extern (C) void function(int, core.sys.posix.signal.siginfo_t*, void*) core.sys.posix.signal.sigaction_t.sa_sigactionsa_sigaction = &void sa_resethand_probe.onSegv(int __param_0, core.sys.posix.signal.siginfo_t* __param_1, void* __param_2) nothrow @nogconSegv;
(local variable) core.sys.posix.signal.sigaction_t sasa.(field) int core.sys.posix.signal.sigaction_t.sa_flagssa_flags = (constant) int core.sys.posix.signal.SA_SIGINFO = 4SA_SIGINFO | (constant) uint core.sys.posix.signal.SA_RESETHAND = 2147483648uSA_RESETHAND;
int core.sys.posix.signal.sigemptyset(core.sys.posix.signal.sigset_t*) nothrow @nogcsigemptyset(&(local variable) core.sys.posix.signal.sigaction_t sasa.(field) core.sys.posix.signal.sigset_t core.sys.posix.signal.sigaction_t.sa_masksa_mask);
int core.sys.posix.signal.sigaction(int, scope const(core.sys.posix.signal.sigaction_t*), core.sys.posix.signal.sigaction_t*) nothrow @nogcsigaction((constant) int core.stdc.signal.SIGSEGV = 11SIGSEGV, &(local variable) core.sys.posix.signal.sigaction_t sasa, null);
void core.volatile.volatileStore(uint* ptr, uint value) nothrow @nogc @safeRead/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 @trustedclose((local variable) int[2] fdsfds[1]);
enum (constant) int sa_resethand_probe.main.cap = 100cap = 100;
int (local variable) int deliveriesdeliveries;
ubyte (local variable) ubyte bb;
while ((local variable) int deliveriesdeliveries < (constant) int sa_resethand_probe.main.cap = 100cap && long core.sys.posix.unistd.read(int, void*, ulong) nothrow @nogcread((local variable) int[2] fdsfds[0], &(local variable) ubyte bb, 1) == 1)
++(local variable) int deliveriesdeliveries;
const (local variable) const(bool) cappedcapped = (local variable) int deliveriesdeliveries >= (constant) int sa_resethand_probe.main.cap = 100cap;
if ((local variable) const(bool) cappedcapped)
int core.sys.posix.signal.kill(int, int) nothrow @nogckill((local variable) const(int) pidpid, (constant) int core.sys.posix.signal.SIGKILL = 9SIGKILL);
int (local variable) int statusstatus;
int core.sys.posix.sys.wait.waitpid(int, int*, int) nothrow @nogcwaitpid((local variable) const(int) pidpid, &(local variable) int statusstatus, 0);
const (local variable) const(int) signalledsignalled = bool core.sys.posix.sys.wait.WIFSIGNALED!()(int status) pure nothrow @nogc @safeWIFSIGNALED((local variable) int statusstatus) ? int core.sys.posix.sys.wait.WTERMSIG!()(int status) pure nothrow @nogc @safeWTERMSIG((local variable) int statusstatus) : 0;
if (!(local variable) const(bool) cappedcapped && (local variable) int deliveriesdeliveries == 1 && (local variable) const(int) signalledsignalled == (constant) int core.stdc.signal.SIGSEGV = 11SIGSEGV)
void std.stdio.writeln!string(string __param_0) @safeEquivalent 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);
}
}
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) @safeEquivalent 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);
}
}
writeln("SA_RESETHAND IGNORED: ", (local variable) int deliveriesdeliveries, (local variable) const(bool) cappedcapped ? "+" : "",
" deliveries, child ", (local variable) const(bool) cappedcapped ? "killed by the parent" : "exited otherwise",
" (signal ", (local variable) const(int) signalledsignalled, ")");
return 0;
}