test: strengthen tests for sigTest
This commit is contained in:
1 parent
978d2ad905
commit
febf5254a4
1 file changed
+155
+155
@@ -626,3 +626,158 @@ KTEST(sigConsumePendingDefersSameSignalRaisedAgainWhileItsOwnHandlerIsActive)
|
||||
KT_TRUE(t, threadDiscard(id));
|
||||
}
|
||||
|
||||
/* sigFlagNoDefer's whole point: registered for a signal, that signal's own
|
||||
bit is no longer added to sigBlocked when its handler starts, so raising
|
||||
the SAME signum again while that handler is still active genuinely
|
||||
nests a second instance of it -- the direct opposite outcome of the
|
||||
test just above, same signum, only the flag differs. */
|
||||
KTEST(sigConsumePendingNoDeferFlagLetsSameSignalNestIntoItsOwnActiveHandler)
|
||||
{
|
||||
uint32 id = threadCreate("sigNoDeferOnTarget", noopThreadEntry);
|
||||
KT_NE(t, id, threadInvalidId);
|
||||
Thread* thread = threadAt(id);
|
||||
Registers regs = makeUserRegs();
|
||||
int signum = -1;
|
||||
|
||||
uint64 handlerAddr = 0x40009000ull;
|
||||
sigSetHandler(thread, sigUsr1, handlerAddr, 0, sigFlagNoDefer);
|
||||
sigRaise(id, sigUsr1);
|
||||
|
||||
KT_EQ(t, sigConsumePending(thread, ®s, &signum), sigOutcomeHandler);
|
||||
KT_EQ(t, signum, sigUsr1);
|
||||
/* The one thing sigFlagNoDefer actually changes: sigUsr1's own bit is
|
||||
NOT in sigBlocked, unlike the default case above. */
|
||||
KT_FALSE(t, thread->sigBlocked & (1ull << sigUsr1));
|
||||
KT_EQU(t, thread->sigHandlerDepth, 1u);
|
||||
|
||||
Registers midHandler = regs;
|
||||
sigRaise(id, sigUsr1);
|
||||
int nestedSignum = -1;
|
||||
KT_EQ(t, sigConsumePending(thread, ®s, &nestedSignum), sigOutcomeHandler);
|
||||
KT_EQ(t, nestedSignum, sigUsr1);
|
||||
KT_EQU(t, regs.rip, sigTrampolineVirtualAddr);
|
||||
KT_EQU(t, regs.rax, handlerAddr);
|
||||
KT_EQU(t, thread->sigHandlerDepth, 2u);
|
||||
/* Still no self-block even at the nested depth -- a third raise would
|
||||
nest again the same way, bounded only by sigMaxNestDepth, never by
|
||||
sigUsr1 blocking itself. */
|
||||
KT_FALSE(t, thread->sigBlocked & (1ull << sigUsr1));
|
||||
KT_EQ(t, memcmp(&thread->sigSavedRegs[1], &midHandler, sizeof(midHandler)), 0);
|
||||
|
||||
/* Unwinding pops the nested instance first, resuming the outer one
|
||||
exactly where the re-raise interrupted it -- LIFO, same as any other
|
||||
nested delivery. */
|
||||
KT_TRUE(t, sigOnSigReturn(thread, ®s));
|
||||
KT_EQ(t, memcmp(®s, &midHandler, sizeof(midHandler)), 0);
|
||||
KT_EQU(t, thread->sigHandlerDepth, 1u);
|
||||
|
||||
KT_TRUE(t, sigOnSigReturn(thread, ®s));
|
||||
KT_EQU(t, thread->sigHandlerDepth, 0u);
|
||||
|
||||
KT_TRUE(t, threadDiscard(id));
|
||||
}
|
||||
|
||||
KTEST(sigConsumePendingCustomHandlerRewritesTrapframeAndPushesContext)
|
||||
{
|
||||
uint32 id = threadCreate("sigConsumeHandler", noopThreadEntry);
|
||||
KT_NE(t, id, threadInvalidId);
|
||||
Thread* thread = threadAt(id);
|
||||
Registers regs = makeUserRegs();
|
||||
Registers before = regs;
|
||||
int signum = -1;
|
||||
|
||||
uint64 handlerAddr = 0x40009000ull;
|
||||
sigSetHandler(thread, sigUsr1, handlerAddr, 0, 0);
|
||||
sigProcMask(thread, sigMaskBlock, (1ull << sigUsr2));
|
||||
uint64 blockedBefore = thread->sigBlocked;
|
||||
sigRaise(id, sigUsr1);
|
||||
|
||||
KT_EQ(t, sigConsumePending(thread, ®s, &signum), sigOutcomeHandler);
|
||||
KT_EQ(t, signum, sigUsr1);
|
||||
|
||||
/* Trapframe rewritten to enter the trampoline: rip is the shared
|
||||
trampoline entry, rax is the handler address its `call *%rax`
|
||||
invokes, rdi is the signal number (the handler's sole argument). */
|
||||
KT_EQU(t, regs.rip, sigTrampolineVirtualAddr);
|
||||
KT_EQU(t, regs.rax, handlerAddr);
|
||||
KT_EQU(t, regs.rdi, (uint64)sigUsr1);
|
||||
/* Untouched: the trampoline runs on the interrupted thread's own
|
||||
existing stack, no separate signal stack. */
|
||||
KT_EQU(t, regs.rsp, before.rsp);
|
||||
|
||||
/* sigUsr1's own sa_mask is empty (0 above), so the ONLY new bit added
|
||||
to sigBlocked is sigUsr1 itself (self-blocked, like real Unix) --
|
||||
whatever was already blocked (sigUsr2, from sigProcMask) survives
|
||||
untouched, a union not a replacement. */
|
||||
KT_EQU(t, thread->sigHandlerDepth, 1u);
|
||||
KT_EQU(t, thread->sigBlocked, blockedBefore | (1ull << sigUsr1));
|
||||
KT_EQU(t, thread->sigSavedBlocked[0], blockedBefore);
|
||||
|
||||
/* The ENTIRE pre-signal trapframe must be recoverable later -- not
|
||||
just the fields this test happens to check above. */
|
||||
KT_EQ(t, memcmp(&thread->sigSavedRegs[0], &before, sizeof(before)), 0);
|
||||
|
||||
/* An ALREADY-blocked signal (sigUsr2, blocked since before sigUsr1 was
|
||||
even raised) stays deferred while a handler is active, exactly like
|
||||
it would with no handler running at all -- the depth stack does not
|
||||
change what sigBlocked itself gates. */
|
||||
sigRaise(id, sigUsr2);
|
||||
int blockedSignum = -1;
|
||||
KT_EQ(t, sigConsumePending(thread, ®s, &blockedSignum), sigOutcomeNone);
|
||||
KT_EQ(t, blockedSignum, -1);
|
||||
KT_TRUE(t, thread->sigPending & (1ull << sigUsr2));
|
||||
|
||||
/* A signal NOT covered by sigUsr1's mask nor its own self-block --
|
||||
sigHup -- genuinely interrupts the active handler with a NESTED one
|
||||
of its own: this is the behavior a single "one handler at a time"
|
||||
bool (this code used to have) could never produce, since it gated
|
||||
delivery before sigBlocked was even consulted. */
|
||||
Registers midUsr1Handler = regs;
|
||||
uint64 handlerAddrHup = 0x4000B000ull;
|
||||
sigSetHandler(thread, sigHup, handlerAddrHup, 0, 0);
|
||||
sigRaise(id, sigHup);
|
||||
int nestedSignum = -1;
|
||||
KT_EQ(t, sigConsumePending(thread, ®s, &nestedSignum), sigOutcomeHandler);
|
||||
KT_EQ(t, nestedSignum, sigHup);
|
||||
KT_EQU(t, regs.rip, sigTrampolineVirtualAddr);
|
||||
KT_EQU(t, regs.rax, handlerAddrHup);
|
||||
KT_EQU(t, regs.rdi, (uint64)sigHup);
|
||||
KT_EQU(t, thread->sigHandlerDepth, 2u);
|
||||
/* sigHup's own handler has no mask either, so its blocked set is
|
||||
whatever sigUsr1's handler already made it, plus sigHup's own bit. */
|
||||
KT_EQU(t, thread->sigBlocked,
|
||||
blockedBefore | (1ull << sigUsr1) | (1ull << sigHup));
|
||||
/* What got pushed for THIS (nested) frame is exactly the trapframe
|
||||
sigUsr1's handler was actually running with the instant sigHup
|
||||
preempted it -- not the original pre-sigUsr1 `before` snapshot. */
|
||||
KT_EQ(t, memcmp(&thread->sigSavedRegs[1], &midUsr1Handler,
|
||||
sizeof(midUsr1Handler)), 0);
|
||||
KT_EQU(t, thread->sigSavedBlocked[1], blockedBefore | (1ull << sigUsr1));
|
||||
|
||||
/* Unwinding: first sigOnSigReturn resumes sigUsr1's still-active
|
||||
handler exactly where sigHup interrupted it, not the original
|
||||
program -- LIFO, like a real call stack of signal frames. */
|
||||
KT_TRUE(t, sigOnSigReturn(thread, ®s));
|
||||
KT_EQ(t, memcmp(®s, &midUsr1Handler, sizeof(midUsr1Handler)), 0);
|
||||
KT_EQU(t, thread->sigHandlerDepth, 1u);
|
||||
KT_EQU(t, thread->sigBlocked, blockedBefore | (1ull << sigUsr1));
|
||||
|
||||
/* Second sigOnSigReturn finally resumes the ORIGINAL interrupted
|
||||
program, restoring the exact pre-sigUsr1 trapframe and blocked
|
||||
mask. */
|
||||
KT_TRUE(t, sigOnSigReturn(thread, ®s));
|
||||
KT_EQ(t, memcmp(®s, &before, sizeof(before)), 0);
|
||||
KT_EQU(t, thread->sigHandlerDepth, 0u);
|
||||
KT_EQU(t, thread->sigBlocked, blockedBefore);
|
||||
|
||||
KT_TRUE(t, threadDiscard(id));
|
||||
}
|
||||
|
||||
KTEST(sigConsumePendingRefusesNewHandlerAtMaxNestDepth)
|
||||
{
|
||||
uint32 id = threadCreate("sigNestDepthLimit", noopThreadEntry);
|
||||
KT_NE(t, id, threadInvalidId);
|
||||
Thread* thread = threadAt(id);
|
||||
Registers regs = makeUserRegs();
|
||||
int signum = -1;
|
||||
|
||||
Reference in new issue
Block a user