Symbolicating Stack Trace without Crash
Asked Answered
Q

4

17

Is there any way to symbolicate a stack trace that is not a full crash report?

I am logging the string result of [NSThread callStackSymbols] to our server. This doesn't give a fully formatted crash report, but just the unsymbolicated stack trace (example below).

I have tried to symbolicate just this. I have also tried replacing the thread 0 stack trace of an actual crash report from the same build. Neither worked. I do have the dSYM of the build in the app archive. Is there any way to do this without leaving symbols in the distribution build?

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
Quotidian answered 31/7, 2012 at 19:44 Comment(2)
possible duplicate of Symbolicating iPhone App Crash ReportsBaram
If you don't have the full crash report, you need to calculate the right address for atos. See NSProgrammer's answer to a similar topic: https://mcmap.net/q/746395/-atos-and-dwarfdump-won-39-t-symbolicate-my-addressPucida
S
7

I know this is a rather old question, but I had the same issue now and it took quite some time to find the answer, so I thought I should rather document it (somewhere).

If you have the dSYM for the app version where the stack trace comes from then you can actually turn that into something useful. Reading this answer here lead to this article which helped me a lot. I had this line on top of my stack trace:

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

You have two options from here, both involves some math. If you go with atos you just have to do the math once though and you can look up all steps with one call.

Using atos

To use atos you need the stack address from the stack trace and you need to find out the load address through some math:

  1. Calculate the load address value by subtracting the symbol offset value from the stack address value (load address = stack address - symbol offset) of course you have to convert them to the same base to do that

    In my case this was 0x1000D4000

  2. Look up your stack trace entries with atos using the load address and the stack addresses from the stack trace with atos -arch <architecture> -o <path to executable inside (!) the dSYM> -l <load address> <stack address 1> <stack address 2> ...

    In my case this was atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -l 0x1000D4000 0x000000010010da68

Please keep in mind that you have to supply the path to the actual executable inside the dSYM, otherwise you'll only get an error message. The nice thing about doing all this with atos is that you can just list all the addresses from your stack trace and you'll get a readable format at once.

Using dwarfdump

To use dwarfdump you need the file address corresponding to the stack address in the stack trace.

  1. Find out the slide value for the architecture where the stack trace comes from (see Getting the Slide Value in the linked article).

    In my case this was 0x100000000 for 64-bit.

  2. Convert the symbol offset value (the number right after MyApp + ... in the stack trace, 236136 in my case) to hex and add the result to the slide value. The number you get now is called the file address (file address = symbol offset + slide)

    In my case this resulted in 0x100039A68.

  3. Look up your stack trace entries with dwarfdump using the file address with dwarfdump --lookup <file address> --arch <architecture> <path to dSYM>

    In my case this was dwarfdump --lookup 0x100039A68 --arch arm64 MyApp.dSYM

Seagoing answered 20/9, 2016 at 12:41 Comment(3)
Here's an example from a .ips file in 2019 format: 4 MyApp 0x0000000102dd0068 0x102db8000 + 98408, where 0x102db8000 is the load address and 0x0000000102dd0068 is the ready to use stack address. Special thanks for "inside the dSYM" comment, it wasn't obvious.Fumikofumitory
atos one is giving another hex valueNummary
atos one is giving another hex value tooBiotechnology
C
3

I ran into the same issue and this answer worked for me: https://mcmap.net/q/80371/-symbolicating-iphone-app-crash-reports

You can use atos to symbolicate individual addresses as long as you have the dSYM.

example command:

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

Convalesce answered 28/9, 2012 at 18:11 Comment(1)
For the example given above, how do you find the address of your process (replacing 0x000000000 with the address in memory of domino free)?Manheim
S
1

You could get binary image information at runtime for the build in question, then use that information to symbolicate frames of your stack trace using the atos command.

Using below code, the output looks like this for example:

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

Code:

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
    }
}

Further reading:

Also available as swift package on GitHub.

Soni answered 17/8, 2021 at 19:18 Comment(0)
M
0

I don't think this is possible. [NSThread callStackSymbols] return the memory address of the functions. It can't be symbolicated without dump the memory right after crashing. When crashing, the addresses are different for each device. Even on one device, if you reboot the phone, addresses changed after another crash. Several guys mentioned atos but it's for crash log, not for callStackSymbols.

Md answered 8/12, 2015 at 7:52 Comment(1)
That's not true, if you have the dSYM you can actually retrieve useful information from the stack trace. Just need to Google a lot to find out the right answers ;)Seagoing

© 2022 - 2024 — McMap. All rights reserved.