Hello Siddhesh, Thanks for your response! On 04/20/2017 08:05 AM, Siddhesh Poyarekar wrote: > On Wednesday 19 April 2017 08:37 PM, Michael Kerrisk (man-pages) wrote: >> 1. If looking for a versioned symbol (NAME@VERSION), the DL will search >> starting from the start of the link map ("namespace") until it finds the >> first instance of either a matching unversioned NAME or an exact version >> match on NAME@VERSION. Preloading takes advantage of the former case to >> allow easy overriding of versioned symbols in a library that is loaded >> later in the link map. > > I believe it is the other way around, i.e. it looks for the versioned > symbol first and if it is not found, link against the unversioned symbol > provided that it is public. I think that I have failed to provide enough detail for you to understand what I meant. Consider the following: 1. We want to interpose some symbol in glibc (say, "malloc@GLIBC_2.0") with a symbol of our own (perhaps via a preloaded library). 2. In our preloaded shared library, the interposing "malloc" need not be a versioned symbol. At least >> 2. The version notation NAME@@VERSION denotes the default version >> for NAME. This default version is used in the following places: >> >> a) At static link time, this is the version that the static >> linker will bind to when creating the relocation record >> that will be used by the DL. >> b) When doing a dlsym() look-up on the unversioned symbol NAME. >> (See check_match() in elf/dl-lookup.c) >> >> Is the default version used in any other circumstance? > > Only (a) afaict, where do you see (2) happening? Unversioned symbol > lookups seem to happen independent of the @@ version. See the following (tarball of code attached): $ cat sv_lib_v3.c /*#* sv_lib_v3.c COPYRIGHT-NOTICE */ #include #ifndef DEF_XYZ_V2 __asm__(".symver xyz_newest,xyz@@VER_3"); __asm__(".symver xyz_new,xyz@VER_2"); #else __asm__(".symver xyz_newest,xyz@VER_3"); __asm__(".symver xyz_new,xyz@@VER_2"); #endif __asm__(".symver xyz_old,xyz@VER_1"); __asm__(".symver pqr_new,pqr@@VER_3"); __asm__(".symver pqr_old,pqr@VER_2"); __asm__(".symver tuv_newest,tuv@@VER_3"); __asm__(".symver tuv_new,tuv@VER_2"); __asm__(".symver tuv_old,tuv@VER_1"); void xyz_old(void) { printf("v1 xyz\n"); } void xyz_new(void) { printf("v2 xyz\n"); } void xyz_newest(void) { printf("v3 xyz\n"); } void tuv_old(void) { printf("v1 tuv\n"); } void tuv_new(void) { printf("v2 tuv\n"); } void tuv_newest(void) { printf("v3 tuv\n"); } void pqr_new(void) { printf("v3 pqr\n"); } void pqr_old(void) { printf("v2 pqr\n"); } void abc(void) { printf("v3 abc\n"); } void v123(void) { printf("v3 v123\n"); } $ cat sv_v3.map VER_1 { global: xyz; tuv; local: [a-uw-z]*; # Hide all other symbols }; VER_2 { global: pqr; } VER_1; VER_3 { global: abc; } VER_2; $ # Build version 3 of shared library, where the default (@@) version $ # of xyz is VER_3 $ gcc -g -c -fPIC -Wall sv_lib_v3.c $ gcc -g -shared -o libsv.so sv_lib_v3.o -Wl,--version-script,sv_v3.map $ # Build version 3 of shared library, where the default (@@) version $ # of xyz is VER_2 $ gcc -DDEF_XYZ_V2 -g -c -fPIC -Wall sv_lib_v3.c $ gcc -g -shared -o libsv_def_xyz_v2.so sv_lib_v3.o -Wl,--version-script,sv_v3.map $ # Verify symbol versions in the two DSOs: $ $ readelf --dyn-syms libsv.so | grep xyz 20: 0000000000000930 19 FUNC GLOBAL DEFAULT 12 xyz@VER_1 21: 0000000000000956 19 FUNC GLOBAL DEFAULT 12 xyz@@VER_3 22: 0000000000000943 19 FUNC GLOBAL DEFAULT 12 xyz@VER_2 $ readelf --dyn-syms libsv_def_xyz_v2.so | grep xyz 20: 0000000000000943 19 FUNC GLOBAL DEFAULT 12 xyz@@VER_2 21: 0000000000000956 19 FUNC GLOBAL DEFAULT 12 xyz@VER_3 22: 0000000000000930 19 FUNC GLOBAL DEFAULT 12 xyz@VER_1 $ cat dynload.c #include #include #include #include #include #include #include int main(int argc, char *argv[]) { void *libHandle; /* Handle for shared library */ void (*funcp)(void); /* Pointer to function with no arguments */ const char *err; if (argc != 3 || strcmp(argv[1], "--help") == 0) { fprintf(stderr, "Usage: %s lib-path func-name\n", argv[0]); exit(EXIT_FAILURE); } /* Load the shared library and get a handle for later use */ libHandle = dlopen(argv[1], RTLD_LAZY); if (libHandle == NULL) { fprintf(stderr, "dlopen: %s\n", dlerror()); exit(EXIT_FAILURE); } /* Search library for symbol named in argv[2] */ (void) dlerror(); /* Clear dlerror() */ *(void **) (&funcp) = dlsym(libHandle, argv[2]); err = dlerror(); if (err != NULL) { fprintf(stderr, "dlsym: %s\n", err); exit(EXIT_FAILURE); } /* Try calling the address returned by dlsym() as a function that takes no arguments */ (*funcp)(); dlclose(libHandle); /* Close the library */ exit(EXIT_SUCCESS); } $ gcc -o dynload dynload.c -ldl $ ./dynload ./libsv.so xyz v3 xyz $ ./dynload ./libsv_def_xyz_v2.so xyz v2 xyz Note the last line: dlsym() found xyz@@VER_2 (not xyz@VER_3). >> 3. There can of course be only one NAME@@VERSION definition. > > Right. > >> 4. The version notation NAME@VERSION denotes a "hidden" version of the >> symbol. Such versions are not directly accessible, but can be >> accessed via asm(".symver") magic. There can be multiple "hidden" >> versions of a symbol. > > It is hidden only to the static linker, i.e. it links against either > unversioned or @@ versions of a symbol. Yes. >> 5. When resolving a reference to an unversioned symbol, NAME, >> in an executable that was linked against a nonsymbol-versioned >> library, the DL will, if it finds a symbol-versioned library >> in the link map use the earliest version of the symbol provided >> by that library. >> >> I presume that this behavior exists to allow easy migration >> of a non-symbol-versioned application onto a system with >> a symbol-versioned versioned library that uses the same major >> version real name for the library as was formerly used by >> a non-symbol-versioned library. (My knowledge of this area >> was pretty much nonexistent at that time, but presumably this >> is what was done in the transition from glibc 2.0 to glibc 2.1.) >> >> To clarify the scenario I am talking about: >> >> a) We have prog.c which calls xyz() and is linked against a >> non-symbol-versioned libxyz.so.2. >> >> b) Later, a symbol-versioned libxyz.so.2 is created that defines >> (for example): >> >> xyz@@VER_3 >> xyz@VER_2 >> xyz@VER_1 >> >> (Alternatively, we preload a shared library that defines >> these three versions of 'xyz'.) >> >> c) If we run the ancient binary 'prog' which requests refers >> to an unversioned 'xyz', that will resolve to xyz@VER_1. > > That seems correct. The VERSYM section orders the versions by index > (which seems to be based on ordering of the symbols in the version > script) and the odest in that sequence seems to win for unversioned > lookup. For a dlsym(), the newest one wins. Thanks for the confirmation. >> 6. [An additional detail to 5, which surprised me at first, but >> I can sort of convince myself it makes sense...] >> >> In the scenario described in point 5, an unversioned >> reference to NAME will be resolved to the earliest versioned >> symbol NAME inside a symbol-versioned library if there is >> is a version of NAME in the *lowest* version provided >> by the library. Otherwise, it will resolve to the *latest* >> version of NAME (and *not* to the default NAME@@VERSION >> version of the symbol). >> >> To clarify with an example: >> >> We have prog.c that calls abc() and xyz(), and is linked >> against a non-symbol-versioned library, lib_nonver.so, >> that provides definitions of abc() and xyz(). >> >> Then, we have a symbol-versioned library, lib_ver.so, >> that has three versions, VER_1, VER_2, and VER_3, and defines >> the following symbols: >> >> xyz@@VER_3 >> xyz@VER_2 >> xyz@VER_1 >> >> abc@@VER_3 >> abc@VER_2 >> >> Then we run 'prog' using: >> >> LD_PRELOAD=./lib_ver.so ./prog >> >> In this case, 'prog' will call xyz@VER_1 and abc@@VER_3 >> (*not* abc@VER_2) from lib_ver.so. >> >> I can convince myself (sort of) that this makes some sense by >> thinking about things from the perspective of the scenario of >> migrating from the non-symbol-versioned shared library to the >> symbol-versioned shared library: the old non-symbol-versioned library >> never provided a symbol 'abc()' so in this scenario, use the latest >> version of 'abc'. This applies even if the the latest version is not >> the 'default'. In other words, even if the versions of 'abc' >> provided by lib_ver.so were the following, it would still be the >> VER_3 of abc() that is called: >> >> abc@VER_3 >> abc@@VER_2 >> >> Am I right about my rough guess for the rationale for point 6, >> or is there something else I should know/write about? > > This seems odd, I hope someone here knows why this really is and > (hopefully) point to resources. Florian commented on this point already. See his mail. > Documentation about the dynamic linker > are generally very hard to find, It sure is... > so I'm glad you're doing this. Let's see if I can make something useful... Cheers, Michael -- Michael Kerrisk Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/ Linux/UNIX System Programming Training: http://man7.org/training/