diff --git a/gdb-01.org b/gdb-01.org new file mode 100644 index 0000000..83a47b9 --- /dev/null +++ b/gdb-01.org @@ -0,0 +1,743 @@ +#+title: Curso prático de introdução ao GDB +#+author: Blau Araujo +#+email: blau@debxp.org + +* Parte 1: Conceitos Básicos + +** O que é o GDB + +O GDB (/GNU Debugger/) é uma ferramenta de depuração de programas escritos em +diversas linguagens. Ele possibilita a inspeção da execução de um programa +em tempo real ou após uma falha, fornecendo recursos como: + +- Pontos de parada (/breakpoints/); +- Execução passo a passo (/step-by-step/); +- Visualização e modificação de variáveis; +- Análise de memória e registradores; +- Inspeção da pilha de chamadas (/backtrace/); +- Avaliação de expressões em tempo de execução; +- Depuração de múltiplas /threads/; +- Depuração remota (via =gdbserver=). + +Por operar em baixo nível, o GDB oferece uma visão detalhada do comportamento +interno do programa, sendo essencial tanto para o desenvolvimento quanto para +o diagnóstico de erros complexos. + +** Primeiro contato + +Considere o seguinte programa em C (=demo.c=): + +#+begin_src c +#include + +int soma(int a, int b) { + int resultado = a + b; + return resultado; +} + +int main() { + int x = 10; + int y = 20; + int z = soma(x, y); + printf("Resultado: %d\n", z); + return 0; +} +#+end_src + +Compilação com símbolos de depuração: + +#+begin_example +gcc -g -o demo demo.c +#+end_example + +Carregando o binário no GDB: + +#+begin_example +gdb ./demo +#+end_example + +*** Listando o código-fonte + +No GDB, podemos listar o código-fonte (comando =list=): + +#+begin_example +(gdb) list +3 int soma(int a, int b) { +4 int resultado = a + b; +5 return resultado; +6 } +7 +8 int main() { +9 int x = 10; +10 int y = 20; +11 int z = soma(x, y); +12 printf("Resultado: %d\n", z); +#+end_example + +Por padrão, somente 10 linhas são exibidas, mas podemos teclar =Enter= algumas vezes +até que todo o código seja listado. + +#+begin_example +(gdb) +13 return 0; +14 } +#+end_example + +Chagando ao final, podemos reiniciar a listagem com =list .=: + +#+begin_example +(gdb) list . +3 int soma(int a, int b) { +4 int resultado = a + b; +5 return resultado; +6 } +7 +8 int main() { +9 int x = 10; +10 int y = 20; +11 int z = soma(x, y); +12 printf("Resultado: %d\n", z); +#+end_example + +*** Ponto de parada e execução + +Nós podemos definir pontos de parada com o comando =break=: + +#+begin_example +(gdb) break main +Breakpoint 1 at 0x115b: file demo.c, line 9. +#+end_example + +Assim, quando o programa for executado (com o comando =run=), a execução será +pausada nos símbolos definidos como pontos de parada: + +#+begin_example +(gdb) run +Starting program: /home/blau/tmp/gdb/demo +[Thread debugging using libthread_db enabled] +Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". + +Breakpoint 1, main () at demo.c:9 +9 int x = 10; +#+end_example + +Neste exemplo, o ponto de parada é a função =main=, e nós vemos a próxima linha +a ser executada (linha =9=). Para avançar para as próximas linhas, nós podemos +executar o comando =next=: + +#+begin_example +(gdb) next +10 int y = 20; +#+end_example + +A linha =9= foi executada e a próxima será a linha =10=. Se quisermos continuar +executando o comando =next=, basta teclar =Enter= imediatamente em seguida: + +#+begin_example +(gdb) +11 int z = soma(x, y); +#+end_example + +Neste ponto, as variáveis =x= e =y= já foram carregadas e nós podemos conferir +seus valores com o comando =print=: + +#+begin_example +(gdb) print x +$1 = 10 +(gdb) print y +$2 = 20 +#+end_example + +#+begin_quote +O resultado de cada avaliação é armazenado no GDB em uma variável especial +numerada (=$n=) de acordo com a ordem da avaliação. +#+end_quote + +A próxima linha a ser executada será a linha =11=, onde temos a chamada da +função =soma=. Se executarmos =next= novamente, a função será executada e nós +iremos para a linha seguinte na função =main=. Mas nós também podemos entrar +na função =soma= e acompanhar a sua execução passo a passo com o comando +=step=: + +#+begin_example +(gdb) step +soma (a=10, b=20) at demo.c:4 +4 int resultado = a + b; +#+end_example + +Aqui, nós podemos inspecionar os valores de =a=, =b= e do valor inicial de =resultado=, +antes da linha =4= ser executada: + +#+begin_example +(gdb) print a +$3 = 10 +(gdb) print b +$4 = 20 +(gdb) print resultado +$5 = 0 +#+end_example + +Com o comando =next=, nós avançamos na função e já podemos exibir o novo valor +de =resultado=: + +#+begin_example +(gdb) next +5 return resultado; +(gdb) print resultado +$6 = 30 +#+end_example + +Se, a partir daqui, nós quisermos executar todo o restante do programa, +basta executar comando =continue=: + +#+begin_example +(gdb) continue +Continuing. +Resultado: 30 +[Inferior 1 (process 214693) exited normally] +#+end_example + +Para sair do GDB... + +#+begin_example +(gdb) quit +:~$ +#+end_example + +** Instalação + +*** Debian e derivados + +#+begin_example +sudo apt update +sudo apt install gdb +#+end_example + +O pacote =build-essential= instala outras ferramentas úteis para desenvolvimento, +incluindo o =gcc= (/GNU Compiler Collection/), o =make= e as dependências mais comuns. + +#+begin_example +sudo apt install build-essential +#+end_example + +*** Fedora e derivados + +#+begin_example +sudo dnf install gdb +#+end_example + +Para um ambiente completo de desenvolvimento: + +#+begin_example +sudo dnf groupinstall "Development Tools" +#+end_example + +*** Arch Linux e derivados + +#+begin_example +sudo pacman -S gdb +#+end_example + +O grupo =base-devel= contém ferramentas úteis para compilação e depuração: + +#+begin_example +sudo pacman -S base-devel +#+end_example + +*** Verificação da instalação + +Versão: + +#+begin_example +gdb --version +#+end_example + +Ajuda: + +#+begin_example +gdb --help +#+end_example + +** Personalização e configurações de início + +O GDB pode ser configurado por meio de arquivos de inicialização lidos +automaticamente ao iniciar. Esses arquivos permitem predefinir opções úteis, +automatizar tarefas e estender o ambiente de depuração. + +*** Configuração por usuário + +O arquivo =~/.gdbinit=: + +O GDB executa esse arquivo sempre que for iniciado, a menos que seja desativado +com a opção =-nh=. + +Configuração de exemplo: + +#+begin_src gdb +set pagination off # Não pausa a saída do GDB +set confirm off # Não pede confirmação para alguns comandos +set print pretty on # Formata a apresentação de structs e arrays +set history save on # Salva histórico entre seções +set history size 1000 # Tamanho máximo do histórico +set disassembly-flavor intel # Define a sintaxe Intel na desmontagem de binários +#+end_src + +Também pode ser interessante omitir as mensagens de versão no início do GDB, +o que é feito no arquivo =~/.config/gdb/gdbearlyinit=: + +#+begin_example +set startup-quietly on +#+end_example + +#+begin_quote +O arquivo =gdbearlyinit= é carregado antes do GDB executar outros arquivos +de início e só recebe definições que afetam o seu próprio comportamento. +#+end_quote + +*** Configuração por projeto + +Arquivo =.gdbinit= no diretório do projeto (configuração local): + +Se existir um arquivo =.gdbinit= no diretório corrente, o GDB pode executá-lo, +mas isso é bloqueado por padrão: + +#+begin_example +:~/projeto$ gdb +Warning: File ".gdbinit" auto-loading has been declined by your `auto-load safe-path'... +#+end_example + +Para permitir o carregamento, temos que adicionar o caminho do projeto ao +arquivo do usuário: + +#+begin_src sh +echo "add-auto-load-safe-path $(pwd)" >> ~/.gdbinit +#+end_src + +#+begin_quote +Isso é muito utilizado para definir /breakpoints/ automáticos, carregar símbolos +extras, configurar scripts, etc. +#+end_quote + +*** Configuração global + +O arquivo de configuração do sistema, =/etc/gdb/gdbinit=, é lido antes de +=~/.gdbinit= e pode ser usado para definir opções globais em ambientes +compartilhados (ex: laboratórios ou servidores educacionais). + +*** Comandos customizados + +Nos arquivos =.gdbinit=, também é possível definir comandos customizados +com a sintaxe: + +#+begin_example +define COMANDO + LISTA DE COMANDOS +end +document COMANDO + DESCRIÇÃO +end +#+end_example + +*** Scripts em Python + +É possível estender o GDB usando scripts em Python no =.gdbinit=, mas isso +foge do escopo deste curso. + +** Binários ELF, símbolos e códigos-fonte + +Todo arquivo executável pelo sistema é construído em um binário segundo um +formato padrão. No GNU/Linux, esse formato é o *ELF* (/Executable and Linkable Format/), +que inclui no binário: + +- O código de máquina do programa; +- Dados de variáveis globais e estáticas; +- Dados constantes; +- Tabelas de símbolos; +- Informações de depuração (se incluídas). + +Os primeiros bytes de um arquivo ELF contém um cabeçalho com diversas +informações sobre o binário, o que nós podemos listar com o utilitário +=readelf=: + +#+begin_example +:~$ readelf -h demo +Cabeçalho ELF: + Magia: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 + Classe: ELF64 + Dados: complemento 2, little endian + Versão: 1 (actual) + OS/ABI: UNIX - System V + Versão ABI: 0 + Tipo: DYN (Position-Independent Executable file) + Máquina: Advanced Micro Devices X86-64 + Versão: 0x1 + Endereço do ponto de entrada: 0x1050 + Início dos cabeçalhos do programa: 64 (bytes no ficheiro) + Start of section headers: 14944 (bytes no ficheiro) + Bandeiras: 0x0 + Tamanho deste cabeçalho: 64 (bytes) + Tamanho dos cabeçalhos do programa:56 (bytes) + Nº de cabeçalhos do programa: 14 + Tamanho dos cabeçalhos de secção: 64 (bytes) + Nº dos cabeçalhos de secção: 37 + Índice de tabela de cadeias da secção: 36 +#+end_example + +*** Símbolos e o código-fonte + +Símbolos são nomes associados a elementos do programa, como funções e +variáveis. Quando os símbolos de depuração são incluídos no binário +(compilando com =-g=, por exemplo), o GDB é capaz de: + +- Exibir nomes legíveis, em vez de endereços de memória; +- Acompanhar o fluxo do programa com o código-fonte; +- Receber definições de /breakpoints/ por nomes de funções; +- Fazer a associação de códigos binários com as linhas do código-fonte; +- Acessar nomes de variáveis, tipos, estruturas, etc. + +Nós podemos verificar se o programa foi compilado com os símbolos de +depuração com o utilitário =file=... + +Sem a opção =-g=: + +#+begin_example +:~$ gcc -o demo demo.c +:~$ file demo +demo: ELF 64-bit LSB pie executable [...] not stripped +#+end_example + +Com a opção =-g=: + +#+begin_example +:~$ gcc -g -o demo demo.c +:~$ file demo +demo: ELF 64-bit LSB pie executable [...] with debug_info, not stripped +#+end_example + +Repare que, desta vez, nós temos a informação =with debug_info= no final +da linha. + +Nós também podemos verificar se há símbolos de depuração a partir das +informações no formato ELF: + +#+begin_example +:~$ readelf -S demo | grep debug + [28] .debug_aranges PROGBITS 0000000000000000 00003037 + [29] .debug_info PROGBITS 0000000000000000 00003067 + [30] .debug_abbrev PROGBITS 0000000000000000 00003180 + [31] .debug_line PROGBITS 0000000000000000 0000324a + [32] .debug_str PROGBITS 0000000000000000 000032ba + [33] .debug_line_str PROGBITS 0000000000000000 00003367 +#+end_example + +Mas o próprio GDB informa se há símbolos de depuração ao iniciar... + +Sem a opção =-g=: + +#+begin_example +:~$ gcc -o demo demo.c +:~$ gdb demo +Reading symbols from demo... +(No debugging symbols found in demo) +#+end_example + +Com a opção =-g=: + +#+begin_example +:~$ gcc -g -o demo demo.c +:~$ gdb demo +Reading symbols from demo... +#+end_example + +** 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 + + ; saída limpa com código 0 + 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 ; saída limpa com código 0 +15 mov rax, 60 ; syscall: exit +16 xor rdi, rdi ; status 0 +17 syscall +#+end_example + +Executando: + +#+begin_example +(gdb) r +Starting program: /home/blau/tmp/gdb/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) +15 mov rax, 60 ; syscall: exit +#+end_example + +Observando o no 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 (=somac.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 somac somac.c +#+end_example + +Abrindo com o GDB e definindo o ponto de parada na função =soma=: + +#+begin_example +:~$ gdb ./somac +Reading symbols from ./somac... +(gdb) b soma +Breakpoint 1 at 0x1143: file somac.c, line 4. +#+end_example + +Iniciando a execução do programa: + +#+begin_example +(gdb) r +Starting program: /home/blau/tmp/gdb/somac +[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 somac.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 somac.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 somac.c:11 +11 printf("Resultado: %d\n", z); +(gdb) backtrace +#0 main () at somac.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 + +** Interface TUI + + + + +