C++ UBSAN 使用派生对象产生误报 [英] C++ UBSAN produces false positives with derived objects

查看:75
本文介绍了C++ UBSAN 使用派生对象产生误报的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I wanted to use UBSAN (undefined behavior sanitizer) but found it completely worthless as it reports to many false positives.

例如一个简单的 std::make_shared(42); 足以触发像

E.g. a simple std::make_shared<int>(42); is enough to trigger warnings like

地址 0x00000236de70 内的成员访问不指向类型为_Sp_counted_base"的对象

member access within address 0x00000236de70 which does not point to an object of type '_Sp_counted_base'

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

Reducing this example to a MWE shows that the problem is more general with base classes and inheritance:

示例:

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"的对象

example.cpp:15:16: runtime error: member call on address 0x000000726e70 which does not point to an object of type 'Bar'

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

0x000000726e70: note: object has invalid vptr

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

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

How are not even these simple cases properly handled? Did I miss anything? How should I properly use UBSAN to check my code? (This requires [almost] no false positives)

由于 MWE 似乎只适用于 Godbolt,原始代码如下所示:

As it seems the MWE only works on godbolt, the original code looks like this:

#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 编译并运行,结果是错误如

Compile with clang++-8 -fsanitize=undefined -fvisibility=hidden -I /opt/boost_1_64_0/include/ test.cpp /opt/boost_1_64_0/lib/libboost_iostreams.so and run which results in errors like

运行时错误:地址 0x00000126ef30 上的成员调用未指向类型为boost::detail::sp_counted_base"的对象

runtime error: member call on address 0x00000126ef30 which does not point to an object of type 'boost::detail::sp_counted_base'

推荐答案

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

Trying to answer this myself after the comments and creating another MWE.

TLDR:确保在使用 -fvisibility=hidden

TLDR: Make sure all classes containing virtual functions are exported when compiling with -fvisibility=hidden

考虑一个共享库 Foo

Consider a shared library Foo with

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.cpp #include "foo.h"

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

使用clang++-8 foo.cpp -shared -fPIC -o foo.so

还有一个使用虚函数链接的可执行文件,但带有 -fvisibility:

And an executable linked against this using the virtual functions but with -fvisibility:

main.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"类型的对象

runtime error: member call on address 0x00000290cea0 which does not point to an object of type 'Foo'

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

This is the same error as described in https://bugs.llvm.org/show_bug.cgi?id=39191 (thanks @Nikita Petrenko)

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

Summary: With fvisibility=hidden not exported symbols (functions, classes not decorated with the attribute __attribute__((visibility("default"))) are not considered the same when used in different DSOs (e.g. executable and shared library). Hence the base class Foo in the shared library and the executable are distinct (they have different vtables) which UBSAN detects: The executable "expects" an object witg vtable of Exe::Foo but instead gets Library::Foo

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

In the boost case the class sp_counted_base is the culprit as it is not exported until Boost 1.69 which adds BOOST_SYMBOL_EXPORT, so switching to Boost 1.69+ fixes the issue.

这篇关于C++ UBSAN 使用派生对象产生误报的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆