17 votes

Symbolisation de la trace de la pile sans crash

Existe-t-il un moyen de symboliser une trace de pile qui n'est pas un rapport de crash complet ?

J'enregistre le résultat de la chaîne de caractères de [NSThread callStackSymbols] sur notre serveur. Cela ne donne pas un rapport de crash complètement formaté, mais juste la trace de pile non symbolisée (exemple ci-dessous).

C'est ce que j'ai essayé de symboliser. J'ai également essayé de remplacer la trace de pile du thread 0 par un rapport de crash réel provenant de la même construction. Aucun des deux n'a fonctionné. J'ai le dSYM de la version dans l'archive de l'application. Existe-t-il un moyen de faire cela sans laisser de symboles dans la distribution ?

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

6voto

Peter Tutervai Points 773

Je sais que cette question est assez ancienne, mais j'ai eu le même problème et il m'a fallu un certain temps pour trouver la réponse, alors j'ai pensé que je devrais plutôt la documenter (quelque part).

Si vous avez le dSYM de la version de l'application d'où provient la trace de pile, vous pouvez alors en faire quelque chose d'utile. Lecture de l'article cette réponse ici conduisent à cet article qui m'a beaucoup aidé. J'avais cette ligne en haut de ma trace de pile :

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

Deux options s'offrent à vous, toutes deux impliquant des calculs. Si vous optez pour atos Il suffit de faire le calcul une fois pour pouvoir consulter toutes les étapes en un seul appel.

Utilisation atos

Pour utiliser atos vous avez besoin de la adresse de la pile à partir de la trace de la pile et vous devez trouver la adresse de chargement par le biais d'un calcul :

  1. Calculer le adresse de chargement en soustrayant la valeur décalage du symbole de la valeur adresse de la pile valeur ( load address = stack address - symbol offset ) Bien sûr, il faut les convertir dans la même base pour ce faire.

    Dans mon cas, il s'agissait de 0x1000D4000

  2. Recherchez les entrées de votre trace de pile avec atos en utilisant le adresse de chargement et le adresses de la pile à partir de la trace de la pile avec atos -arch <architecture> -o <path to executable inside (!) the dSYM> -l <load address> <stack address 1> <stack address 2> ...

    Dans mon cas, il s'agissait de atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -l 0x1000D4000 0x000000010010da68

N'oubliez pas que vous devez fournir le chemin d'accès à l'exécutable à l'intérieur du dSYM, sinon vous obtiendrez un message d'erreur. L'avantage de faire tout cela avec atos est que vous pouvez simplement lister toutes les adresses de votre trace de pile et vous obtiendrez immédiatement un format lisible.

Utilisation dwarfdump

Pour utiliser dwarfdump vous avez besoin de la adresse du fichier correspondant à la adresse de la pile dans la trace de la pile.

  1. Découvrez les diapositive pour l'architecture d'où provient la trace de pile (voir Obtenir la valeur de la diapositive dans l'article en lien).

    Dans mon cas, il s'agissait de 0x100000000 pour 64 bits.

  2. Convertir les décalage du symbole (le nombre juste après MyApp + ... dans la trace de la pile, 236136 dans mon cas) en hexadécimal et ajouter le résultat à l'élément diapositive valeur. Le nombre que vous obtenez maintenant est appelé adresse du fichier ( file address = symbol offset + slide )

    Dans mon cas, cela s'est traduit par 0x100039A68 .

  3. Recherchez les entrées de votre trace de pile avec dwarfdump en utilisant le adresse du fichier con dwarfdump --lookup <file address> --arch <architecture> <path to dSYM>

    Dans mon cas, il s'agissait de dwarfdump --lookup 0x100039A68 --arch arm64 MyApp.dSYM

3voto

einsteinx2 Points 6624

J'ai rencontré le même problème et cette réponse a fonctionné pour moi : https://stackoverflow.com/a/4954949/299262

Vous pouvez utiliser atos pour symboliser des adresses individuelles tant que vous avez le dSYM.

exemple de commande :

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

1voto

sts2055 Points 371

Vous pourriez obtenir des informations sur l'image binaire au moment de l'exécution pour la version en question, puis utiliser ces informations pour symboliser les cadres de votre trace de pile en utilisant la fonction atos commande.

En utilisant le code ci-dessous, la sortie ressemble à ceci par exemple :

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

Código:

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

Pour en savoir plus :

Également disponible en paquet rapide sur GitHub .

0voto

dqshll Points 31

Je ne pense pas que cela soit possible. [NSThread callStackSymbols] renvoie l'adresse mémoire des fonctions. Il ne peut pas être symbolisé sans vider la mémoire juste après le plantage. Lors d'un plantage, les adresses sont différentes pour chaque appareil. Même sur un appareil, si vous redémarrez le téléphone, les adresses changent après un nouveau plantage. Plusieurs personnes ont mentionné atos mais c'est pour le journal des crashs, pas pour callStackSymbols.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X