Skip to content

Report rebased PT_DYNAMIC and dlpi_adds from dl_iterate_phdr - #19

Open
buyemune-sys wants to merge 3 commits into
pg83:mainfrom
buyemune-sys:fix/dl-iterate-phdr-adds
Open

buyemune-sys wants to merge 3 commits into
pg83:mainfrom
buyemune-sys:fix/dl-iterate-phdr-adds

Conversation

@buyemune-sys

Copy link
Copy Markdown

The problem

Two things in what dl_iterate_phdr hands out differ from glibc. Together they break LuaJIT's memprof and sysprof, which name C functions by walking the loaded objects (lj_symtab.c).

  1. dlpi_adds and dlpi_subs were always zero. glibc counts every object that ever joined the list, and those that left it: adds=4 at startup, 5 after a dlopen. memprof dumps the libraries added since its previous dump. With zero adds it dumps none, and a profile names no C function. Tarantool's gh-5813-resolving-of-c-symbols test fails all 5 checks.

  2. PT_DYNAMIC entries kept their link-time offsets. ld.so rebases the address-valued entries of a writable dynamic section in place, and walkers read them that way: DT_STRTAB, DT_SYMTAB, the hash tables, the relocation tables, DT_PLTGOT, DT_VERSYM. For a stripped library memprof reads the dynamic symbols through them.

    glibc: /usr/lib/x86_64-linux-gnu/libz.so.1  base=0x727c1afe1000 strtab=0x727c1afe2258
    solo:  /usr/lib/x86_64-linux-gnu/libz.so.1  base=0x7dc8f6d3e000 strtab=0x1258
    

    This stayed hidden only because of (1). Once memprof sees the libraries, it dereferences 0x240 and crashes (misclib-memprof-lapi.test.lua).

The fix

Two commits, in this order:

  • Leave PT_DYNAMIC addresses absolute after loading, as ld.so does.
    • Right after parseDynamic, and before RELRO seals the section, the loader applies glibc's adjustment to the same tags glibc adjusts, for images whose PT_DYNAMIC is writable (LinkMap::rebaseDynamic, lib/elf_loader.cpp:1705). parseDynamic has already cached everything the loader itself reads.
    • The bridge's own image is no object ld.so would have loaded, and its static-PIE startup left the offsets in place. dl_iterate_phdr reports it with PT_DYNAMIC blanked to PT_NULL (hostHeaders, lib/glibc_shim.cpp:3772), so walkers skip it, as LuaJIT skips the read-only vDSO under glibc.
  • Count loaded objects in dl_iterate_phdr's dlpi_adds.
    • The loader counts the images that reach Ready.
    • dl_iterate_phdr adds the host entries it reports itself.
    • Nothing unloads, so dlpi_subs stays zero, which is also what glibc reports before any dlclose.

Test

The conformance battery's dynamicLinking() checks two things:

  • no reported image (vDSO aside) holds an offset in DT_STRTAB;
  • dlpi_adds covers the images walked.

On main both fail, one per commit. With the fix: ./build test passes.

Under solo, LuaJIT's test suite then fails only where it also fails natively, plus lj-522, which needs LD_PRELOAD.

buyemune-sys and others added 3 commits October 20, 2026 15:42
Security updates replaced libapparmor1 4.0.1really4.0.1-0ubuntu0.24.04.7
with .9 and libexpat1 2.6.1-2ubuntu0.4 with ubuntu0.6, and the pool kept
only the new files, so the cold-cache Fetch x86_64 job answers 404 and
every x86-64 job behind it is skipped. Both pins move to the versions
noble-updates ships now; their SHA256s match the archive's Packages index.
The other twelve pins are still served.
ld.so rebases the address-valued entries of a writable dynamic section
in place: DT_STRTAB, DT_SYMTAB, the hash tables, the relocation tables,
DT_PLTGOT and DT_VERSYM hold addresses once an object is loaded. Code
that walks PT_DYNAMIC through dl_iterate_phdr or link_map's l_ld reads
them that way. LuaJIT's memprof and sysprof do so for every stripped
library, to dump its dynamic symbols. Under the bridge the entries kept
their link-time offsets, and the walk dereferenced them as addresses
near zero.

The loader now applies glibc's adjustment to every image with a writable
dynamic section, right after parsing it and before RELRO seals it. The
bridge's own image is no object ld.so would have loaded, and its static
PIE startup left the offsets in place. dl_iterate_phdr reports it with
PT_DYNAMIC blanked to PT_NULL, so walkers skip it, as LuaJIT skips the
read-only vDSO under glibc.

The conformance battery checks that no reported image still holds an
offset in DT_STRTAB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
glibc reports in dlpi_adds how many objects ever joined the list, and in
dlpi_subs how many left it, so a walker can tell whether anything changed
since it last looked. The bridge left both at zero.

LuaJIT's memprof and sysprof rely on exactly that: they dump the symbols
of the libraries added since the previous dump. With zero adds they see
no libraries at all. A profile taken under solo then names no C function,
which is what Tarantool's gh-5813-resolving-of-c-symbols test catches.

The loader now counts the images that reach Ready, and dl_iterate_phdr
adds the host entries it reports itself. Nothing unloads, so dlpi_subs
stays zero, which is also what glibc reports before any dlclose.

The conformance battery checks that dlpi_adds covers the images walked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@buyemune-sys
buyemune-sys force-pushed the fix/dl-iterate-phdr-adds branch from 43f5d45 to a1e71e7 Compare October 20, 2026 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant