Skip to content

fix: promote loader symbols to global scope before loading embedded .so#521

Closed
xroche wants to merge 1 commit intoDataDog:mainfrom
algolia:fix/loader-rtld-global-constructor
Closed

fix: promote loader symbols to global scope before loading embedded .so#521
xroche wants to merge 1 commit intoDataDog:mainfrom
algolia:fix/loader-rtld-global-constructor

Conversation

@xroche
Copy link
Copy Markdown
Contributor

@xroche xroche commented Mar 21, 2026

Summary

Fixes #520. When the loader is dlopen'd with RTLD_GLOBAL, its constructor fails to load the embedded .so because ddprof_lib_state is not yet in global scope.

How

Before loading the embedded .so, re-open the loader itself with RTLD_NOLOAD | RTLD_GLOBAL. This promotes the loader's symbols (including ddprof_lib_state) into global scope without loading it a second time.

The call is harmless when the loader was not opened with RTLD_GLOBAL (the common LD_PRELOAD case): RTLD_NOLOAD on an already-loaded library with RTLD_LOCAL simply returns the existing handle.

Test

A new test (loader_rtld_global) loads libdd_profiling.so via dlopen(..., RTLD_GLOBAL) and calls ddprof_start_profiling(). Without the fix it returns -1 (embedded library failed to load). With the fix it succeeds.

When an application loads libdd_profiling.so via dlopen with RTLD_GLOBAL,
glibc does not add the library's symbols to the global scope until dlopen
returns. The loader's constructor runs before that point and attempts to
dlopen the embedded .so with RTLD_NOW. The embedded library references
ddprof_lib_state (a TLS variable introduced in DataDog#490 for fork safety) as
an extern symbol. Because the loader's symbols are not yet global,
resolution fails:

  undefined symbol: ddprof_lib_state

Fix: before loading the embedded .so, re-open the loader with
RTLD_NOLOAD | RTLD_GLOBAL to promote its symbols into global scope.
RTLD_NOLOAD prevents a second load; it only updates the visibility.

A test exercises this exact pattern: dlopen the loader with RTLD_GLOBAL
and verify the embedded library loads.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@r1viollet
Copy link
Copy Markdown
Collaborator

r1viollet commented Mar 23, 2026

Thanks for the proposal, I think this makes sense, it is not super easy to understand the dl_open of ourselves inside a dlopen. Some notes on why we need this:

  dlopen("libdd_profiling.so", RTLD_GLOBAL)   ← outer, hasn't returned yet
    └─ constructor() fires                                                                                                                                                                                  
         └─ my_dlopen("libdd_profiling.so", RTLD_NOLOAD|RTLD_GLOBAL)  ← promotes symbols                                                                                                                    
         └─ my_dlopen(embedded_lib, RTLD_LOCAL|RTLD_NOW)              ← needs ddprof_lib_state visible                                                                                                      

I'll put this in a branch and try things out 👍

@r1viollet
Copy link
Copy Markdown
Collaborator

delivering in #522 , thanks!

@r1viollet r1viollet closed this Mar 23, 2026
r1viollet added a commit that referenced this pull request Mar 24, 2026
- Update loader_rtld_global_test.c comment to accurately describe both
  failure modes (glibc RTLD_GLOBAL timing and musl initial-exec TLS
  rejection) and the pthread_key_t fix
- Add comment on kInvalidKey sentinel explaining the POSIX assumption
- Add comment in free() explaining why pthread_key_delete is skipped
  (race with concurrent get_tl_state() callers)
- Document why ensure_key_initialized() is public (fork test needs it
  without a full ring-buffer init)
- Add BM_GetTlState microbenchmark to measure raw TLS access overhead
  (pthread_getspecific vs initial-exec __thread)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

ddprof_lib_state undefined symbol when loader is dlopen'd with RTLD_GLOBAL

2 participants