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 从不显示它插入的断点,它总是显示它覆盖的指令。如果您没有看到插入的断点,那是设计好的。

无论如何,我不知道这其中的哪一部分不适合你?断点没有设置?还是不会被击中?或者您还有其他我想念的问题吗?