2025-04-26 11:16:54 -03:00
#+title : Curso prático de introdução ao GDB
#+author : Blau Araujo
#+email : blau@debxp.org
* 1. Primeiro contato guiado
** Objetivos
- Entender o que é o GDB;
- Conhecer o conceito de /depuração/ ;
2025-04-28 10:51:02 -03:00
- Demonstrar o GDB em ação;
- Apresentar uma lista de comandos essenciais.
2025-04-26 11:16:54 -03:00
** 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
2025-04-27 11:52:21 -03:00
o diagnóstico de erros complexos (isso, sem falar de seu enorme potencial como
ferramenta de exploração no aprendizado de sistemas).
2025-04-26 11:16:54 -03:00
** O que é depurar (/debugar/)
Depuração (ou _/debugging/_ ) é o processo de identificar, analisar e corrigir
erros (/bugs/ ) em um programa ou sistema. Durante a depuração, o programador
examina o comportamento do código para entender onde e por que ele não funciona
como esperado. O objetivo é encontrar a origem dos problemas e corrigi-los,
até que o software opere corretamente.
Sem o auxílio de ferramentas especializadas, a depuração pode ser feita, por
exemplo:
- Com uma revisão cuidadosa do que está escrito no código-fonte;
- Inserindo a exibição de mensagens em pontos suspeitos do código para avaliar
variáveis (/"o valor está correto neste ponto?"/ ) e o próprio fluxo de execução
(/"o programa chegou a este ponto?"/ );
- Com a análise manual de algoritmos, seguindo a execução do programa no código
tendo em mente alguns exemplos de entrada.
Mas existem ferramentas (como o GDB) que auxiliam o processo de depuração que,
sem alterar diretamente o código, oferecem formas de:
- Definir pontos de parada (/breakpoints/ );
- Monitorar a alteração de valores em variáveis (/whatchpoints/ );
- Executar o programa passo a passo;
- Examinar dados em endereços específicos da memória através de /símbolos/ ;
- Examinar o conteúdo da pilha de chamadas de sistema;
- Explorar informações sobre o processo do programa na memória...
Entre várias outras possibilidades.
Também existem ferramentas especializadas em aspectos e métodos específicos da
depuração, como...
- *Valgrind:* para examinar a memória e detectar vazamentos e acessos inválidos.
- *Linters e analisadores:* para analisar como o código-fonte foi escrito e apontar
erros de sintaxe, de estilo e violações de especificações e convenções da linguagem
em uso sem, sequer, compilar (se for o caso) e executar o código.
*** Uma nota sobre depuração preventiva
Alguns /ambientes integrados de desenvolvimento/ (IDE) e editores de código
oferecem meios para sinalizar erros de sintaxe (a falta de um =;= em C, por
exemplo) ou destaques coloridos para diferentes tipos de elementos da
linguagem. Muitos deles integram-se com /protocolos de servidores de linguagem/
(LSP) para funcionarem como /linters/ e analisadores de código em tempo
real (ou seja, durante a digitação do código), o que antecipa e ajuda a
evitar potenciais causas de erros.
** Uma pequena demonstração
Considere o seguinte programa em C (=demo.c= ):
#+begin_src c
#include <stdio.h >
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
#+begin_quote
Sem os símbolos de depuração, nós teríamos que descobrir quais seriam os
endereços de dados em variáveis e de outros elementos do código para inspecionar
seus valores e comportamentos.
#+end_quote
*** Iniciando o GDB para depuração
O GDB pode ser iniciado de várias formas para depurar programas. A mais comum,
porém, é através da passagem do caminho de um binário executável como argumento
na sua invocação na linha de comandos.
No nosso exemplo:
#+begin_example
gdb ./demo
#+end_example
Sem a opção =-q= (/quiet/ ) ou sem configurações de início, o GDB exibe informações
de versão e /copyright/ antes das mensagens relativas à depuração, propriamente
dita, e do prompt de seu shell de comandos...
#+begin_example
:~$ gdb ./demo
GNU gdb (Debian 16.3-1) 16.3
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html >
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 "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/ >.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/ >.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./demo...
(gdb)
#+end_example
Apesar da poluição visual, essa mensagem padrão pode ser útil para iniciantes,
especialmente este trecho sobre ajuda:
#+begin_example
For help, type "help".
Type "apropos word" to search for commands related to "word"...
#+end_example
Ou seja...
- Com o comando =help= (ou apenas =h= ), nós teremos uma lista dos tópicos de
ajuda (classes de comandos) e as instruções de uso do próprio comando
=help= ;
- Com o comando =apropos PALAVRA= , nós podemos buscar comandos que casem com
=PALAVRA= , que é interpretada como uma expressão regular (REGEX).
*** Terminando o GDB
Para sair do GDB, nós podemos teclar =Ctrl+D= ou executar =quit= (ou apenas =q= ) no
prompt do shell de comandos:
#+begin_example
(gdb) quit
#+end_example
*** Omitindo a mensagem de início
A forma mais simples de omitir a mensagem de início é utilizando a opção =-q=
na invocação do GDB:
#+begin_example
:~$ gdb ./demo
Reading symbols from ./demo...
(gdb)
#+end_example
Desta forma, a primeira mensagem relevante para a depuração é a única a ser
exibida antes do prompt:
#+begin_example
Reading symbols from ./demo...
#+end_example
Ela diz que o nosso binário foi compilado com símbolos para depuração e que
esses símbolos foram lidos e registrados. Mas, vamos sair e compilar novamente
o programa para ver o que aconteceria...
#+begin_example
(gdb) q
:~$ gcc -o demo demo.c
:~$ gdb ./demo
Reading symbols from ./demo...
(No debugging symbols found in ./demo)
(gdb)
#+end_example
Desta vez, sem os símbolos de depuração, nós tivemos a mensagem:
#+begin_example
Reading symbols from ./demo...
(No debugging symbols found in ./demo)
#+end_example
*** Uma nota sobre compilação com símbolos de depuração
Normalmente, os projetos são compilados para duas finalidades: desenvolvimento
e publicação (/release/ ). É na compilação em desenvolvimento que os símbolos de
depuração podem ser úteis. Embora isso não cause prejuízos de desempenho,
binários compilados e publicados com símbolos de depuração resultam em arquivos
maiores e, dependendo do caso, podem expor informações internas sensíveis.
Isso não costuma ser relevante no contexto do Software Livre, já que o acesso
aos fontes deve ser garantido, mas existem casos de programas escritos para
uso interno em alguma cadeia de produção que vão requerer mais cuidados.
#+begin_quote
É possível compilar o código com os símbolos de depuração e, depois, removê-los
do binário que será distribuído(=strip -g= ), ou ainda, gerar e armazenar
cópias dos símbolos em arquivos separados (=objcopy --only-keep-debug= ), mas isso
foge do nosso escopo de interesses.
#+end_quote
Sendo assim, vamos sair e compilar novamente o nosso programa com os
símbolos de depuração.
#+begin_example
(gdb) q
:~$ gcc -g -o demo demo.c
:~$ gdb ./demo
Reading symbols from ./demo...
(gdb)
#+end_example
*** Listando o código-fonte
No GDB, podemos listar o código-fonte com o 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
2025-04-27 11:52:21 -03:00
** Opções de início do GDB
| Comando | Descrição |
|--------------------------------+---------------------------------------------------------------------------------------|
| =gdb --help= | Listas todas as opções de linha de comando com uma breve explicação. |
| =gdb= | Iniciar o GDB sem arquivos para depurar. |
| =gdb PROGRAMA= | Inciar o GDB para depurar =PROGRAMA=. |
| =gdb PROGRAMA DESPEJO= | Inciar o GDB para depurar =PROGRAMA= e salvar o conteúdo da memória no arquivo =DESPEJO=. |
| =gdb --args PROGRAMA ARGUMENTOS= | Iniciar o GDB para depurar =PROGRAMA= passando =ARGUMENTOS=. |
| =gdb PROGRAMA -d DIR= | Iniciar GDB para depurar =PROGRAMA= utilizando =DIR= para localizar os fontes. |
** Comandos Básicos
As próximas tabelas listam os comandos mais relevantes para quem está começando
a aprender a utilizar o GDB, mas tenha sempre em mente que há muitos outros comandos
e funcionalidades.
*** Sair do GDB
| Comando | Descrição |
|-----------+---------------------------------------------|
| =quit= ou =q= | Sair do GDB |
| =Ctrl+D= | Terminar o shell de comandos (sair do GDB). |
*** Informações
| Comando | Descrição |
|---------------------+----------------------------------------------------------------------------|
| =help= ou =h= | Exibe ajuda e tópicos relacionados a um dado comando. |
| =apropos REGEX= | Busca comandos segundo a expressão regular =REGEX=. |
| =show DEF= | Exibe informações sobre a definição de configuração =DEF= do depurador. |
| =print=, =inspect= ou =p= | Imprime o valor de uma expressão. |
| =info=, =inf= ou =i= | Exibe informações relacionadas ao programa sendo depurado. |
| =x/FORMATO ENDEREÇO= | Examina o conteúdo da memória em um dado =ENDEREÇO= no =FORMATO= especificado. |
*** Arquivos
| Comando | Descrição |
|----------------+-------------------------------------------------------------------------|
| =file ARQUIVO= | Utiliza =ARQUIVO= como caminho do executável e fonte de símbolos. |
| =exec ARQUIVO= | Utiliza =ARQUIVO= apenas como caminho do executável a ser depurado. |
| =symbol ARQUIVO= | Utiliza =ARQUIVO= apenas como fonte de símbolos. |
| =dir DIR= | Adiciona =DIR= ao início da lista de caminhos de busca por códigos-fonte. |
*** Execução
| Comando | Descrição |
|---------------+-------------------------------------------------------------------------------------------------|
| =run= ou =r= | Inicia a execução do programa com uma lista opcional de argumentos. |
| =kill= | Mata a execução do programa sendo depurado. |
| =continue= ou =c= | Continua a execução após uma condição de parada. |
| =step= ou =s= | Executa a próxima linha do programa, mesmo que esteja numa função ou sub-rotina. |
| =next= ou =n= | Executa a próxima linha do código sem entrar em funções ou sub-rotinas. |
| =jump= ou =j= | Salta a execução do programa para uma localização dada na forma de um endereço ou de uma linha. |
*** Pontos de parada
| Comando | Descrição |
|---------------+---------------------------------------------------------------------------------------|
| =break= ou =b= | Define o ponto de parada em um dado endereço, número de linha ou símbolo. |
| =watch EXP= | Define uma parada para observar alterações de valores na expressão =EXP=. |
| =catch EVENTO= | Define uma parada na ocorrência de um dado =EVENTO=. |
| =i breakpoints= | Lista informações sobre todos os pontos de parada. |
| =del [IDs]= | Deleta todos os pontos de parada ou os pontos de parada identificados pelo número =ID=. |
*** Inspeção da pilha de execução
| Comando | Descrição |
|-------------------------+-------------------------------------------------------------------------------------------------------------------------------|
| =backtrace= ou =bt= | Exibe todos os quadros de chamadas de funções na pilha. |
| =info frame [SUBCOMANDO]= | Exibe informações sobre o quadro de pilha selecionado ou, com =SUBCOMANDOS=, possibilita a especificação de um modo de seleção. |
*** Informações sobre processos
Sintaxe geral:
#+begin_example
info proc [SUBCOMANDO][PID]
#+end_example
Onde:
- =SUBCOMANDO= : uma informação específica sobre um dado processo.
- =PID= : número de identificação de um processo qualquer.
Se =PID= não for informado, será utilizado o PID do programa em depuração.
| Comando | Descrição |
|--------------------------+---------------------------------------------------------------------------------------|
| =info proc [PID]= | Exibe a linha de comando, o diretório corrente e o caminho do executável do processo. |
| =info proc all [PID]= | Exibe todas as informações disponíveis sobre o processo. |
| =info proc files [PID]= | Lista todos os arquivos abertos pelo processo. |
| =info proc mappings [PID]= | Lista todas as regiões de memória mapeadas para o processo. |
*** Modos de exibição
| Comando | Descrição |
|-------------------------------+----------------------------------------------------------------------------------------------------|
| =tui enable= | Entra no modo TUI. |
| =tui disable= | Sai do modo TUI. |
| =layout asm= | Entra no modo TUI com a janela da desmontagem do programa em assembly. |
| =layout src= | Entra no modo TUI com a janela do código-fonte do programa. |
| =layout regs= | Entra no modo TUI com as janelas de registradores e de código-fonte ou de desmontagem do programa. |
| =layout split= | Entra no modo TUI com as janelas de código-fonte e desmontagem do programa. |
| =layout next= | Muda para o próximo layout TUI. |
| =layout prev= | Muda para o layout TUI anterior. |
| =tui new-layout NOME DEFINIÇÃO= | Define um novo layout customizado. |
Notas:
- Nós podemos alternar o modo TUI com o atalho =C-x a= ;
- Todos os comandos =layout ...= são abreviações de =tui layout ...= ;
- Todos os layouts TUI padrão são compostos por janelas, uma barra de status e uma janela de comandos;
- Nós podemos circular pelas janelas do modo TUI com o atalho =C-x o= ;
- Os layouts customizados não são salvos entre sessões do GDB, mas podem ser definidos nos seus arquivos de início.
Exemplo de layout customizado:
#+begin_example
(gdb) tui new-layout only-regs regs 1 status 0 cmd 1
(gdb) layout
List of tui layout subcommands:
tui layout asm -- Apply the "asm" layout.
tui layout next -- Apply the next TUI layout.
tui layout only-regs -- Apply the "only-regs" layout.
tui layout prev -- Apply the previous TUI layout.
tui layout regs -- Apply the TUI register layout.
tui layout split -- Apply the "split" layout.
tui layout src -- Apply the "src" layout.
[...]
(gdb)
#+end_example
Outras combinações de teclas para o modo TUI:
| Teclas | Descrição |
|--------+------------------------------------------------------|
| =C-x 1= | No layout /split/, exibir apenas a janela selecionada. |
| =C-x 2= | Utiliza pelo menos duas janelas no modo TUI. |
| =C-l= | Redesenha as janelas no modo TUI. |