首页 > 解决方案 > 将 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;
}

编译:

https://whatofhow.wordpress.com/2015/03/17/odr-rtti-dso解释说,这是由于共享库中的 RTTI 信息和二进制文件不同。

一个非常相似的问题发生了,当您在共享库中导出一个函数时,将其导入dlsym并尝试调用它。结果将是call to function <...> through pointer to incorrect function type '<...>'for -fsanitize=functionclang。

有没有办法解决这个问题?我没有使用 Clang 或玩,-fvisibility所以不知道在这里做什么。

标签: c++shared-librariesvirtual-functionsrttiubsan

解决方案


使用 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


推荐阅读