用 qbdi trace 跑一段目标函数时,如果目标函数里会间接调用到某个会抛 C++ 异常的函数(内部用 __cxa_throw),进程经常会直接 abort
原因:
QBDI 把执行交给“原生”跑被调函数时,会在栈上改掉返回地址,让函数返回时回到 QBDI 的 trampoline。C++ 异常在找 catch 时会按栈上的返回地址做 unwind;这时看到的是 QBDI 的地址,没有对应的异常处理信息,就被当成未处理异常 → 直接 abort。所以:会 throw 的那段代码不能在被 QBDI 插桩/控制的这条“假栈链”里,要么别插桩它,要么别让它出现在你 trace 的这条调用链的“插桩范围”里。
解决思路:会调用 __cxa_throw 的函数、会接住这次异常的函数、以及它们之间的所有调用,都不要被 QBDI 插桩。这样异常在 “纯原生” 的栈上抛和接(实际也是使用qbdi的虚拟栈),就不会和 QBDI 的返回地址打架。可以用两种方式实现:
方式1
只插桩你要 trace 的那一块(addInstrumentedRange)
// 只插桩目标函数,例如 [funcPtr, funcPtr + 0x10B80)
// 确保“会抛异常的函数”的地址不在这段里
vm->addInstrumentedRange(funcPtr, funcPtr + 0x10B80);
Frida 里把目标函数入口和长度传给 C++ 即可;长度要覆盖目标函数内所有会走到的指令(含异常相关代码),但不要把会 throw 的函数的起始地址包含进来。
方式2
先插桩整模块,再挖掉会抛异常的那几段(removeInstrumentedRange)
vm->addInstrumentedModuleFromAddr(funcPtr);
// 挖掉会 __cxa_throw 的函数的上几级
QBDI::rword base = ...; // 当前 so 基址
vm->removeInstrumentedRange(base + 0x74C500, base + 0x74C53C);
这样,执行一进入被挖掉的那几段就会交给原生跑,异常在 “原生栈”(实际也是使用qbdi的虚拟栈)上完成 throw/catch,不会触发 QBDI 导致的 abort