Vous mettez à jour un hôte Debian, il démarre, les services se lancent… et puis des processus aléatoires commencent à mourir avec le même message lapidaire : Segmentation fault.
Pas d’erreur claire, pas de motif évident. Le noyau dépose des cores comme des confettis et votre canal d’astreinte s’agite.
Ce cas (n°55) porte sur la transformation de cette plainte vague « segfault après mise à jour » en un défaut précis et nommable : l’incompatibilité exacte d’une bibliothèque partagée
(quel fichier, quel paquet, quel symbole/ABI), plus le chemin de décision pour réparer sans aggraver le système.
Ce qui se passe réellement quand « ça segfault »
Après une mise à jour majeure, « segfault » n’est généralement pas un mystère de l’informatique moderne. C’est une erreur de comptabilité.
Le processus a chargé un ensemble d’objets partagés qui ne s’entendent pas sur un contrat : ABI, versions de symboles, conventions d’appel, disposition des structures,
ou même des conventions concernant le stockage thread-local. Le CPU fait ce qu’on lui dit, pas ce que vous vouliez.
Sur Debian, ces contrats sont normalement appliqués par la discipline des paquets : dépendances, versionnage des symboles, shlibs, triggers,
et la règle simple de ne pas mélanger les bibliothèques d’une release stable avec des blobs aléatoires dans /usr/local.
Quand vous mettez à jour et que vous obtenez encore des segfaults, vous êtes souvent dans l’une de ces situations :
- Mise à jour partielle : une partie du userland est nouvelle et utilise d’anciennes bibliothèques (ou inversement).
- Bibliothèques en doublon :
/usr/local/libou un bundle d’application écrase les bibliothèques de la distribution. - Bizarreries RPATH/RUNPATH : le binaire a des chemins de recherche codés en dur qui prennent le pas sur des valeurs raisonnables.
- Mauvaise architecture / mélange multiarch : le chargeur trouve une bibliothèque du même nom mais avec une classe ELF/architecture incompatible.
- Plugins hors-arbre : des modules compilés contre d’anciens en-têtes se chargent dans un processus plus récent et corrompent la mémoire.
- Fichiers corrompus : moins fréquent, mais une mise à jour interrompue ou un souci de disque peut produire des objets ELF subtilement cassés.
Votre travail n’est pas « arrêter le segfault ». Votre travail est « identifier l’objet incompatible et le prouver ». Quand vous pouvez nommer le fichier et le paquet,
les corrections deviennent ennuyeuses : réinstaller, supprimer les overrides, aligner les versions, reconstruire les plugins ou revenir en arrière.
Feuille de route pour un diagnostic rapide
En production, vous ne commencez pas par un débogage philosophique. Vous commencez par le chemin le plus rapide pour réduire le rayon d’impact et obtenir une hypothèse concrète.
1) D’abord : confirmez que c’est le chargeur dynamique et non le noyau ou le matériel
- Vérifiez
journalctlpour des IP/fichiers librairies cohérents. - Confirmez s’il s’agit de nombreux binaires ou d’un seul service.
- Scannez la présence évidente de
/usr/localou d’un chargeur custom impliqué.
2) Ensuite : récupérez un core dump et produisez une backtrace avec les chemins des bibliothèques
- Utilisez
coredumpctlou le chemin du core file. - Dans
gdb, affichez les bibliothèques partagées chargées et la backtrace. - Identifiez la première bibliothèque dont le chemin, la version ou l’absence d’informations de symbole semble « étrange ».
3) Troisième étape : vérifiez l’intégrité des paquets et l’alignement des versions
apt policypour les bibliothèques suspectes et le binaire en échec.dpkg -Vetdebsumspour détecter corruption/écrasements.- Cherchez des paquets en attente, des diversions et des priorités épinglées.
4) Quatrième étape : tracez les décisions du chargeur
LD_DEBUG=libs,versionssur le binaire fautif (dans un environnement sûr).- Confirmez quelle
.soexacte a été chargée, depuis quel répertoire et pourquoi.
5) Cinquième étape : corrigez avec le plus petit rayon d’impact possible
- Privilégiez la suppression des overrides et la réinstallation des paquets officiels.
- Ne « liez » pas avec des symlinks pour vous en sortir, sauf si vous aimez chasser des fantômes plus tard.
Une citation pour rester honnête. Werner Vogels (CTO d’Amazon) l’a dit à peu près comme ça — idée paraphrasée :
« Tout échoue tout le temps ; concevez et opérez en supposant la défaillance. » La version opérateur : supposez qu’une mise à jour peut laisser un monde mixte derrière elle.
Faits et contexte intéressants (pour que vous cessiez d’être surpris)
- « Segmentation fault » est plus vieux que votre carrière. Le terme provient de la segmentation mémoire dans d’anciens designs en mode protégé, bien avant que Linux n’existe.
- Linux rapporte « segfault » même quand la cause racine est une rupture d’ABI. Le CPU se plante ; l’OS rapporte ; votre vrai bug est à un niveau supérieur.
- Le versionnage de symboles dans glibc est une superpuissance de stabilité. Les bibliothèques peuvent exporter plusieurs versions d’un même symbole pour maintenir la compatibilité — jusqu’à ce que vous contourniez le système.
- Le packaging Debian a tout un système de métadonnées pour les bibliothèques partagées. Le mécanisme shlibs/symbols existe pour prévenir précisément ceci, mais il ne peut pas défendre contre des overrides locaux.
- RPATH est arrivé en premier ; RUNPATH est arrivé ensuite. RUNPATH change la façon dont les dépendances transitives sont résolues ; cela peut rendre les échecs « ça marche sur une machine » particulièrement agaçants.
/usr/localest historiquement destiné aux builds administrateur locaux. Il précède la culture des conteneurs et reste un endroit courant pour planquer des bombes à retardement.- Les grosses mises à jour exposent la dette de « comportement indéfini ». Un programme qui « fonctionnait » en dépendant de l’UB peut planter quand le compilateur, libc ou le comportement de l’allocateur change.
- Les problèmes d’ABI C++ sont un genre récurrent. Mélanger des versions de compilateur et des attentes de libstdc++ peut produire des crashs ressemblant à une corruption mémoire aléatoire.
Blague n°1 : Un segfault, c’est juste la façon dont votre programme vous dit qu’il aimerait aller se reposer un peu, de préférence dans la mémoire de quelqu’un d’autre.
Tâches pratiques : commandes, sorties et décisions
Ce ne sont pas des commandes « essayez des trucs ». Chaque tâche inclut ce que vous cherchez et la décision à prendre ensuite.
Exécutez-les sur l’hôte affecté ou, mieux, sur un clone/snapshot si vous êtes en production.
Task 1: Confirm the crash signature in the journal
cr0x@server:~$ journalctl -b -p warning..alert | grep -E "segfault|SIGSEGV|coredump" | tail -n 30
Dec 30 10:21:14 server kernel: myapp[22198]: segfault at 0 ip 00007f2d2a8f4c90 sp 00007ffd3b2a1c10 error 4 in libssl.so.3[7f2d2a860000+87000]
Dec 30 10:21:14 server systemd-coredump[22213]: Process 22198 (myapp) of user 1001 dumped core.
Dec 30 10:21:18 server kernel: myapp[22302]: segfault at 0 ip 00007f2d2a8f4c90 sp 00007ffd3b2a1c10 error 4 in libssl.so.3[7f2d2a860000+87000]
Ce que cela signifie : Si le noyau nomme une bibliothèque (in libssl.so.3), c’est votre premier suspect, pas votre dernier.
L’IP fautive à l’intérieur d’une bibliothèque suggère soit un vrai bug dans cette bibliothèque soit (plus probablement après une mise à jour) une incompatibilité entre l’appelant et le callee.
Décision : Choisissez un PID/core en échec et examinez le chemin exact du fichier de bibliothèque et la version du paquet pour cette bibliothèque.
Task 2: See whether crashes are widespread or isolated
cr0x@server:~$ coredumpctl list --no-pager | tail -n 10
TIME PID UID GID SIG COREFILE EXE
Mon 2025-12-30 10:21:14 UTC 22198 1001 1001 11 present /usr/local/bin/myapp
Mon 2025-12-30 10:21:18 UTC 22302 1001 1001 11 present /usr/local/bin/myapp
Mon 2025-12-30 10:22:03 UTC 22511 0 0 11 present /usr/sbin/nginx
Ce que cela signifie : Si à la fois des binaires de la distribution (/usr/sbin/nginx) et des binaires locaux (/usr/local/bin/myapp) plantent,
vous pouvez avoir un problème de bibliothèque système généralisé. Si seulement /usr/local plante, commencez par ne pas faire confiance à /usr/local.
Décision : Si des binaires système plantent aussi, priorisez la vérification des bibliothèques système de base et l’intégrité des paquets.
Si seuls des apps locales plantent, concentrez-vous sur les chemins du chargeur, les libs embarquées et les hypothèses de build.
Task 3: Identify OS and architecture baseline
cr0x@server:~$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
VERSION_ID="13"
VERSION="13 (trixie)"
ID=debian
cr0x@server:~$ uname -a
Linux server 6.12.0-1-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.0-1 (2025-11-20) x86_64 GNU/Linux
Ce que cela signifie : Le noyau et l’architecture importent pour les symboles de débogage et pour écarter des bibliothèques de mauvaise architecture.
Décision : Si vous voyez des architectures mélangées installées (par ex. i386 sur amd64), soyez prêt à vérifier les chemins multiarch et la sélection du chargeur.
Task 4: Verify the crashing executable’s origin and linkage
cr0x@server:~$ file /usr/local/bin/myapp
/usr/local/bin/myapp: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b7c7d6..., for GNU/Linux 3.2.0, stripped
Ce que cela signifie : « dynamically linked » signifie que vous êtes dans le domaine des bibliothèques partagées. « stripped » signifie que les backtraces seront moins détaillées.
Décision : Si le binaire est local et stripé, prévoyez d’installer les symboles de débogage pour les bibliothèques système et, si possible, obtenez une build non stripée.
Task 5: Quick dependency view with ldd (but don’t worship it)
cr0x@server:~$ ldd /usr/local/bin/myapp | head -n 20
linux-vdso.so.1 (0x00007ffd7b7d7000)
libssl.so.3 => /usr/local/lib/libssl.so.3 (0x00007f2d2a860000)
libcrypto.so.3 => /usr/local/lib/libcrypto.so.3 (0x00007f2d2a3d0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2d2a1ef000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2d2a9f0000)
Ce que cela signifie : Le chargeur choisit /usr/local/lib/libssl.so.3 au lieu de l’OpenSSL empaqueté par Debian dans
/usr/lib/x86_64-linux-gnu. C’est un gros signal d’alerte après une mise à jour.
Décision : Traitez /usr/local/lib/libssl.so.3 comme suspect n°1. Confirmez sa version et s’il correspond à l’ABI attendue.
Task 6: Ask the dynamic linker what it’s doing (LD_DEBUG)
cr0x@server:~$ LD_DEBUG=libs,versions /usr/local/bin/myapp 2>&1 | head -n 40
22891: find library=libssl.so.3 [0]; searching
22891: search path=/usr/local/lib:/usr/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu (system search path)
22891: trying file=/usr/local/lib/libssl.so.3
22891: find library=libcrypto.so.3 [0]; searching
22891: trying file=/usr/local/lib/libcrypto.so.3
22891: checking for version `OPENSSL_3.2.0' in file /usr/local/lib/libssl.so.3 [0] required by file /usr/local/bin/myapp [0]
22891: checking for version `GLIBC_2.38' in file /lib/x86_64-linux-gnu/libc.so.6 [0] required by file /usr/local/lib/libssl.so.3 [0]
Ce que cela signifie : Vous avez maintenant la preuve de l’ordre de résolution et des exigences de version des symboles.
Si vous voyez la demande d’une version de symbole non fournie par vos bibliothèques installées, vous obtiendrez des erreurs du chargeur.
Mais les segfaults surviennent quand le chargeur réussit et que l’ABI reste incompatible avec ce que l’appelant attend.
Décision : Si la préférence de chemin est mauvaise, corrigez la préférence de chemin. N’essayez pas de « bricoler » une incompatibilité d’ABI à l’exécution.
Task 7: Confirm which package owns the suspect library (or that none does)
cr0x@server:~$ dpkg -S /usr/local/lib/libssl.so.3
dpkg-query: no path found matching pattern /usr/local/lib/libssl.so.3
Ce que cela signifie : Ce n’est pas un fichier géré par Debian. Cela peut être un OpenSSL installé à la main, un tarball fournisseur, ou des restes d’un « quick fix ».
Décision : Si une bibliothèque crypto essentielle n’est pas gérée, envisagez fortement de la retirer des chemins du chargeur et d’utiliser la bibliothèque empaquetée par Debian.
Task 8: Inspect the suspect ELF metadata and BuildID
cr0x@server:~$ readelf -n /usr/local/lib/libssl.so.3 | sed -n '1,80p'
Displaying notes found in: .note.gnu.build-id
Owner Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: 4b2f5d0a1c6b0f7a9e6d5d3a2b1f0c9d8e7a6b5c
Ce que cela signifie : Les BuildID vous permettent de faire correspondre des binaires à des symboles de débogage et à des builds connus. Une bibliothèque locale avec un BuildID aléatoire est un autre indice.
Décision : Si vous ne pouvez pas tracer la provenance, prévoyez de la remplacer par une bibliothèque empaquetée ou de la reconstruire dans une pipeline contrôlée.
Task 9: Check Debian’s OpenSSL library version and candidates
cr0x@server:~$ apt-cache policy libssl3
libssl3:
Installed: 3.3.2-1
Candidate: 3.3.2-1
Version table:
*** 3.3.2-1 500
500 http://deb.debian.org/debian trixie/main amd64 Packages
100 /var/lib/dpkg/status
Ce que cela signifie : L’OpenSSL de Debian est installé et cohérent avec la release.
Si votre app ne l’utilise pas, vous avez un problème d’override, pas « Debian qui casse la crypto ».
Décision : Préférez /usr/lib/x86_64-linux-gnu/libssl.so.3 de Debian sauf raison auditable et convaincante de ne pas le faire.
Task 10: Verify loader configuration and search paths
cr0x@server:~$ grep -R --line-number "/usr/local/lib" /etc/ld.so.conf /etc/ld.so.conf.d/* 2>/dev/null
/etc/ld.so.conf.d/local.conf:1:/usr/local/lib
cr0x@server:~$ ldconfig -v 2>/dev/null | head -n 25
/usr/local/lib: (from /etc/ld.so.conf.d/local.conf:1)
libssl.so.3 -> libssl.so.3
libcrypto.so.3 -> libcrypto.so.3
/usr/lib/x86_64-linux-gnu:
libssl.so.3 -> libssl.so.3
libcrypto.so.3 -> libcrypto.so.3
Ce que cela signifie : Le cache du chargeur système inclut les deux, mais la préférence donne effectivement à /usr/local/lib une place de choix.
C’est ainsi que vous obtenez des incompatibilités après les mises à jour : les bibliothèques de la distribution ont changé, vos bibliothèques locales ne l’ont pas fait (ou ont changé différemment).
Décision : Retirez /usr/local/lib de la configuration globale du chargeur sauf si vous êtes absolument sûr de vouloir qu’il écrase les bibliothèques de la distribution.
Si vous avez besoin de bibliothèques locales, contraignez-les par application, pas globalement.
Task 11: Capture a core dump and extract loaded libraries
cr0x@server:~$ coredumpctl info /usr/local/bin/myapp | sed -n '1,80p'
PID: 22198
UID: 1001
GID: 1001
Signal: 11 (SEGV)
Timestamp: Mon 2025-12-30 10:21:14 UTC
Command Line: /usr/local/bin/myapp --serve
Executable: /usr/local/bin/myapp
Control Group: /system.slice/myapp.service
Unit: myapp.service
Message: Process 22198 (myapp) of user 1001 dumped core.
cr0x@server:~$ coredumpctl debug /usr/local/bin/myapp
GNU gdb (Debian 15.2-1) 15.2
Reading symbols from /usr/local/bin/myapp...
(No debugging symbols found in /usr/local/bin/myapp)
(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0x00007f2d2a9f5000 0x00007f2d2aa1b000 Yes (*) /lib64/ld-linux-x86-64.so.2
0x00007f2d2a860000 0x00007f2d2a8e2000 Yes (*) /usr/local/lib/libssl.so.3
0x00007f2d2a3d0000 0x00007f2d2a84d000 Yes (*) /usr/local/lib/libcrypto.so.3
0x00007f2d2a1ef000 0x00007f2d2a3b8000 Yes (*) /lib/x86_64-linux-gnu/libc.so.6
(gdb) bt
#0 0x00007f2d2a8f4c90 in ?? () from /usr/local/lib/libssl.so.3
#1 0x000000000040f23a in ?? ()
#2 0x0000000000410a11 in ?? ()
#3 0x00007f2d2a20a2ca in __libc_start_call_main () from /lib/x86_64-linux-gnu/libc.so.6
#4 0x00007f2d2a20a385 in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
#5 0x000000000040b4e5 in ?? ()
Ce que cela signifie : Vous avez confirmé que le crash se produit à l’intérieur du libssl local. Cela ne prouve pas que libssl est « bogué » ; cela prouve qu’il est dans la ligne de mire.
L’essentiel est la discordance entre les attentes de l’appelant et l’ABI/données de la fonction appelée.
Décision : Installez les symboles de débogage pour les bibliothèques suspectes (ou basculez vers les libs Debian) pour obtenir une backtrace significative.
Task 12: Install debug symbols for system libraries (when available)
cr0x@server:~$ sudo apt-get install -y gdb libc6-dbg libssl3-dbgsym
Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
gdb libc6-dbg libssl3-dbgsym
0 upgraded, 3 newly installed, 0 to remove and 0 not upgraded.
Ce que cela signifie : Avec les symboles, vous pouvez voir les noms de fonctions et parfois des indices sur les arguments. Si la bibliothèque en faute est dans /usr/local,
les dbgsym de la distribution n’aideront pas pour ce fichier exact — mais ils vous aideront à comprendre la frontière d’appel et si glibc est impliquée.
Décision : Si la bibliothèque fautive n’est pas gérée, soit obtenez des symboles correspondants auprès de son constructeur, soit arrêtez de l’utiliser.
Task 13: Check for partial upgrades, held packages, and pins
cr0x@server:~$ apt-mark showhold
libc6
cr0x@server:~$ apt-get -s dist-upgrade | sed -n '1,80p'
Reading package lists... Done
Building dependency tree... Done
Calculating upgrade... Done
The following packages will be upgraded:
libc6 libc6-dev
2 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Ce que cela signifie : Un libc6 en standby est essentiellement une bifurcation de compatibilité auto-infligée. Si vous avez mis à jour le userland mais épinglé glibc, vous prenez un risque.
Décision : Retirez le hold et complétez la mise à jour, ou revenez en arrière et gardez le système cohérent. « Demi‑Debian » est l’endroit où naissent les segfaults.
Task 14: Verify package integrity for core components
cr0x@server:~$ sudo dpkg -V libc6 libssl3 | head -n 50
..5...... /lib/x86_64-linux-gnu/libc.so.6
Ce que cela signifie : Le 5 indique un mismatch MD5 : le fichier diffère de ce que le paquet attend.
Cela peut arriver si quelque chose l’a écrasé, si vous êtes sur un snapshot de système de fichiers avec des bizarreries, ou si la mise à jour a été interrompue.
Décision : Réinstallez immédiatement les paquets affectés. Si le mismatch réapparaît, suspectez une corruption disque ou un outil de gestion de configuration écrivant dans des chemins système.
Task 15: Reinstall the damaged packages (surgical repair)
cr0x@server:~$ sudo apt-get install --reinstall -y libc6 libssl3
Reading package lists... Done
Building dependency tree... Done
0 upgraded, 0 newly installed, 2 reinstalled, 0 to remove and 0 not upgraded.
Ce que cela signifie : Vous avez restauré les bibliothèques gérées par paquet aux versions attendues.
Décision : Retestez le service en échec. S’il charge encore /usr/local/lib, vous n’avez pas réparé l’incompatibilité — vous avez juste nettoyé la baseline.
Repérer l’incompatibilité exacte (l’objectif réel)
« Incompatibilité de bibliothèque » est un diagnostic vague. Nous voulons quelque chose que vous pouvez mettre dans un rapport d’incident sans honte :
« /usr/local/lib/libssl.so.3 compilée contre OpenSSL X et glibc Y a été préchargée via ld.so.conf global et a planté lorsqu’elle a été appelée par myapp compilé contre libssl3 Debian. »
Ce niveau de précision est atteignable avec quelques vérifications ciblées supplémentaires.
Step A: Prove the wrong library is being selected
Vous l’avez déjà vu dans ldd et LD_DEBUG. Maintenant prouvez que c’est dû à la configuration globale du chargeur plutôt qu’à des réglages spécifiques à l’application.
cr0x@server:~$ /lib64/ld-linux-x86-64.so.2 --list /usr/local/bin/myapp | head -n 30
linux-vdso.so.1 (0x00007ffda91f2000)
libssl.so.3 => /usr/local/lib/libssl.so.3 (0x00007f6b4d3c0000)
libcrypto.so.3 => /usr/local/lib/libcrypto.so.3 (0x00007f6b4cf30000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6b4cd4f000)
Décision : Si le chargeur confirme la correspondance, arrêtez de débattre. Corrigez la correspondance.
Step B: Check for RPATH/RUNPATH and old build habits
cr0x@server:~$ readelf -d /usr/local/bin/myapp | grep -E "RPATH|RUNPATH"
0x000000000000001d (RUNPATH) Library runpath: [/usr/local/lib]
Ce que cela signifie : Le binaire est codé pour préférer /usr/local/lib.
Même si vous retirez /usr/local/lib de ld.so.conf, ce binaire cherchera toujours là.
Décision : Recompilez le binaire sans ce RUNPATH, ou patchez-le avec précaution (et documentez la modification comme un adulte).
Step C: Identify symbol version expectations
Parfois l’incompatibilité est subtile : la bibliothèque existe, se charge, exporte les bons noms, mais les versions ne correspondent pas à ce que le binaire attend.
cr0x@server:~$ objdump -T /usr/local/bin/myapp | grep -E "OPENSSL_|GLIBC_" | head -n 20
0000000000000000 DF *UND* 0000000000000000 (OPENSSL_3.3.0) EVP_MD_fetch
0000000000000000 DF *UND* 0000000000000000 (OPENSSL_3.3.0) SSL_CTX_new
0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.38) memcpy
cr0x@server:~$ objdump -T /usr/local/lib/libssl.so.3 | grep -E "OPENSSL_" | head -n 20
000000000003d2a0 g DF .text 00000000000000f5 OPENSSL_3.2.0 SSL_CTX_new
0000000000047b10 g DF .text 0000000000000120 OPENSSL_3.2.0 EVP_MD_fetch
Ce que cela signifie : Le binaire veut des symboles OPENSSL_3.3.0. La bibliothèque exporte OPENSSL_3.2.0.
Habituellement, cela échouerait au chargement avec « version not found ». Mais des incompatibilités peuvent se cacher si le binaire est moins strict, ou si des plugins appellent indirectement différentes versions.
Quoi qu’il en soit, vous avez maintenant une déclaration d’incompatibilité précise.
Décision : Alignez les versions d’OpenSSL : utilisez la libssl de Debian qui correspond aux hypothèses de build du binaire, ou recompilez le binaire contre la bibliothèque que vous comptez déployer.
Step D: Detect “two copies of the same dependency” in one process
Là ça devient épicé : deux copies d’une même bibliothèque avec des symboles similaires, chargées depuis des chemins différents, dans un même processus.
Cela peut arriver avec des plugins, dlopen et des SDK fournisseurs.
cr0x@server:~$ sudo gdb -q /usr/local/bin/myapp -ex 'set pagination off' -ex 'run --serve' -ex 'info proc mappings' -ex 'quit'
Reading symbols from /usr/local/bin/myapp...
(No debugging symbols found in /usr/local/bin/myapp)
Starting program: /usr/local/bin/myapp --serve
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
process 23102
Mapped address spaces:
Start Addr End Addr Size Offset Perms objfile
0x7f2d2a860000 0x7f2d2a8e2000 0x82000 0x0 r-xp /usr/local/lib/libssl.so.3
0x7f2d29f00000 0x7f2d29f82000 0x82000 0x0 r-xp /usr/lib/x86_64-linux-gnu/libssl.so.3
Ce que cela signifie : Les deux copies sont mappées. Ce n’est pas « un peu mauvais ». C’est « comportement indéfini en costume ».
Décision : Trouvez qui charge la seconde copie (souvent un plugin ou un chemin dlopen) et éliminez la duplication. Un processus, une seule libssl.
Step E: Find the loader trigger: environment, systemd unit, wrapper scripts
cr0x@server:~$ systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Service]
ExecStart=/usr/local/bin/myapp --serve
Environment=LD_LIBRARY_PATH=/usr/local/lib
Ce que cela signifie : Quelqu’un a défini LD_LIBRARY_PATH dans une unité systemd. Cela contourne la plupart des garde-fous du packaging Debian.
Décision : Retirez l’override d’environnement et déployez les dépendances correctement. Si vous devez utiliser LD_LIBRARY_PATH, restreignez-le fortement et documentez pourquoi.
Step F: Confirm the package state is consistent across the upgrade
cr0x@server:~$ dpkg -l | awk '$1 ~ /^(ii|iF|iU|rc)$/ {print $0}' | grep -E '^(iF|iU)'
iU libgcc-s1:amd64 14.2.0-3 amd64 GCC support library
cr0x@server:~$ sudo dpkg --configure -a
Setting up libgcc-s1:amd64 (14.2.0-3) ...
Processing triggers for libc-bin (2.38-6) ...
Ce que cela signifie : Un paquet « non configuré » peut laisser un état à moitié écrit, y compris des entrées du cache de bibliothèque anciennes, des triggers non exécutés et des dépendances incohérentes.
Décision : Toujours terminer la configuration et les triggers après une mise à jour interrompue, puis relancez ldconfig si nécessaire.
Step G: Validate glibc and libstdc++ ABI expectations (common post-upgrade crash vector)
Si vos frames en échec se trouvent dans libstdc++.so.6, libgcc_s.so.1 ou des allocateurs mémoire, vous êtes peut-être dans le territoire de l’ABI C++.
cr0x@server:~$ ldd /usr/local/bin/myapp | grep -E "libstdc\+\+|libgcc_s"
libstdc++.so.6 => /usr/local/lib/libstdc++.so.6 (0x00007f7a1a400000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f7a1a1e0000)
cr0x@server:~$ strings /usr/local/lib/libstdc++.so.6 | grep -E "GLIBCXX_" | tail -n 5
GLIBCXX_3.4.29
GLIBCXX_3.4.30
GLIBCXX_3.4.31
GLIBCXX_3.4.32
GLIBCXX_3.4.33
Ce que cela signifie : Un libstdc++ local qui écrase le libstdc++ de la distribution est une cause fréquente de « crashes après mise à jour », surtout quand des plugins ont été construits avec un toolchain différent.
Les versions GLIBCXX_ vous aident à voir ce que la bibliothèque prétend supporter.
Décision : Évitez d’écraser libstdc++ globalement. Utilisez le toolchain de la distribution de bout en bout, ou fournissez l’ensemble du runtime du fournisseur dans un conteneur/sandbox contrôlé.
Blague n°2 : Si vous déboguez un segfault avec LD_LIBRARY_PATH défini globalement, vous ne dépannez pas — vous rejouez un film d’horreur.
Trois mini-histoires du monde corporate (anonymisées, douloureusement réelles)
Mini-histoire 1 : L’incident causé par une mauvaise hypothèse
Une entreprise de taille moyenne gérait une flotte de serveurs Debian pour une API client. La mise à jour vers Debian 13 a été planifiée : d’abord l’OS, puis l’application.
L’équipe supposait que les binaires de l’app étaient « suffisamment autonomes » car ils déployaient un tarball avec quelques .so à côté de l’exécutable.
Le premier signe de trouble n’a pas été une indisponibilité totale. C’était du bruit : un nouveau filet de 502 et quelques workers qui mouraient par heure. Les logs noyau incriminaient libcrypto.
Les développeurs haussèrent les épaules — « on n’a pas touché la crypto » — et l’incident resta dans une zone inconfortable : pas assez cassé pour un rollback, pas assez stable pour l’ignorer.
La mauvaise hypothèse était subtile : ils croyaient que « les bibliothèques embarquées n’étaient utilisées que par notre app ». En réalité, un admin précédent avait ajouté /opt/vendor/lib
à /etc/ld.so.conf.d des années auparavant pour satisfaire un outil legacy. Après la mise à jour de l’OS, le chargeur commença à sélectionner d’abord l’OpenSSL du fournisseur
pour plusieurs processus, pas seulement les workers API.
La correction fut ennuyeuse et immédiate : retirer le chemin global du chargeur, exécuter ldconfig, redémarrer les services affectés, et s’assurer que les workers API utilisent l’OpenSSL Debian
(ou fournir leur propre runtime isolé via un runpath qui ne pollue pas l’hôte).
La leçon retenue : les bibliothèques partagées sont partagées. Si vous apprenez un nouveau tour au chargeur globalement, tous les processus l’apprennent, y compris ceux que vous appréciez.
Mini-histoire 2 : L’optimisation qui s’est retournée contre eux
Une autre organisation a optimisé sa chaîne de build en produisant un paquet binaire « portable ». Ils construisaient sur un toolchain plus récent et se liaient dynamiquement
contre un ensemble de bibliothèques copiées dans /usr/local/lib sur chaque hôte. Cela réduisait les artefacts de build et accéléravait les déploiements.
Cela a aussi rendu les mises à jour fragiles.
Après la migration vers Debian 13, ils ont observé des segfaults intermittents sous charge. Les échecs étaient corrélés aux handshakes TLS et au trafic HTTP/2,
et les traces variaient. Certaines pointaient vers libssl, d’autres vers l’allocateur mémoire. Les ingénieurs ont cherché des conditions de course pendant des jours.
Ils ont ajouté des retries. Ils ont ajusté les pools de threads. Ils ont même modifié des flags du compilateur. Les segfaults ont poli la plaisanterie et ont continué.
Finalement, quelqu’un a lancé LD_DEBUG=libs et a remarqué un motif : parfois le processus chargeait le libcrypto de Debian, parfois celui local,
selon la manière dont les plugins étaient chargés et quels chemins étaient actifs. Deux copies d’OpenSSL dans un même espace d’adresses n’est pas une configuration supportée ; c’est un générateur de chaos.
L’« optimisation » consistait à utiliser des chemins hôtes partagés comme substrat de déploiement. Cela semblait efficace, mais cela a brouillé les responsabilités et les frontières de versions.
La correction a exigé de revenir en arrière : soit lier statiquement quand c’était possible, soit fournir un runtime isolé approprié (conteneur, chroot, ou au moins un runpath par service),
et cesser de mélanger copies hôtes et fournisseurs.
Le postmortem fut brutal : la commodité est une dépendance. Si vous optimisez en contournant le solveur de dépendances de la distribution, vous devenez le solveur.
Mini-histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une société de services financiers avait une règle : avant toute mise à jour majeure Debian, prendre un snapshot du système de fichiers, capturer les sélections dpkg,
et exporter l’état exact du pinning APT. Personne n’aimait cette règle. Cela ressemblait à de la paperasserie pour des ordinateurs.
Pendant leur mise à jour vers Debian 13, un job background commença à planter avec des segfaults dans libpq. L’application avait des plugins, et
le fournisseur du plugin livrait une extension binaire-only compilée « pour Debian ». Cette phrase ne signifie rien sans détails, mais les achats étaient contents.
Le SRE de permanence a comparé les inventaires de bibliothèques avant et après la mise à jour et a remarqué que l’installateur du fournisseur avait déposé une copie de libstdc++.so.6
dans /usr/local/lib sur un sous-ensemble d’hôtes quelques semaines plus tôt. La plupart des machines n’en avaient pas. Celles qui en avaient plantaient.
Parce qu’ils avaient des snapshots, ils ont pu différencier rapidement l’état du cache du chargeur et confirmer le changement.
La correction fut simple : supprimer la bibliothèque locale rebelle, restaurer la propriété des paquets Debian, et imposer une politique interdisant aux installateurs fournisseurs de modifier les chemins globaux du chargeur.
L’extension fournisseur fut ensuite reconstruite correctement, mais l’incident fut arrêté en une heure parce que l’équipe pouvait prouver « ce qui a changé » sans deviner.
La pratique ennuyeuse n’était pas de l’héroïsme. C’était de l’hygiène : snapshots, inventaire et reproductibilité. Ça ne vous rend pas plus rapide tous les jours. Ça vous rend rapide quand il le faut.
Erreurs courantes : symptôme → cause racine → correction
Voici les récidivistes que je vois après des mises à jour Debian. L’objectif est la reconnaissance de motif : vous voyez le symptôme, vous sautez vers le bon sous‑système,
et vous évitez le « debugging au hasard ».
1) Symptom: Only locally deployed apps crash; Debian services are fine
- Cause racine : Binaires locaux construits contre des libs plus anciennes/plus récentes ; RUNPATH pointant vers
/usr/local/lib; libs embarquées masquent celles de la distro. - Correction : Retirez le RUNPATH vers des emplacements globaux ; recompilez contre le toolchain Debian 13 ; fournissez les dépendances dans un répertoire runtime isolé de l’app, pas des chemins globaux du chargeur.
2) Symptom: Many unrelated binaries segfault shortly after upgrade
- Cause racine : Mise à jour partielle ou paquets core retenus (glibc, libgcc) ; cache du chargeur incohérent ; configuration dpkg interrompue.
- Correction : Complétez la mise à jour (
dpkg --configure -a,apt-get dist-upgrade), retirez les holds, réinstallez les libs core, reconstruisez initramfs si pertinent.
3) Symptom: Backtrace points into libssl/libcrypto but app code changed little
- Cause racine : Deux copies d’OpenSSL chargées ; mauvais
libssl.sorésolu depuis/usr/localou des répertoires fournisseurs ; plugin compilé contre un OpenSSL différent. - Correction : Assurez-vous qu’il n’y ait qu’un seul OpenSSL par processus ; retirez les overrides globaux ; recompilez les plugins ; utilisez la libssl de la distro ou fournissez une pile isolée complète du fournisseur.
4) Symptom: Crash only happens when a plugin/module loads
- Cause racine : Incompatibilité d’ABI de plugin (ABI C++, disposition des structures, attentes glibc). L’hôte a été mis à jour ; le plugin ne l’a pas été.
- Correction : Recompilez le plugin contre les en-têtes/libs mis à jour ; imposez des vérifications de compatibilité de versions pour les plugins ; évitez les plugins binaires-only sauf si vous contrôlez le toolchain.
5) Symptom: dpkg -V shows mismatches in libc or other core libs
- Cause racine : Fichier écrasé, corruption, mise à jour interrompue, ou gestion de configuration écrivant dans les répertoires système.
- Correction : Réinstallez les paquets ; enquêtez sur qui a modifié les fichiers (audit de gestion de configuration, règles d’infrastructure immuable) ; exécutez des contrôles santé du disque si les mismatches persistent.
6) Symptom: Loader errors like “version `GLIBC_2.xx’ not found” or “undefined symbol”
- Cause racine : Mismatch dur dans la version de symbole requise ; binaire construit sur une glibc/libstdc++ plus récente que la cible.
- Correction : Construisez sur la plus ancienne cible dont vous avez besoin (ou dans un environnement de build Debian 13) ; ne copiez pas des
libc.so.6au hasard ; alignez le toolchain.
Listes de vérification / plan étape par étape
Checklist A: Triage in the first 15 minutes
- Obtenez une signature de crash claire depuis
journalctl(nom de bibliothèque fautive, service, fréquence). - Confirmez si l’exécutable en échec est géré par la distribution (
dpkg -S) ou local (/usr/local,/opt). - Récupérez un core dump avec
coredumpctlet listez les bibliothèques partagées chargées dansgdb. - Exécutez
lddetreadelf -dpour identifier RUNPATH/RPATH et la sélection des bibliothèques. - Vérifiez la présence de
LD_LIBRARY_PATHdans les unités systemd, wrappers, cronjobs.
Checklist B: Confirm package consistency (stop living in a partial upgrade)
- Vérifiez les holds :
apt-mark showhold. Retirez les holds sauf si vous avez une raison entièrement testée. - Vérifiez l’état cassé :
dpkg --audit, puisdpkg --configure -a. - Simulez une mise à jour complète :
apt-get -s dist-upgrade. - Vérifiez l’intégrité :
dpkg -Vpour libc et les bibliothèques suspectes. Réinstallez tout ce qui ne correspond pas.
Checklist C: Fix the mismatch with minimal collateral damage
- Préféré : cessez d’écraser globalement les bibliothèques de la distribution. Retirez
/usr/local/libdeld.so.confsi ce n’est pas strictement requis. - Supprimez ou renommez les bibliothèques en conflit non gérées (gardez une copie de rollback en lieu sûr, pas dans les chemins du chargeur).
- Exécutez
ldconfiget redémarrez uniquement les services affectés (ou redémarrez si l’état du chargeur paraît douteux). - Si l’app a besoin de libs personnalisées, déployez-les dans un répertoire propriété de l’app et référencez-les via RUNPATH limité à cette app — ou utilisez des conteneurs.
- Recompilez les plugins/modules contre les bibliothèques et le toolchain Debian 13 ; considérez les plugins binaires-only comme suspects tant qu’ils ne sont pas validés.
Checklist D: Make it not happen again
- Politique : aucun installateur fournisseur ne doit modifier
/etc/ld.so.conf.dou déposer des libs dans/usr/local/libsans revue. - Inventaire : scannez périodiquement les bibliothèques ELF non gérées dans les chemins du chargeur.
- Runbook de mise à jour : imposez « pas de holds pour glibc/libgcc/libstdc++ pendant les upgrades de release ».
- Disciple de build : construisez dans un environnement Debian 13 ; publiez un manifeste des versions de bibliothèques requises.
FAQ
1) Why does a library mismatch cause a segfault instead of a clean loader error?
Les erreurs du chargeur surviennent quand les symboles requis ne peuvent pas être résolus. Les segfaults surviennent quand les symboles sont résolus mais que le contrat ABI est rompu :
mauvaise disposition de structure, mauvaises attentes sur la propriété d’allocation, types C++ incompatibles, ou bibliothèques dupliquées dans un même processus.
2) Is ldd reliable for diagnosing what will happen at runtime?
C’est un bon premier aperçu, pas un évangile. ldd peut être influencé par des variables d’environnement et ne montre pas tout le comportement de dlopen/plugins.
Pour la vérité à l’exécution, utilisez LD_DEBUG=libs et inspectez les mappings d’un processus vivant ou d’un core dump.
3) Can Debian 13 upgrades legitimately introduce segfaults in stable software?
C’est possible mais moins fréquent qu’on le pense. La plupart des segfaults post-upgrade sont déclenchés par des overrides locaux, des plugins, des mises à jour partielles,
ou des binaires construits sur une baseline différente. Considérez l’hypothèse « Debian a cassé ça » comme une hypothèse à prouver par des preuves.
4) Should I “fix” it by symlinking /usr/local/lib/libssl.so.3 to Debian’s libssl?
Non. C’est un bricolage fragile qui rend la provenance opaque et casse les attentes de la gestion de paquets. Retirez l’override et laissez le chargeur sélectionner la bibliothèque empaquetée,
ou recompilez proprement l’application. Les hacks par symlink sont la façon dont votre futur-vous finit par répondre à un incident pendant les vacances.
5) What if I must ship a custom OpenSSL for compliance reasons?
Alors déployez-le dans un répertoire isolé détenu par l’application, et assurez-vous que seule cette application l’utilise.
Évitez les modifications globales du chargeur. Rendez explicite la frontière de dépendance (RUNPATH limité à l’app, runtime conteneurisé ou chroot).
6) How do I tell if two copies of the same library are loaded?
Utilisez gdb (info proc mappings), inspectez /proc/$PID/maps, ou analysez les mappings du core dump.
Si vous voyez à la fois /usr/local/lib/libssl.so.3 et /usr/lib/x86_64-linux-gnu/libssl.so.3, vous avez trouvé une cause sérieuse.
7) Why do these crashes sometimes show up only under load?
Les incompatibilités d’ABI et les bibliothèques dupliquées peuvent se comporter comme des conditions de course parce que la disposition mémoire et le timing changent avec la concurrence.
Sous faible charge, vous ne toucherez peut-être pas le chemin corrompu. Sous charge, vous y arriverez — assez régulièrement pour gâcher votre semaine.
8) What’s the cleanest remediation when the crashing binary is in /usr/local?
Recompilez-le contre Debian 13 dans un environnement contrôlé, retirez le RUNPATH vers des emplacements globaux, et éliminez la dépendance à LD_LIBRARY_PATH globale.
Si la recompilation n’est pas possible, isolez le runtime (conteneur/chroot) pour qu’il cesse d’interférer avec l’hôte.
9) Do core dumps create security risk?
Oui : les cores peuvent contenir des secrets. Utilisez les politiques de stockage de systemd-coredump, restreignez l’accès et envisagez de désactiver les cores pour les services sensibles.
Mais pour cette classe de problèmes, un core dump bien géré peut vous faire gagner des jours de conjectures.
10) When should I roll back instead of debugging forward?
Si les bibliothèques système montrent des mismatches d’intégrité inexplicables, si plusieurs services critiques plantent, ou si vous suspectez une corruption de stockage,
revenir à un snapshot connu bon est souvent l’option la plus sûre. Déboguez sur un clone, pas sur l’hôte en sang.
Conclusion : prochaines étapes exploitables
« Segfault après mise à jour » cesse d’être effrayant une fois que vous le traitez comme un problème d’inventaire : quel binaire a chargé quels objets partagés exacts depuis quels chemins exacts,
et ces objets s’accordent-ils sur l’ABI et les versions de symboles.
Si vous ne retenez qu’une chose du cas n°55, retenez ceci : cessez de laisser /usr/local/lib (ou des chemins fournisseurs) écraser globalement les bibliothèques Debian.
Repérez l’incompatibilité avec coredumpctl + gdb + LD_DEBUG, prouvez-la avec des chemins de fichiers et des versions de symboles,
puis corrigez-la en restaurant un ensemble de bibliothèques cohérent et unique.
Prochaines étapes pratiques :
- Choisissez un crash et extrayez la liste des bibliothèques chargées depuis le core dump.
- Utilisez
LD_DEBUG=libs,versionspour prouver le choix du chargeur et les exigences de version. - Éliminez les overrides globaux (
ld.so.conf.d,LD_LIBRARY_PATH, RUNPATH vers des emplacements partagés). - Réinstallez et vérifiez les paquets core si
dpkg -Vindique des mismatches. - Recompilez ou isolez tout ce qui n’est pas géré par le packaging Debian.