ldls: the stubs, and the driver's own voice

docs/plan/ldls.md milestone L1. Every undefined symbol resolves to a
trampoline -- twenty generated bytes that load the symbol's index and jump to
a shared thunk, which saves the argument registers and reports the call. Then
init_module is simply called.

    $ ldls-run 8139too.ko
      module  8139too
      license GPL
      wants   94 symbols
      calling init_module at 0x416fc020

        1  _printk(0x416fb6c0, 0, 0)
          | 8139too: 8139too Fast Ethernet driver 0.9.28
        2  __pci_register_driver(0x416fb400, 0x416fff80, 0x416fbdbb)

      init_module returned 0 after 2 stub call(s)

That second line is the driver's own banner, printed through a _printk that
is ours, from a string in the module's own .rodata that a relocation put
there. There is no stronger evidence that a load worked than the loaded thing
speaking.

Stubs return zero rather than stopping, which departs from what the milestone
originally said and does so deliberately: stopping at the first call tells you
one name, carrying on tells you the sequence, and the sequence is the thing a
symbol table cannot give you. A driver that then dereferences the zero faults,
and that is a result too -- it says how far it got before it needed the kernel
to have really done something.

The first run failed on neither of those. It failed on a relocation:

    ldls-run: relocate: a signed 32-bit relocation does not fit

A module is compiled -mcmodel=kernel, so the compiler emits R_X86_64_32S for
anything it believes lives in the top 2 GB, and an ordinary mmap at 0x7f...
has nowhere to put it. Linux gives modules a region within 2 GB of the kernel
text; the harness uses MAP_32BIT to reach the same window from the other end.
On SchOS this costs nothing -- a process's mmap area starts at 0x40000000 and
is already inside it -- but it is a real constraint on where the host may load
a module, and a loader that did not check its own arithmetic would have found
it much later and much more confusingly.

Swept over 500 stock modules: 449 ran init to completion, 22 faulted on a
stub's zero, 29 have no init_module, and zero hit a loader error.

Recorded honestly in "what it will not do": running init is not working. Every
symbol is a stub, so what has been shown is that drivers ask for things in a
readable order, not that anything answers. Also recorded: every undefined
symbol in a module is NOTYPE, so the stubs cannot tell jiffies from
pci_iomap and both get a trampoline -- BTF distinguishes VAR from FUNC and
that is where the fix belongs rather than in a guess.

make ldls-check is 23 checks now, five of them L1's, all against
ldls/test/refmod.c which is ours and now has an init_module of its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
Nexoniarz 2026-09-14 08:02:29 +02:00
commit 7a7c7611e0
8 changed files with 636 additions and 10 deletions

View file

@ -925,12 +925,21 @@ crashqr:
# and one found on the host costs a second instead of a boot.
# -----------------------------------------------------------------------
LDLS_INSPECT = $(BUILD)/tools/ldls-inspect$(EXE)
LDLS_RUN = $(BUILD)/tools/ldls-run$(EXE)
LDLS_REFMOD = $(BUILD)/ldls/refmod.o
$(LDLS_INSPECT): ldls/host/inspect.c ldls/ko.c ldls/ko.h
@mkdir -p $(BUILD)/tools
$(HOSTCC) $(HOSTWARN) -O1 -o $@ ldls/host/inspect.c ldls/ko.c
# L1. Calls a module's init with every symbol stubbed, to read off the call
# order. -fno-strict-aliasing because this deliberately treats loaded bytes as
# code, and no optimiser assumption about that is one we want.
$(LDLS_RUN): ldls/host/run.c ldls/ko.c ldls/stub.c ldls/ko.h ldls/stub.h
@mkdir -p $(BUILD)/tools
$(HOSTCC) $(HOSTWARN) -O1 -fno-strict-aliasing -o $@ \
ldls/host/run.c ldls/ko.c ldls/stub.c
# The test object, compiled and never linked. Ours, because docs/plan/ldls.md
# rule 1 means no .ko is committed here -- see the header of refmod.c.
#
@ -941,12 +950,12 @@ $(LDLS_REFMOD): ldls/test/refmod.c
@mkdir -p $(BUILD)/ldls
$(HOSTCC) -c -O1 -fno-pic -fno-asynchronous-unwind-tables -o $@ $<
ldls: $(LDLS_INSPECT)
ldls: $(LDLS_INSPECT) $(LDLS_RUN)
# Load our own object and check the loader got it right. No network, no QEMU,
# no module from anywhere.
ldls-check: $(LDLS_INSPECT) $(LDLS_REFMOD)
$(PYTHON) tools/python/ldls_check.py $(LDLS_INSPECT) $(LDLS_REFMOD)
ldls-check: $(LDLS_INSPECT) $(LDLS_RUN) $(LDLS_REFMOD)
$(PYTHON) tools/python/ldls_check.py $(LDLS_INSPECT) $(LDLS_REFMOD) $(LDLS_RUN)
$(MKFS): tools/mkfs.c include/fs.h include/stat.h include/param.h
@mkdir -p $(BUILD)/tools