使用-fsanitize=address编译时调用虚函数触发崩溃的原因咨询
using namespace std; class A { public: virtual void fun1()=0; }; class B : public A { public: virtual void fun1(); }; void B::fun1() { cout << " In B::fun1 function" << endl; } class C { public: A *pobj; void Reset(A *pobj); void fun2(); }; void C::fun2() { pobj->fun1(); } void C::Reset(A* p_obj) { pobj = p_obj; } class D { public: C cobj; bool initialized; void Initialize(A *pAobj); }; void D::Initialize(A *pAobj) { // casting if(initialized == false) { initialized = true; cobj.Reset(pAobj); } cobj.fun2(); } class E { public: E(); ~E(); void fun4(); void fun5(); D *p_Dobj; }; E::E() : p_Dobj(new D){ } E::~E() { if(p_Dobj != NULL) { delete p_Dobj; } } void E::fun4() { B Bobj; // Created Local Object which may causing issue. p_Dobj->Initialize(&Bobj); } void E::fun5() { fun4(); fun4(); } int main() { E Eobj; Eobj.fun5(); return 0; }
编译运行结果对比
正常编译运行
使用命令 clang++ Demo.cpp 编译后运行,输出如下:
$./a.out In B::fun1 function In B::fun1 function
AddressSanitizer编译运行报错
使用命令 clang++ -fsanitize=address Demo.cpp 编译后运行,触发错误:
In B::fun1 function
32155ERROR: AddressSanitizer: stack-use-after-return on address 0x7f49b5900060 at pc 0x565479ba8963 bp 0x7fff9ca67730 sp 0x7fff9ca67728
READ of size 8 at 0x7f49b5900060 thread T0
#0 0x565479ba8962 (/home/excellarate/Desktop/All Tasks/Wasm/a.out+0xf4962)
#1 0x565479ba8aae (/home/excellarate/Desktop/All Tasks/Wasm/a.out+0xf4aae)
#2 0x565479ba8cab (/home/excellarate/Desktop/All Tasks/Wasm/a.out+0xf4cab)
#3 0x565479ba8d21 (/home/excellarate/Desktop/All Tasks/Wasm/a.out+0xf4d21)
#4 0x565479ba8e04 (/home/excellarate/Desktop/All Tasks/Wasm/a.out+0xf4e04)
#5 0x7f49b7f8f082 (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
#6 0x565479ad337d (/home/excellarate/Desktop/All Tasks/Wasm/a.out+0x1f37d)Address 0x7f49b5900060 is located in stack of thread T0 at offset 32 in frame
#0 0x565479ba8bbf (/home/excellarate/Desktop/All Tasks/Wasm/a.out+0xf4bbf)This frame has 1 object(s):
[32, 40) 'Bobj' (line 68) <== Memory access at offset 32 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions are supported)
SUMMARY: AddressSanitizer: stack-use-after-return (/home/excellarate/All Tasks/Wasm/a.out+0xf4962)
Shadow bytes around the buggy address:
0x7f49b58ffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f49b58ffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f49b58ffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f49b58fff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f49b58fff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7f49b5900000: f1 f1 f1 f1 00 f3 f3 f3 f5 f5 f5 f5[f5]f5 f5 f5
0x7f49b5900080: f1 f1 f1 f1 00 f3 f3 f3 00 00 00 00 00 00 00 00
0x7f49b5900100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f49b5900180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f49b5900200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f49b5900280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
错误原因解释
核心问题:栈对象生命周期过期后的非法引用
在E::fun4()函数中,Bobj是栈上分配的局部对象,它的生命周期仅限于fun4()函数执行期间:
- 第一次调用
fun4()时,D::Initialize()中initialized初始为false,会调用cobj.Reset(&Bobj),让C::pobj指向这个栈上的Bobj。随后调用cobj.fun2(),通过指针调用B::fun1(),此时Bobj仍有效,输出正常。 fun4()执行完毕后,Bobj被销毁,对应的栈内存被系统回收。- 第二次调用
fun4()时,D::Initialize()中initialized已经是true,不会重置pobj,此时pobj仍然指向已经销毁的Bobj内存地址。调用cobj.fun2()时,通过这个无效指针调用虚函数fun1(),属于**栈内存使用后返回(stack-use-after-return)**的未定义行为。
虚函数调用的特殊性
虚函数调用依赖对象内部的虚函数表指针(vptr),该指针指向类的虚函数表(vtable)。当栈对象销毁后,其内存空间可能被后续操作覆盖,虚函数表指针会失效。此时通过无效指针访问虚函数表,就会触发内存访问错误,这也是AddressSanitizer能检测到该问题的原因。
正常编译未报错的原因
未定义行为的结果是不确定的:第一次调用后,栈空间可能还没被其他数据覆盖,虚函数表指针暂时有效,所以能正常输出。但这种情况是偶然的,程序随时可能崩溃或出现异常结果。
相关技术参考
- C++标准明确规定:引用或指向已销毁对象的指针进行操作属于未定义行为,程序的行为不可预测。
- AddressSanitizer的
stack-use-after-return检测机制,专门用于捕获栈对象生命周期结束后,对其内存的非法访问行为,帮助开发者提前发现这类内存安全问题。
内容的提问来源于stack exchange,提问作者Kiran Pawar

