Site de Jean-Michel RICHER

Maître de Conférences en Informatique à l'Université d'Angers

Ce site est en cours de reconstruction certains liens peuvent ne pas fonctionner ou certaines images peuvent ne pas s'afficher.


<<<L3

1. TP débuguer un programme assembleur ou C/C++ sous Linux

1.1. Compiler

1.1.1. compiler un programme NASM doté d'une méthode main

On peut écrire un programme assembleur qui pourra être compilé en exécutable. Il faut pour cela qu'il dispose d'une méthode main comme pour les programmes C/C++ :

Afficher le code    ens/l3/archi/hello_world_nasm.asm
  1. global main
  2. extern printf
  3.  
  4. ; DONNEES
  5. section .data
  6.  
  7.   message: db "hello world!", 10, 0
  8.  
  9. ; CODE
  10. section .text
  11.  
  12. main:
  13.   push  ebp
  14.   mov   ebp, esp
  15.  
  16.   push  dword message
  17.   call  printf
  18.   add   esp, 4
  19.  
  20.   xor   eax, eax
  21.   mov   esp, ebp
  22.   pop   ebp
  23.   ret
  24.  
  25.  

Pour compiler :

nasm compilation process one assembly file

> nasm -f elf hello_world_nasm.asm 
 >  gcc -o hello_world_nasm.exe hello_world_nasm.o -m32
 >  ./hello_world_nasm.exe 
Hello World !
  • la première ligne appelle nasm et génère un fichier objet (.o) au format ELF (Executable and Linkable Format)
  • la seconde appelle gcc pour réaliser l'édition de liens avec la bibliothèque C et générer un exécutable en 32 bits (-m32)

Note : lorsqu'on travaille en mode 64 bits on utilisera :

  • nasm -f elf64
  • gcc -o mon_exe [fichiers objet] -m64

L'option -m64 de gcc peut être omise si l'on est sur un système 64 bits, le compilateur utilisant ce mode par défaut

1.1.2. appeler une méthode assembleur depuis un fichier C ou C++

Il est parfois plus simple d'écrire un programme C/C++ et de ne coder en assembleur que la partie que l'on désire optimiser. Dans ce cas il faut déclarer en externe les méthodes assembleur. Prenons en exemple concret, on désire appeller la fonction array_sum écrite en assembleur dans le fichier array_sum_nasm.asm depuis le fichier C++ array_sum.cpp

Afficher le code    ens/l3/archi/array_sum_nasm.asm
  1. global array_sum
  2.  
  3. section .text
  4.  
  5. array_sum:
  6.   push  ebp
  7.   mov   ebp, esp
  8.  
  9.   mov   edx, [ ebp + 8 ]
  10.   xor   eax, eax
  11.   xor   ecx, ecx
  12. .for:
  13.   cmp   ecx, [ ebp + 12]
  14.   jge   .endfor
  15.  
  16.   add   eax, [edx + 4 * ecx ]
  17.  
  18.   inc   ecx
  19.   jmp   .for
  20. .endfor:
  21.  
  22.   mov   esp, ebp
  23.   pop   ebp
  24.   ret
  25.  
Afficher le code    ens/l3/archi/array_sum.cpp
  1. #include <iostream>
  2. using namespace std;
  3.  
  4. extern "C" {
  5.   int array_sum( int *tab, int n );
  6. }
  7.  
  8. int main() {
  9.  
  10.   int my_array[6] = { 1, 2, 3, 4, 5, 6 };
  11.  
  12.   cout << "sum=" << array_sum( my_array, 6 ) << endl;
  13.  
  14.   return EXIT_SUCCESS;
  15.  
  16. }
  17.  

Pour compiler et exécuter, on utilise les commandes suivantes :

nasm compilation process with C++ and assembly files

> nasm -f elf array_sum_nasm.asm 
> g++ -c array_sum.cpp -m32
> g++ -o array_sum.exe array_sum.o array_sum_nasm.o -m32
> ./array_sum.exe 
sum=55

La troisième ligne réalise l'édition de lien en prenant le fichier objet C++ et la fichier objet Nasm.

1.2. Déboguer / Débuguer

1.2.1. logiciels requis

1.2.1.a  Electric-fence

Utilisation des dépôts

On peut éventuellement installer le paquet Electric-Fence qui permet de traquer les erreurs d'accès à la mémoire :

sudo apt-get install electric-fence

On peut vérifier que les librairies sont installées :

> ls /usr/lib/libef*
/usr/lib/libefence.a   /usr/lib/libefence.so.0
/usr/lib/libefence.so  /usr/lib/libefence.so.0.0

ou Compilation des sources

Si vous êtes sur une architecture 64 bits et que vous voulez disposer des sources 32 bits par exemple.

  • Téléchargez les sources depuis github
  • Modifiez le Makefile pour que CFLAGS contienne "-m32"
  • Lancez make afin de compiler
  • Renommez libefence.a en libefence32.a

Pour compiler votre programme, attention vous devriez obtenir des warnings :

gcc -o a_nasm.exe a_nasm.o -ggdb -m32 libefence32.a -lpthread
libefence32.a(page.o): In function `stringErrorReport':
/home/richer/install/electric-fence-master/page.c:49: warning: `sys_errlist' is deprecated; use `strerror' or `strerror_r' instead
/home/richer/install/electric-fence-master/page.c:48: warning: `sys_nerr' is deprecated; use `strerror' or `strerror_r' instead

1.2.1.b  ddd (Data Display Debugger)

Installer le débugueur en mode graphique ddd, celui-ci représente une interface au débugueur en ligne de commande gdb :

sudo apt-get install ddd

1.2.1.c  Exemple

On utilise le programme suivant pour exemple de travail a_nasm.asm :


Warning: file_get_contents(ens/l3/archi/a_nasm.asm): Failed to open stream: No such file or directory in /home/jeanmichel.richer/public_html/ez_web.php on line 418
Afficher le code    ens/l3/archi/a_nasm.asm
  1.  

On utilise lors de la compilation, les arguments pour chaque programme qui permettent d'inclure des informations qui seront utiles au débogueur :

  • pour nasm : format dwarf
    • pour une architecture 64 bits : utiliser -f elf64
  • pour gcc/g++ : option -ggdb
    • ajouter, si nécessaire, -lefence pour inclure Electric-Fence
nasm -f elf -g -F dwarf a_nasm.asm
gcc -o a_nasm.exe a_nasm.o -ggdb -lefence

ou cf ci-dessus si vous êtes sur un système 64 bits et que vous avez installé electric-fence en compilant le code source en 32 bits :

gcc -o a_nasm.exe a_nasm.o -ggdb libefence32.a -lpthread

1.3. Débuguer

1.3.1. avec gdb

gdb, le GNU débugueur, est un débugueur en mode console :

> gdb a_nasm.exe
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /home/richer/dev/asm/a_nasm.exe...done.
(gdb) 

On donne un argument au programme et on l'exécute :

(gdb) set args 10
(gdb) run
Starting program: /home/richer/dev/asm/a_nasm.exe 10
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".

  Electric Fence 2.1 Copyright (C) 1987-1998 Bruce Perens.

Program received signal SIGSEGV, Segmentation fault.
main.for1 () at a_nasm.asm:45
45			mov		[ebx + ecx*4], eax
(gdb) 

Remarque : on aurait pu utiliser plus simplement run 10

L'erreur d'accès mémoire s'est produite en ligne 45 et provient forcément de l'adresse : [ebx + ecx*4] qui n'est pas valide; on examine alors les registres :

(gdb) info registers
eax            0x4	4
ecx            0x3	3
edx            0x1	1
ebx            0xb7cf8ff4	-1211133964
esp            0xbffff248	0xbffff248
ebp            0xbffff268	0xbffff268
esi            0x0	0
edi            0x0	0
eip            0x8048518	0x8048518 
eflags         0x210297	[ CF PF AF SF IF RF ID ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51

soit ebx n'est pas une adresse valide, soit c'est l'indice ecx, en relisant le programme on s'apercevra qu'on a alloué que 10 octets (et non pas 10 entiers), et donc lorsqu'on tente d'accèder au 3*4 = 12 ième octet, une erreur est donc levée.

1.3.2. avec ddd

ddd a_nasm.exe

ddd (data display debugger)

Dans le menu principal cliquer sur Program, puis Run.

Saisir le pamamètre : 10, dans le champ "Run with Arguments"

ddd (data display debugger)

Dans le menu principal cliquer sur Status, puis Registers :

ddd (data display debugger)

Dans le menu Status, le sous menu Backtrace permet d'avoir accès à la pile des appels de sous-programmes.

1.4. Modifier les registres

(gdb) set \$eax=1
(gdb) set \$st0=3.14
(gdb) set \$xmm0.v4_int32={1,2,3,4}
(gdb) set \$xmm0.v4_float={1.1,2.13,3.14926,4e-5}

1.5. Affichage des registres

On peut afficher les registres dans leur totalité, par catégorie ou en détaillant le type du registre :

  • info registers (i r) donne les registres généraux ainsi que les registres de segment, le registre de flags et le compteur de programme (eip)
  • info registers float (i r f) donne les registres de la FPU
  • info registers vec (i r v) donne les registres vectoriels
  • info registers all (i r a) donne tous les registres

On peut également choisir d'afficher un registre en particulier :

(gdb) print \$eax
\$2 = 1448448008
(gdb) set \$xmm0.v4_int32={1,2,3,4}
(gdb) print \$xmm0
\$5 = {v4_float = {1.40129846e-45, 2.80259693e-45, 4.20389539e-45, 5.60519386e-45}, v2_double = {4.2439915824246103e-314, 8.4879831653432862e-314}, v16_int8 = {1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0}, v8_int16 = {1, 0, 2, 0, 3, 0, 4, 0}, v4_int32 = {1, 2, 3, 4}, v2_int64 = {8589934593, 17179869187}, uint128 = 316912650112397582603894390785}
(gdb) print \$xmm0.v4_int32
\$6 = {1, 2, 3, 4}
(gdb) 

1.6. Affichage de la mémoire

Considérons la section de données suivante que l'on désire afficher :

section .data
    
        v: dd 1, 2, 3, 4
    
        x:  db 1, 2, 3, 4, 5, 6, 7, 8
            db 255, 6, 5, 4, 3, 2, 1, 128

L'adresse de v est 0x56559008. On peut l'obtenir par commande :

(gdb) print &v
\$7 = (<data variable, no debug info> *) 0x56559008 <v>

On utilise la commande x/ suivie :

  • du nombre d'éléments
  • du mode d'affichage
    • c caractère
    • d entier signé
    • u entier non signé
    • x hexadécimal
    • f float
    • i instruction
  • du format des éléments
    • b byte (octet)
    • h half (mot)
    • b word (double mot)
    • g giant (quadruple mot)

Par exemple, pour afficher 32 octets affichés sous forme d'entiers (signés) :

(gdb) x/32db 0x56559008
0x56559008 <v>:	1	0	0	0	2	0	0	0
0x56559010:	3	0	0	0	4	0	0	0
0x56559018 <x>:	1	2	3	4	5	6	7	8
0x56559020:	-1	6	5	4	3	2	1	-128

Pour afficher 32 octets affichés sous forme d'entiers non signés :

(gdb) x/32ub 0x56559008
0x56559008 <v>:	1	0	0	0	2	0	0	0
0x56559010:	3	0	0	0	4	0	0	0
0x56559018 <x>:	1	2	3	4	5	6	7	8
0x56559020:	255	6	5	4	3	2	1	128

Pour afficher 10 double mots en hexadécimal, on utilisera :

(gdb) x/10xw 0x56559008
0x56559008 <v>:	0x00000001	0x00000002	0x00000003	0x00000004
0x56559018 <x>:	0x04030201	0x08070605	0x040506ff	0x80010203
0x56559028 <completed>:	0x00000000	0x00000000