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

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

Open
esaurez wants to merge 1 commit into
mainfrom
feat/dlfcn-init-array-and-runpath
Open

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

Conversation

@esaurez

@esaurez esaurez commented Jun 8, 2026

Copy link
Copy Markdown

Summary

Add a new test suite dlfcn-init-runpath-c that exercises three System V ABI capabilities of the Nanvix dynamic loader:

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

The companion loader PR that implements these features is nanvix/nanvix#2473.

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 nanvix/nanvix#2473:

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)

This PR is unlinked-but-meaningful without the loader change: the test would fail with "destructor sentinel not set" / "DT_RUNPATH dependency search" / "constructor sentinel not set" on an unpatched loader.

Copilot AI review requested due to automatic review settings June 8, 2026 21:33
@esaurez esaurez requested a review from ppenna as a code owner June 8, 2026 21:33

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 POSIX test suite that validates Nanvix’s dynamic loader behavior around ELF constructors/destructors and DT_RUNPATH resolution, integrating it into the build and standalone test harness wiring.

Changes:

  • Introduces new suite dlfcn-init-runpath-c that checks .init_array on dlopen, .fini_array on dlclose, and DT_RUNPATH-driven DT_NEEDED lookup.
  • Registers the suite in the build system (Makefile, src/Makefile) so it’s built alongside existing suites.
  • Hooks the suite into .nanvix/z.py standalone execution and ramfs library staging (including staging libchild.so under lib/subdir/).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/Makefile Adds dlfcn-init-runpath-c to the full suite build list.
src/dlfcn-init-runpath-c/Makefile Builds the new test ELF and its companion shared libraries (libctor.so, libparent.so, libchild.so).
src/dlfcn-init-runpath-c/main.c Implements the three runtime checks for ctor/dtor execution and DT_RUNPATH dependency search.
src/dlfcn-init-runpath-c/libs/subdir/child.c Defines libchild.so dependency target for DT_NEEDED resolution via DT_RUNPATH.
src/dlfcn-init-runpath-c/libs/parent.c Defines libparent.so that declares DT_NEEDED on libchild.so.
src/dlfcn-init-runpath-c/libs/ctor.c Defines libctor.so with constructor/destructor sentinels used by the test.
Makefile Adds dlfcn-init-runpath-c to the Docker/host build suite list.
.nanvix/z.py Registers suite and stages required shared libraries into the ramfs (including subdir placement for DT_RUNPATH).

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

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>
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