Depurando com o GDB
Aprendendo a usar o depurador GDB do projeto GNU.
O GDB é um depurador de linha de comando que faz parte do projeto GNU. O Mingw-w64 já instala o GDB junto com o GCC, e no Linux ele pode ser instalado pelo pacote gdb:
$ sudo apt install gdb
O GDB pode ser usado para depurar c√≥digo tanto visualizando o Assembly como tamb√©m o c√≥digo-fonte. Para isso √© necess√°rio compilar o bin√°rio adicionando informa√ß√Ķes de depura√ß√£o, com o GCC basta adicionar a op√ß√£o -g3 ao compilar. Exemplo:
$ gcc -g3 test.c -o test
E pode rodar o GDB passando o caminho do bin√°rio assim:
$ gdb ./test
O caminho do binário é opcional. Caso especificado o GDB já inicia com esse binário como alvo para depuração, mas existem comandos do GDB que podem ser usados para escolher um alvo conforme será explicado mais abaixo.
O GDB funciona com comandos, quando voc√™ o inicia ele te apresenta um prompt onde voc√™ pode ir inserindo comandos para executar determinadas a√ß√Ķes. Mais abaixo irei apresentar os principais comandos e como utiliz√°-los.
Esse depurador suporta depurar código de diversas linguagens de programação (incluindo C++, Go e Rust), mas aqui será demonstrado seu uso somente em um código escrito em C. O seguinte código será usado para demonstração:
test.c
#include <stdio.h>
‚Äč
#define DEFINED_VALUE 12345
‚Äč
int add(int a, int b)
{
return a + b;
}
‚Äč
int main(int argc, char **argv)
{
char str[] = "a =";
int x = 8;
‚Äč
printf("%s %d\n", str, add(x, 3));
return 0;
}
E ser√° compilado da seguinte forma:
$ gcc -g3 test.c -o test
A op√ß√£o -g √© usada para adicionar informa√ß√Ķes de depura√ß√£o ao execut√°vel. Esse 3 seria o n√≠vel de informa√ß√Ķes que ser√£o adicionadas, onde 3 √© o maior n√≠vel.
Para mais informa√ß√Ķes consulte a documenta√ß√£o do GCC.

Express√Ķes

Determinadas instru√ß√Ķes do GDB recebem uma express√£o como argumento onde √© poss√≠vel usar qualquer tipo de constante, vari√°vel ou operador da linguagem que est√° sendo depurada (neste caso C). Isso inclui casts, strings literais, macros e at√© mesmo chamadas de fun√ß√Ķes. Logo a express√£o interpretada √© quase id√™ntica a uma express√£o que voc√™ escreveria na linguagem que est√° sendo depurada (no nosso caso C).
Também é possível referenciar o valor de algum registrador na expressão usando o prefixo $, como $rax por exemplo. Na imagem abaixo é uma demonstração usando o comando print:
Saída do GDB ao usar o comando `print`

Comandos

O GDB aceita abrevia√ß√Ķes dos comandos, onde ele identifica o comando a ser executado de acordo com suas primeiras letras ou abrevia√ß√Ķes definidas pelo depurador. Por exemplo o comando breakpoint pode ser executado tamb√©m como break, br ou apenas b.
Ao apertar enter sem digitar nenhum comando o GDB ir√° reexecutar o √ļltimo comando que voc√™ executou.

quit

quit [EXPR]
Finaliza o GDB. A expressão opcional é avaliada e o resultado dela é usado como código de saída. Se a expressão não for passada o GDB sai com código 0.

file

file FILE
Usa o arquivo binário especificado como alvo para depuração. O programa é procurado no diretório atual ou em qualquer caminho registrado na variável de ambiente PATH.

attach e detach

attach <process-id>
detach
O comando attach faz o attach no processo de ID especificado. J√° o comando detach desfaz o attach no processo que est√° atualmente conectado.
Você também pode iniciar a execução do GDB com a opção -p para ele já inicializar fazendo attach em um processo, como em:
$ gdb -p 12345

breakpoint

break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM] [if CONDITION]
Se o comando for executado sem qualquer argumento o breakpoint será adicionado na instrução atual.
LOCATION √© a posi√ß√£o onde o breakpoint deve ser inserido e pode ser o n√ļmero de uma linha, endere√ßo ou posi√ß√£o expl√≠cita.
Ao especificar o n√ļmero da linha, o nome do arquivo e o n√ļmero da linha s√£o separados por :. Se n√£o especificar o nome do arquivo o breakpoint ser√° adicionado a linha do arquivo atual. Exemplos:
(gdb) b 15
(gdb) b test.c:17
Onde o primeiro adicionaria o breakpoint na linha 15 do arquivo atual, e o segundo adicionaria na linha 17 do arquivo test.c.
O endereço pode ser simplesmente o nome de uma função ou então uma expressão, onde nesse caso é necessário usar * como prefixo ao símbolo ou endereço de memória. Como em:
(gdb) b main
(gdb) b *main + 8
(gdb) b *0x12345
No primeiro caso um breakpoint seria adicionado a função main. No segundo caso o endereço da primeira instrução da função main seria somado com 8, e o endereço resultante seria onde o breakpoint seria inserido. Já no terceiro caso o breakpoint seria inserido no endereço 0x12345.
Também é possível especificar para qual thread o breakpoint deve ser inserido, onde por padrão o breakpoint é válido para todas as threads. Exemplo:
(gdb) b add thread 2
Isso adicionaria o breakpoint somente para a thread de ID 2.
√Č poss√≠vel usar o comando info threads para obter a lista de threads e seus n√ļmeros de identifica√ß√£o.
E por fim dá para adicionar uma condição de parada ao breakpoint. Onde CONDITION é uma expressão booleana. Exemplo:
(gdb) b 7 if a == 8
Onde no contexto do nosso c√≥digo de exemplo, a seria o primeiro par√Ęmetro da fun√ß√£o add.

clear

clear [LOCATION]
Remove um breakpoint no local especificado. LOCATION funciona da mesma forma que no comando breakpoint.
Caso LOCATION não seja especificado remove o breakpoint na posição atual.

run

run [arg1, arg2, arg3...]
O comando run inicia (ou reinicia) a execu√ß√£o do programa alvo. Opcionalmente pode-se passar argumentos de linha de comando para o programa. Caso os argumentos n√£o sejam especificados, os mesmos argumentos utilizados na √ļltima execu√ß√£o de run ser√£o utilizados.
Nos argumentos é possível usar o caractere curinga *, ele será expandido pela shell do sistema. Também é possível usar os redirecionadores <, > ou >>.

kill

Finaliza a execução do programa que está sendo depurado.

start, starti

start [arg1, arg2, arg3...]
starti [arg1, arg2, arg3...]
O uso desses dois comandos é idêntico ao uso de run. Porém o comando start inicia a execução do programa parando no começo da função main. Já o starti inicia parando na primeira instrução do programa.

next, nexti

next [N]
nexti [N]
O comando next (ou apenas n) executa uma linha de c√≥digo. Se N for especificado ele executa N linhas de c√≥digo. J√° o comando nexti (ou apenas ni) executa uma ou N instru√ß√Ķes Assembly.
Os dois comandos atuam como um step over, ou seja, n√£o entram em chamadas de procedimentos.

step, stepi

step [N]
stepi [N]
O step (ou s) executa uma ou N linhas de c√≥digo. J√° o stepi (ou si) executa uma ou N instru√ß√Ķes Assembly. Os dois comandos entram em chamadas de procedimentos.

jump

jump LOCATION
Salta (modifica RIP) para o ponto do c√≥digo especificado. Onde LOCATION √© id√™ntico ao caso do comando breakpoint onde √© poss√≠vel especificar um n√ļmero de linha ou endere√ßo.

advance

advance LOCATION
Esse comando continua a execução do programa até o ponto do código especificado, daí para a execução lá. Assim como na instrução jump, o comando advance (ou adv) recebe um LOCATION como argumento.
O comando advance também para quando a função atual retorna.

finish

Executa até o retorno da função atual. Quando a função retorna é criada uma variável (como no caso do comando print) com o valor de retorno da função.

continue

Continua a execução normal do programa.

record e reverse-*

Imagine que m√°gico seria se o depurador pudesse voltar no tempo e desfazer as instru√ß√Ķes executadas no programa, fazendo ele executar de maneira reversa parecido com rebobinar uma fita. Bom, o GDB pode fazer isso.
ūüėé
‚Äč
Quando o programa j√° est√° em execu√ß√£o voc√™ pode executar o comando record full para iniciar a grava√ß√£o das instru√ß√Ķes executadas e record stop para parar de gravar.
Quando há a gravação é possível executar o programa em ordem reversa usando os comandos: reverse-step (rs), reverse-stepi (rsi), reverse-next (rn), reverse-nexti (rni) e reverse-continue (rc).
Esses comandos fazem a mesma coisa que os comandos normais, por√©m executando o programa ao reverso. Cada instru√ß√£o revertida tem suas modifica√ß√Ķes na mem√≥ria ou registradores desfeitas. Conforme demonstra a imagem abaixo.
GDB usando execução reversa.
Outros subcomandos de record s√£o:

record goto

Salta para uma determinada instru√ß√£o que foi gravada. Pode-se usar record goto begin para voltar ao in√≠cio da grava√ß√£o (desfazendo todas as instru√ß√Ķes), record goto end para ir para o final da grava√ß√£o ou record goto N onde N seria o n√ļmero da instru√ß√£o na grava√ß√£o para saltar para ela.

record save <filename>

Salva os logs de execução no arquivo.

record restore <filename>

Restaura os logs de execução a partir do arquivo.

thread

thread <thread-id>
thread apply <thread-id> <command>
thread find <regex>
thread name <thread-name>
O comando thread pode ser usado para trocar entre threads do processo. Você pode usar o comando info threads para listar as threads do processo e obter seus ID. Exemplo:
(gdb) thread 2
Isso trocaria para a thread de ID 2. Esse comando também tem os seguintes subcomandos:

thread apply

Executa um comando na thread especificada.

thread name

Define um nome para a thread atual, facilitando a identificação dela.

thread find

Recebe uma expressão regular como argumento que é usada para listar as threads cujo o nome coincida com a expressão regular. O comando exibe o ID das threads listadas.

print

print[/FMT] [EXPR]
O comando print (ou p) exibe no terminal o resultado da expressão passada como argumento. Opcionalmente pode-se especificar o formato de saída, onde os formatos são os mesmos utilizados no comando x. Exemplo:
(gdb) p/x 15
$1 = 0xf
Repare que a cada execu√ß√£o do comando print ele define uma vari√°vel ($1, $2 etc.) que armazena o resultado da express√£o do comando. Voc√™ tamb√©m pode usar o valor dessas vari√°veis em uma express√£o e assim reaproveitar o resultado de uma execu√ß√£o anterior do comando. Os s√≠mbolos $ e $ se referem aos valores da √ļltima e pen√ļltima execu√ß√£o do comando, respectivamente. Exemplo:
(gdb) p x + $3
Existe tamb√©m o operador bin√°rio @ que pode ser usado para tratar o valor no endere√ßo especificado como uma array. O formato do uso desse operador √© [email protected], passando √† esquerda o primeiro elemento da array.
Onde o tipo de cada elemento da array √© definido de acordo com o tipo do objeto que est√° sendo referenciado. Na imagem abaixo √© demonstrado o uso desse operador para visualizar todo o conte√ļdo da array argv.
Saída do GDB ao usar Artificial Array

printf

printf "format string", ARG1, ARG2, ARG3, ..., ARG
Esse comando pode ser usado de maneira semelhante a fun√ß√£o printf da libc. Cada argumento √© separado por v√≠rgula e o primeiro argumento √© a format string que suporta quase todos os formatos suportados pela fun√ß√£o printf. Os demais argumentos s√£o express√Ķes.
Exemplo de uso:
(gdb) printf "%p\n", $rsp
0x7fffffffdf20

dprintf

dprintf LOCATION, "format string", ARG1, ARG2, ARG3, ..., ARG
Esse comando insere um breakpoint no código onde, toda vez que ele é alcançado, o comando printf é executado e depois a execução continua. O uso desse comando é semelhante ao do comando printf. Exemplo:
(gdb) dprintf 7, "%d + %d\n", a, b
No nosso código de exemplo, isso inseria o dynamic printf na linha 7 que está dentro da função add. Conforme a imagem abaixo demonstra:
Demonstração de uso do dprintf no GDB

x

x[/FMT] ADDRESS
O comando x serve para ver valores na mem√≥ria. O argumento FMT (opcional) √© o n√ļmero de valores a serem exibidos, seguido de uma letra indicando o formato do valor seguido de uma letra que indica o tamanho do valor. Por padr√£o exibe apenas um valor caso o n√ļmero n√£o seja especificado. O formato e tamanho padr√£o √© o mesmo utilizado na √ļltima execu√ß√£o do comando x.
As letras de formato são: o (octal), x (hexadecimal), d (decimal), u (decimal não-sinalizado), t (binário), f (float), a (endereço), i (instrução), c (caractere de 1 byte), s (string) e z (hexadecimal com zeros à esquerda).
Ao usar o formato i ser√° feito o disassembly do c√≥digo no endere√ßo. O n√ļmero de valores √© usado para especificar o n√ļmero de instru√ß√Ķes para fazer o disassembly.
Exemplo:
(gdb) x/x 0x7fffffffdf64
0x7fffffffdf64: 0x003d2061
As letras de tamanho são: b (byte), h (metade de uma palavra), w (palavra) e g (giant, 8 bytes). Na arquitetura x86-64 uma palavra é 32-bit (4 bytes).
Exemplos:
(gdb) x/xb 0x7fffffffdf64
0x7fffffffdf64: 0x61
(gdb) x/4xb 0x7fffffffdf64
0x7fffffffdf64: 0x61 0x20 0x3d 0x00

disassembly

disassembly[/MODIFIER] [ADDRESS]
disassembly[/MODIFIER] start,end
disassembly[/MODIFIER] start,+length
O comando disassembly (ou disas) pode ser usado para exibir o disassembly de uma função ou range de endereço. O argumento ADDRESS (opcional) é uma expressão, sem esse argumento ele faz o disassembly na posição ou função atual.
Tamb√©m √© poss√≠vel especificar um range de endere√ßos para exibir o dissasembly das instru√ß√Ķes, separando o endere√ßo inicial e final por v√≠rgula. Se usar o + no segundo argumento separado por v√≠rgula, ele √© considerado como o tamanho em bytes do range iniciado em start.
Exemplos:
(gdb) disas 0x00005555555551b4,0x00005555555551b9
Dump of assembler code from 0x5555555551b4 to 0x5555555551b9:
0x00005555555551b4 <main+51>: mov $0x3,%esi
End of assembler dump.
(gdb) disas 0x00005555555551b4,+5
Dump of assembler code from 0x5555555551b4 to 0x5555555551b9:
0x00005555555551b4 <main+51>: mov $0x3,%esi
End of assembler dump.
O argumento MODIFIER é uma (ou mais) das seguintes letras:
  • s - Exibe tamb√©m as linhas de c√≥digo correspondentes as instru√ß√Ķes em Assembly.
  • r - Tamb√©m exibe o c√≥digo de m√°quina em hexadecimal.
Exemplo:
(gdb) disas/rs 0x00005555555551b4,+5
Dump of assembler code from 0x5555555551b4 to 0x5555555551b9:
test.c:
15 printf("%s %d\n", str, add(x, 3));
0x00005555555551b4 <main+51>: be 03 00 00 00 mov $0x3,%esi
End of assembler dump.
Por padrão o disassembly é feito em sintaxe AT&T, mas você pode modificar para sintaxe Intel com o comando: set disassembly-flavor intel

list

list
list LINENUM
list FILE:LINENUM
list FUNCTION
list FILE:FUNCTION
list *ADDRESS
Exibe a listagem de código na linha ou início da função especificada. Um endereço também pode ser especificado usando um * como prefixo, as linhas de código correspondentes ao endereço serão exibidas.
Caso list seja executado sem argumentos mais linhas s√£o exibidas a partir da √ļltima linha exibida pela √ļltima execu√ß√£o de list.
O n√ļmero de linhas exibido √© por padr√£o 10, mas esse valor pode ser alterado com o comando set listsize <number-of-lines>.

backtrace

backtrace [COUNT]
O comando backtrace (ou bt) exibe o stack backtrace atual. O argumento COUNT √© o n√ļmero m√°ximo de stack frames que ser√£o exibidos. Se for um n√ļmero negativo exibe os primeiros stack frames.
Exemplo:
(gdb) bt
#0 add (a=8, b=3) at test.c:7
#1 0x00005555555551c0 in main (argc=1, argv=0x7fffffffe068) at test.c:15

frame

frame [FRAME_NUMBER]
Sem argumentos exibe o stack frame selecionado. Caso seja especificado um n√ļmero como argumento, seleciona e exibe o stack frame indicado pelo n√ļmero. Esse n√ļmero pode ser consultado com o comando backtrace.
Esse comando tem os seguintes subcomandos:

frame address

frame address STACK_ADDRESS
Exibe o stack frame no endereço especificado.

frame apply

frame apply COUNT COMMAND
frame apply all COMMAND
frame apply level FRAME_NUMBER COMMAND
O comando frame apply executa o mesmo comando em um ou mais stack frames. Esse subcomando √© √ļtil, por exemplo, para ver o valor das vari√°veis locais que est√£o em uma fun√ß√£o de outro stack frame al√©m do atual.
COUNT √© o n√ļmero de frames onde o comando ser√° executado. Por exemplo frame apply 2 p x executaria o comando print nos √ļltimos 2 frames (o atual e o anterior).
O frame apply all executa o comando em todos os frames. Já o frame apply level executa o comando em um frame específico. exemplo:
(gdb) frame apply level 1 info locals
#1 0x00005555555551c0 in main (argc=1, argv=0x7fffffffe068) at test.c:15
str = "a ="
x = 8

frame function

frame function FUNCTION_NAME
Exibe o stack frame da função especificada.

frame level

frame level FRAME_NUMBER
Exibe o stack frame do n√ļmero especificado.

info

O comando info cont√©m diversos subcomandos para exibir informa√ß√Ķes sobre o programa que est√° sendo depurado. Abaixo ser√° listado apenas os subcomandos principais.

info registers

Exibe os valores dos registradores. Pode-se passar como argumento uma lista (separada por espaço) dos registradores para exibir. Sem argumentos exibe o valor de todos os registradores de propósito geral, registradores de segmento e EFLAGS. Exemplo:
(gdb) info reg rax rbx

info frame

O uso desse subcomando √© semelhante ao uso do comando frame e cont√©m os mesmos subcomandos. A diferen√ßa √© que ele exibe todas as informa√ß√Ķes relacionadas ao stack frame. Enquanto o comando frame apenas exibe informa√ß√Ķes de um ponto de vista de alto-n√≠vel.

info args

info args [NAMEREGEXP]
Exibe os argumentos passados para a função do stack frame atual. Se NAMEREGEXP for especificado exibe apenas os argumentos cujo o nome coincida com a expressão regular.

info locals

info locals [NAMEREGEXP]
Uso idêntico ao de info args só que exibe o valor das variáveis locais.

info functions

info functions [NAMEREGEXP]
Exibe todas as fun√ß√Ķes cujo o nome coincida com a express√£o regular. Se o argumento n√£o for especificado lista todas as fun√ß√Ķes.

info breakpoints

Exibe os breakpoints definidos no programa.

info source

Exibe informa√ß√Ķes sobre o c√≥digo-fonte atual.

info threads

Lista as threads do processo.

display e undisplay

display[/FMT] EXPRESSION
undisplay [NUM]
Esse comando pode ser usado da mesma maneira que o comando print. Ele registra uma expressão para ser exibida a cada vez que a execução do processo faz uma parada. Exemplo:
(gdb) display/7i $rip
Isso exibiria o disassembly de 7 instru√ß√Ķes a partir de RIP a cada passo executado.
Se display for executado sem argumentos ele exibe todas as express√Ķes registradas para auto-display.
Enquanto o comando undisplay remove a express√£o com o n√ļmero especificado. Sem argumentos remove todas as express√Ķes registradas por display.

source

source FILE
Carrega o arquivo especificado e executa os comandos no arquivo como um script.
Quando o GDB inicia ele faz o source autom√°tico do script de nome .gdbinit presente na sua pasta home. Exceto se o GDB for iniciado com a flag --nh.

help

O comando help, sem argumentos, lista as classes de comandos. √Č poss√≠vel rodar help CLASS para obter a lista de comandos daquela classe.
Tamb√©m √© poss√≠vel rodar help COMMAND para obter ajuda para um comando espec√≠fico, pode-se inclusive usar abrevia√ß√Ķes. E tamb√©m √© poss√≠vel obter ajuda para subcomandos, conforme exemplos:
(gdb) help ni
(gdb) help info reg
(gdb) help frame apply level

Text User Interface (TUI)

√Č poss√≠vel usar o GDB com uma interface textual permitindo que seja mais agrad√°vel acompanhar a execu√ß√£o enquanto observa o c√≥digo-fonte. Para isso basta iniciar o GDB com a flag -tui, como em:
$ gdb -tui ./test
GDB Text User Interface

Atalhos de teclado

Atalho de teclado
Descrição
Ctrl+x a
O atalho Ctrl+x a (Ctrl+x seguido da tecla a) alterna para o modo TUI caso tenha iniciado o GDB normalmente.
Ctrl+x 1
Alterna para o layout de janela √ļnica.
Ctrl+x 2
Alterna para o layout de janela dupla. Quando já está no layout de janela dupla o próximo layout com duas janelas é selecionado. Onde é possível exibir código-fonte+Assembly, registradores+Assembly e registradores+código-fonte.
Ctrl+x o
Muda a janela ativa.
Ctrl+x s
Muda para o modo Single Key Mode.
PgUp
Rola a janela ativa uma p√°gina para cima.
PgDn
Rola a janela ativa uma p√°gina para baixo.
‚ÜĎ (Up)
Rola a janela ativa uma linha para cima.
‚Üď (Down)
Rola a janela ativa uma linha para baixo.
‚Üź (Left)
Rola a janela ativa uma coluna para a esquerda.
‚Üí (Right)
Rola a janela ativa uma coluna para a direita.
Ctrl+L
Redesenha a tela.

Single Key Mode

Quando se est√° no modo Single Key √© poss√≠vel executar alguns comandos pressionando uma √ļnica tecla, conforme tabela abaixo:
Tecla
Comando
Nota
c
continue
‚Äč
d
down
‚Äč
f
finish
‚Äč
n
next
‚Äč
o
nexti
"o" de step over.
q
-
Sai do modo Single Key.
r
run
‚Äč
s
step
‚Äč
i
stepi
‚Äč
u
up
‚Äč
v
info locals
"v" de variables.
w
where
Alias para o comando backtrace.
Qualquer outra tecla alterna temporariamente para o modo de comandos. Após um comando ser executado ele retorna para o modo Single Key.
Export as PDF
Copy link
On this page
Express√Ķes
Comandos
quit
file
attach e detach
breakpoint
clear
run
kill
start, starti
next, nexti
step, stepi
jump
advance
finish
continue
record e reverse-*
thread
print
printf
dprintf
x
disassembly
list
backtrace
frame
info
display e undisplay
source
help
Text User Interface (TUI)
Atalhos de teclado
Single Key Mode