【问题标题】:How does lldb handle special purpose _swift_runtime_on_report function?lldb 如何处理特殊用途的 _swift_runtime_on_report 函数?
【发布时间】: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 函数来打破这种机制。显然是通过符号/定义冲突,但到底发生了什么?

【问题讨论】:

    标签: swift lldb


    【解决方案1】:

    我不确定我是否明白你在问什么。

    _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 顶层可能发生的任何抛出,否则它们会导致 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_runtime_on_report 中的“常规”bp 没有受到打击,但我仍然可以设置它。更有趣的是,当在我的程序中定义上述“冒名顶替”C 函数时,lldb“特殊”bp 不再出现在break list -i 中。我假设 lldb 找到了 _swift_runtime_on_report 符号,但由于它不在 libswiftCore.dylib 中,它会忽略它。我的理解正确吗?
    • lldb 是否报告运行时错误停止原因或同一函数处的断点似乎有点不一致。看起来我们报告了用户断点,如果它有除 libSwiftCore.dylib 中的位置之外的任何位置,但如果只有一个位置,我们会报告错误停止。尽管这是一个非常角落的角落案例,但这似乎有点奇怪。但我永远无法让 lldb 不设置内部的。
    • 但是我什至不需要“冒名顶替”C函数中的任何bp,它在程序中的存在足以影响break list -i的输出
    • 这似乎是一个错误,但遗憾的是我无法重现。如果您有一个小项目和一些显示此行为的步骤,请将其提交给 Swift 错误报告者 (bugs.swift.com)
    猜你喜欢
    • 2013-02-09
    • 2012-07-08
    • 1970-01-01
    • 1970-01-01
    • 2021-11-08
    • 2014-12-13
    • 2012-04-05
    • 2013-08-22
    • 1970-01-01
    相关资源
    最近更新 更多