Имея ./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 библиотек. Это имеет ряд преимуществ:
GCC Wiki Visibility Page (где я нашел большую часть информации для этого ответа) содержит много информации об этом, включая советы по передовой практике.
это нормально для разделяемой библиотеки в Linux.
Общие библиотеки не похожи на Windows DLL. в этой теме это больше похоже на статическую библиотеку.
Когда вы компилируете библиотеку, она пропускает method
, если ваш основной не будет поставлять его, он просто не сможет загрузить библиотеку и dlopen
не удастся, и вы должны использовать dlerror
чтобы проверить почему.
можешь попробовать:
handle = dlopen("./library.so", RTLD_LAZY);
if (!handle) {
cerr<< dlerror()<<endl;
exit(-1);
}