8

I am building a shared library in C that is dynamically loaded by a program that I do not have source access to. The target platform is a 64-bit Linux platform and we're using gcc to build. I was able to build a reproduction of the issue in ~100 lines, but it's still a bit to read. Hopefully it's illustrative.

The core issue is I have two non-static functions (bar and baz) defined in my shared library. Both need to be non-static as we expect the caller to be able to dlsym them. Additionally, baz calls bar. The program that is using my library also has a function named bar, which wouldn't normally be an issue, but the calling program is compiled with -rdynamic, as it has a function foo that needs to be called in my shared library. The result is my shared library ends up linked to the calling program's version of bar at runtime, producing unintuitive results.

In an ideal world I would be able to include some command line switch when compiling my shared library that would prevent this from happening.

The current solution I have is to rename my non-static functions as funname_local and declare them static. I then define a new function: funname() { return funname_local(); }, and change any references to funname in my shared library to funname_local. This works, but it feels cumbersome, and I'd much prefer to just tell the linker to prefer symbols defined in the local compilation unit.

internal.c

#include <stdio.h>
#include "internal.h"

void
bar(void)
{
  printf("I should only be callable from the main program\n");
}

internal.h

#if !defined(__INTERNAL__)
#define __INTERNAL__

void
bar(void);

#endif /* defined(__INTERNAL__) */

main.c

#include <dlfcn.h>
#include <stdio.h>
#include "internal.h"

void
foo(void)
{
  printf("It's important that I am callable from both main and from any .so "
         "that we dlopen, that's why we compile with -rdynamic\n");
}

int
main()
{
  void *handle;
  void (*fun1)(void);
  void (*fun2)(void);
  char *error;

  if(NULL == (handle = dlopen("./shared.so", RTLD_NOW))) { /* Open library */
    fprintf(stderr, "dlopen: %s\n", dlerror());
    return 1;
  }
  dlerror(); /* Clear any existing error */

  *(void **)(&fun1) = dlsym(handle, "baz"); /* Get function pointer */
  if(NULL != (error = dlerror()))  {
    fprintf(stderr, "dlsym: %s\n", error);
    dlclose(handle);
    return 1;
  }
  *(void **)(&fun2) = dlsym(handle, "bar"); /* Get function pointer */
  if(NULL != (error = dlerror()))  {
    fprintf(stderr, "dlsym: %s\n", error);
    dlclose(handle);
    return 1;
  }

  printf("main:\n");
  foo();
  bar();
  fun1();
  fun2();

  dlclose(handle);
  return 0;
}

main.h

#if !defined(__MAIN__)
#define __MAIN__

extern void
foo(void);

#endif /* defined(__MAIN__) */

shared.c

#include <stdio.h>
#include "main.h"

void
bar(void)
{
  printf("bar:\n");
  printf("It's important that I'm callable from a program that loads shared.so"
         " as well as from other functions in shared.so\n");
}

void
baz(void)
{
  printf("baz:\n");
  foo();
  bar();
  return;
}

compile:

$ gcc -m64 -std=c89 -Wall -Wextra -Werror -pedantic -o main main.c internal.c -l dl -rdynamic
$ gcc -m64 -std=c89 -Wall -Wextra -Werror -pedantic -shared -fPIC -o shared.so shared.c

run:

$ ./main
main:
It's important that I am callable from both main and from any .so that we dlopen, that's why we compile with -rdynamic
I should only be callable from the main program
baz:
It's important that I am callable from both main and from any .so that we dlopen, that's why we compile with -rdynamic
I should only be callable from the main program
bar:
It's important that I'm callable from a program that loads shared.so as well as from other functions in shared.so
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
TheGeneral
  • 165
  • 1
  • 7
  • Read more about the [visibility](https://gcc.gnu.org/wiki/Visibility) attribute. It probably is relevant. Consider also `#define foo foo_internal`. BTW you might automate part of that, perhaps by customizing your compiler (e.g. with [GCC MELT](http://gcc-melt.org/)....) – Basile Starynkevitch Sep 13 '16 at 19:00
  • Note that C does not permit the same program to contain two different entities with external linkage to have the same name, so you're running in questionable territory to begin with. Although ELF *does* permit such symbol duplication, isn't there any way you can rename of the shared library's `bar()` to something unique, and to instruct the main program to look it up by that name (e.g. some kind of program configuration)? – John Bollinger Sep 13 '16 at 19:35
  • You might consider giving [Ulrich Drepper's paper on ELF](https://software.intel.com/sites/default/files/m/a/1/e/dsohowto.pdf) a read. It has quite a bit to say about symbol resolution. I'm not sure offhand whether what you want is really possible, but Drepper should help answer that question. – John Bollinger Sep 13 '16 at 19:38

2 Answers2

10

Have you tried -Bsymbolic linker option (or -Bsymbolic-functions)? Quoting from ld man:

-Bsymbolic

When creating a shared library, bind references to global symbols to the definition within the shared library, if any. Normally, it is possible for a program linked against a shared library to override the definition within the shared library. This option can also be used with the --export-dynamic option, when creating a position independent executable, to bind references to global symbols to the definition within the executable. This option is only meaningful on ELF platforms which support shared libraries and position independent executables.

It seems to solve the problem:

$ gcc -m64 -std=c89 -Wall -Wextra -Werror -pedantic -shared -fPIC -o shared.so shared.c
$ gcc -m64 -std=c89 -Wall -Wextra -Werror -pedantic -o main main.c internal.c -l dl -rdynamic
$ ./main 
main:
It's important that I am callable from both main and from any .so that we dlopen, that's why we compile with -rdynamic
I should only be callable from the main program
baz:
It's important that I am callable from both main and from any .so that we dlopen, that's why we compile with -rdynamic
I should only be callable from the main program
bar:
It's important that I'm callable from a program that loads shared.so as well as from other functions in shared.so
$ gcc -m64 -std=c89 -Wall -Wextra -Werror -pedantic -shared -fPIC -Wl,-Bsymbolic -o shared.so shared.c
$ ./main 
main:
It's important that I am callable from both main and from any .so that we dlopen, that's why we compile with -rdynamic
I should only be callable from the main program
baz:
It's important that I am callable from both main and from any .so that we dlopen, that's why we compile with -rdynamic
bar:
It's important that I'm callable from a program that loads shared.so as well as from other functions in shared.so
bar:
It's important that I'm callable from a program that loads shared.so as well as from other functions in shared.so
Roman Khimov
  • 4,807
  • 1
  • 27
  • 35
1

A common solution for this problem is to not actually depend on a global symbol not being overriden. Instead do the following:

  • Call the function bar from your library mylib_bar or something like that
  • Hide mylib_bar with __attribute__((visibility("hidden"))) or similar
  • Make bar a weak symbol, referring to mylib_bar like this:

    #pragma weak bar = mylib_bar
    
  • Make your library call mylib_bar everywhere instead of bar

Now everything works as expected:

  • When your library calls mylib_bar, this always refers to the definition in the library as the visibility is hidden.
  • When other code calls bar, this calls mylib_bar by default.
  • If someone defines his own bar, this overrides bar but not mylib_bar, leaving your library untouched.
fuz
  • 88,405
  • 25
  • 200
  • 352
  • Although it uses a slightly different mechanism, this seems much along the lines of the approach the OP is looking to replace. Of course, the method he already has is the very one I was about to recommend to him, so perhaps the take-home message is that such techniques are not so bad after all. – John Bollinger Sep 13 '16 at 19:28
  • @JohnBollinger Indeed. The key difference is the use of weak symbols and visibility to force this idea to work. – fuz Sep 13 '16 at 20:14
  • 1
    @JohnBollinger the issue with my initial solution to the problem is it means everything ends up declared as static. If `bar` is invoked from multiple difference source files inside of my shared object then we have file scope issues. @FUZxxl has a better solution as it doesn't break if `bar` is called from multiple file scopes within my shared library. That said, @roman-khimov has a solution that best answers my initial ask. – TheGeneral Sep 13 '16 at 21:30
  • Hiding symbols is platform-dependent and unnecessary: using mylib_ prefix (or some even more plausible prefix) does the trick. Also `#pragma` is platform-dependent; `bar` could be a wrapper function that calls `mylib_bar`; if the main program (or some other shared library) replaces symbol `bar`, your code will still work, because you use `mylib_bar` not `bar`. – Zsigmond Lőrinczy Sep 14 '16 at 04:28
  • @ZsigmondLőrinczy That pragma is standard from System V and widely supported on UNIX-like systems. But yes, it's not part of the C standard. – fuz Sep 14 '16 at 09:10