lldb 如何处理特殊用途的 _swift_runtime_on_report 函数?
How does lldb handle special purpose _swift_runtime_on_report function?
在分析 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
捕获断点而不会触发致命错误。因此,0x7fff64d1cd50
处的读取程序内存被 lldb
捕获似乎是合理的,最终以优雅的方式出现致命错误。
现在是谜语中最令人困惑的部分。在我的简约 XCode 项目中,我有 main.swift
由
组成
assertionFailure()
但是如果我有意添加一个像这样定义的 C 函数(body 在这里无关紧要):
#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 函数来破坏这个机制。显然是通过符号/定义冲突,但到底发生了什么?
我不确定我是否理解你的问题。
_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], locations = 1, resolved = 1, hit count = 1
-2.1: where = libswiftCore.dylib`_swift_runtime_on_report, address = 0x00007fff689d5380, resolved, hit count = 1
Swift 承诺在遇到致命错误时调用此函数,然后再终止您的程序。这允许 lldb - 在正常调试中 - 捕获错误并将其报告给您,而不是让程序从您下面退出。它还允许 swift REPL 捕获并清除可能在 REPL 的 top-level 处发生的任何抛出,否则它们会导致 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 从不显示它插入的断点,它总是显示它覆盖的指令。如果您没有看到插入的断点,那是设计好的。
无论如何,我不知道这其中的哪一部分不适合你?断点没有设置?还是不会被击中?或者您还有其他我想念的问题吗?
在分析 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
捕获断点而不会触发致命错误。因此,0x7fff64d1cd50
处的读取程序内存被 lldb
捕获似乎是合理的,最终以优雅的方式出现致命错误。
现在是谜语中最令人困惑的部分。在我的简约 XCode 项目中,我有 main.swift
由
assertionFailure()
但是如果我有意添加一个像这样定义的 C 函数(body 在这里无关紧要):
#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 函数来破坏这个机制。显然是通过符号/定义冲突,但到底发生了什么?
我不确定我是否理解你的问题。
_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], locations = 1, resolved = 1, hit count = 1
-2.1: where = libswiftCore.dylib`_swift_runtime_on_report, address = 0x00007fff689d5380, resolved, hit count = 1
Swift 承诺在遇到致命错误时调用此函数,然后再终止您的程序。这允许 lldb - 在正常调试中 - 捕获错误并将其报告给您,而不是让程序从您下面退出。它还允许 swift REPL 捕获并清除可能在 REPL 的 top-level 处发生的任何抛出,否则它们会导致 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 从不显示它插入的断点,它总是显示它覆盖的指令。如果您没有看到插入的断点,那是设计好的。
无论如何,我不知道这其中的哪一部分不适合你?断点没有设置?还是不会被击中?或者您还有其他我想念的问题吗?