Extension de la pile du débogueur Linux !
Présentation | Parfois, l'information la plus importante que vous devez connaître est la manière dont l'état actuel de votre programme en est arrivé là. Il existe une commande backtrace, qui vous donne la chaîne d'appels de fonction actuelle de votre programme. Cet article vous montrera comment implémenter le déroulement de la pile sur x86_64 pour générer un tel traçage. |
Ces liens seront mis en ligne au fur et à mesure que d'autres articles seront publiés.
- Préparer l'environnement
- Point d'arrêt
- Registres et mémoire
- ELF et NAIN
- Code source et signaux
- Exécution étape par étape au niveau du code source
- Points d'arrêt au niveau de la source
- Extension de la pile
- Lire les variables
- Prochaines étapes
Utilisez le programme suivant comme exemple :
void a() { //stopped here } void b() { a(); } void c() { a(); } int main() { b(); c(); }
Si le débogueur s'arrête à la ligne //stopped here', il existe deux manières d'y accéder : main->b->a ou main->c->a`. Si nous définissons un point d'arrêt avec LLDB, continuons l'exécution et demandons un traçage, alors nous obtenons ce qui suit :
* frame #0: 0x00000000004004da a.out`a() + 4 at bt.cpp:3 frame #1: 0x00000000004004e6 a.out`b() + 9 at bt.cpp:6 frame #2: 0x00000000004004fe a.out`main + 9 at bt.cpp:14 frame #3: 0x00007ffff7a2e830 libc.so.6`__libc_start_main + 240 at libc-start.c:291 frame #4: 0x0000000000400409 a.out`_start + 41
Cela signifie que nous sommes actuellement dans la fonction a, a saute de la fonction b, b saute de la fonction principale, etc. Les deux dernières images indiquent comment le compilateur amorce la fonction principale.
La question est maintenant de savoir comment l'implémenter sur x86_64. L'approche la plus robuste serait d'analyser la partie .eh_frame du fichier ELF et de comprendre comment dérouler la pile à partir de là, mais ce serait pénible. Vous pourriez le faire en utilisant libunwind ou similaire, mais c'est ennuyeux. Au lieu de cela, nous supposons que le compilateur a configuré la pile d'une manière ou d'une autre et nous la parcourrons manuellement. Pour ce faire, nous devons d’abord comprendre la disposition de la pile.
High | ... | +---------+ +24| Arg 1 | +---------+ +16| Arg 2 | +---------+ + 8| Return | +---------+ EBP+--> |Saved EBP| +---------+ - 8| Var 1 | +---------+ ESP+--> | Var 2 | +---------+ | ... | Low
Comme vous pouvez le voir, le pointeur du dernier frame de pile est stocké au début du frame de pile actuel, créant une liste chaînée de pointeurs. La pile est déroulée en fonction de cette liste chaînée. Nous pouvons trouver la fonction de la trame suivante dans la liste en recherchant l'adresse de retour dans le message DWARF. Certains compilateurs ignoreront le suivi de l'adresse de base de trame d'EBP, car celle-ci peut être exprimée sous la forme d'un décalage par rapport à ESP et libérer un registre supplémentaire. Même avec les optimisations activées, passer -fno-omit-frame-pointer à GCC ou Clang le forcera à suivre les conventions dont nous dépendons.
Nous ferons tout le travail dans la fonction print_backtrace :
void debugger::print_backtrace() {
La première chose à décider est le format à utiliser pour imprimer les informations du cadre. J'ai utilisé un lambda pour déployer cette méthode :
auto output_frame = [frame_number = 0] (auto&& func) mutable { std::cout << "frame #" << frame_number++ << ": 0x" << dwarf::at_low_pc(func) << ' ' << dwarf::at_name(func) << std::endl; };
La première image imprimée est l’image en cours d’exécution. Nous pouvons obtenir des informations sur cette trame en recherchant le compteur de programme actuel dans DWARF :
auto current_func = get_function_from_pc(get_pc()); output_frame(current_func);
Ensuite, nous devons obtenir le pointeur de trame et l'adresse de retour de la fonction actuelle. Le pointeur de trame est stocké dans le registre rbp et l'adresse de retour est empilée sur 8 octets à partir du pointeur de trame.
auto frame_pointer = get_register_value(m_pid, reg::rbp); auto return_address = read_memory(frame_pointer+8);
Nous avons maintenant toutes les informations dont nous avons besoin pour étendre la pile. Je continue de me dérouler jusqu'à ce que le débogueur atteigne main, mais vous pouvez également choisir de vous arrêter lorsque le pointeur de trame est 0x0, qui sont les fonctions que vous appelez avant d'appeler la fonction principale. Nous récupérerons le pointeur de trame et l’adresse de retour de chaque trame et imprimerons les informations.
while (dwarf::at_name(current_func) != "main") { current_func = get_function_from_pc(return_address); output_frame(current_func); frame_pointer = read_memory(frame_pointer); return_address = read_memory(frame_pointer+8); } }
C'est tout ! Voici l'intégralité de la fonction :
void debugger::print_backtrace() { auto output_frame = [frame_number = 0] (auto&& func) mutable { std::cout << "frame #" << frame_number++ << ": 0x" << dwarf::at_low_pc(func) << ' ' << dwarf::at_name(func) << std::endl; }; auto current_func = get_function_from_pc(get_pc()); output_frame(current_func); auto frame_pointer = get_register_value(m_pid, reg::rbp); auto return_address = read_memory(frame_pointer+8); while (dwarf::at_name(current_func) != "main") { current_func = get_function_from_pc(return_address); output_frame(current_func); frame_pointer = read_memory(frame_pointer); return_address = read_memory(frame_pointer+8); } }
Bien sûr, nous devons exposer cette commande à l'utilisateur.
else if(is_prefix(command, "backtrace")) { print_backtrace(); }
Une façon de tester cette fonctionnalité consiste à écrire un programme de test avec un tas de petites fonctions qui s'appellent les unes les autres. Définissez quelques points d'arrêt, parcourez le code et assurez-vous que votre traçage est exact.
Nous avons parcouru un long chemin depuis un programme qui ne pouvait que générer et s'attacher à d'autres programmes. L'avant-dernier article de cette série complétera l'implémentation du débogueur en prenant en charge la lecture et l'écriture de variables. En attendant, vous pouvez trouver le code de cet article ici.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

AI Hentai Generator
Générez AI Hentai gratuitement.

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Pour ouvrir un fichier web.xml, vous pouvez utiliser les méthodes suivantes: Utilisez un éditeur de texte (tel que le bloc-notes ou TextEdit) pour modifier les commandes à l'aide d'un environnement de développement intégré (tel qu'Eclipse ou NetBeans) (Windows: Notepad web.xml; Mac / Linux: Open -A TextEdit web.xml)

Le multithreading dans la langue peut considérablement améliorer l'efficacité du programme. Il existe quatre façons principales d'implémenter le multithreading dans le langage C: créer des processus indépendants: créer plusieurs processus en cours d'exécution indépendante, chaque processus a son propre espace mémoire. Pseudo-Multithreading: Créez plusieurs flux d'exécution dans un processus qui partagent le même espace mémoire et exécutent alternativement. Bibliothèque multi-thread: Utilisez des bibliothèques multi-threades telles que PTHEADS pour créer et gérer des threads, en fournissant des fonctions de fonctionnement de thread riches. Coroutine: une implémentation multi-thread légère qui divise les tâches en petites sous-tâches et les exécute tour à tour.

Linux est mieux utilisé comme gestion de serveurs, systèmes intégrés et environnements de bureau. 1) Dans la gestion des serveurs, Linux est utilisé pour héberger des sites Web, des bases de données et des applications, assurant la stabilité et la fiabilité. 2) Dans les systèmes intégrés, Linux est largement utilisé dans les systèmes électroniques intelligents et automobiles en raison de sa flexibilité et de sa stabilité. 3) Dans l'environnement de bureau, Linux fournit des applications riches et des performances efficaces.

Debianlinux est connu pour sa stabilité et sa sécurité et est largement utilisé dans les environnements de serveur, de développement et de bureau. Bien qu'il y ait actuellement un manque d'instructions officielles sur la compatibilité directe avec Debian et Hadoop, cet article vous guidera sur la façon de déployer Hadoop sur votre système Debian. Exigences du système Debian: Avant de commencer la configuration de Hadoop, assurez-vous que votre système Debian répond aux exigences de fonctionnement minimales de Hadoop, qui comprend l'installation de l'environnement d'exécution Java (JRE) nécessaire et des packages Hadoop. Étapes de déploiement de Hadoop: Télécharger et unzip Hadoop: Téléchargez la version Hadoop dont vous avez besoin sur le site officiel d'Apachehadoop et résolvez-le

Dois-je installer un client Oracle lors de la connexion à une base de données Oracle à l'aide de Go? Lorsque vous développez GO, la connexion aux bases de données Oracle est une exigence commune ...

"Debianstrings" n'est pas un terme standard, et sa signification spécifique n'est pas encore claire. Cet article ne peut pas commenter directement la compatibilité de son navigateur. Cependant, si "DebianStrings" fait référence à une application Web exécutée sur un système Debian, sa compatibilité du navigateur dépend de l'architecture technique de l'application elle-même. La plupart des applications Web modernes se sont engagées à compatibilité entre les navigateurs. Cela repose sur les normes Web suivantes et l'utilisation de technologies frontales bien compatibles (telles que HTML, CSS, JavaScript) et les technologies back-end (telles que PHP, Python, Node.js, etc.). Pour s'assurer que l'application est compatible avec plusieurs navigateurs, les développeurs doivent souvent effectuer des tests croisés et utiliser la réactivité

Les principales raisons pour lesquelles vous ne pouvez pas vous connecter à MySQL en tant que racines sont des problèmes d'autorisation, des erreurs de fichier de configuration, des problèmes de mot de passe incohérents, des problèmes de fichiers de socket ou une interception de pare-feu. La solution comprend: vérifiez si le paramètre Bind-Address dans le fichier de configuration est configuré correctement. Vérifiez si les autorisations de l'utilisateur racine ont été modifiées ou supprimées et réinitialisées. Vérifiez que le mot de passe est précis, y compris les cas et les caractères spéciaux. Vérifiez les paramètres et les chemins d'autorisation du fichier de socket. Vérifiez que le pare-feu bloque les connexions au serveur MySQL.

J'ai développé un projet appelé Lua-Libuv et je suis heureux de partager mon expérience. L'intention initiale du projet est d'explorer comment utiliser Libuv (une bibliothèque d'E / S asynchrone écrite en c) pour créer un serveur HTTP simple sans avoir à apprendre le langage C en profondeur. Avec l'aide de Chatgpt, j'ai terminé le code de base de HTTP.C. Lorsque je traite des connexions persistantes, j'ai réussi à mettre en œuvre la clôture de la connexion et à libérer les ressources au bon moment. Au début, j'ai essayé de créer un serveur simple qui a mis fin au programme principal en fermant la connexion, mais j'ai eu quelques problèmes. J'ai essayé d'envoyer des blocs de données à l'aide de streaming, et pendant que cela fonctionne, cela bloque le thread principal. En fin de compte, j'ai décidé d'abandonner cette approche parce que mon objectif n'était pas d'apprendre la langue C en profondeur. Enfin, je
