为什么在外部模块之外实现的方法仍然可以访问该模块
why are methods implemented outside an external module still accessible to the module
拥有/library.so(rary.cpp
)和/main(main.cpp
)可执行文件,两者共享相同的api.h
。
唯一的方法(void method (void)
)在api.h
中只有其签名,实际实现在main.cpp
中。
在编译时,rary.cpp
不包括该方法的实际定义:在制作library.so
文件时从未提及main.cpp
。尽管如此,如果我通过dlopen
和dlsym
访问共享文件,那么从library.so
内部对该方法的任何调用实际上都引用了仅在主程序中实现的方法。
据我所知,library.so
从未出现在该方法的实现中,所以编译器(我认为)应该抱怨调用了一个未实现的方法。
所以我的问题是:
- 这正常吗?在主二进制文件中实现的方法应该直接从动态加载的模块中访问吗?(我认为无法保证编译后如何在二进制文件中实际调用符号)
- 有办法防止这种情况发生吗?假设我正在为某人提供一个API来为我的程序编写插件,这样他们就可以猜测主二进制文件中方法的名称,并做一些。。。黑客
- 我可以期望编译器和操作系统之间的这种行为是一致的吗
- 我应该依赖这种行为吗?还是这是一种好的做法
源代码
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
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可见性页面(我在其中找到了这个答案的大部分信息)包含了很多关于这方面的信息,包括最佳实践技巧。
这对于Linux中的共享库来说是正常的。
共享库与windowsdll不同。在这个主题中,它更类似于静态库。
编译lib时,它缺少method
。如果您的main不提供它,它将无法加载库,dlopen
将失败,您应该使用dlerror
来检查原因。
你可以试试:
handle = dlopen("./library.so", RTLD_LAZY);
if (!handle) {
cerr<< dlerror()<<endl;
exit(-1);
}
- 通过方法访问结构
- 使用不带参数的函数访问结构元素
- 如果我只是不访问queue_front节点的子节点,而是将它们推到队列中呢?还是BFS吗
- 用于访问容器<T>数据成员的正确 API
- 访问者访问变体并返回不同类型时出错
- 尝试通过多个向量访问变量时,向量下标超出范围
- 无法访问嵌套类.类的使用无效
- 尝试导入pybind-opencv模块时出现libgtk错误
- 写入位置0x0000000C时发生访问冲突
- 我们可以访问一个不存在的联盟的成员吗
- C++从另一个类访问公共静态向量的正确方法是什么
- 我的简单if-else语句是如何无法访问的代码
- 从C++dll访问C#中的一行主要参数
- 概念TS检查忽略私有访问修饰符
- Python C API:使用 MSVC 尝试示例模块时的访问冲突
- 通过进程模块C 枚举时,访问被拒绝
- 另一个子模块错误的 omnet 访问方法 - 调用 'check_and_cast(cModule*&)' 没有匹配函数
- 从 C++ 访问 Fortran 模块的变量
- 模块"ntdll.dll"中地址 * 的访问冲突。写入地址 *
- 为什么在外部模块之外实现的方法仍然可以访问该模块