The emit-obj xtensa link shim does not provide the PAL backend's ESP-IDF symbols
FOLDED. The DEFECT is
[[bug-a-emit-obj-retains-pxxassert-so-one-ansistring-in-it-imports-the-whole-esp-pal]]
and always was. This ticket keeps only the HARNESS half — the link step naming
its stubs by hand, and the two xtensa links separated by ;. Everything it once
said about why the object imports lwip has been moved to frankZ's ticket, which
had it a day earlier and with a bisect; leaving a second copy here would have
been one defect on two tickets, and the shallower copy is the one people find
first because it is named after the symptom.
Found 2026-09-06 while closing
[[bug-a-the-i386-pic-prefix-guard-reads-a-displacement-byte-as-a-prefix]] — a
textbook case of the loud defect hiding the quiet one. The i386 relocation
assertion aborted test-emit-obj roughly 700 recipe lines before this step, so
it had not been reached in however long the i386 row had been red, and its
failure therefore arrived looking brand new.
Why the recipe's own comment pointed the wrong way
The recipe carried a paragraph from regression-test-emit-obj-test-emit-obj
saying the shim's gap "read as riscv32-specific and is not: all three objects
carry UND ext_aliased_link identically … Only the riscv32 line appeared in the
log because make ABORTS there and never reaches the xtensa links." That was
right about ext_aliased_link, which the shim defines. The gap that replaced it
is a different set of symbols and is backend-specific. Reading that
paragraph today told you the opposite of what held.
What the step is actually for
It asserts an emitted .o is LINKABLE — the assertions above it check
REL (Relocatable file), Xtensa, a GLOBAL app_main and R_XTENSA_32.
Nothing here wants a working ESP-IDF, and pulling one in would make a CI-visible
row depend on an SDK checkout.
So: extend the shim with empty stubs for what the PAL backend imports, the way
ext_notify/ext_aliased_link are already stubbed. Generate the stub list
from the object's own UND symbols rather than typing today's 25 — a hand-typed
list is a hand-counted constant over an emitter that can grow, and the next PAL
addition re-reds the row with an unrelated-looking diff. readelf -sW filtered
to UND gives the list; anything the shim already defines is skipped.
The alternative — dropping the link and keeping only the readelf assertions — loses the one check that the relocations are consumable, and is a narrowing. Prefer the stubs.
Repro
make test-emit-obj # PXX_ALLOW_FULL_SUITE=1; fails at the xtensa link,
# after every i386 row now passes
RESOLVED — the shim is generated from the object's own UND list
test-emit-obj is GREEN. tools/emit_obj_stub_shim.sh <obj>... reads each
object's undefined symbols and emits void f(void) {} for every one the shim
does not own, so the next PAL addition cannot re-red this row with a diff that
looks unrelated to the PAL.
ext_notify, ext_aliased_link, main and app_main are excluded by name.
ext_aliased_link matters most: the readelf assertions above demand the object
leave it UND, so letting the generator answer for it would make the script the
reason the row passes rather than the compiler.
The counts, and why riscv32's zero is not a failure
| object | UND seen | stubs generated |
|---|---|---|
test_emit_obj_rv.o |
2 | 0 |
test_emit_obj_xt.o + _xt_windowed.o |
35 | 33 |
riscv32 imports only the two names the shim owns; xtensa's PAL backend brings
lwip_*, esp_timer_get_time, vTaskDelay and the libc surface. So a zero
stub count is a real answer and the guard cannot be built on it — the first
version of this asserted stubs generated >= 1 and failed riscv32 immediately,
which is the flag-whose-default-is-a-real-answer shape. The assertion is on
und seen, where 0 genuinely does mean the object was never read.
What the step can still fail on, since generating the stubs narrows it
It can no longer fail on a missing IMPORT. It still fails on the relocations, which is what it was written for. Two guards keep that honest and both were measured, not assumed:
- the stub shim linked ALONE must fail (no object, so
app_mainis undefined) — verified rc=1. Without this, a link step that had stopped being able to fail would certify every object after it. und seennon-zero, so a generator that read nothing is caught rather than reproducing the original failure and looking like a pass.
-fno-builtin, because the stubs redefine names gcc knows as builtins
(calloc, fwrite, …) with a void f(void) signature. Without it the link
succeeds while printing conflicting-type notes, and an instrument that warns and
passes anyway is one whose next real message nobody reads.
One defect fixed in passing
The two xtensa links were separated by ;, so the first one's failure was
swallowed and only the last command's rc reached make. That is why only the
windowed line ever appeared in a log, and it is what made this read as a
windowed-ABI problem. Both links now || exit 1.
Verified: PXX_ALLOW_FULL_SUITE=1 make test-emit-obj rc=0 — needed because
the row under repair is the target itself and quick does not run it.
tools/gate.sh quick GREEN.
Log
- 2026-09-06 — resolved; this names the commit that carried the resolve, which is not always the one that carried the change — commit d058a3dae.