test: test more of sigTest

This commit is contained in:
dualfroz committed 2026-09-12 15:59:37 +02:00
1 parent febf5254a4
commit 4e46511f2a
1 file changed
+155
+155
View File
@@ -781,3 +781,158 @@ KTEST(sigConsumePendingRefusesNewHandlerAtMaxNestDepth)
Registers regs = makeUserRegs(); Registers regs = makeUserRegs();
int signum = -1; int signum = -1;
/* Poke the depth field directly to the bound rather than actually
nesting sigMaxNestDepth real handlers -- this test is about the
GUARD, which the fault-injection check below confirms is load-
bearing (removing it does not, by itself, corrupt anything a plain
"one more handler" scenario would exercise; it needs the table
genuinely full to matter). */
thread->sigHandlerDepth = sigMaxNestDepth;
sigSetHandler(thread, sigUsr1, 0x40009000ull, 0, 0);
sigRaise(id, sigUsr1);
KT_EQ(t, sigConsumePending(thread, &regs, &signum), sigOutcomeNone);
KT_EQ(t, signum, -1);
/* Left pending, not lost -- exactly like a blocked signal, it simply
waits for room (a sigOnSigReturn dropping the depth back down). */
KT_TRUE(t, thread->sigPending & (1ull << sigUsr1));
KT_EQU(t, thread->sigHandlerDepth, (uint32)sigMaxNestDepth);
thread->sigHandlerDepth = 0;
KT_TRUE(t, threadDiscard(id));
}
/*
* SA_RESTART / EINTR (signalnum.h's sigFlagRestart, sched.h's
* Thread.sigInRetryableSyscall) -- signal.h's file comment has the full
* story. These four drive sigConsumePending directly instead of a live
* syscall retry, the same division of labor the rest of this file already
* uses: user/restarttarget.c/restarttest.c prove the real ring-3 round
* trip, these prove the exact rip/rax rewrite (or lack of it) is gated on
* precisely the right condition.
*/
KTEST(sigConsumePendingReturnsEintrForInterruptedRetryableSyscallWithoutRestartFlag)
{
uint32 id = threadCreate("sigEintrTarget", noopThreadEntry);
KT_NE(t, id, threadInvalidId);
Thread* thread = threadAt(id);
Registers regs = makeUserRegs();
/* Stand-in for the exact state sysRead/sysWrite/sysWait (syscall.c)
leave a thread in right before blocking: rip rewound 2 bytes behind
the `int $0x80` that trapped in, so re-entering ring 3 normally
would re-execute it. */
regs.rip = 0x40001232ull;
int signum = -1;
uint64 handlerAddr = 0x4000A000ull;
sigSetHandler(thread, sigUsr1, handlerAddr, 0, 0);
thread->sigInRetryableSyscall = true;
sigRaise(id, sigUsr1);
KT_EQ(t, sigConsumePending(thread, &regs, &signum), sigOutcomeHandler);
KT_EQ(t, signum, sigUsr1);
/* The handler still runs normally (trampoline entry) -- the rewrite
only touches the SAVED, about-to-resume context below, never how
this handler itself gets entered. */
KT_EQU(t, regs.rip, sigTrampolineVirtualAddr);
KT_EQU(t, regs.rax, handlerAddr);
/* What actually resumes once this handler returns: rip past the
interrupted `int $0x80` entirely (not just past the rewind -- the
retry itself is skipped), rax the raw negative "errno" a real
syscall would return -- genuine EINTR, not a silent retry. */
KT_EQU(t, thread->sigSavedRegs[0].rip, 0x40001232ull + 2);
KT_EQU(t, thread->sigSavedRegs[0].rax, (uint64)(int64)errInterrupted);
/* Consumed -- a later, nested signal must never see this as still
true (see the next test). */
KT_FALSE(t, thread->sigInRetryableSyscall);
KT_TRUE(t, threadDiscard(id));
}
KTEST(sigConsumePendingPreservesTransparentRetryWhenRestartFlagSet)
{
uint32 id = threadCreate("sigRestartTarget", noopThreadEntry);
KT_NE(t, id, threadInvalidId);
Thread* thread = threadAt(id);
Registers regs = makeUserRegs();
regs.rip = 0x40001232ull;
Registers before = regs;
int signum = -1;
sigSetHandler(thread, sigUsr1, 0x4000A000ull, 0, sigFlagRestart);
thread->sigInRetryableSyscall = true;
sigRaise(id, sigUsr1);
KT_EQ(t, sigConsumePending(thread, &regs, &signum), sigOutcomeHandler);
/* sigFlagRestart: the saved, about-to-resume context is untouched --
exactly the original retry point, so the syscall genuinely retries
once this handler returns, same as every handler behaved before this
flag existed. */
KT_EQ(t, memcmp(&thread->sigSavedRegs[0], &before, sizeof(before)), 0);
KT_FALSE(t, thread->sigInRetryableSyscall);
KT_TRUE(t, threadDiscard(id));
}
KTEST(sigConsumePendingLeavesRegsAloneWhenNotInterruptingARetryableSyscall)
{
uint32 id = threadCreate("sigNoRetryTarget", noopThreadEntry);
KT_NE(t, id, threadInvalidId);
Thread* thread = threadAt(id);
Registers regs = makeUserRegs();
Registers before = regs;
int signum = -1;
/* No sigFlagRestart, AND this thread was never actually mid a
retryable syscall (a fresh thread's sigInRetryableSyscall starts
false) -- ordinary running code being interrupted, exactly like a
plain sysSleep or no syscall at all. The gate must refuse to invent
an EINTR here just because the flag is absent. */
sigSetHandler(thread, sigUsr1, 0x4000A000ull, 0, 0);
KT_FALSE(t, thread->sigInRetryableSyscall);
sigRaise(id, sigUsr1);
KT_EQ(t, sigConsumePending(thread, &regs, &signum), sigOutcomeHandler);
KT_EQ(t, memcmp(&thread->sigSavedRegs[0], &before, sizeof(before)), 0);
KT_TRUE(t, threadDiscard(id));
}
KTEST(sigConsumePendingNestedHandlerNeverInheritsOuterRetryState)
{
uint32 id = threadCreate("sigNestedRetryTarget", noopThreadEntry);
KT_NE(t, id, threadInvalidId);
Thread* thread = threadAt(id);
Registers regs = makeUserRegs();
regs.rip = 0x40001232ull;
int outerSignum = -1;
sigSetHandler(thread, sigUsr1, 0x4000A000ull, 0, sigFlagRestart);
sigSetHandler(thread, sigHup, 0x4000B000ull, 0, 0);
thread->sigInRetryableSyscall = true;
sigRaise(id, sigUsr1);
KT_EQ(t, sigConsumePending(thread, &regs, &outerSignum), sigOutcomeHandler);
KT_FALSE(t, thread->sigInRetryableSyscall);
Registers midOuterHandler = regs;
int nestedSignum = -1;
sigRaise(id, sigHup);
KT_EQ(t, sigConsumePending(thread, &regs, &nestedSignum), sigOutcomeHandler);
/* sigHup has no sigFlagRestart, but it never interrupted a retryable
syscall -- it interrupted sigUsr1's OWN already-running handler.
thread->sigInRetryableSyscall was already consumed (cleared) by the
outer delivery above, so this nested push must leave regs alone
regardless of sigHup's own flags: the saved context is exactly the
mid-outer-handler snapshot, not an invented EINTR. */
KT_EQ(t, memcmp(&thread->sigSavedRegs[1], &midOuterHandler,
sizeof(midOuterHandler)), 0);
KT_TRUE(t, threadDiscard(id));
}