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();
|
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, ®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