Можно ли скомпилировать общий объект так, чтобы он предпочитал локальные символы, даже если он загружается программой, скомпилированной с параметром -rdynamic?

Я создаю общую библиотеку на C, которая динамически загружается программой, к которой у меня нет доступа к исходному коду. Целевой платформой является 64-битная платформа Linux, и мы используем gcc для сборки. Мне удалось воспроизвести выпуск примерно в 100 строк, но это все еще немного для чтения. Надеюсь, это иллюстративно.

Основная проблема заключается в том, что у меня есть две нестатические функции (bar и baz), определенные в моей общей библиотеке. Оба должны быть нестатическими, так как мы ожидаем, что вызывающая сторона сможет их dlsym. Кроме того, baz вызывает bar. Программа, использующая мою библиотеку, также имеет функцию с именем bar, которая обычно не вызывает проблем, но вызывающая программа скомпилирована с -rdynamic, так как у нее есть функция foo, которую нужно вызывать в моей общей библиотеке. В результате моя разделяемая библиотека оказывается связанной с версией bar вызывающей программы во время выполнения, что приводит к неинтуитивным результатам.

В идеальном мире я мог бы включить некоторый переключатель командной строки при компиляции моей общей библиотеки, который предотвратил бы это.

Текущее решение, которое у меня есть, состоит в том, чтобы переименовать мои нестатические функции как funname_local и объявить их статическими. Затем я определяю новую функцию: funname() { return funname_local(); } и меняю все ссылки на funname в моей общей библиотеке на funname_local. Это работает, но кажется громоздким, и я бы предпочел просто сказать компоновщику, чтобы он предпочитал символы, определенные в локальной единице компиляции.

внутренний.c

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

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

внутр.ч

#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__) */

общий.с

#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;
}

скомпилировать:

$ 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

бежать:

$ ./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

person TheGeneral    schedule 13.09.2016    source источник
comment
Узнайте больше об атрибуте видимости. Вероятно, это актуально. Рассмотрим также #define foo foo_internal. Кстати, вы можете автоматизировать часть этого, возможно, настроив свой компилятор (например, с помощью GCC MELT....)   -  person Basile Starynkevitch    schedule 13.09.2016
comment
Обратите внимание, что C не позволяет одной и той же программе содержать два разных объекта с внешней связью с одинаковым именем, поэтому вы с самого начала работаете на сомнительной территории. Хотя ELF разрешает такое дублирование символов, нет ли способа переименовать bar() разделяемой библиотеки во что-то уникальное и указать основной программе искать его по этому имени (например, какому-то конфигурации программы)?   -  person John Bollinger    schedule 13.09.2016
comment
Вы можете рассмотреть возможность предоставления статьи Ульриха Дреппера об ELF чтение. В нем есть что сказать о разрешении символов. Я не уверен навскидку, действительно ли возможно то, что вы хотите, но Дреппер должен помочь ответить на этот вопрос.   -  person John Bollinger    schedule 13.09.2016


Ответы (2)


Вы пробовали вариант компоновщика -Bsymbolic (или -Bsymbolic-functions)? Цитата из ld человека:

-Bсимволический

При создании общей библиотеки привяжите ссылки на глобальные символы к определению в общей библиотеке, если таковые имеются. Обычно программа, связанная с общей библиотекой, может переопределить определение в общей библиотеке. Эту опцию также можно использовать с опцией --export-dynamic при создании независимого от позиции исполняемого файла, чтобы связать ссылки на глобальные символы с определением в исполняемом файле. Эта опция имеет смысл только на платформах ELF, которые поддерживают разделяемые библиотеки и исполняемые файлы, независимые от позиции.

Вроде решил проблему:

$ 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
person Roman Khimov    schedule 13.09.2016
comment
Это именно то, что я искал. Спасибо! - person TheGeneral; 14.09.2016

Обычное решение этой проблемы состоит в том, чтобы фактически не зависеть от того, что глобальный символ не переопределяется. Вместо этого сделайте следующее:

  • Вызов функции bar из вашей библиотеки mylib_bar или что-то в этом роде
  • Скрыть mylib_bar с помощью __attribute__((visibility("hidden"))) или аналогичного
  • Сделайте bar слабым символом, ссылаясь на mylib_bar следующим образом:

    #pragma weak bar = mylib_bar
    
  • Сделайте так, чтобы ваша библиотека везде вызывала mylib_bar вместо bar

Теперь все работает как положено:

  • Когда ваша библиотека вызывает mylib_bar, это всегда относится к определению в библиотеке, поскольку видимость скрыта.
  • Когда другой код вызывает bar, по умолчанию вызывается mylib_bar.
  • Если кто-то определяет свой собственный bar, это переопределяет bar, но не mylib_bar, оставляя вашу библиотеку нетронутой.
person fuz    schedule 13.09.2016
comment
Хотя он использует немного другой механизм, он, похоже, очень похож на подход, который OP стремится заменить. Конечно, метод, который у него уже есть, — это тот самый метод, который я собирался ему порекомендовать, так что, возможно, основная идея заключается в том, что такие методы в конце концов не так уж плохи. - person John Bollinger; 13.09.2016
comment
@JohnBollinger Действительно. Ключевое отличие заключается в использовании слабых символов и видимости, чтобы заставить эту идею работать. - person fuz; 13.09.2016
comment
@JohnBollinger проблема с моим первоначальным решением проблемы в том, что это означает, что все в конечном итоге объявляется статическим. Если bar вызывается из нескольких исходных файлов различий внутри моего общего объекта, у нас возникают проблемы с областью действия файла. У @FUZxxl есть лучшее решение, поскольку оно не ломается, если bar вызывается из нескольких областей файлов в моей общей библиотеке. Тем не менее, у @roman-khimov есть решение, которое лучше всего отвечает на мой первоначальный вопрос. - person TheGeneral; 14.09.2016
comment
Скрытие символов зависит от платформы и не является необходимым: использование префикса mylib_ (или какого-либо еще более правдоподобного префикса) делает свое дело. Также #pragma зависит от платформы; bar может быть функцией-оболочкой, вызывающей mylib_bar; если основная программа (или какая-то другая разделяемая библиотека) заменит символ bar, ваш код все равно будет работать, потому что вы используете mylib_bar, а не bar. - person Zsigmond Lőrinczy; 14.09.2016
comment
@ZsigmondLőrinczy Эта прагма является стандартной для System V и широко поддерживается в UNIX-подобных системах. Но да, это не часть стандарта C. - person fuz; 14.09.2016