From ba20328e27c16306efa63dfcfe1b440970615062 Mon Sep 17 00:00:00 2001 From: Michael Kerrisk Date: Tue, 2 Jul 2019 15:55:09 +0200 Subject: [PATCH] dlopen.3: Note that symbol use might keep a dlclose'd object in memory MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit My earlier commit was in error: commit 4a1af09bd1aae1fdfc783dd017cb5bc392dccc1b Author: Michael Kerrisk Date: Sat Mar 14 21:40:35 2015 +0100 dlopen.3: Amend error in description of dlclose() behavior -If the reference count drops to zero and no other loaded libraries use -symbols in it, then the dynamic library is unloaded. +If the reference count drops to zero, +then the dynamic library is unloaded. I doubted the removed text, because it provide little clue about the scenario. The POSIX dlclose(3) specification actually details the scenario sufficiently: Although a dlclose() operation is not required to remove any functions or data objects from the address space, neither is an implementation prohibited from doing so. The only restriction on such a removal is that no func‐ tion nor data object shall be removed to which references have been relocated, until or unless all such references are removed. For instance, an executable object file that had been loaded with a dlopen() operation specifying the RTLD_GLOBAL flag might provide a target for dynamic relo‐ cations performed in the processing of other relocatable objects—in such environments, an application may assume that no relocation, once made, shall be undone or remade unless the executable object file containing the relo‐ cated object has itself been removed. Verified by experiment: $ cat openlibs.c # Test program int main(int argc, char *argv[]) { void *libHandle[MAX_LIBS]; int lcnt; if (argc < 2) { fprintf(stderr, "Usage: %s lib-path...\n", argv[0]); exit(EXIT_FAILURE); } lcnt = 0; for (int j = 1; j < argc; j++) { if (argv[j][0] != '-') { if (lcnt >= MAX_LIBS) { fprintf(stderr, "Too many libraries (limit: %d)\n", MAX_LIBS); exit(EXIT_FAILURE); } printf("[%d] Opening %s\n", lcnt, argv[j]); libHandle[lcnt] = dlopen(argv[j], RTLD_NOW | RTLD_GLOBAL); if (libHandle[lcnt] == NULL) { fprintf(stderr, "dlopen: %s\n", dlerror()); exit(EXIT_FAILURE); } lcnt++; } else { /* "-N" closes the Nth handle */ int i = atoi(&argv[j][1]); printf("Closing handle %d\n", i); dlclose(libHandle[i]); } sleep(1); printf("\n"); } printf("Program about to exit\n"); exit(EXIT_SUCCESS); } $ cat lib_x1.c void x1_func(void) { printf("Hello world\n"); } __attribute__((constructor)) void x1_cstor(void) { printf("Called %s\n", __FUNCTION__); } __attribute__((destructor)) void x1_dstor(void) { printf("Called %s\n", __FUNCTION__); } $ cat lib_y1.c void y1_func(void) { printf("Hello world\n"); } __attribute__((constructor)) void y1_cstor(void) { printf("Called %s\n", __FUNCTION__); } __attribute__((destructor)) void y1_dstor(void) { printf("Called %s\n", __FUNCTION__); } static void testref(void) { /* The following reference, to a symbol in lib_x1.so shows that RTLD_GLOBAL may pin a library when it might otherwise have been released with dlclose() */ extern void x1_func(void); x1_func(); } $ cc -shared -fPIC -o lib_x1.so lib_x1.c $ cc -shared -fPIC -o lib_y1.so lib_y1.c $ cc -o openlibs openlibs.c -ldl $ LD_LIBRARY_PATH=. ./openlibs lib_x1.so lib_y1.so -0 -1 [0] Opening lib_x1.so Called x1_cstor [1] Opening lib_y1.so Called y1_cstor Closing handle 0 Closing handle 1 Called y1_dstor Called x1_dstor Program about to exit Note that x1_dstor was called only when handle 1 (lib_y1.so) was closed. But, if we edit lib_y1 to remove the reference to x1_func(), things are different: $ cat lib_y1.c # After editing void y1_func(void) { printf("Hello world\n"); } __attribute__((constructor)) void y1_cstor(void) { printf("Called %s\n", __FUNCTION__); } __attribute__((destructor)) void y1_dstor(void) { printf("Called %s\n", __FUNCTION__); } static void testref(void) { // extern void x1_func(void); // x1_func(); } $ cc -shared -fPIC -o lib_y1.so lib_y1.c $ LD_LIBRARY_PATH=. ./openlibs lib_x1.so lib_y1.so -0 -1 [0] Opening lib_x1.so Called x1_cstor [1] Opening lib_y1.so Called y1_cstor Closing handle 0 Called x1_dstor Closing handle 1 Called y1_dstor Program about to exit This time, x1_dstor was called when handle 0 (lib_x1.so) was closed. Signed-off-by: Michael Kerrisk --- man3/dlopen.3 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/man3/dlopen.3 b/man3/dlopen.3 index 2a32690f71..2328831641 100644 --- a/man3/dlopen.3 +++ b/man3/dlopen.3 @@ -294,9 +294,16 @@ The function decrements the reference count on the dynamically loaded shared object referred to by .IR handle . -If the reference count drops to zero, +.PP +If the object's reference count drops to zero +and no symbols in this object are required by other objects, then the object is unloaded after first calling any destructors defined for the object. +(Symbols in this object might be required in another object +because this object was opened with the +.BR RTLD_GLOBAL +flag and one of its symbols satisfied a relocation in another object.) +.PP All shared objects that were automatically loaded when .BR dlopen () was invoked on the object referred to by -- 2.39.2