D543
- dlsym succeeds where the console refuses, and the reason
written down was not the reason
decided - 2026-09-04
First tick on a new axis:
crates/orbistoun-service/tests/hardware.rs, which the loop
notes name as the second place to look for work and which had not been
opened once in this run.
Sixty outstanding measurements, and reading all sixty - not the top
of the list - collapses them to sixteen distinct
blockers. Fourteen need a capture this project does not have.
One is a coincidence it declines to claim (the console's L1 line size is
0x40 and so is this host's, read through the same
cpuid the guest executes natively). One was wrong.
The reason written down had stopped being true, and had never been the reason
110-modules/symbol:sceKernelDlsym:memcpy
"0x80020003 is errno::NO_SUCH; needs a loaded module to resolve against,
which the loader does not yet provide"
The loader has provided one since D517 added guest exports. But that was never the mechanism: orbistoun does not fail this call. It succeeds.
obSCEne's 110-modules/symbol loads libkernel - handle
0x2001, a value three separate measurements agree on - and
asks it for memcpy. The console answers ESRCH: libkernel
does not export it. Orbistoun publishes every function it implements in
one flat by-name table (symbols::resolvable()) and
dlsym looks a name up there without consulting the
module handle at all. So it answers 0 and writes
an address into the guest's out-parameter, for a symbol the module the
guest named does not have.
Measured rather than read - the runtime table is installed during a
load, so a test binary sees dlsym answer the placeholder
and would have told a comfortable story. Standing a one-entry table in
for it:
without thunks dlsym(0x2001, "memcpy") = 0x7fff0001 (this project's placeholder)
with thunks dlsym(0x2001, "memcpy") = 0x0, out = the address
the console dlsym(0x2001, "memcpy") = 0x80020003 (ESRCH)
Three answers for one call depending on what is installed, and the entry described none of them.
The trap underneath it
Orbistoun already answers 0x80020003 from
dlsym - for a negative module handle, the
branch obSCEne's 060-module/dlsym-rejects-bad-handle covers
(D366). A test written to claim this measurement with an invalid handle
passes, in green, having exercised a branch with nothing to do with what
was measured.
That is check 11 and check 19 in one place: the new claim shares a
replay path with an old one, and the branch that produced the matching
value was not the branch under discussion. It is also the reason this
got a test rather than only a corrected sentence - the next person to
read "expected 0x80020003, orbistoun answers
0x80020003" will reach for the easy claim.
Recorded, not fixed
The fix is a per-module export list for the platform's own libraries, and nothing lawful here provides one. Inventing which symbols libkernel exports would not be a wrong answer, it would be a fabricated one - and unlike a wrong answer it would look authoritative.
Nor is the current behaviour obviously worse for a guest: a title
asking for memcpy and getting it keeps running. What is
wrong is that a guest asking whether a symbol exists is told
yes by something that never looked. So it goes in
OUTSTANDING with the mechanism written down, which is what
that list is for.
tests/dlsym_divergence.rs pins it, green on purpose -
this file's doctrine is that a permanently red test is a build people
learn to ignore. What it buys is noticing when the divergence stops
being the one that was written down: made to fail by making
dlsym consult the handle, which is exactly the change that
should send somebody back to the entry.
The general shape
Second time in two ticks that a stated blocker was wrong about its own mechanism, and the third in this run that reading a thing beat trusting what was written about it. The pattern is specific enough to name: an entry recording why something cannot be done is written once, at the moment of giving up, and nothing re-derives it afterwards. The measurements are regenerated from captures; the reasons beside them are not.