【发布时间】:2020-09-29 19:33:44
【问题描述】:
在分析 Swift assertionFailure() 如何在后台工作时,我注意到实际的致命错误是通过 _swift_runtime_on_report 函数报告的。它的实现在这里定义:https://github.com/apple/swift/blob/master/stdlib/public/runtime/Errors.cpp as
SWIFT_NOINLINE SWIFT_RUNTIME_EXPORT void
_swift_runtime_on_report(uintptr_t flags, const char *message,
RuntimeErrorDetails *details) {
// Do nothing. This function is meant to be used by the debugger.
// The following is necessary to avoid calls from being optimized out.
asm volatile("" // Do nothing.
: // Output list, empty.
: "r" (flags), "r" (message), "r" (details) // Input list.
: // Clobber list, empty.
);
}
显然,这只是对一个实际上什么都不做的函数的花哨的写法。它只是坐在那里等待lldb 受到特殊待遇。我的意思是:
libswiftCore.dylib`_swift_runtime_on_report:
-> 0x7fff64d1cd50 <+0>: push rbp
0x7fff64d1cd51 <+1>: mov rbp, rsp
0x7fff64d1cd54 <+4>: pop rbp
0x7fff64d1cd55 <+5>: ret
0x7fff64d1cd56 <+6>: nop word ptr cs:[rax + rax]
x86-64 在这里并不那么重要。 Xcode 中的第一行(带有箭头->)致命错误(无论它究竟是什么意思)的事实是。有趣的是,即使事先在内存地址0x7fff64d1cd50 处设置断点也不会触发它,无论如何它都会在没有命中断点的情况下出现致命错误。
当我修改程序计数器 (rip) 时,我可以跳到 0x7fff64d1cd51 并在 0x7fff64d1cd54 处捕获断点,而不会触发致命错误。因此,lldb 捕获到 0x7fff64d1cd50 的程序内存读取似乎是合理的,这最终以一种优雅的方式出现致命错误。
现在是谜语中最令人困惑的部分。在我的简约 XCode 项目中,我有 main.swift 包括
assertionFailure()
但是如果我故意添加一个像这样定义的 C 函数(这里的主体无关紧要):
#include <stdint.h>
_swift_runtime_on_report(uintptr_t flags, const char *message,
void *details) {
int i = 0;
i++;
return i;
}
它将混淆lldb 足以解除对香草_swift_runtime_on_report 函数的内存限制(此时我现在可以在0x7fff64d1cd50 处捕获断点)。最终它省略了“优雅”的致命错误步骤,它将在 ud2 上失败(即 x86 故意错误指令)
有趣的是,从 Swift 调用我的本地“冒名顶替者”函数也是完全合法的,一切都像一个完全正常的 C 函数一样工作。
所以我的问题是 lldb 如何在 _swift_runtime_on_report / 0x7fff64d1cd50 中完全失败?以及为什么我能够用这个甚至没有被调用的本地 C 函数来打破这种机制。显然是通过符号/定义冲突,但到底发生了什么?
【问题讨论】: