#+title: Curso prático de introdução ao GDB #+author: Blau Araujo #+email: blau@debxp.org * 4. Aplicações em baixo e alto nível ** Objetivo Conhecer os recursos do GDB que podem auxiliar no desenvolvimento e depuração de programas em assembly e em C/C++. ** O GDB e a programação em baixo nível O GDB permite observar e controlar a execução de programas no nível mais próximo da máquina, o que inclui: - Acompanhamento de registradores da CPU; - Inspeção de endereços de memória brutos; - Execução instrução por instrução em assembly (=stepi=, =nexti=); - Observação do /stack frame/ e chamadas (=backtrace=, =info frame=); - Controle direto de /flags/, pilha, heap e segmentos do processo. Isso o torna útil para: - Diagnóstico de /segmentation faults/; - Estudo de chamadas de sistema; - Reversão e engenharia de baixo nível; - Entendimento de como o compilador transforma código em instruções. *** Exemplo com um programa em assembly Código-fonte (=soma.asm=): #+begin_src asm section .data a dq 10 b dq 20 resultado dq 0 section .text global _start _start: mov rax, [a] add rax, [b] mov [resultado], rax mov rax, 60 ; syscall: exit xor rdi, rdi ; status 0 syscall #+end_src Montagem e link-edição: #+begin_example nasm -f elf64 -g soma.asm -o soma.o ld soma.o -o soma #+end_example Abertura com o GDB: #+begin_example gdb ./soma Reading symbols from ./soma... #+end_example Definindo =_start= como ponto de parada: #+begin_example (gdb) b _start Breakpoint 1 at 0x401000: file soma.asm, line 10. #+end_example Listando o fonte: #+begin_example (gdb) l 1 section .data 2 a dq 10 3 b dq 20 4 resultado dq 0 5 6 section .text 7 global _start 8 9 _start: 10 mov rax, [a] (gdb) 11 add rax, [b] 12 mov [resultado], rax 13 14 mov rax, 60 ; syscall: exit 15 xor rdi, rdi ; status 0 16 syscall #+end_example Executando: #+begin_example (gdb) r Starting program: /home/blau/git/gdb-pratico/mods/03/asm/soma Breakpoint 1, _start () at soma.asm:10 10 mov rax, [a] #+end_example Observando os dados rotulados como =a=, =b= e =resultado= (assembly não tem variáveis): #+begin_example (gdb) x/1gx &a 0x402000 : 0x000000000000000a (gdb) x/1gx &b 0x402008 : 0x0000000000000014 (gdb) x/1gx &resultado 0x402010 : 0x0000000000000000 #+end_example #+begin_quote O comando =x= exibe um conteúdo na memória e, com as opções =/1gx=, eu estou pedindo para mostrar uma /giant word/ (=1g=), que é uma palavra de 8bytes, em base hexadecimal (=x=). #+end_quote Executando as três instruções seguintes: #+begin_example (gdb) n 11 add rax, [b] (gdb) 12 mov [resultado], rax (gdb) 14 mov rax, 60 ; syscall: exit #+end_example Observando o novo valor em =resultado=: #+begin_example (gdb) x/1gx &resultado 0x402010 : 0x000000000000001e #+end_example Exibindo o estado atual de todos os registradores da CPU: #+begin_example (gdb) i registers rax 0x1e 30 rbx 0x0 0 rcx 0x0 0 rdx 0x0 0 rsi 0x0 0 rdi 0x0 0 rbp 0x0 0x0 rsp 0x7fffffffe020 0x7fffffffe020 r8 0x0 0 r9 0x0 0 r10 0x0 0 r11 0x0 0 r12 0x0 0 r13 0x0 0 r14 0x0 0 r15 0x0 0 rip 0x401018 0x401018 <_start+24> eflags 0x206 [ PF IF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0 fs_base 0x0 0 gs_base 0x0 0 #+end_example Continuando a execução até o término: #+begin_example (gdb) c Continuing. [Inferior 1 (process 222188) exited normally] #+end_example *** Notações de tamanhos de palavra | Tamanho | Arquitetura x86-64 | NASM / Assembly | GDB (comando =x=) | |---------+--------------------+-----------------+-----------------| | 1 byte | byte | =byte= | =b= | | 2 bytes | word | =word= | =h= (/halfword/) | | 4 bytes | doubleword | =dword= | =w= (/word/) | | 8 bytes | quadword | =qword= | =g= (/giant word/) | ** O GDB e a programação em C/C++ O GDB tem suporte avançado para C e C++, oferecendo: - Identificação de tipos, nomes, escopos e estruturas; - Acesso a variáveis locais, globais e parâmetros; - Depuração de ponteiros e aritmética de ponteiros; - Interação com /structs/, =typedef=, =enum=, =union=, etc.; - Acompanhamento de chamadas recursivas e /stack frames/; - Em C++: suporte a classes, herança, /namespaces/ e /templates/. *** Exemplo com um programa em C Código-fonte (=soma.c=): #+begin_src c #include int soma(int a, int b) { int resultado = a + b; return resultado; } int main() { int x = 10, y = 20; int z = soma(x, y); printf("Resultado: %d\n", z); return 0; } #+end_src Compilação: #+begin_example gcc -g -o soma soma.c #+end_example Abrindo com o GDB e definindo o ponto de parada na função =soma=: #+begin_example :~$ gdb ./soma Reading symbols from ./soma... (gdb) b soma Breakpoint 1 at 0x1143: file soma.c, line 4. #+end_example Iniciando a execução do programa: #+begin_example (gdb) r Starting program: /home/blau/git/gdb-pratico/mods/03/c/soma [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, soma (a=10, b=20) at soma.c:4 4 int resultado = a + b; #+end_example Exibindo chamadas de funções na pilha: #+begin_example (gdb) backtrace #0 soma (a=10, b=20) at soma.c:4 #1 0x0000555555555178 in main () at somac.c:10 #+end_example Executando passo a passo até a chamada da função =printf=: #+begin_example (gdb) n 5 return resultado; (gdb) n 6 } (gdb) n main () at soma.c:11 11 printf("Resultado: %d\n", z); (gdb) backtrace #0 main () at soma.c:11 #+end_example Entrando na função =printf= (=glibc=): #+begin_example (gdb) s __printf (format=0x555555556004 "Resultado: %d\n") at ./stdio-common/printf.c:28 warning: 28 ./stdio-common/printf.c: Arquivo ou diretório inexistente #+end_example Observando as chamadas de funções na pilha: #+begin_example (gdb) backtrace #0 __printf (format=0x555555556004 "Resultado: %d\n") at ./stdio-common/printf.c:28 #1 0x0000555555555194 in main () at somac.c:11 #+end_example Continuando a execução até o término: #+begin_example (gdb) c Continuing. Resultado: 30 [Inferior 1 (process 223980) exited normally] #+end_example