Skip to content
This repository was archived by the owner on Jun 24, 2026. It is now read-only.

[dlfcn] E: Add dlfcn-weak-c tests for STB_WEAK loader semantics#176

Open
esaurez wants to merge 1 commit into
mainfrom
feat/dlfcn-weak-c-tests
Open

[dlfcn] E: Add dlfcn-weak-c tests for STB_WEAK loader semantics#176
esaurez wants to merge 1 commit into
mainfrom
feat/dlfcn-weak-c-tests

Conversation

@esaurez

@esaurez esaurez commented Jun 9, 2026

Copy link
Copy Markdown

Summary

Adds a new src/dlfcn-weak-c/ regression suite covering STB_WEAK undefined-symbol semantics through the public POSIX dlfcn interface (dlopen / dlsym). This is the canonical end-to-end test that pins the Nanvix loader's STB_WEAK handling in place so future loader refactors cannot silently re-break the contract.

Why this matters

The System V gABI ("Symbol Table" chapter) specifies that an unresolved STB_WEAK undefined symbol takes value zero at dynamic link time, and the program is responsible for null-checking before use. Every mainstream ELF loader (glibc elf/dl-lookup.c, musl ldso/dynlink.c, FreeBSD rtld-elf/rtld.c, Android Bionic linker/linker_relocate.cpp) implements this rule. The Nanvix loader honours the contract; until this PR there was no hermetic C-level test driven through the public POSIX dlfcn API to anchor that behaviour.

Test matrix

7 cases, all in main.c. Magic string "ok" is written on full success; assert() aborts on failure (matches the convention of the sibling dlfcn-c, dlfcn-pie-c, dlfcn-global-c, and dlfcn-needed-c suites).

# Fixture Relocation Symbol Expected behaviour
5 libstrong-missing.so R_386_JUMP_SLOT strong_missing dlopen(RTLD_NOW) fails
1 libweak-func-resolved.so R_386_GLOB_DAT main_callback resolves to main exe; try_callback()==70
2 libweak-func-missing.so R_386_GLOB_DAT missing_callback &fn == NULL; try_callback() == -1
3 libweak-data-resolved.so R_386_GLOB_DAT weak_data resolves to main exe; read_weak_data()==99
4 libweak-data-missing.so R_386_GLOB_DAT missing_weak_data &data == NULL; read_weak_data() == -1
6 libweak-plt-resolved.so R_386_JUMP_SLOT main_callback resolves to main exe; PLT call returns 70
7 libweak-plt-missing.so R_386_JUMP_SLOT missing_plt_callback dlopen(RTLD_NOW) succeeds

Case 5 runs first so that a regression in the strong-undefined path is reported independently of the weak cases. It is also the only case that passes against a loader without STB_WEAK support.

Case 7 deliberately does not call the function pointer it resolves. The JMP_SLOT was zeroed by the loader (weak undefined missing → 0); invoking it would jump to NULL. The case only validates that dlopen(..., RTLD_NOW) accepts the .so despite the weak undefined PLT entry.

Relocation-class coverage

The weak NULL-guarded pattern if (&fn) fn(); in libs/weak_func.c and libs/weak_data.c forces a GOT-indirect call/load and only exercises R_386_GLOB_DAT. To also exercise weak references via the PLT, libs/weak_func_plt.c (an unguarded call) emits R_386_JUMP_SLOT. Together, the two helpers cover both relocation classes the loader must handle for weak undefined symbols in i686 ELF. The strong-undefined regression guard libs/strong_missing.c is unguarded by construction and also emits R_386_JUMP_SLOT.

Build-time fixture verification

The suite Makefile asserts via readelf -rW and readelf -WsD that each produced .so contains both: (1) the intended relocation type AND symbol name (proves the fixture is exercising the symbol we think it is, not some accidental CRT-injected weak symbol); and (2) the intended dynamic-symbol binding (WEAK / GLOBAL) and that the symbol is UND. Helper .so files are compiled with the suite-wide CFLAGS, keeping the test representative of the project's normal -O2 + warning environment.

Linker flags

Main executable: -pie -rdynamic -Wl,--no-dynamic-linker. -pie + -rdynamic export main_callback and weak_data in the main exe's .dynsym so the resolved cases can look them up (matches the existing dlfcn-pie-c pattern). --no-dynamic-linker because Nanvix uses an in-process loader via syscalls, not a separate ld.so.

Suite wiring

The new suite is wired into src/Makefile (SUITES list and the standalone-only comment) and into .nanvix/z.py as an ALL_SUITES entry, a STANDALONE_ONLY_SUITES entry, and a SUITE_RAMFS_LIBS mapping that bundles all 7 fixture .so files into the ramfs under lib/ where main.c opens them.

Validation

End-to-end on a Nanvix microvm guest with the current 0.16.17 release (which already contains the STB_WEAK loader support): all 7 cases pass. The full posix-tests suite (17 binaries, 15 integration runs) also passes. Local z lint (pyright) and z format --check (black) report clean.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new dlfcn-weak-c regression suite that validates ELF STB_WEAK undefined-symbol semantics end-to-end via the public dlopen/dlsym interface, and wires it into the build/test runner so the required .so fixtures are bundled into the ramfs for standalone runs.

Changes:

  • Introduces src/dlfcn-weak-c/ with a standalone driver (main.c) plus helper shared-library fixtures covering both R_386_GLOB_DAT and R_386_JUMP_SLOT weak-undefined cases.
  • Adds a suite Makefile that builds fixtures and asserts their relocation/symbol-table properties via readelf.
  • Wires the new suite into src/Makefile and .nanvix/z.py (suite lists + ramfs library bundling).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Makefile Adds dlfcn-weak-c to the suite list and updates standalone-only suite comment.
src/dlfcn-weak-c/Makefile Builds the test binary and 7 .so fixtures; adds readelf-based assertions for relocations and dynsym bindings.
src/dlfcn-weak-c/main.c Implements the 7-case weak/strong undefined-symbol behavior checks through dlopen/dlsym.
src/dlfcn-weak-c/libs/weak_func.c Fixture for weak undefined function via GOT (R_386_GLOB_DAT) with NULL-guard.
src/dlfcn-weak-c/libs/weak_func_plt.c Fixture for weak undefined function via PLT (R_386_JUMP_SLOT) without NULL-guard.
src/dlfcn-weak-c/libs/weak_data.c Fixture for weak undefined data via GOT (R_386_GLOB_DAT) with NULL-guard.
src/dlfcn-weak-c/libs/strong_missing.c Fixture for strong undefined function via PLT (R_386_JUMP_SLOT) to ensure dlopen(RTLD_NOW) fails.
.nanvix/z.py Registers the suite and bundles its .so fixtures into the ramfs for standalone test runs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/dlfcn-weak-c/Makefile Outdated
Adds a new src/dlfcn-weak-c/ regression suite covering STB_WEAK
undefined-symbol semantics through the public POSIX dlfcn interface
(dlopen / dlsym).

The System V gABI ("Symbol Table" chapter) specifies that an
unresolved STB_WEAK undefined symbol takes value zero at dynamic
link time, with the program responsible for null-checking before
use.  Every mainstream ELF loader (glibc elf/dl-lookup.c, musl
ldso/dynlink.c, FreeBSD rtld-elf/rtld.c, Android Bionic
linker/linker_relocate.cpp) implements this rule.  The Nanvix loader
honours the contract; this suite pins the behaviour in place so
future loader refactors cannot silently re-break it.

Test matrix -- 7 cases driven from src/dlfcn-weak-c/main.c.  Magic
string "ok" is written to stdout on full success; assert() aborts
on failure (the convention shared by dlfcn-c, dlfcn-pie-c,
dlfcn-global-c, dlfcn-needed-c).

  1. libweak-func-resolved.so   weak func ref + GOT-indirect
                                resolved at exe scope -> calls succeed
  2. libweak-func-missing.so    weak func ref + GOT-indirect
                                unresolved -> &fn == NULL
  3. libweak-data-resolved.so   weak data ref + GOT-indirect
                                resolved at exe scope
  4. libweak-data-missing.so    weak data ref + GOT-indirect
                                unresolved -> &data == NULL
  5. libstrong-missing.so       strong undefined symbol;
                                dlopen(RTLD_NOW) MUST fail
                                (regression guard, runs first)
  6. libweak-plt-resolved.so    weak func via PLT resolved
  7. libweak-plt-missing.so     weak func via PLT unresolved;
                                dlopen(RTLD_NOW) succeeds
                                (the case deliberately resolves but
                                 does not call the NULL PLT entry)

Case 5 runs first so a regression in the strong-undefined path is
reported independently of the weak cases.  It is also the only
case that passes against a loader without STB_WEAK support.

Relocation-class coverage: the weak NULL-guarded pattern
`if (&fn) fn();` in libs/weak_func.c and libs/weak_data.c forces a
GOT-indirect call/load and exercises R_386_GLOB_DAT.
libs/weak_func_plt.c (unguarded call) exercises R_386_JUMP_SLOT.
The strong-undefined regression guard (libs/strong_missing.c) is
unguarded by construction and also emits R_386_JUMP_SLOT.  Together
the helpers cover both relocation classes the loader must handle
for undefined symbols in i686 ELF.

The suite Makefile asserts via `readelf -rW` and `readelf -WsD`
that each produced .so contains both:

  1. the intended relocation type AND symbol name, proving the
     fixture is exercising the symbol we think it is and not some
     accidental CRT-injected weak symbol;
  2. the intended dynamic-symbol binding (WEAK / GLOBAL) and that
     the symbol is UND.

Main executable link flags: `-pie -rdynamic -Wl,--no-dynamic-linker`.
-pie + -rdynamic export main_callback and weak_data in the main
exe's .dynsym so the resolved cases can look them up (matches the
existing dlfcn-pie-c pattern).  --no-dynamic-linker because Nanvix
uses an in-process loader via syscalls, not a separate ld.so.

The dlfcn-weak-c suite has also been wired into .nanvix/z.py as
both an ALL_SUITES and STANDALONE_ONLY_SUITES entry, with a
SUITE_RAMFS_LIBS mapping that copies the 7 fixture .so files into
the ramfs under lib/ where main.c expects them.

Validation: all 7 cases pass end-to-end on a Nanvix microvm guest;
the full posix-tests suite (17 ELFs, 15 integration runs) passes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@esaurez esaurez force-pushed the feat/dlfcn-weak-c-tests branch from c1fa45e to 5d6ff41 Compare June 9, 2026 21:53
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants