← board

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:

-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