【问题标题】:Symbolicating Stack Trace without Crash符号化堆栈跟踪而不会崩溃
【发布时间】:2012-07-29 16:09:58
【问题描述】:

有什么方法可以表示不是完整崩溃报告的堆栈跟踪?

我正在将 [NSThread callStackSymbols] 的字符串结果记录到我们的服务器。这并没有给出完整格式的崩溃报告,而只是未符号化的堆栈跟踪(下面的示例)。

我试图象征性地表达这一点。我还尝试从同一构建中替换实际崩溃报告的线程 0 堆栈跟踪。都没有奏效。我确实在应用程序存档中有构建的 dSYM。有什么方法可以做到这一点而不在分发版本中留下符号?

0   domino free                         0x00072891 domino free + 465041
1   domino free                         0x000ea205 domino free + 954885
2   domino free                         0x000ea033 domino free + 954419
3   domino free                         0x0007fe55 domino free + 519765
4   domino free                         0x0006f6d5 domino free + 452309
5   domino free                         0x0006f7a3 domino free + 452515
6   domino free                         0x0006fb9b domino free + 453531
7   Foundation                          0x30558c29 __65-[NSURLConnectionInternal _withConnectionAndDelegate:onlyActive:]_block_invoke_0 + 16
8   Foundation                          0x304b06d9 -[NSURLConnectionInternalConnection invokeForDelegate:] + 28
9   Foundation                          0x304b06a3 -[NSURLConnectionInternal _withConnectionAndDelegate:onlyActive:] + 198
10  Foundation                          0x304b05c5 -[NSURLConnectionInternal _withActiveConnectionAndDelegate:] + 60
11  CFNetwork                           0x31f297f5 _ZN19URLConnectionClient23_clientDidFinishLoadingEPNS_26ClientConnectionEventQueueE + 192
12  CFNetwork                           0x31f1e4a5 _ZN19URLConnectionClient26ClientConnectionEventQueue33processAllEventsAndConsumePayloadEP20XConnectionEventInfoI12XClientEvent18XClientEventParamsEl + 424
13  CFNetwork                           0x31f1e599 _ZN19URLConnectionClient26ClientConnectionEventQueue33processAllEventsAndConsumePayloadEP20XConnectionEventInfoI12XClientEvent18XClientEventParamsEl + 668
14  CFNetwork                           0x31f1e1a3 _ZN19URLConnectionClient13processEventsEv + 106
15  CFNetwork                           0x31f1e0d9 _ZN17MultiplexerSource7performEv + 156
16  CoreFoundation                      0x30abead3 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 14
17  CoreFoundation                      0x30abe29f __CFRunLoopDoSources0 + 214
18  CoreFoundation                      0x30abd045 __CFRunLoopRun + 652
19  CoreFoundation                      0x30a404a5 CFRunLoopRunSpecific + 300
20  CoreFoundation                      0x30a4036d CFRunLoopRunInMode + 104
21  GraphicsServices                    0x30e7f439 GSEventRunModal + 136
22  UIKit                               0x3123acd5 UIApplicationMain + 1080
23  domino free                         0x0004fd3b domino free + 322875
24  domino free                         0x00004004 domino free + 12292

【问题讨论】:

标签: ios xcode stack-trace symbolicate


【解决方案1】:

您可以在运行时获取相关构建的二进制图像信息,然后使用该信息使用atos 命令来表示堆栈跟踪的帧。

使用下面的代码,输出如下所示:

YourApp 0x00000001adb1e000 - arm64e - E9B05479-3D07-390C-BD36-73EEDB2B1F75
CoreGraphics 0x00000001a92dd000 - arm64e - 2F7F6EE8-635C-332A-BAC3-EFDA4894C7E2
CoreImage 0x00000001afc00000 - arm64e - CF56BCB1-9EE3-392D-8922-C8894C9F94C7

代码:

import Foundation
import MachO

public struct BinaryImagesInspector {

    #if arch(x86_64) || arch(arm64)
    typealias MachHeader = mach_header_64
    #else
    typealias MachHeader = mach_header
    #endif

    /// Provides binary infos that are then used with the atos command to symbolicate stack traces
    /// - Parameter imageNamesToLog: an optional array of binary image names to restrict the infos to
    /// - Returns: An array of strings containing info on loaded binary name, its load address, architecture
    /// - Note: Example:
    ///
    /// atos -arch arm64 -o [YOUR-DSYM-ID].dSYM/Contents/Resources/DWARF/[YOUR APP] -l 0x0000000000000000 0x0000000000000000
    public static func getBinaryImagesInfo(imageNamesToLog: [String]? = nil) -> [String] {
        let count = _dyld_image_count()

        var stringsToLog = [String]()

        for i in 0..<count {

            guard let dyld = _dyld_get_image_name(i) else { continue }

            let dyldStr = String(cString: dyld)
            let subStrings = dyldStr.split(separator: "/")
            guard let imageName = subStrings.last else { continue }

            if let imageNamesToLog = imageNamesToLog {
                guard imageNamesToLog.contains(String(imageName)) else { continue }
            }

            guard let uncastHeader = _dyld_get_image_header(i) else { continue }
            let machHeader = uncastHeader.withMemoryRebound(to: MachHeader.self, capacity: MemoryLayout<MachHeader>.size) { $0 }
            guard let info = NXGetArchInfoFromCpuType(machHeader.pointee.cputype, machHeader.pointee.cpusubtype) else { continue }
            guard let archName = info.pointee.name else { continue }
            let uuid = getBinaryImageUUID(machHeader: machHeader)
            let logStr = "\(imageName) \(machHeader.debugDescription) - \(String(cString: archName)) - \(uuid ?? "uuid not found")"
            stringsToLog.append(logStr)
        }

        return stringsToLog
    }

    private static func getBinaryImageUUID(machHeader: UnsafePointer<MachHeader>) -> String? {

        guard var header_ptr = UnsafePointer<UInt8>.init(bitPattern: UInt(bitPattern: machHeader)) else {
            return nil
        }

        header_ptr += MemoryLayout<MachHeader>.size

        guard var command = UnsafePointer<load_command>.init(bitPattern: UInt(bitPattern: header_ptr)) else {
            return nil
        }

        for _ in 0..<machHeader.pointee.ncmds {

            if command.pointee.cmd == LC_UUID {
                guard let ucmd_ptr = UnsafePointer<uuid_command>.init(bitPattern: UInt(bitPattern: header_ptr)) else { continue }
                let ucmd = ucmd_ptr.pointee

                let cuuidBytes = CFUUIDBytes(byte0: ucmd.uuid.0,
                                             byte1: ucmd.uuid.1,
                                             byte2: ucmd.uuid.2,
                                             byte3: ucmd.uuid.3,
                                             byte4: ucmd.uuid.4,
                                             byte5: ucmd.uuid.5,
                                             byte6: ucmd.uuid.6,
                                             byte7: ucmd.uuid.7,
                                             byte8: ucmd.uuid.8,
                                             byte9: ucmd.uuid.9,
                                             byte10: ucmd.uuid.10,
                                             byte11: ucmd.uuid.11,
                                             byte12: ucmd.uuid.12,
                                             byte13: ucmd.uuid.13,
                                             byte14: ucmd.uuid.14,
                                             byte15: ucmd.uuid.15)
                guard let cuuid = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, cuuidBytes) else {
                    return nil
                }
                let suuid = CFUUIDCreateString(kCFAllocatorDefault, cuuid)
                let encoding = CFStringGetFastestEncoding(suuid)
                guard let cstr = CFStringGetCStringPtr(suuid, encoding) else {
                    return nil
                }
                let str = String(cString: cstr)

                return str
            }

            header_ptr += Int(command.pointee.cmdsize)
            guard let newCommand = UnsafePointer<load_command>.init(bitPattern: UInt(bitPattern: header_ptr)) else { continue }
            command = newCommand
        }

        return nil
    }
}

进一步阅读:

也可以在GitHub 上以 swift 包的形式提供。

【讨论】:

    【解决方案2】:

    我知道这是一个相当老的问题,但我现在遇到了同样的问题,并且花了很长时间才找到答案,所以我认为我应该(在某个地方)记录它。

    如果您拥有堆栈跟踪来源的应用版本的 dSYM,那么您实际上可以将其转化为有用的东西。阅读this answer here 导致this article 这对我帮助很大。我的堆栈跟踪顶部有这一行:

    0    MyApp                           0x000000010010da68 MyApp + 236136
                                         ^ stack address            ^ symbol offset
    

    您有两个选择,都涉及到一些数学问题。如果您选择atos,您只需计算一次,然后一次调用即可查找所有步骤。

    使用atos

    要使用atos,您需要堆栈跟踪中的堆栈地址,并且需要通过一些数学运算找出加载地址

    1. 通过从堆栈地址值中减去符号偏移量值来计算加载地址值(load address = stack address - @ 987654329@) 当然,您必须将它们转换为相同的基础才能做到这一点

      就我而言,这是0x1000D4000

    2. 使用加载地址和来自atos -arch &lt;architecture&gt; -o &lt;path to executable inside (!) the dSYM&gt; -l &lt;load address&gt; &lt;stack address 1&gt; &lt;stack address 2&gt; ...的堆栈跟踪中的堆栈地址查找带有atos的堆栈跟踪条目

      就我而言,这是atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -l 0x1000D4000 0x000000010010da68

    请记住,您必须提供 dSYM 中实际可执行文件的路径,否则您只会收到错误消息。 使用atos 完成所有这些操作的好处是,您只需列出堆栈跟踪中的所有地址,即可立即获得可读格式。

    使用dwarfdump

    要使用dwarfdump,您需要与堆栈跟踪中的堆栈地址相对应的文件地址

    1. 找出堆栈跟踪来自的架构的 slide 值(请参阅链接文章中的获取 Slide 值)。

      在我的例子中,0x100000000 表示 64 位。

    2. symbol offset 值(堆栈跟踪中 MyApp + ... 之后的数字,在我的情况下为 236136)转换为十六进制并添加结果幻灯片值。你现在得到的号码叫做文件地址file address = symbol offset + slide

      就我而言,这导致0x100039A68

    3. 使用带有dwarfdump --lookup &lt;file address&gt; --arch &lt;architecture&gt; &lt;path to dSYM&gt;文件地址查找带有dwarfdump的堆栈跟踪条目

      就我而言,这是dwarfdump --lookup 0x100039A68 --arch arm64 MyApp.dSYM

    【讨论】:

    • 以下是 2019 格式的 .ips 文件的示例:4 MyApp 0x0000000102dd0068 0x102db8000 + 98408,其中0x102db8000 是加载地址,0x0000000102dd0068 是准备使用的堆栈地址。特别感谢“dSYM 内部”的评论,这并不明显。
    • atos 一个正在给出另一个十六进制值
    【解决方案3】:

    我认为这是不可能的。 [NSThread callStackSymbols] 返回函数的内存地址。如果没有在崩溃后立即转储内存,就无法对其进行符号化。 崩溃时,每个设备的地址都不同。即使在一台设备上,如果您重新启动手机,地址也会在另一次崩溃后更改。 几个人提到了atos,但它是用于崩溃日志,而不是用于callStackSymbols。

    【讨论】:

    • 这不是真的,如果您有 dSYM,您实际上可以从堆栈跟踪中检索有用的信息。只需要谷歌很多才能找到正确的答案;)
    【解决方案4】:

    我遇到了同样的问题,这个答案对我有用:https://stackoverflow.com/a/4954949/299262

    只要您有 dSYM,您就可以使用 atos 来表示各个地址。

    示例命令:

    atos -arch armv7 -o 'app name.app'/'app name' 0x000000000

    【讨论】:

    • 对于上面给出的例子,你如何找到你的进程的地址(将0x000000000替换为domino free的内存地址)?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多