test: test more of sigTest
This commit is contained in:
1 parent
febf5254a4
commit
4e46511f2a
1 file changed
+155
+155
@@ -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));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user