c++ - 将 UBSAN 与动态加载的共享库一起使用
问题描述
我试图在一个项目中使用 UBSAN,但遇到了一个似乎无法解决的问题:该项目使用了一个通过共享库实现的插件系统。也就是说,每个插件都提供了一个工厂方法,该方法返回一个带有插件特定派生类的抽象类的实例。然后该项目遍历文件夹中的所有共享库,使用 打开它们dlopen
,通过获取工厂方法dlsym
并创建插件实例,然后使用该实例。
然而,在使用任何接口方法 UBSAN 抛出member call on address 0x... which does not point to an object of type '...'
MWE:
foo.h
struct Foo{
virtual int g() = 0;
};
extern "C" Foo* create();
foo.cpp
#include "foo.h"
struct Bar: Foo{
int g(){ return 42; }
};
Foo* create(){
return new Bar();
}
主文件
#include "foo.h"
#include <dlfcn.h>
#include <cassert>
int main(){
void* h = dlopen("libfoo.so", RTLD_GLOBAL | RTLD_NOW);
assert(h);
void* c = dlsym(h, "create");
assert(c);
using create_t = Foo*();
Foo* f = reinterpret_cast<create_t*>(c)();
return f->g() != 42;
}
编译:
g++ -shared -fPIC -o libfoo.so foo.cpp
g++ -fsanitize=vptr main.cpp -ldl
./a.out
https://whatofhow.wordpress.com/2015/03/17/odr-rtti-dso解释说,这是由于共享库中的 RTTI 信息和二进制文件不同。
一个非常相似的问题发生了,当您在共享库中导出一个函数时,将其导入dlsym
并尝试调用它。结果将是call to function <...> through pointer to incorrect function type '<...>'
for -fsanitize=function
clang。
有没有办法解决这个问题?我没有使用 Clang 或玩,-fvisibility
所以不知道在这里做什么。
解决方案
使用 clang with-fsanitize=function
确实已经报告了create
调用违规:
通过指向不正确函数类型'Foo ()()'的指针调用函数创建
这再次看起来像一个误报,并且仅当共享库和可执行文件使用“-fsanitize=...”编译时才会发生。
比较不同编译共享库的 typeinfo 与nm -C libfoo.so | grep typeinfo
显示带有消毒剂的共享库有一个额外的 typeinfo 用于typeinfo for Foo* ()
.
为什么要输入信息?那么UBSAN比较typeinfo来决定传递的指针(函数或类)是否是预期的。typeinfo
比较是通过指针比较来完成的。这对速度很有好处,但引入了一个微妙的问题:即使实际类型实际上是相同的,它们也可能不会共享相同的typeinfo
.
这里是这种情况:库和可执行文件中的 typeinfo 没有合并,因此有 2 个相同 typeinfo 的实例。
解决方案是-rdynamic
在创建可执行文件时通过。来自GCC 手册
在支持它的目标上将标志 -export-dynamic 传递给 ELF 链接器。这指示链接器将所有符号(不仅是使用的符号)添加到动态符号表中。dlopen 的某些用途或允许从程序中获取回溯需要此选项。
看来我们这里有这么一个“使用dlopen”。
对于 CMake,使用ENABLE_EXPORTS
可执行目标上的属性。
有关 UBSAN 问题的更多乐趣,请参阅相关问题Call to function (unknown) through pointer to wrong function type
推荐阅读
- c++ - 从二进制图像到 shapefile
- python - Git yaml 代码未将结果拉取/发送到数据库
- r - 我想做同样的事情,但使用 .csv 文件。这是可能的?
- java - eclipse中的调试模式确实跳过断点并执行程序
- javascript - 如何在同一对象间隔中使用 amcharts 在甘特图中显示数据
- android - 奇怪的while循环行为
- firebase - 将电子邮件重新用于 Firebase 身份验证开发目的
- uikit - 如何在 SwiftUI 视图中包装 UIBarButtonItem?
- amazon-s3 - 如何将文件上传到 node.js 中 AWS s3 存储桶中的特定文件夹
- c++ - 为什么从 constexpr 引用生成的汇编代码与 constexpr 指针不同?