无崩溃日志的符号化堆栈跟踪

17

有没有办法对不完整的崩溃报告进行符号化堆栈跟踪?

我将[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

可能是Symbolicating iPhone App Crash Reports的重复问题。 - Ky -
如果您没有完整的崩溃报告,您需要计算正确的地址以使用atos。请参考NSProgrammer在类似主题上的回答:https://dev59.com/LWkv5IYBdhLWcg3w8FSY#12464678 - Kasia K.
4个回答

7

我知道这是一个相当老的问题,但是我现在也遇到了同样的问题,并花费了相当长的时间来找到答案,因此我认为我应该把它记录下来(在某个地方)。

如果您拥有堆栈跟踪所在应用程序版本的dSYM文件,则可以将其转换为实用的东西。读取此答案得出这篇文章对我非常有帮助。 我的堆栈跟踪顶部有这行:

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

从这里开始,你有两个选项,都需要用到一些数学。如果你选择使用atos,你只需进行一次数学计算,就可以通过一个调用查找所有步骤。

使用atos

要使用atos,你需要获取堆栈跟踪的堆栈地址并通过一些数学计算找出加载地址

  1. 通过减去符号偏移量值得出加载地址的值(load address = stack address - symbol offset),当然你需要将它们转换成相同的进制才能进行计算。

    在我的情况下,这个值是0x1000D4000

  2. 使用atos -arch <architecture> -o <path to executable inside (!) the dSYM> -l <load address> <stack address 1> <stack address 2> ...命令,通过加载地址和来自堆栈跟踪的堆栈地址查找你的堆栈跟踪条目。

    在我的情况下,这个命令是atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -l 0x1000D4000 0x000000010010da68

请注意,你必须提供dSYM内实际可执行文件的路径,否则你将只会收到一个错误消息。使用atos的好处是,你可以列出堆栈跟踪中的所有地址,并一次性得到可读格式。

使用dwarfdump

要使用dwarfdump,你需要获取对应于堆栈跟踪中的堆栈地址文件地址

  1. 查找堆栈跟踪所在的体系结构的幻灯片(slide)值(请参见链接文章中的获取幻灯片值)。

    在我的情况下,64位的值为0x100000000

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

    在我的情况下,这导致0x100039A68

  3. 使用dwarfdump --lookup <file address> --arch <architecture> <path to dSYM>使用文件地址查找您的堆栈跟踪条目。

    在我的情况下,这是dwarfdump --lookup 0x100039A68 --arch arm64 MyApp.dSYM


以下是一个来自2019格式的.ips文件的示例:4 MyApp 0x0000000102dd0068 0x102db8000 + 98408,其中0x102db8000是加载地址,0x0000000102dd0068是可用堆栈地址。特别感谢“inside the dSYM”评论,这并不明显。 - Victor Sergienko
1
atos one 正在给出另一个十六进制值。 - Geetanshu Gulati
atos one 给出了另一个十六进制值。 - Izabella Melo

3
我遇到了同样的问题,这个答案对我很有帮助:https://dev59.com/43M_5IYBdhLWcg3wQQld#4954949 只要有dSYM,您就可以使用atos来符号化单个地址。
示例命令: atos -arch armv7 -o '应用名称.app'/'应用名称' 0x000000000

对于上面给出的示例,您如何找到进程的地址(将“0x000000000”替换为内存中“domino free”的地址)? - Dov

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包使用。


0

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


这不是真的,如果你有dSYM文件,实际上可以从堆栈跟踪中检索到有用的信息。只需要大量搜索才能找到正确的答案 ;) - Peter Tutervai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接