почему методы, реализованные вне внешнего модуля, все еще доступны для модуля

0

Имея исполняемые rary.cpp./library.so(rary.cpp) и. /main (main.cpp), оба используют один и тот же api.h

Единственный метод (void method (void)) имеет только свою подпись в api.h, фактическая реализация - main.cpp.

Во время компиляции rary.cpp не включает фактическое определение метода: main.cpp никогда не упоминается при создании файла library.so. Тем не менее, если я получаю доступ к общему файлу через dlopen и dlsym, любые вызовы из library.so метод действительно ссылается на метод, который был реализован только в основной программе.

Я не ожидал, что это произойдет, поскольку я знаю, что library.so никогда не присутствовал в реализации метода, поэтому компилятор (я думаю) должен жаловаться на то, что вызывается не реализованный метод.

Поэтому мои вопросы:

  1. Это нормально? являются ли методы, реализованные в основном двоичном формате, которые должны быть доступны непосредственно из динамически загружаемых модулей только по их имени? (Я думал, что нет никакой гарантии того, как символы могут быть фактически вызваны в двоичном файле после компиляции)
  2. Есть ли способ предотвратить это? Скажем, что я предоставляю API для того, чтобы кто-то написал плагин для моей программы, таким образом, они могли угадать имена методов в главном двоичном файле и сделать некоторые... взломать?
  3. Могу ли я ожидать, что такое поведение будет согласованным между компиляторами и операционными системами?
  4. Должен ли я или это хорошая практика полагаться на это поведение?


Исходники

main.cpp

#include "api.hpp"
#include <iostream>
#include <dlfcn.h>

using std::cout;
using std::endl;

void method (void)
{
    cout << "method happened" << endl;
}

void method (int num)
{
    cout << "method happened with num " << num << endl;
}

int main (void)
{
    cout << "started main" << endl;

    typedef void(* initfunc)();

    void * handle = dlopen("./library.so", RTLD_LAZY);

    initfunc func = reinterpret_cast<initfunc> (dlsym(handle, "init"));

    func();

    return 0;
}

rary.cpp

#include "api.hpp"
#include <iostream>

using std::cout;
using std::endl;

extern "C" void init (void)
{
    method();
    method(69);
    cout << "library ran" << endl;
}

api.h

void method (void);
void method (int);

CMakeLists.txt

project(apitest CXX)

add_executable(apitest "main.cpp")
add_library(rary MODULE "rary.cpp")
target_link_libraries(apitest dl)

составление

cmake .
make

результат

$ ./apitest
started core
method happened
method happened with num 69
library ran
Теги:
dynamic-loading

2 ответа

2
Лучший ответ

Q1. Это нормально? являются ли методы, реализованные в основном двоичном формате, которые должны быть доступны непосредственно из динамически загружаемых модулей только по их имени? (Я думал, что нет никакой гарантии того, как символы могут быть фактически вызваны в двоичном файле после компиляции)

Да. По умолчанию символы видны снаружи, за исключением случаев, когда они обозначены иначе (с GCC как минимум).

Q2. Есть ли способ предотвратить это? Скажем, что я предоставляю API для того, чтобы кто-то написал плагин для моей программы, таким образом, они могли угадать имена методов в главном двоичном файле и сделать некоторые... взломать?

Да, это можно предотвратить, скомпилировав программу с помощью опции -fvisibility=hidden. Я изменил ваш пример, добавив его в CMakeLists.txt:

set (CMAKE_CXX_FLAGS "-fvisibility=hidden")

Я изменил определение функции init так, чтобы оно было заметным, например:

extern "C" __attribute__ ((visibility ("default"))) void init (void)

Я также изменил ваш main.cpp, чтобы проверить наличие ошибок от dlsym (поскольку ваша оригинальная версия не была):

initfunc func = reinterpret_cast<initfunc> (dlsym(handle, "init"));

if (func == NULL) {
    cout << dlerror() << endl;
    return 1;
}

После этого ваша программа выводит следующее:

$ ./apitest 
started main
./apitest: symbol lookup error: ./library.so: undefined symbol: _Z6methodv

Поскольку функция init отмечена видимой, ее можно вызвать из основной функции. Но функция method отсутствует, поэтому вы получаете неопределенную ошибку символа.

Q3. Могу ли я ожидать, что такое поведение будет согласованным между компиляторами и операционными системами?

Нет. Похоже, что в Windows поведение более или менее обратное - по умолчанию символы не экспортируются, если явно не отмечены.

Q4. Должен ли я или это хорошая практика полагаться на это поведение?

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

  • Время связывания будет закорочено
  • Меньше шансов столкновения символов
  • Лучшая оптимизация компилятора

Страница видимости Wiki Wiki (где я нашел большую часть информации для этого ответа) содержит много информации об этом, включая советы по лучшей практике.

1

это нормально для общей библиотеки в Linux.

общие библиотеки не похожи на windows dll. в этом вопросе он больше похож на статическую библиотеку.

Когда вы компилируете lib, он пропускает method. если ваш основной не будет поставлять его, он просто не сможет загрузить библиотеку, и dlopen будет dlerror и вы должны использовать dlerror для проверки причины.

можешь попробовать:

handle = dlopen("./library.so", RTLD_LAZY);
if (!handle) {
    cerr<< dlerror()<<endl;
    exit(-1);
}
  • 0
    Я ничего не знаю о библиотеках Windows, хотя никогда там не разрабатывался.
  • 1
    Библиотеки @Alec Windows полностью связаны, поэтому вы не можете оставить неразрешенные символы.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню