534 lines
21 KiB
Org Mode
534 lines
21 KiB
Org Mode
#+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/;
|
|
- Demonstrar o GDB em ação;
|
|
- Apresentar uma lista de comandos essenciais.
|
|
|
|
** 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 (isso, sem falar de seu enorme potencial como
|
|
ferramenta de exploração no aprendizado de sistemas).
|
|
|
|
** 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
|
|
|
|
** 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. |
|