<<<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++ :

  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 !

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

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

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 :

  1. ; example that does not allocate enough memory for an array
  2. ; this will generate an error when accessing some elements
  3. ; of the array
  4. global main
  5. extern malloc
  6. extern atoi
  7.  
  8. ;=========================
  9. ; DATA
  10. ;========================
  11. section .data
  12.  
  13. ; read from command line argument, size of the array tab
  14. size: dd 0 
  15. ; int *tab which will be allocated dynamically
  16. tab: dd 0
  17.  
  18. ;=========================
  19. ; CODE
  20. ;========================
  21. section .text
  22.  
  23. ; int main(int argc, char *argv[])
  24. main:
  25.   push  ebp
  26.   mov   ebp, esp
  27.   pushad
  28.  
  29.   ; get size of tab
  30.   ; size = atoi(argv[1]);
  31.   mov   ebx, [ebp + 12]
  32.   push  dword [ebx + 4]
  33.   call  atoi
  34.   add   esp, 4
  35.   mov   [size], eax  
  36.  
  37.   ; we make a mistake here !!!!!!!!!!!!!!!!!!!!!!!!
  38.   ; we write :
  39.   ; tab = (int *) malloc(size)
  40.   ; instead of :
  41.   ; tab = (int *) malloc(size * sizeof(int))
  42.  
  43.   push  eax
  44.   call  malloc
  45.   add   esp, 4
  46.   mov   [tab], eax
  47.  
  48.  
  49.   ; for (int i=0; i<size; ++i) tab[i] = i+1;
  50.   mov   ebx, [tab]    ; tab
  51.   xor   ecx, ecx    ; i
  52. .for1:
  53.   cmp   ecx, [size]
  54.   jge   .end_for1
  55.  
  56.     lea   eax, [ecx + 1]
  57.     mov   [ebx + ecx*4], eax
  58.      
  59.   inc   ecx
  60.   jmp   .for1
  61. .end_for1:
  62.  
  63.   ; int sum=0;
  64.   ; for (int i=0; i<size; ++i) sum += tab[i]
  65.  
  66.   xor   eax, eax    ; sum
  67.   xor   ecx, ecx    ; i
  68. .for2:
  69.   cmp   ecx, [size]
  70.   jge   .end_for2
  71.  
  72.     add   eax, [ebx + ecx*4]
  73.      
  74.   inc   ecx
  75.   jmp   .for2
  76. .end_for2:
  77.  
  78.  
  79.   popad
  80.   mov   ebp, ebp
  81.   pop   ebp
  82.   xor   eax, eax
  83.   ret
  84.  

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

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 :

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 :

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