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

Имея ./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

0

Решение

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 (где я нашел большую часть информации для этого ответа) содержит много информации об этом, включая советы по передовой практике.

2

Другие решения

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

Общие библиотеки не похожи на Windows DLL. в этой теме это больше похоже на статическую библиотеку.

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

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

handle = dlopen("./library.so", RTLD_LAZY);
if (!handle) {
cerr<< dlerror()<<endl;
exit(-1);
}
1

По вопросам рекламы [email protected]