C++ UBSAN 对派生对象产生误报
C++ UBSAN produces false positives with derived objects
我想使用 UBSAN(未定义行为消毒剂),但发现它完全没有价值,因为它会报告许多误报。
例如一个简单的 std::make_shared<int>(42);
就足以触发像
这样的警告
member access within address 0x00000236de70 which does not point to an object of type '_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: runtime error: member call on address 0x000000726e70 which does not point to an object of type 'Bar'
0x000000726e70: note: object has invalid 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
和 运行 编译会导致类似
的错误
runtime error: member call on address 0x00000126ef30 which does not point to an object of type 'boost::detail::sp_counted_base'
尝试在评论后自己回答并创建另一个 MWE。
TLDR:确保在使用 -fvisibility=hidden
编译时导出所有包含虚函数的 classes
考虑一个共享库 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
:
main.cpp:
#include "foo.h"
int main(){
Foo* f = create();
return f->g() != 42;
}
编译为 clang++-8 -fsanitize=undefined -fvisibility=hidden main.cpp foo.so
这将报告
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)
总结:使用fvisibility=hidden
未导出的符号(函数,class没有用属性__attribute__((visibility("default")))
装饰的在不同的DSO中使用时不被认为是相同的(例如可执行和共享库)。因此,共享库中的基础 class Foo
和可执行文件是不同的(它们具有不同的 vtable),UBSAN 检测到:可执行文件 "expects" 具有 [=20 的 vtable 的对象=] 而是得到 Library::Foo
在 boost 的情况下,class sp_counted_base
是罪魁祸首,因为它直到 Boost 1.69 才导出 BOOST_SYMBOL_EXPORT
,因此切换到 Boost 1.69+ 解决了这个问题。
我想使用 UBSAN(未定义行为消毒剂),但发现它完全没有价值,因为它会报告许多误报。
例如一个简单的 std::make_shared<int>(42);
就足以触发像
member access within address 0x00000236de70 which does not point to an object of type '_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: runtime error: member call on address 0x000000726e70 which does not point to an object of type 'Bar'
0x000000726e70: note: object has invalid 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
和 运行 编译会导致类似
runtime error: member call on address 0x00000126ef30 which does not point to an object of type 'boost::detail::sp_counted_base'
尝试在评论后自己回答并创建另一个 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
:
main.cpp:
#include "foo.h"
int main(){
Foo* f = create();
return f->g() != 42;
}
编译为 clang++-8 -fsanitize=undefined -fvisibility=hidden main.cpp foo.so
这将报告
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)
总结:使用fvisibility=hidden
未导出的符号(函数,class没有用属性__attribute__((visibility("default")))
装饰的在不同的DSO中使用时不被认为是相同的(例如可执行和共享库)。因此,共享库中的基础 class Foo
和可执行文件是不同的(它们具有不同的 vtable),UBSAN 检测到:可执行文件 "expects" 具有 [=20 的 vtable 的对象=] 而是得到 Library::Foo
在 boost 的情况下,class sp_counted_base
是罪魁祸首,因为它直到 Boost 1.69 才导出 BOOST_SYMBOL_EXPORT
,因此切换到 Boost 1.69+ 解决了这个问题。