1. Hébergement cloud
  2. Blog
  3. Pipeline de symbolisation de crash et d'archivage dSYM

Pipeline de symbolisation de crash et d'archivage dSYM

DevOps et CI/CD ·~6 min de lecture

Pipeline de symbolisation de crash et d'archivage dSYM

L'équipe de test nous a envoyé la semaine dernière un log de crash TestFlight — la stack trace n'affichait que des adresses hexadécimales du type 0x1042a3f88, et le rapport concernait un build datant de trois versions en arrière. En fouillant dans ~/Library/Developer/Xcode/Archives sur le Mac local, cette archive avait déjà été nettoyée automatiquement par Xcode. Ce genre de situation revient presque chaque trimestre dans l'équipe — les postes de développement locaux manquent d'espace disque, les runners CI sont détruits juste après le build, et les fichiers dSYM, générés une seule fois au moment du build et introuvables ensuite, exigent par nature un nœud d'archivage dédié. Un Mac mini cloud, loué à la semaine ou au mois, avec un disque persistant jamais purgé, vient combler exactement ce vide.

Pourquoi la symbolisation mérite son propre pipeline

Les adresses d'un log de crash ne peuvent être retraduites en noms de fonctions et numéros de ligne qu'à l'aide du dSYM issu exactement du même build, et le lien entre le dSYM et le binaire est verrouillé strictement par un UUID. Utiliser par erreur le dSYM d'une autre version pour symboliser ne provoque aucune erreur — seulement des numéros de ligne faux. C'est le piège le plus facile à négliger, et le plus difficile à détecter après coup.

Les runners des plateformes CI sont généralement des instances jetables, détruites après le build, qui ne conservent aucun dSYM historique ; les postes de développement locaux travaillent chacun de leur côté, et aucune machine individuelle ne réunit jamais toutes les versions de toute l'équipe. La solution est on ne peut plus simple : disposer d'une machine dont le disque n'est jamais purgé et accessible en SSH à tout moment pour exécuter des scripts, servant exclusivement de nœud d'archivage, où chaque dSYM produit par un archivage est stocké classé par numéro de build.

Préparer l'environnement : faire atterrir le dSYM avec les artefacts de build

Commencez par vérifier que le format des informations de débogage du projet Xcode est réglé sur dwarf-with-dsym (c'est la valeur par défaut en configuration Release) — c'est seulement dans ce cas qu'un package .dSYM distinct est produit lors de l'archivage :

xcodebuild archive \
  -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -configuration Release \
  -archivePath build/MyApp-$(git rev-parse --short HEAD).xcarchive \
  DEBUG_INFORMATION_FORMAT=dwarf-with-dsym

Une fois l'archivage terminé, le dSYM se trouve en réalité caché dans le répertoire .xcarchive/dSYMs/ — ne vous contentez pas de copier le .ipa. C'est le premier piège dans lequel tombent de nombreuses équipes : oublier cette étape lors de l'export du package d'installation, ce qui, après coup, ne peut être réparé qu'en relançant un build sur le même commit.

Conception du répertoire d'archivage et vérification d'UUID

Sur le Mac mini cloud, créez des répertoires classés par numéro de build, et effectuez immédiatement après chaque upload une vérification d'UUID pour garantir que le fichier stocké est bien le bon :

dwarfdump --uuid MyApp.app.dSYM/Contents/Resources/DWARF/MyApp
dwarfdump --uuid MyApp.app/MyApp

Les UUID renvoyés par les deux commandes doivent correspondre caractère par caractère. Il est conseillé de tenir l'historique d'archivage sous forme d'un tableau simple, pour faciliter la traçabilité :

build git commit dSYM UUID(arm64) Heure d'upload
214 a91f3c2 6B2E1A4F-... 07-18 09:12
215 c0d7e91 9F1D8C33-... 07-25 14:03
216 e3a4f10 2C77B0A9-... 08-01 10:47

La structure des répertoires est fixée à /archive/dsym/{build}/, chaque niveau ne contenant qu'un seul dSYM accompagné d'un fichier meta.json consignant le commit et l'UUID. Le script de recherche localise directement l'entrée via le numéro de build, sans avoir à faire de grep manuel à chaque fois.

Script de symbolisation : du log de crash à une stack lisible

Une fois le log de crash récupéré, utilisez atos pour retraduire les adresses une par une — c'est plus rapide pour repérer une frame suspecte que de parcourir un rapport symbolicatecrash complet :

atos -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
     -arch arm64 \
     -l 0x1000 \
     0x1042a3f88 0x1042a4210

Après -l figure l'adresse de base de chargement du binaire, que l'on peut copier directement depuis la section Binary Images du log de crash ; la liste d'adresses peut être passée en lot, et la sortie fournit alors le nom de la fonction ainsi que le fichier source et le numéro de ligne. Pour traiter un log entier, un petit script shell de quelques lignes suffit pour extraire les adresses ligne par ligne, construire la commande et réinjecter le résultat — nul besoin d'un outil graphique.

Pièges courants et liste de vérification

  • Confusion d'architecture : une application tournant sur une puce M dispose d'une seule architecture, arm64, mais le paramètre -arch x86_64, encore fréquent dans les anciens scripts, fait que atos renvoie silencieusement l'adresse brute sans effectuer aucune conversion.
  • Adresse de base de chargement mal calculée : utiliser une valeur par défaut comme 0x100000000 sans la confronter à l'adresse de base réelle indiquée dans la section Binary Images du log est la deuxième cause la plus fréquente de numéros de ligne erronés.
  • Droits trop permissifs sur le nœud d'archivage : les dSYM constituent des informations de débogage au niveau du code source ; il est recommandé de ne donner qu'un accès en lecture au répertoire d'archivage au responsable des releases et au compte de service CI, afin d'éviter toute suppression ou écrasement accidentel lorsque la machine est partagée avec d'autres équipes.

Notre expérience interne : avant de disposer d'un nœud d'archivage, retrouver le dSYM correspondant à un log de crash vieux de trois mois demandait en moyenne une demi-journée à deux personnes. Avec un workflow d'archivage établi, ce type de demande se résout désormais en général en moins de cinq minutes.

Intégrer le pipeline de symbolisation dans les releases au quotidien

Ajoutez à la fin de la lane de release fastlane une étape qui synchronise immédiatement le dSYM venant d'être généré vers le nœud d'archivage, plutôt que de le chercher dans l'urgence une fois le problème survenu :

lane :release do
  build_app(scheme: "MyApp")
  sh("scp -r ../build/MyApp.app.dSYM dev@archive-node:/archive/dsym/#{ENV['BUILD_NUMBER']}/")
end

Cette étape suppose que le nœud d'archivage soit disponible en permanence et dispose d'assez d'espace disque — ce qu'une instance CI temporaire ne peut pas offrir, alors qu'un Mac mini cloud en fonctionnement continu remplit exactement ce rôle. Louer au mois une machine dédiée qui ne fait que tourner les scripts d'archivage et de symbolisation revient bien moins cher que d'acheter du matériel uniquement pour cet usage, et cela n'occupe pas non plus les ressources de la machine de build utilisée pour le packaging.

Questions fréquentes

Pourquoi la symbolisation échoue souvent en local mais réussit sur un nœud d'archivage dédié ?

Les dSYM locaux disparaissent lors des nettoyages Xcode ou des réinstallations système, et aucun poste de l'équipe ne conserve tous les builds historiques. Un nœud d'archivage garde chaque dSYM classé par numéro de build, donc tout ancien rapport de crash trouve son fichier symbole à l'UUID correspondant.

Comment vérifier que le binaire d'un crash log correspond bien au dSYM que j'utilise ?

Exécuter dwarfdump --uuid sur le dSYM et le binaire .app : les UUID doivent correspondre caractère pour caractère. Une divergence ne génère aucune erreur, seulement des numéros de ligne faux — c'est le piège le plus facile à manquer.

Quelle différence entre le dSYM auto-généré par App Store Connect et celui exporté depuis sa propre CI ?

Le contenu est identique, mais le téléchargement depuis App Store Connect est retardé et le nombre de builds conservés limité. Un pipeline maison enregistre le dSYM dès l'archivage et le conserve indéfiniment, utile pour retracer un crash d'une version datant de six mois.

Besoin d'un Mac mini dédié pour faire tourner vos builds ?

Louer un Mac mini sur le portail