D632 - One function, two answers, decided by how the guest asked

Status: measured Date: 2026-09-08

The claim that was true of two-thirds of the table

sceKernelDlsym's own doc comment says what makes it trustworthy:

A name is looked up in the same table of stubs the linker resolves imports into, so a function reached this way and the same function reached by an import are the same address.

It is true of every function this project implements, and false of every one it merely declares. The by-name table is built from symbols::resolvable() - the implemented set - so a name reached by import lands on a stub answering the placeholder, and the same name reached by name is refused. 949 declared, 680 implemented: 269 names with two answers depending on how the guest asked.

One payload run asks by name for 266 of them.

Four entries on the differential are that, and they say so in the probe's own words: 101-input-ext/keyboard-presence, 101-input-ext/mouse-presence, 107-videodec/symbols, 108-audiodec/ajm - all "loaded but no symbols resolved", for libraries orbistoun declares and stubs.

Two-sided, so measured rather than argued

The console resolves both. Whether orbistoun should is not obvious, and D125's reasoning against is real: a guest handed a stub calls it and gets a placeholder, where a guest handed null may take a fallback path it would have preferred. An argument does not settle that; a run does.

So ORBISTOUN_DLSYM_STUBS - declared as Effect::Intervenes, off by default, and the table it installs is empty unless a run asks, which is what makes the two runs comparable at all.

What the runs said

The conformance payload, which resolves by name:

orbistoun: 105 declared name(s) are resolvable by name under ORBISTOUN_DLSYM_STUBS
  imports  226 distinct (+3) …  fault the guest called exit   verdict FURTHER

And the second observation, of a different kind, because an intervention that moves a wall is not a diagnosis - the probe's own verdicts against the console:

baseline under the flag
checks both ran, concluding differently 255 240
passed there, failed here 115 106
distinct findings 17 13

The four "loaded but no symbols resolved" entries are gone, and the absent-library census shrank from 99 to 94.

And three titles are byte-identical: PPSA03416, PPSA02664 and PPSA04263 report the same imports, the same call counts, the same standing and the same fault address with the flag and without. They resolve by import, so this cannot touch them - now measured rather than assumed.

Left as a flag

No guest in the corpus got worse and one got better, which is evidence and not proof: four guests is a small population, and the case against is about a guest that prefers a null it can branch on

Changing what dlsym answers by default is a user-visible behaviour change, which this project flags rather than assumes. The flag, the measurement and the recommendation are here; the default stays as it was until somebody decides.

The narrowing in D629 sits above this and is unaffected: a libkernel handle still refuses a name declared elsewhere, whether or not this table is installed. Order matters and is fixed - an implementation first, then the guest's own export, then this - so a stub for a declared name can never shadow either.