diff --git a/gdb-01.org b/gdb-01.org deleted file mode 100644 index 83a47b9..0000000 --- a/gdb-01.org +++ /dev/null @@ -1,743 +0,0 @@ -#+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 - - - - -