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
9 changes: 9 additions & 0 deletions src/lib/loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,15 @@ static void __attribute__((constructor)) loader() {
ensure_libpthread_is_loaded();
ensure_librt_is_loaded();

// When the loader is dlopen'd with RTLD_GLOBAL, glibc does not promote its
// symbols to global scope until dlopen returns. The embedded .so references
// ddprof_lib_state (defined here in the loader) as an undefined symbol, so
// loading it with RTLD_NOW during our constructor fails with
// "undefined symbol: ddprof_lib_state".
// Fix: re-open ourselves with RTLD_NOLOAD | RTLD_GLOBAL to promote our
// symbols before loading the embedded .so.
my_dlopen("libdd_profiling.so", RTLD_GLOBAL | RTLD_NOLOAD | RTLD_NOW);

s_profiling_lib_handle = my_dlopen(lib_profiling_path, RTLD_LOCAL | RTLD_NOW);
free(lib_profiling_path);
if (s_profiling_lib_handle) {
Expand Down
13 changes: 13 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,19 @@ if(NOT CMAKE_BUILD_TYPE STREQUAL "SanitizedDebug")
COMMAND ${CMAKE_SOURCE_DIR}/tools/check_for_unsafe_libc_functions.py
$<TARGET_FILE:dd_profiling-embedded>)
endif()

# Test that the loader works when dlopen'd with RTLD_GLOBAL (the pattern
# used by applications that load libdd_profiling.so at runtime).
add_executable(loader_rtld_global_test loader_rtld_global_test.c)
target_link_libraries(loader_rtld_global_test PRIVATE dl)
add_dependencies(loader_rtld_global_test dd_profiling-shared)
add_test(
NAME loader_rtld_global
COMMAND loader_rtld_global_test
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_tests_properties(
loader_rtld_global
PROPERTIES ENVIRONMENT "TEST_DD_PROFILING_LIB=$<TARGET_FILE:dd_profiling-shared>")
endif()

if(NOT CMAKE_BUILD_TYPE STREQUAL "SanitizedDebug")
Expand Down
77 changes: 77 additions & 0 deletions test/loader_rtld_global_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0. This product includes software
// developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present
// Datadog, Inc.

// Test that libdd_profiling.so (the loader) works when loaded at runtime
// with RTLD_GLOBAL.
//
// This reproduces the pattern used by applications that dlopen the profiling
// library at startup (e.g., after reading a config flag).
// The loader's constructor extracts and dlopen's the embedded .so, which
// references ddprof_lib_state as an extern symbol defined in the loader.
// On glibc, RTLD_GLOBAL only takes effect after dlopen returns, so without
// the self-promotion fix the embedded library fails with:
// "undefined symbol: ddprof_lib_state"

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

typedef int (*start_fn_t)(void);
typedef void (*stop_fn_t)(int);

int main(void) {
const char *lib_path = getenv("TEST_DD_PROFILING_LIB");
if (!lib_path) {
lib_path = "./libdd_profiling.so";
}

fprintf(stderr, "loading %s with RTLD_LAZY | RTLD_GLOBAL...\n", lib_path);

void *handle = dlopen(lib_path, RTLD_LAZY | RTLD_GLOBAL);
if (!handle) {
fprintf(stderr, "FAIL: dlopen: %s\n", dlerror());
return 1;
}

// Verify ddprof_lib_state is resolvable in global scope
void *sym = dlsym(RTLD_DEFAULT, "ddprof_lib_state");
if (!sym) {
fprintf(stderr, "FAIL: ddprof_lib_state not in global scope\n");
dlclose(handle);
return 1;
}

// Call ddprof_start_profiling. If the embedded library failed to load
// (the bug we're testing for), this returns -1.
start_fn_t start = (start_fn_t)dlsym(handle, "ddprof_start_profiling");
if (!start) {
fprintf(stderr, "FAIL: ddprof_start_profiling not found\n");
dlclose(handle);
return 1;
}

int rc = start();
// The profiler may fail to start for environment reasons (no perf events,
// etc.), but a return of -1 specifically means the embedded library was
// never loaded (the loader's start function returns -1 when its function
// pointer is NULL).
if (rc == -1) {
fprintf(stderr,
"FAIL: ddprof_start_profiling returned -1 "
"(embedded library not loaded)\n");
dlclose(handle);
return 1;
}

// Clean up: stop profiling
stop_fn_t stop = (stop_fn_t)dlsym(handle, "ddprof_stop_profiling");
if (stop) {
stop(1000);
}

fprintf(stderr, "PASS: loader constructor succeeded with RTLD_GLOBAL\n");
dlclose(handle);
return 0;
}