D648 - Half a signal contract is worth implementing

Status: measured Date: 2026-09-09

What came back

REQ-20260909T1215Z-6d31 asked obSCEne for the return contract and the ordering of sceKernelInstallExceptionHandler and sceKernelRaiseException. Sweep 20260909-140114 answered with both symbols resolved as libkernel exports and six return values:

call return
install(30, handler) 0x0
install(30, other) 0x8002_0023
install(30, NULL) while 30 is installed 0x8002_0023
remove(30) 0x0
raise(1, 30) with a handler installed 0x8002_0003
raise(1, 30) with the handler removed 0x8002_0003
inverted raise(30, thread) 0x8002_0016

The inverted call confirms the argument order independently of PPSA25872's own cmp edi, 0x1e.

The ordering question was not answered, because the probe never reached it

Every raise passed the literal thread handle 1, so every raise failed on the handle and no handler ever ran. The one bit the request was built around - is the flag set when raise returns - is not in the sweep. That is not a complaint: it is the difference between the branches that can be implemented and the one that cannot, and it is what this record is about.

Implemented: install and remove. Refused: the delivering raise

install records the handler and answers 0x0, or errno 35 for a duplicate. remove answers 0x0 for a signal that has one. raise answers ESRCH for a handle this process never issued - and the loud placeholder for everything else, including the case PPSA25872 actually makes.

Answering 0 there would move the wall today. It would also decide, silently and without evidence, that the call is asynchronous - because a synchronous call returning before its handler ran is not a contract any platform has. The next wall would then be built on that guess, and the guess would be invisible. This is the same trade D645 refused before the measurement existed, and the measurement did not change the part it refused.

The EINVAL for signal 31 is not modelled either. SIGUSR2 is 31 in this platform's own harvested headers, so "31 is not a valid signal" cannot be what it measured; and the same run answers ESRCH for signal 30 with no handler, which the "no handler" reading would make EINVAL too. Two readings, one observation, no way to choose. It is left out and asked again.

The gloss was wrong and the number was right

The sweep glosses 0x8002_0023 as "35 = EEXIST". 35 is right; EEXIST is 17 in the FreeBSD-derived numbering this project harvested from the platform's own headers, and 35 is EAGAIN. orbistoun_core::errno::AGAIN is therefore recorded as 35 with the measurement as its provenance and the gloss explicitly rejected in its own doc comment.

A wrong constant name is worse than a wrong value, because it is reused. Every later implementation reaching for EXISTS would have got 35 and been right by accident until the day something needed the real 17. Consuming a sibling project's measurement means consuming the numbers and re-deriving the names.

What it did to the run, and what "BACK" meant

PPSA25872: stub calls 8 → 6, both calls now answered as measured. The guest still goes quiet 0.2s into a 20-second run, because the branch it needs is the refused one.

The report said BACK reaching less of the interface than it did - 140 distinct against 141. That was worth chasing rather than explaining away, so both builds were run and their label sets compared: identical, 114 labels each. Nothing the guest reaches was lost. distinct counts symbol indices with a nonzero count, and two indices can carry one label, so implementing a function can move the count without moving the guest. The verdict is not wrong about what it measures; it is measuring something narrower than the sentence it prints.