Skip to content

[dlfcn] E: Add tests for ctors/dtors and DT_RUNPATH#2

Closed
esaurez wants to merge 16 commits into
mainfrom
feat/dlfcn-init-array-and-runpath
Closed

[dlfcn] E: Add tests for ctors/dtors and DT_RUNPATH#2
esaurez wants to merge 16 commits into
mainfrom
feat/dlfcn-init-array-and-runpath

Conversation

@esaurez

@esaurez esaurez commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Summary

Add a new test suite dlfcn-init-runpath-c that exercises the three System V ABI capabilities the Nanvix dynamic loader now implements (esaurez/nanvix#27):

  1. .init_array constructor invocation on dlopen
  2. .fini_array destructor invocation on dlclose
  3. DT_RUNPATH-driven DT_NEEDED resolution

Test design

  • libctor.so — has __attribute__((constructor)) writing a sentinel to a library-local global, and __attribute__((destructor)) writing a different sentinel to the test program's extern volatile int g_dtor_ran (exported by -rdynamic). The destructor witness survives the library being unloaded because the global lives in the main executable.
  • libparent.so — linked with -lchild -Wl,--enable-new-dtags,-rpath,lib/subdir. The linker emits DT_NEEDED libchild.so plus DT_RUNPATH lib/subdir.
  • libchild.so — staged into lib/subdir/ only (never lib/) via SUITE_RAMFS_LIBS. The only way the loader can find it is by honouring libparent's DT_RUNPATH.

Hooks

File Change
src/dlfcn-init-runpath-c/{Makefile,main.c,libs/*} New suite
Makefile, src/Makefile Register dlfcn-init-runpath-c in SUITES
.nanvix/z.py Register suite in ALL_SUITES, STANDALONE_ONLY_SUITES, and SUITE_RAMFS_LIBS (the latter stages libchild.so under lib/subdir/)

Validation

Run on a standalone Nanvix VM with the loader changes from esaurez/nanvix#27:

RUN  dlfcn-init-runpath-c...
=== dlfcn init_array + DT_RUNPATH tests ===
  PASS: init_array fires on dlopen
  PASS: fini_array fires on dlclose
  PASS: DT_RUNPATH dependency search

3 passed, 0 failed
OK   dlfcn-init-runpath-c
        *** POSIX tests PASSED ***

All 15 testable suites pass (the new suite plus the 14 pre-existing ones).

ELF metadata sanity check

$ readelf -d build/libparent.so | head -4
 0x00000001 (NEEDED)                     Shared library: [libchild.so]
 0x0000001d (RUNPATH)                    Library runpath: [lib/subdir]

$ readelf -d build/libctor.so | grep -E "INIT_ARRAY|FINI_ARRAY"
 0x00000019 (INIT_ARRAY)                 0x1188
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x0000001a (FINI_ARRAY)                 0x118c
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)

Companion PR

esaurez/nanvix#27 — the loader changes this suite exercises. This PR is unlinked-but-meaningful without it: the test would fail with "destructor sentinel not set" / "DT_RUNPATH dependency search" / "constructor sentinel not set" on an unpatched loader.

github-actions Bot and others added 7 commits June 5, 2026 12:08
[ci] E: Update nanvix workflow refs to v2.2.0
Add a new test suite (`dlfcn-init-runpath-c`) that exercises the
three System V ABI capabilities the Nanvix dynamic loader now
implements (esaurez/nanvix PR `feat/dlfcn-init-array-and-runpath`):

1. `libctor.so` defines `.init_array` and `.fini_array` entries via
   `__attribute__((constructor))` / `((destructor))`. The
   constructor writes a sentinel into a library-local global; the
   destructor writes a different sentinel into the test program's
   exported `g_dtor_ran` global so the witness survives the
   library being unloaded. The test asserts both sentinels appear
   in the expected order.

2. `libparent.so` is linked against `libchild.so` (creating a
   `DT_NEEDED` edge) and built with `-Wl,--enable-new-dtags,
   -rpath,lib/subdir`, which the linker emits as `DT_RUNPATH
   lib/subdir`. At runtime `libchild.so` is staged into
   `lib/subdir/` only — never `lib/` — so the only way `dlopen`
   can succeed is by honouring `libparent.so`'s `DT_RUNPATH`.

Both libraries are built flat in `build/` and re-staged into the
correct ramfs paths via the existing `SUITE_RAMFS_LIBS` mechanism
in `.nanvix/z.py`, matching the pattern already used by
`dlfcn-c`. The suite is registered in `STANDALONE_ONLY_SUITES`
because it requires ramfs-bundled `.so` files, and in
`ALL_SUITES`, the host `Makefile`, and the container
`src/Makefile`.

Test output on a standalone Nanvix VM running the updated loader:

    === dlfcn init_array + DT_RUNPATH tests ===
      PASS: init_array fires on dlopen
      PASS: fini_array fires on dlclose
      PASS: DT_RUNPATH dependency search

    3 passed, 0 failed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@esaurez esaurez force-pushed the feat/dlfcn-init-array-and-runpath branch from 04b2a38 to ee7a87a Compare June 8, 2026 21:32
@esaurez

esaurez commented Jun 9, 2026

Copy link
Copy Markdown
Owner Author

Superseded by upstream PR nanvix/posix-tests#174, which carries the same test contribution -- rebased onto current upstream/main (pulled in zutils v0.11.1 from #175), deep-reviewed, validated end-to-end against the local nanvix init-array build (all 3 cases plus the full posix-tests suite pass). One MUST FIX applied: stripped a non-ASCII em-dash from main.c. One SHOULD FIX applied: added build-time readelf assertions in the suite Makefile that libctor.so contains INIT_ARRAY+FINI_ARRAY, libparent.so contains DT_NEEDED libchild.so + DT_RUNPATH lib/subdir, AND that the legacy DT_RPATH is NOT present (prevents silent regression if a future toolchain change flips --enable-new-dtags). The upstream loader companion is nanvix/nanvix#2473. Closing this fork PR; tracking continues upstream.

@esaurez esaurez closed this Jun 9, 2026
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.

3 participants