9

我想使用 UBSAN(未定义的行为消毒剂),但发现它完全没有价值,因为它会报告许多误报。

例如,一个简单std::make_shared<int>(42);的就足以触发警告,例如

地址 0x00000236de70 内的成员访问,它不指向“_Sp_counted_base”类型的对象

将此示例简化为 MWE 表明问题在基类和继承方面更为普遍:

例子:

struct Foo{
    int f(){ return g(); }
    virtual int g() = 0;
};

struct Bar: Foo{
    int g(){ return 42; }
};

int main(){
    auto f = new Bar();
    return f->g();
}

编译-fsanitize=undefined并观看

example.cpp:15:16:运行时错误:对地址 0x000000726e70 的成员调用,它不指向“Bar”类型的对象

0x000000726e70:注意:对象的 vptr 无效

请参阅https://godbolt.org/z/0UiVtu

连这些简单的案件都处理不好怎么办?我错过了什么吗?我应该如何正确使用 UBSAN 来检查我的代码?(这需要[几乎]没有误报)

编辑:似乎 MWE 仅适用于 Godbolt,原始代码如下所示:

#include <boost/iostreams/device/mapped_file.hpp>
#include <boost/iostreams/stream.hpp>
using MMStream = boost::iostreams::stream<boost::iostreams::mapped_file_source>;

int main(){
  MMStream stream;
  stream.open("a.out");
  return !stream;
}

编译clang++-8 -fsanitize=undefined -fvisibility=hidden -I /opt/boost_1_64_0/include/ test.cpp /opt/boost_1_64_0/lib/libboost_iostreams.so并运行会导致错误,例如

运行时错误:对地址 0x00000126ef30 的成员调用,它不指向“boost::detail::sp_counted_base”类型的对象

4

1 回答 1

9

在评论后尝试自己回答这个问题并创建另一个 MWE。

TLDR:确保在编译时导出所有包含虚函数的类-fvisibility=hidden

考虑一个共享库 Foo

foo.h

#define EXPORT __attribute__((visibility("default")))

struct Foo{
    virtual int g() = 0;
};

struct Bar: Foo{
    int g(){ return 42; }
};

EXPORT Foo* create();

foo.cpp #include "foo.h"

Foo* create(){
  return new Bar();
}

编译clang++-8 foo.cpp -shared -fPIC -o foo.so

以及一个使用虚函数与​​此相关的可执行文件,但具有-fvisibility

主.cpp:

#include "foo.h"

int main(){
  Foo* f  = create();
  return f->g() != 42;
}

编译clang++-8 -fsanitize=undefined -fvisibility=hidden main.cpp foo.so

这将报告

运行时错误:对地址 0x00000290cea0 的成员调用,它不指向“Foo”类型的对象

这与https://bugs.llvm.org/show_bug.cgi?id=39191中描述的错误相同(感谢@Nikita Petrenko)

总结:fvisibility=hidden没有导出符号(函数,没有用属性修饰的类__attribute__((visibility("default")))在不同的DSO(例如可执行文件和共享库)中使用时被认为是不一样的。因此共享库Foo中的基类和可执行文件是不同的(它们有不同的UBSAN 检测到的 vtables):可执行文件“期望”一个带有 vtable 的对象,Exe::Foo但取而代之的是Library::Foo

在 boost 的情况下,类sp_counted_base是罪魁祸首,因为它直到 Boost 1.69 才被导出BOOST_SYMBOL_EXPORT,所以切换到 Boost 1.69+ 解决了这个问题。

于 2019-08-01T07:44:38.560 回答