From a4a523ed1180fe10a1256e3b71e2bfc18c503a8f Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 12 Sep 2026 16:39:30 +0200 Subject: [PATCH] test: extend coverage of sigTest --- src/sigTest.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/src/sigTest.c b/src/sigTest.c index 29c3c87..5ed983f 100644 --- a/src/sigTest.c +++ b/src/sigTest.c @@ -1095,3 +1095,153 @@ KTEST(sigConsumePendingLeavesRsiRdxUntouchedWithoutSigFlagSigInfo) int signum = -1; KT_EQ(t, sigConsumePending(thread, ®s, &signum), sigOutcomeHandler); + /* Untouched, exactly as documented -- a handler with no + sigFlagSigInfo never reads either register, so sigConsumePending + must not spend any effort computing values for them. */ + KT_EQU(t, regs.rsi, 0x5151515151515151ull); + KT_EQU(t, regs.rdx, 0x6262626262626262ull); + + KT_TRUE(t, threadDiscard(id)); +} + +KTEST(sigRaisePlainWrapperDefaultsToSiCodeKernelWithNoSenderOrStatus) +{ + uint32 id = threadCreate("sigInfoDefaultTarget", noopThreadEntry); + KT_NE(t, id, threadInvalidId); + Thread* thread = threadAt(id); + + sigSetHandler(thread, sigHup, 0x40009000ull, 0, sigFlagSigInfo); + /* The plain, pre-existing sigRaise -- every one of this file's other + ~50 call sites uses exactly this, and none of them supply detail, + so sigRaise's own thin-wrapper default (siCodeKernel/0/0) is what + they all still get. */ + KT_TRUE(t, sigRaise(id, sigHup)); + + Registers regs = makeUserRegs(); + int signum = -1; + KT_EQ(t, sigConsumePending(thread, ®s, &signum), sigOutcomeHandler); + + SigInfo* info = (SigInfo*)regs.rsi; + KT_EQ(t, info->siSignum, sigHup); + KT_EQ(t, info->siCode, siCodeKernel); + KT_EQ(t, info->siPid, 0); + KT_EQ(t, info->siStatus, 0); + + KT_TRUE(t, threadDiscard(id)); +} + +KTEST(sigRaiseInfoOverwritesStalePendingInfoOnReRaiseBeforeDelivery) +{ + uint32 id = threadCreate("sigInfoOverwriteTarget", noopThreadEntry); + KT_NE(t, id, threadInvalidId); + Thread* thread = threadAt(id); + + sigSetHandler(thread, sigUsr2, 0x40009000ull, 0, sigFlagSigInfo); + KT_TRUE(t, sigRaiseInfo(id, sigUsr2, siCodeUser, 111, 1)); + /* Same signum raised again before it was ever delivered -- non-queuing + signals coalesce, so only the SECOND raise's detail should ever be + observable, matching sigPending itself staying a single bit rather + than a count. */ + KT_TRUE(t, sigRaiseInfo(id, sigUsr2, siCodeUser, 222, 2)); + + Registers regs = makeUserRegs(); + int signum = -1; + KT_EQ(t, sigConsumePending(thread, ®s, &signum), sigOutcomeHandler); + + SigInfo* info = (SigInfo*)regs.rsi; + KT_EQ(t, info->siPid, 222); + KT_EQ(t, info->siStatus, 2); + + KT_TRUE(t, threadDiscard(id)); +} + +/* + * sigRaiseGroup (signal.h) -- the group-targeted half of real kill(2)'s + * pid<0/pid==0 convention (syscall.c's sysSigSend) and keyboard.c's own + * real Ctrl-C/Ctrl-Z, delivered to the FOREGROUND process group + * (sched.h's Thread.pgid) rather than one hardcoded target. The live + * ring-3 proof that this scoping actually protects a DIFFERENT group from + * a keyboard-generated signal is user/pgroupfg.c (killed by it) versus + * user/pgroupbg.c (proven still alive afterwards) in kmain.c's boot + * sequence; what is deterministic here is the plain fan-out-and-filter + * logic itself, mirroring schedTest.c's threadKillGroup coverage for the + * exact same shape of primitive one layer down. + */ + +KTEST(sigRaiseGroupDeliversToEveryLiveMemberOfThatGroupOnly) +{ + uint32 a = threadCreate("sigGroupA", noopThreadEntry); + uint32 b = threadCreate("sigGroupB", noopThreadEntry); + uint32 outsider = threadCreate("sigGroupOutsider", noopThreadEntry); + KT_NE(t, a, threadInvalidId); + KT_NE(t, b, threadInvalidId); + KT_NE(t, outsider, threadInvalidId); + + uint32 pgid = threadAt(a)->pgid; + KT_TRUE(t, threadSetPgid(b, pgid)); + KT_NE(t, threadAt(outsider)->pgid, pgid); + + KT_TRUE(t, sigRaiseGroup(pgid, sigUsr1, siCodeKernel, 0)); + KT_EQU(t, threadAt(a)->sigPending & (1ull << sigUsr1), (1ull << sigUsr1)); + KT_EQU(t, threadAt(b)->sigPending & (1ull << sigUsr1), (1ull << sigUsr1)); + /* The whole point: a DIFFERENT group must be completely untouched -- + this is what keeps a real Ctrl-C from ever reaching a background + job. */ + KT_EQU(t, threadAt(outsider)->sigPending, 0ull); + + KT_TRUE(t, threadDiscard(a)); + KT_TRUE(t, threadDiscard(b)); + KT_TRUE(t, threadDiscard(outsider)); +} + +KTEST(sigRaiseGroupReturnsFalseWhenNoLiveThreadHasThatPgid) +{ + KT_FALSE(t, sigRaiseGroup(0xBEEF0001u, sigUsr1, siCodeKernel, 0)); +} + +KTEST(sigRaiseGroupSkipsDeadOrUnusedMembersButStillFindsLiveOnes) +{ + uint32 a = threadCreate("sigGroupDeadA", noopThreadEntry); + uint32 b = threadCreate("sigGroupDeadB", noopThreadEntry); + KT_NE(t, a, threadInvalidId); + KT_NE(t, b, threadInvalidId); + + uint32 pgid = threadAt(a)->pgid; + KT_TRUE(t, threadSetPgid(b, pgid)); + KT_TRUE(t, threadKill(b)); + + KT_TRUE(t, sigRaiseGroup(pgid, sigUsr2, siCodeKernel, 0)); + KT_EQU(t, threadAt(a)->sigPending & (1ull << sigUsr2), (1ull << sigUsr2)); + /* Dead members are silently skipped, exactly like sigRaiseInfo's own + Dead/Unused guard already refuses a single-target send against one + -- no crash, no stale sigPending bit written into a slot that is no + longer this thread's own. */ + KT_EQU(t, threadAt(b)->sigPending, 0ull); + + KT_TRUE(t, threadDiscard(a)); + KT_TRUE(t, threadReap(b)); +} + +KTEST(sigRaiseGroupWithSigStopStopsEveryLiveMemberOfThatGroup) +{ + uint32 a = threadCreate("sigGroupStopA", noopThreadEntry); + uint32 b = threadCreate("sigGroupStopB", noopThreadEntry); + KT_NE(t, a, threadInvalidId); + KT_NE(t, b, threadInvalidId); + + uint32 pgid = threadAt(a)->pgid; + KT_TRUE(t, threadSetPgid(b, pgid)); + + /* sigStop is delegated straight to threadStop per-target, bypassing + sigPending entirely -- the same bypass sigRaiseInfo's own file + comment documents for a single-target sigStop, now proven to apply + independently to every member the group fan-out visits. */ + KT_TRUE(t, sigRaiseGroup(pgid, sigStop, siCodeKernel, 0)); + KT_EQ(t, threadAt(a)->state, threadStopped); + KT_EQ(t, threadAt(b)->state, threadStopped); + + KT_TRUE(t, threadContinue(a)); + KT_TRUE(t, threadContinue(b)); + KT_TRUE(t, threadDiscard(a)); + KT_TRUE(t, threadDiscard(b)); +}