Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .nanvix/z.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@
ALL_SUITES = [
"c-bindings",
"dlfcn-c",
"dlfcn-diamond-c",
"dlfcn-global-c",
"dlfcn-init-runpath-c",
"dlfcn-needed-c",
"dlfcn-pie-c",
"echo-c",
Expand Down Expand Up @@ -97,6 +99,8 @@
# Suites that require ramfs-bundled shared libraries and only run in standalone mode.
STANDALONE_ONLY_SUITES = [
"dlfcn-c",
"dlfcn-diamond-c",
"dlfcn-init-runpath-c",
"dlfcn-pie-c",
]

Expand All @@ -109,6 +113,17 @@
# Maps suite name to a list of (source_filename_in_build_dir, ramfs_target_path).
SUITE_RAMFS_LIBS: dict[str, list[tuple[str, str]]] = {
"dlfcn-c": [("libmul.so", "lib/libmul.so")],
"dlfcn-diamond-c": [
("libbase.so", "lib/libbase.so"),
("libleft.so", "lib/libleft.so"),
("libright.so", "lib/libright.so"),
("libdiamond.so", "lib/libdiamond.so"),
],
"dlfcn-init-runpath-c": [
("libctor.so", "lib/libctor.so"),
("libparent.so", "lib/libparent.so"),
("libchild.so", "lib/subdir/libchild.so"),
],
"dlfcn-pie-c": [("libmul-pie.so", "lib/libmul-pie.so")],
}

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ PROCESS_MODE ?= multi-process
MEMORY_SIZE ?= 128mb

# Test suites to build.
SUITES := c-bindings dlfcn-c dlfcn-pie-c echo-c echo-cpp file-c hello-c hello-cpp memory-c misc-c network-c noop-c noop-cpp thread-c
SUITES := c-bindings dlfcn-c dlfcn-diamond-c dlfcn-init-runpath-c dlfcn-pie-c echo-c echo-cpp file-c hello-c hello-cpp memory-c misc-c network-c noop-c noop-cpp thread-c

# ELF binaries produced by each suite.
BINARIES := $(addsuffix .elf,$(SUITES))
Expand Down
2 changes: 1 addition & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export LIBRARIES_DIR ?= $(BINARIES_DIR)
# Test Suites
#===============================================================================

SUITES := c-bindings dlfcn-c dlfcn-global-c dlfcn-needed-c dlfcn-pie-c echo-c echo-cpp file-c hello-c hello-cpp memory-c misc-c network-c noop-c noop-cpp thread-c
SUITES := c-bindings dlfcn-c dlfcn-diamond-c dlfcn-global-c dlfcn-init-runpath-c dlfcn-needed-c dlfcn-pie-c echo-c echo-cpp file-c hello-c hello-cpp memory-c misc-c network-c noop-c noop-cpp thread-c

#===============================================================================
# Build Rules
Expand Down
55 changes: 55 additions & 0 deletions src/dlfcn-diamond-c/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright(c) The Maintainers of Nanvix.
# Licensed under the MIT License.
#
# dlfcn-diamond-c: Diamond DT_NEEDED graph resolution.
#
# libdiamond.so
# ├── DT_NEEDED libleft.so ──┐
# └── DT_NEEDED libright.so ──┴── DT_NEEDED libbase.so
#
# The loader must consolidate the two DT_NEEDED libbase.so edges onto
# a single in-memory libbase.so instance.

PROGRAM_NAME := dlfcn-diamond-c

SOURCES := $(wildcard *.c)
OBJECTS := $(SOURCES:.c=.o)
BINARY := $(PROGRAM_NAME).elf

all: $(OBJECTS) libs-all
$(CC) $(LDFLAGS) -pie -rdynamic -Wl,--no-dynamic-linker $(OBJECTS) $(LIBRARIES) -o $(BINARIES_DIR)/$(BINARY)

clean: libs-clean
rm -f $(OBJECTS)
rm -f $(BINARIES_DIR)/$(BINARY)

# libbase.so - leaf of the diamond, no deps.
# libleft.so - DT_NEEDED libbase.so.
# libright.so - DT_NEEDED libbase.so.
# libdiamond.so - DT_NEEDED libleft.so, libright.so.
#
# `--enable-new-dtags` ensures DT_RUNPATH (not the deprecated DT_RPATH)
# is emitted; the resolver in Nanvix's loader only honours DT_RUNPATH.
libs-all:
$(CC) libs/base.c -shared -fPIC -o $(LIBRARIES_DIR)/libbase.so
$(CC) libs/left.c -shared -fPIC \
-L$(LIBRARIES_DIR) -lbase \
-Wl,--enable-new-dtags,-rpath,lib \
-o $(LIBRARIES_DIR)/libleft.so
$(CC) libs/right.c -shared -fPIC \
-L$(LIBRARIES_DIR) -lbase \
-Wl,--enable-new-dtags,-rpath,lib \
-o $(LIBRARIES_DIR)/libright.so
$(CC) libs/diamond.c -shared -fPIC \
-L$(LIBRARIES_DIR) -lleft -lright \
-Wl,--enable-new-dtags,-rpath,lib \
-o $(LIBRARIES_DIR)/libdiamond.so

libs-clean:
rm -f $(LIBRARIES_DIR)/libbase.so
rm -f $(LIBRARIES_DIR)/libleft.so
rm -f $(LIBRARIES_DIR)/libright.so
rm -f $(LIBRARIES_DIR)/libdiamond.so

%.o: %.c
$(CC) $(CFLAGS) -fPIE $< -c -o $@
31 changes: 31 additions & 0 deletions src/dlfcn-diamond-c/libs/base.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright(c) The Maintainers of Nanvix.
* Licensed under the MIT License.
*
* libbase.so - shared leaf of the diamond DT_NEEDED graph.
*
* libdiamond.so
* ├── DT_NEEDED libleft.so ──┐
* └── DT_NEEDED libright.so ──┴── DT_NEEDED libbase.so
*
* Both libleft.so and libright.so declare DT_NEEDED libbase.so. When the
* loader walks libdiamond.so's dependencies, it must NOT load libbase.so
* twice — exactly one copy should appear in the registry. Otherwise the
* `unique_counter` global below would be visible at two distinct
* addresses to libleft.so and libright.so, and the assertion in
* dlfcn-diamond-c/main.c would fire.
*/

/* Process-lifetime counter. Bumped by every call to `base_bump()`. */
static int unique_counter = 0;

int base_bump(void)
{
unique_counter += 1;
return unique_counter;
}

int base_get(void)
{
return unique_counter;
}
28 changes: 28 additions & 0 deletions src/dlfcn-diamond-c/libs/diamond.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright(c) The Maintainers of Nanvix.
* Licensed under the MIT License.
*
* libdiamond.so - root of the diamond. Pulls in libleft.so and
* libright.so via DT_NEEDED. Both arms transitively depend on
* libbase.so, so a correct loader must consolidate the two
* "DT_NEEDED libbase.so" edges onto a single libbase.so instance.
*/

extern int left_bump(void);
extern int right_bump(void);
extern int right_get(void);

int diamond_left(void)
{
return left_bump();
}

int diamond_right(void)
{
return right_bump();
}

int diamond_observe(void)
{
return right_get();
}
15 changes: 15 additions & 0 deletions src/dlfcn-diamond-c/libs/left.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright(c) The Maintainers of Nanvix.
* Licensed under the MIT License.
*
* libleft.so - left arm of the diamond. Calls base_bump() so the test
* can observe whether libleft and libright share the same libbase.so
* instance (single counter) or each got their own (independent counters).
*/

extern int base_bump(void);

int left_bump(void)
{
return base_bump();
}
19 changes: 19 additions & 0 deletions src/dlfcn-diamond-c/libs/right.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright(c) The Maintainers of Nanvix.
* Licensed under the MIT License.
*
* libright.so - right arm of the diamond. Symmetric to libleft.so.
*/

extern int base_bump(void);
extern int base_get(void);

int right_bump(void)
{
return base_bump();
}

int right_get(void)
{
return base_get();
}
173 changes: 173 additions & 0 deletions src/dlfcn-diamond-c/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Copyright(c) The Maintainers of Nanvix.
* Licensed under the MIT License.
*
* dlfcn-diamond-c: Exercises diamond DT_NEEDED graph resolution.
*
* libdiamond.so
* ├── DT_NEEDED libleft.so ──┐
* └── DT_NEEDED libright.so ──┴── DT_NEEDED libbase.so
*
* A correct loader walks libdiamond.so's dependencies, opens libleft.so,
* recurses into its DT_NEEDED list and opens libbase.so, then resumes
* the outer loop for libright.so — at which point libbase.so is already
* in the registry and must be bound, NOT re-opened. A broken loader
* either:
* (a) re-opens libbase.so, ending up with two copies and two distinct
* `unique_counter` instances, OR
* (b) trips an internal `unreachable!()` / hangs because the same
* file descriptor is now mapped to two registry entries.
*
* Both failure modes are caught by the assertions below.
*/

#include <dlfcn.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static int tests_passed = 0;
static int tests_failed = 0;

static void pass(const char *name)
{
printf(" PASS: %s\n", name);
fflush(stdout);
tests_passed++;
}

static void fail(const char *name, const char *reason)
{
printf(" FAIL: %s (%s)\n", name, reason);
fflush(stdout);
tests_failed++;
}

/*
* Test 0 (control): dlopen libright.so directly. libright has a single
* DT_NEEDED edge to libbase.so, so this exercises the depth-2 chain
* without involving the diamond shape. If this hangs, the issue is
* not specific to diamond resolution.
*/
static void test_chain_dlopen(void)
{
void *h = dlopen("lib/libright.so", RTLD_NOW);
if (h == NULL) {
fail("dlopen(libright.so) [depth-2]", dlerror());
return;
}
int (*rb)(void) = (int (*)(void))dlsym(h, "right_bump");
if (!rb || rb() != 1) {
fail("dlopen(libright.so) [depth-2]", "right_bump returned wrong value");
dlclose(h);
return;
}
dlclose(h);
pass("dlopen(libright.so) [depth-2 chain]");
}

/*
* Test 1: dlopen(libdiamond.so) must succeed (not hang, not error).
* Successful return implies the loader walked the entire diamond
* graph without re-opening libbase.so or otherwise dead-locking.
*/
static void test_diamond_dlopen(void)
{
void *h = dlopen("lib/libdiamond.so", RTLD_NOW);
if (h == NULL) {
fail("dlopen(libdiamond.so)", dlerror());
return;
}

int (*dl) (void) = (int (*)(void))dlsym(h, "diamond_left");
int (*dr) (void) = (int (*)(void))dlsym(h, "diamond_right");
int (*ob) (void) = (int (*)(void))dlsym(h, "diamond_observe");
if (!dl || !dr || !ob) {
fail("dlopen(libdiamond.so)", "missing diamond_* symbols");
dlclose(h);
return;
}

/* Sanity: each arm can run code from libbase.so. */
int left_val = dl(); /* expected 1 (counter was 0). */
int right_val = dr(); /* expected 2 IF libbase shared. */
int observed = ob(); /* expected 2 IF libbase shared. */

if (left_val != 1) {
char reason[64];
snprintf(reason, sizeof(reason), "left_bump=%d, expected 1", left_val);
fail("dlopen(libdiamond.so)", reason);
dlclose(h);
return;
}
if (right_val != 2 || observed != 2) {
/* This is the giveaway for a duplicate libbase.so: libleft and
* libright each see their own private `unique_counter` instead
* of sharing one. */
char reason[128];
snprintf(reason, sizeof(reason),
"right_bump=%d (expected 2), observed=%d (expected 2) "
"-- libbase.so was loaded twice",
right_val, observed);
fail("dlopen(libdiamond.so)", reason);
dlclose(h);
return;
}

dlclose(h);
pass("dlopen(libdiamond.so)");
}

/*
* Test 2: Second dlopen returns the same handle (libdiamond.so is
* already in the registry).
*/
static void test_diamond_reopen(void)
{
void *h1 = dlopen("lib/libdiamond.so", RTLD_NOW);
if (h1 == NULL) {
fail("re-dlopen(libdiamond.so)", dlerror());
return;
}

void *h2 = dlopen("lib/libdiamond.so", RTLD_NOW);
if (h2 == NULL) {
fail("re-dlopen(libdiamond.so)", dlerror());
dlclose(h1);
return;
}

if (h1 != h2) {
fail("re-dlopen(libdiamond.so)", "second dlopen returned a different handle");
dlclose(h1);
dlclose(h2);
return;
}

dlclose(h1);
dlclose(h2);
pass("re-dlopen(libdiamond.so) returns same handle");
}

int main(int argc, const char *argv[])
{
(void)argc;
(void)argv;

printf("=== dlfcn diamond DT_NEEDED tests ===\n");
fflush(stdout);

test_chain_dlopen();
test_diamond_dlopen();
test_diamond_reopen();

printf("\n%d passed, %d failed\n", tests_passed, tests_failed);
fflush(stdout);

if (tests_failed == 0) {
const char *magic = "ok";
write(STDOUT_FILENO, magic, 3);
}

return tests_failed > 0 ? 1 : 0;
}
Loading