Имея исполняемые 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
никогда не присутствовал в реализации метода, поэтому компилятор (я думаю) должен жаловаться на то, что вызывается не реализованный метод.
Поэтому мои вопросы:
#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;
}
#include "api.hpp"
#include <iostream>
using std::cout;
using std::endl;
extern "C" void init (void)
{
method();
method(69);
cout << "library ran" << endl;
}
void method (void);
void method (int);
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
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 (где я нашел большую часть информации для этого ответа) содержит много информации об этом, включая советы по лучшей практике.
это нормально для общей библиотеки в Linux.
общие библиотеки не похожи на windows dll. в этом вопросе он больше похож на статическую библиотеку.
Когда вы компилируете lib, он пропускает method
. если ваш основной не будет поставлять его, он просто не сможет загрузить библиотеку, и dlopen
будет dlerror
и вы должны использовать dlerror
для проверки причины.
можешь попробовать:
handle = dlopen("./library.so", RTLD_LAZY);
if (!handle) {
cerr<< dlerror()<<endl;
exit(-1);
}