我试图在一个项目中使用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();
}main.cpp
#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.cppg++ -fsanitize=vptr main.cpp -ldl./a.outhttps://whatofhow.wordpress.com/2015/03/17/odr-rtti-dso解释说,这是由于共享库中的RTTI信息和二进制文件不同造成的。
一个非常类似的问题也会发生,当您在共享库中导出一个函数时,使用dlsym导入它并尝试调用它。结果将是call to function <...> through pointer to incorrect function type '<...>'和-fsanitize=function的clang。
有什么办法解决这个问题吗?我不使用Clang,也不玩-fvisibility,所以不知道在这里做什么。
发布于 2019-08-06 15:53:07
在-fsanitize=function中使用clang已经报告了对create调用的违反:
通过指向不正确函数类型'Foo ()()的指针调用函数创建
这看起来同样是一个假阳性,只有当共享库和可执行文件用‘-fsanitize=.’编译时才会发生。
通过将不同编译的共享库的类型信息与nm -C libfoo.so | grep typeinfo进行比较,发现使用消毒液的库中有一个用于typeinfo for Foo* ()的额外类型信息。
为什么要输入信息?那么,UBSAN比较类型信息,以确定传递的指针(函数还是类)是预期的指针。typeinfo比较是通过指针比较完成的。这对速度是很好的,但也带来了一个微妙的问题:即使实际的类型实际上是相同的,它们也可能不共享相同的typeinfo。
这里的情况是这样的:库中的typeinfo和可执行文件没有合并,所以有两个相同类型信息的实例。
解决方案是在创建可执行文件时传递-rdynamic。来自GCC手册
将标志-导出-动态传递给ELF链接器,在支持它的目标上。这将指示链接器向动态符号表中添加所有符号,而不仅仅是使用的符号。对于dlopen的某些用途,或为了允许从程序中获取回溯跟踪,需要此选项。
在这里,我们似乎有这样一个“使用dlopen”。
对于CMake,在可执行目标上使用属性ENABLE_EXPORTS。
有关UBSAN问题的更多乐趣,请参见相关问题通过指向不正确函数类型的指针调用函数(未知)。
https://stackoverflow.com/questions/57361776
复制相似问题