any ideas? best regards Waldemar
--- Begin Message ---Dear Developers of the uClibC, We discovered a memory leak in one of our programs using your library. After using dmalloc, strace and the information of /proc/PID/* we figured out that the "memory leak" "shows itself in /proc/PID/maps, since the HEAP is raising with every iteration of the program. What we also figured out is that it has nothing to do with any malloc or free calls in OUR program. This is why we created a minimal example which is shown below. In general, we are calling dlopen() to bind in a plugin (lets call it "a.plugin"), which is having a dependency to a *empty* sharedobject (lets call it 'libdep_1.so') at compile time. This shared object is also having a dependency to a *empy* sharedobject (lets call it 'libdep_2.so') at compile time. After the first dlopen we are calling a second dlopen() in a loop for a second plugin (lets call ist b.plugin), which has the *same dependencies* as the first plugin. The handle ("b_handle") is closed again right after dlopen() of the b.plugin. After the loop has finished (the effect shows itself inside the heap after like 150 iterations) we call dlclose on the handle ("a_handle") of the a.plugin. Here is what we did and found out so far on that minimal example: - since we are not using malloc / calloc or similar in our minimal example, the issue seems to be inside the uClibc, inside its dlopen files. (ldso/libdl/libdl.c) - this is why we rebuild the uClibc with debug symbols (using buildroot). - with the help of gdb we found out, that dlopen frees 8 byte (when not using the RTLD_GLOBAL flag, which we don't want to use in our case / process) for a "struct init_fini_list" file per sharedobject. *(tmp = malloc(sizeof(struct init_fini_list))) - ldso/libdl/libdl.c:460* - Since we are having two dependencies (libdep_2 and libc), we see a raise of 16 bytes, with each iteration. - Those 16 bytes with every iteration are getting freed when the *last* dlopen is called (dlopen(a_handle)). *(free(runp); - ldso/libdl/libdl.c:984)* - It seems that dlclose of the b.plugin can't free those 8 bytes of that init_fini_list list from the b.plugin dependencies since the a_plugin is still holding on to the same dependencies. - with the RTLD_GLOBAL flag activated on both dlopen calls, the 8 bytes will not be allocated for every .so and therefore we will not see any raise of the heap. (This will only be a workaround, since we normally want all of our plugin symbols to be seen for other plugins.) --- So at last we want to get to our Question: Did we just find a bug in the usage of malloc within your dlopen section / library or do we misinterpret the usage of the dlopen and its RTLD_GLOBAL flag? --- Thank you for your support! Kai --- ## PS.: here is the Source Code of our minimal Example: The main-programm; memorytestd: ```c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> /* Dynamic Loading */ #include <dlfcn.h> /******************************************************************************* * Main * ******************************************************************************/ #define MEMTEST_PLUGIN_A_PATH "/opt/ucu/plugins/builtin/a.plugin" #define MEMTEST_PLUGIN_B_PATH "/opt/ucu/plugins/builtin/b.plugin" typedef bool (*symb_fun_t)(); #define BUFFER_SIZE 512 /** * Helper Function for printing '/proc/PID/maps' to make the 'HEAP fragmentation' visible */ void printmaps(int output_iteration) { FILE *input_fp, *output_fp; input_fp = fopen("/proc/self/maps","r"); if(input_fp == NULL){ perror("fopen /proc/self/maps"); return; } // Create output filename char filename[100]; // Buffer to hold the generated filename sprintf(filename, "/opt/ucu/etc/output/output_%d.txt", output_iteration); // Open the output file in binary mode output_fp = fopen(filename, "wb"); if (output_fp == NULL) { printf("Error opening the output file.\n"); fclose(input_fp); return; } // Read from the input file and write to the output file in chunks size_t bytesRead; char buffer[BUFFER_SIZE]; while ((bytesRead = fread(buffer, sizeof(char), BUFFER_SIZE, input_fp)) > 0) { fwrite(buffer, sizeof(char), bytesRead, output_fp); } // Close the input / output file fclose(input_fp); fclose(output_fp); printf("Written to file with success.\n"); return; } /** * Main routine of the test program: * - dlopen the "a.plugin", which uses the libdep_1.so at compile time, which further * uses the libdep_2.so at compile time. * - in a while loop (here 500 times, since it is enough to show the unwanted effect) * dlopen the "b.plugin", which also uses the libdep_1.so at compile time, which * further uses the libdep_2.so at compile time (same procedure as with "a.plugin") * - dlclose "b_handle" everytime after dlopen inside the while loop * - dlclose "a_hanlde" after the while loop has finished its job * - Since there is no other effect than proving the correct inbind of the plugins, * there is no need for the dlsym functions, which is why they are written as a * comment */ int main (int argc __attribute__((unused)), char *argv[] __attribute__((unused))) { /* open a.plugin */ printf("\nBefore dlopen a.plugin!\n"); void *a_handle = dlopen(MEMTEST_PLUGIN_A_PATH, RTLD_NOW); if (a_handle != NULL) printf("ERROR: could not dlopen a.handle\n"); /*else { symb_fun_t fun_a = (symb_fun_t)dlsym(a_handle, "a_plugin_sym"); if (fun_a != NULL) fun_a(); }*/ /* open b.plugin(s) */ int cnt = 0; while(cnt < 500) { printf("\nBefore dlopen b.plugin!\n"); void *b_handle = dlopen(MEMTEST_PLUGIN_B_PATH, RTLD_NOW); if (b_handle == NULL) printf("ERROR: could not dlopen b.handle\n"); /*else { symb_fun_t fun_b = (symb_fun_t)dlsym(b_handle, "b_plugin_sym"); if (fun_b != NULL) fun_b(); }*/ printmaps(cnt); /* close b_handle */ dlclose (b_handle); printf("#############\n%d\n#############\n", cnt); cnt++; } /* close a_handle */ dlclose(a_handle); return EXIT_SUCCESS; } ``` makefile for memorytestd: ```make # Cross Compiler Prefix CROSS_COMPILE=/home/ucu/os/toolchain/usr/bin/arm-unknown-linux-uclibcgnueabi- # Compiler CC=$(CROSS_COMPILE)gcc all: $(CC) -Wall -Wextra -O2 -o memorytestd memorytestd.c -ldl clean: rm -rf memorytestd memorytestd.c:.c=.o rm -rf *.*~ .PHONY: all clean ``` a.plugin: ```c #include <stdint.h> #include <stdio.h> #include <stdlib.h> void a_plugin_sym () { printf("Hello from a_plugin\n"); return; } ``` ```make # Cross Compiler Prefix CROSS_COMPILE=/home/ucu/os/toolchain/usr/bin/arm-unknown-linux-uclibcgnueabi- # Compiler CC=$(CROSS_COMPILE)gcc all: $(CC) -Wall -Wextra -O2 -o a.plugin a_plugin.c -shared -L"./../../libs/libdep_1/" -ldep_1 clean: rm -rf a.plugin a_plugin.c:.c=.o rm -rf *.*~ .PHONY: all clean ``` b.plugin: ```c #include <stdint.h> #include <stdio.h> #include <stdlib.h> void b_plugin_sym () { printf("Hello from _plugin\n"); return; } ``` ```make # Cross Compiler Prefix CROSS_COMPILE=/home/ucu/os/toolchain/usr/bin/arm-unknown-linux-uclibcgnueabi- # Compiler CC=$(CROSS_COMPILE)gcc all: $(CC) -Wall -Wextra -O2 -o b.plugin b_plugin.c -shared -L"./../../libs/libdep_1/" -ldep_1 clean: rm -rf b.plugin b_plugin.c:.c=.o rm -rf *.*~ .PHONY: all clean ``` libdep_1 and libdep_2: ```c both dependencies are using an empty c-file ``` makefile of libdep_2 ```make # Cross Compiler Prefix CROSS_COMPILE=/home/ucu/os/toolchain/usr/bin/arm-unknown-linux-uclibcgnueabi- # Compiler CC= $(CROSS_COMPILE)gcc all: $(CC) -Wall -Wextra -O2 -o libdep_2.so libdep_2.c -shared clean: rm -rf libdep_2.so libdep_2.c:.c=.o rm -rf *.*~ .PHONY: all clean ``` makefile of libdep_1 ```make # Cross Compiler Prefix CROSS_COMPILE=/home/ucu/os/toolchain/usr/bin/arm-unknown-linux-uclibcgnueabi- # Compiler CC= $(CROSS_COMPILE)gcc all: $(CC) -Wall -Wextra -O2 -Wall -Wextra -O2 -o libdep_1.so libdep_1.c -shared -L"../libdep_2" -ldep_2 clean: rm -rf libdep_1.so libdep_1.c:.c=.o rm -rf *.*~ .PHONY: all clean ```
--- End Message ---
_______________________________________________ devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
