From 4e46511f2a0a80a04906fc0feca819ae7b229902 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 12 Sep 2026 15:59:37 +0200 Subject: [PATCH] test: test more of sigTest --- src/sigTest.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/src/sigTest.c b/src/sigTest.c index f7230f3..ab0889d 100644 --- a/src/sigTest.c +++ b/src/sigTest.c @@ -781,3 +781,158 @@ KTEST(sigConsumePendingRefusesNewHandlerAtMaxNestDepth) Registers regs = makeUserRegs(); 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, ®s, &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, ®s, &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, ®s, &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, ®s, &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, ®s, &outerSignum), sigOutcomeHandler); + KT_FALSE(t, thread->sigInRetryableSyscall); + + Registers midOuterHandler = regs; + int nestedSignum = -1; + sigRaise(id, sigHup); + KT_EQ(t, sigConsumePending(thread, ®s, &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)); +} +