问题描述
在分析Swift assertionFailure()
的工作原理时,我注意到实际的致命错误是通过_swift_runtime_on_report
函数报告的。其实现在这里定义:https://github.com/apple/swift/blob/master/stdlib/public/runtime/Errors.cpp为
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
处捕获断点而不会触发致命错误。因此,似乎0x7fff64d1cd50
捕获了lldb
处的合理的程序存储空间,最终以一种优美的方式致命了错误。
现在是谜语中最令人困惑的部分。在我的简约XCode项目中,我有main.swift
组成
assertionFailure()
#include <stdint.h>
_swift_runtime_on_report(uintptr_t flags,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函数来打破这种机制。显然是通过符号/定义冲突引起的,但实际上是什么情况?
解决方法
我不确定我是否理解你的要求。
_swift_runtime_on_report是一个存在的函数,以便lldb可以在其上设置断点,并检索有关该错误的一些信息以向用户显示。您可以通过发出以下命令在调试swift程序时看到该断点:
(lldb) break list -i
Current breakpoints:
Kind: shared-library-event
-1: address = dyld[0x00000000000121ad],locations = 1,resolved = 1,hit count = 1
-1.1: where = dyld`_dyld_debugger_notification,address = 0x00000001000221ad,resolved,hit count = 1
Kind: swift-language-runtime-report
-2: address = libswiftCore.dylib[0x00007fff66b14380],hit count = 1
-2.1: where = libswiftCore.dylib`_swift_runtime_on_report,address = 0x00007fff689d5380,hit count = 1
Swift承诺在遇到致命错误时会立即调用此函数,然后将其关闭程序。这使lldb(在正常调试中)能够捕获错误并将其报告给您,而不是使程序从您的下方退出。它还允许快速的REPL捕获并清除REPL顶层可能发生的任何抛出,因为否则它们会导致REPL退出。
当我使用“ assertionFailure()”这一行来构建和调试swift文件时,调试器将停止:
Fatal error: file errors/errors.swift,line 2
2020-09-29 16:52:40.373185-0700 errors[17428:2120547] Fatal error: file errors/errors.swift,line 2
Process 17428 stopped
* thread #1,queue = 'com.apple.main-thread',stop reason = Fatal error
frame #0: 0x00007fff689d5380 libswiftCore.dylib`_swift_runtime_on_report
libswiftCore.dylib`_swift_runtime_on_report:
-> 0x7fff689d5380 <+0>: pushq %rbp
0x7fff689d5381 <+1>: movq %rsp,%rbp
0x7fff689d5384 <+4>: popq %rbp
0x7fff689d5385 <+5>: retq
Target 0: (errors) stopped.
这里唯一棘手的事情是,如果在此函数上也设置一个断点,则lldb只会报告停止原因中的错误,因为这是优先级更高的停止原因。可能使您感到困惑的另一件事是,lldb永远不会显示它已插入的断点,它总是显示它重写的指令。如果您没有看到插入的断点,那是按设计的。
无论如何,我无法分辨出哪一部分不适合您?断点没有设置好吗?还是没有受到打击?还是您还有另一个我想念的问题?