Original:
Este é um guia muito rápido e básico destinado a ajudá-lo a começar com o Depurador GNU, gdb, a partir da linha de comando em um terminal. Muitas vezes o gdb é executado por meio de um IDE, mas muitas pessoas por aí evitam IDEs por uma variedade de razões, e este tutorial é para você!
Novamente, este é apenas um guia para começar. Há muito, mas MUITO mais para aprender sobre o que o depurador faz do que está escrito nestes poucos parágrafos. Consulte suas páginas "man" ou os recursos online listados abaixo para obter mais informações.
Este tutorial destina-se a ser lido na ordem, até, mas não incluindo, a seção "Miscelânea".
Você precisa instruir seu compilador a compilar seu código com informações de depuração simbólicas incluídas. Aqui está como fazer isso com o gcc, usando a opção -g:
$ gcc -g hello.c -o hello $ g++ -g hello.cpp -o hello
Depois de fazer isso, você deve ser capaz de visualizar listagens de programas no depurador.
Confira a Documentação Oficial do GDB para mais informações do que você pode imaginar!
Além disso, um bom front-end GNU GDB é o DDD, o DataDisplayDebugger.
Guia Rápido do Beej's para GDB por Brian "Beej Jorgensen" Hall está licenciado sob uma Licença Creative Commons Atribuição-NãoComercial-SemDerivações 3.0 Estados Unidos.
Comecemos pelo princípio: você pode digitar help em qualquer prompt do gdb e obter mais informações. Além disso, você pode digitar quit para sair do depurador. Por fim, apenas pressionar RETURN irá repetir o último comando inserido. Agora vamos começar!
Há várias maneiras de iniciar o depurador (por exemplo, se você estivesse em um IDE, poderia iniciá-lo com um modo específico que não é tão amigável para humanos), mas mencionarei duas delas aqui: modo de console simples e modo com GUI. A GUI é melhor, mas vamos rapidamente cobrir a mais simples e iniciar um programa chamado hello no depurador:
$ gdb hello GNU gdb 6.8 Copyright (C) 2008 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 "i486-slackware-linux"... (gdb) run Starting program: /home/beej/hello Hello, world! Program exited normally. (gdb)
A última linha é o prompt do gdb, esperando que você diga o que fazer. Digite r ou run para executar o programa. (O gdb permite que você abrevie comandos até que eles se tornem ambíguos.)
Para começar no modo de GUI e altamente recomendado, inicie o depurador com gdb -tui. (Para muitos dos exemplos abaixo, mostro a saída do modo de console simples do gdb, mas na vida real eu uso exclusivamente o modo TUI.)
E aqui está uma captura de tela do que você verá, aproximadamente:
Todos os comandos normais do gdb funcionarão no modo de GUI e, adicionalmente, as teclas de seta e as teclas pgup/pgdown rolarão a janela de origem (quando ela estiver com foco, o que acontece por padrão). Além disso, você pode alterar qual arquivo ou função é exibido na janela de origem fornecendo o comando list com um local como argumento, por exemplo, "list hello.c:5" para trazer o arquivo hello.c na linha 5. (Veja "Breakpoints", abaixo, para exemplos de locais — os mesmos locais que funcionam com breakpoints funcionarão com o comando list). Como nota adicional, list também funciona no modo de console simples.
Agora, observe que passamos o nome do executável na linha de comando. Outra opção que você tem é simplesmente iniciar o gdb sem mais nada na linha de comando e, em seguida, dar o comando file hello, e isso fará com que o executável "hello" seja carregado.
Argumentos da linha de comando! E se você precisar colocar algo em argv no seu programa? Passe-os como argumentos para o comando run quando você iniciar a execução:
$ gdb hello GNU gdb 6.8 Copyright (C) 2008 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 "i486-slackware-linux"... (gdb) run arg1 arg2 Starting program: /home/beej/hello arg1 arg2 Hello, world! Program exited normally. (gdb)
Observe onde diz "Starting Program", acima, mostra os argumentos "arg1" e "arg2" sendo passados para "hello".
Apenas iniciar o depurador para executar o programa diretamente não é muito útil — precisamos interromper a execução e entrar no modo de passo a passo.
Primeiro, antes de emitir o comando run, você precisa definir um breakpoint em algum lugar onde deseja parar. Você usa o comando break ou b, e especifica um local, que pode ser um nome de função, um número de linha ou um arquivo de origem e número de linha. Estes são exemplos de locais, que são usados por vários outros comandos, bem como break:
break main | Interrompe no início da função main() |
break 5 | Interrompe na linha 5 do arquivo atual |
break hello.c:5 | Interrompe na linha 5 de hello.c |
Então, para este teste, vamos definir um breakpoint em main() e iniciar o programa:
$ gdb hello GNU gdb 6.8 Copyright (C) 2008 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 "i486-slackware-linux"... (gdb) b main Breakpoint 1 at 0x8048395: file hello.c, line 5. (gdb) r Starting program: /home/beej/hello Breakpoint 1, main () at hello.c:5 5 printf("Hello, world!\n"); (gdb)
Como você pode ver, chegamos à função main() e a execução foi interrompida no breakpoint que definimos lá. Se você estiver executando no modo de console simples, o gdb imprimirá a linha que será executada em seguida. Se estiver executando no modo de GUI, a linha que será executada em seguida será destacada na janela de origem.
Para listar os breakpoints atuais, use o comando info, assim: "info breakpoints" (ou o mais curto "i b"):
(gdb) b main Breakpoint 1 at 0x8048395: file hello.c, line 5. (gdb) i b Num Type Disp Enb Address What 1 breakpoint keep y 0x08048395 in main at hello.c:5
Para remover um breakpoint, use o comando clear com a localização do breakpoint. Você também pode remover um breakpoint pelo número com o comando delete.
Além disso, você pode habilitar enable ou desabilitar disable pontos de interrupção, embora esses dois comandos aceitem um número de breakpoint como argumento, não uma localização! O status habilitado/desabilitado de um breakpoint é visível na coluna "Enb" na lista de pontos de interrupção.
(gdb) i b Num Type Disp Enb Address What 1 breakpoint keep y 0x08048395 in main at hello.c:5 (gdb) disable 1 (gdb) i b Num Type Disp Enb Address What 1 breakpoint keep n 0x08048395 in main at hello.c:5 (gdb) clear main Deleted breakpoint 1 (gdb) i b No breakpoints or watchpoints.
Depois que a execução é interrompida em um breakpoint, você pode instruir o depurador a fazer algumas coisas. Vamos começar com o comando next (ou n). Este comando move você para a próxima instrução na função atual (ou retorna para o chamador da função se você tiver ultrapassado o final da função). Aqui está uma execução de exemplo; lembre-se de que o gdb está imprimindo a linha que será executada em seguida logo antes do prompt "(gdb)". Observe também que quando executamos next na linha do printf(), vemos a saída aparecer.
(gdb) b main Breakpoint 1 at 0x8048395: file hello.c, line 5. (gdb) r Starting program: /home/beej/hello Breakpoint 1, main () at hello.c:5 5 printf("Hello, world!\n"); (gdb) next Hello, world! 7 return 0; (gdb) next 8 } (gdb) next 0xb7d6c6a5 in __libc_start_main () from /lib/libc.so.6 (gdb) next Single stepping until exit from function __libc_start_main, which has no line number information. Program exited normally. (gdb)
(Aquela coisa estranha no final sobre __libc_start_main() mostra que havia outra função que chamou sua função main()! Ela não foi compilada com informações de depuração, então não podemos ver o código-fonte, mas ainda podemos percorrê-la — o que fazemos — e o programa termina normalmente.)
Agora, observe que next pula chamadas de função. Isso não significa que a função não seja chamada; significa que next executará a função até o fim e depois o devolverá para a próxima linha em sua função atual.
E se você tiver uma função na qual deseja entrar a partir de sua função atual e rastreá-la linha por linha? Use o comando step (ou s) para fazer isso. Funciona da mesma forma que next, exceto que entra nas funções.
Vamos dizer que você esteja cansado de passar passo a passo e só queira que o programa seja executado novamente. Use o comando continue (ou c) para continuar a execução.
E se o programa estiver em execução, mas você esqueceu de definir breakpoints? Você pode pressionar CTRL-C e isso irá parar o programa onde quer que ele esteja e retorná-lo para um prompt "(gdb)". Nesse ponto, você pode configurar um breakpoint adequado em algum lugar e continuar continue até esse breakpoint.
Um atalho final é que apenas pressionar RETURN repetirá o último comando inserido; isso economizará o trabalho de digitar next repetidamente.
Se você tem algumas variáveis que deseja inspecionar durante a execução, você pode exibir display elas, mas apenas se a variável estiver atualmente no escopo. Cada vez que você passar o código, o valor da variável será exibido (se estiver no escopo).
(A saída a seguir está sem a saída do código-fonte entre as linhas para maior clareza — é o que você veria no modo de GUI. Imagine que está vendo a barra de destaque se movendo pelo código-fonte enquanto executa isso:)
(gdb) b main Breakpoint 1 at 0x8048365: file hello.c, line 5. (gdb) r Starting program: /home/beej/hello Breakpoint 1, main () at hello.c:5 (gdb) disp i 1: i = -1207447872 (gdb) next 1: i = 1 (gdb) next 1: i = 1 (gdb) next 1: i = 2 (gdb) next 1: i = 2 (gdb) next 1: i = 4 (gdb) next 1: i = 4 (gdb) next 1: i = 4 (gdb)
O número à esquerda de "i", acima, é o número de exibição da variável. Use este número para não exibir undisplay a variável. Se você esquecer os números de exibição, você pode digitar info display para obtê-los:
(gdb) b main Breakpoint 1 at 0x8048365: file hello.c, line 5. (gdb) r Starting program: /home/beej/hello Breakpoint 1, main () at hello.c:5 (gdb) display i 1: i = -1207447872 (gdb) info display Auto-display expressions now in effect: Num Enb Expression 1: y i (gdb) undisplay 1 (gdb)
Se você apenas quiser saber o valor de uma variável rapidamente, você pode imprimir print ela. Aqui vemos que o valor de "i" é 40:
(gdb) print i $1 = 40 (gdb)
(O "$" com o número depois dele significa algo, mas não é importante para iniciantes.)
Também há um comando útil printf que você pode usar para formatar melhor sua saída, se desejar:
(gdb) printf "%d\n", i 40 (gdb) printf "%08X\n", i 00000028 (gdb)
Este é o tipo de coisa que não se encaixa realmente nas seções anteriores, mas é divertido o suficiente para listar em algum lugar.
O comando backtrace (ou bt) irá mostrar a stack de chamadas da função atual, com a função atual no topo e os chamadores em ordem abaixo dela:
(gdb) backtrace #0 subsubfunction () at hello.c:5 #1 0x080483a7 in subfunction () at hello.c:10 #2 0x080483cf in main () at hello.c:16 (gdb)
Digite help stack para mais informações sobre o que você pode fazer com isso.
Para sair da função atual e retornar à função chamadora, use o comando finish.
Para avançar uma única instrução assembly, use o comando stepi.
Para continuar até uma localização específica, use o comando advance, especificando uma localização como aquelas mostradas na seção "Breakpoints", acima. Aqui está um exemplo que avança da localização atual até que a função subsubfunction() seja chamada:
Breakpoint 1, main () at hello.c:15 15 printf("Hello, world!\n"); (gdb) advance subsubfunction Hello, world! subsubfunction () at hello.c:5 5 printf("Deepest!\n"); (gdb)
advance é apenas uma abreviação para "continuar até este breakpoint temporário".
O comando jump funciona exatamente como continue, exceto que ele leva como argumento uma localização para saltar. (Veja a seção "Breakpoints", acima, para mais informações sobre localizações.)
Se você precisa parar no destino do salto, defina um breakpoint lá primeiro.
Você pode usar o comando set variable com uma expressão para avaliar, e isso permite que você altere o valor de uma variável durante a execução. Você também pode abreviar isso usando apenas set com uma expressão entre parênteses depois:
Breakpoint 1, main () at hello.c:15 15 int i = 10; (gdb) print i $1 = -1208234304 (gdb) set (i = 20) (gdb) print i $2 = 20 (gdb) set variable i = 40 (gdb) print i $3 = 40 (gdb)
Isso, juntamente com o comando jump, pode ajudá-lo a repetir seções de código sem reiniciar o programa.
Os watchpoints de hardware são breakpoints especiais que serão acionados sempre que uma expressão mudar. Muitas vezes, você apenas deseja saber quando uma variável muda (é escrita), e para isso você pode usar o comando watch:
Breakpoint 1, main () at hello.c:5 5 int i = 1; (gdb) watch i Hardware watchpoint 2: i (gdb) continue Continuing. Hardware watchpoint 2: i Old value = -1208361280 New value = 2 main () at hello.c:7 7 while (i < 100) { (gdb) continue Continuing. Hardware watchpoint 2: i Old value = 2 New value = 3 main () at hello.c:7 7 while (i < 100) { (gdb)
Observe que watch recebe uma expressão como argumento, então você pode colocar um nome de variável lá, ou algo mais complexo como *(p+5) ou a[15]. Eu até tentei com expressões condicionais como i > 10, mas tive resultados mistos.
Você pode obter uma lista de breakpoints com info break ou info watch, e você pode excluí-los pelo número com o comando delete.
Finalmente, você pode usar rwatch para detectar quando uma variável é lida, e você pode usar awatch para detectar quando uma variável é lida ou escrita.
Se o seu programa já estiver em execução e você quiser pará-lo e depurá-lo, primeiro você precisará do ID do processo (PID), que será um número. (Obtenha-o do comando ps do Unix.) Em seguida, você usará o comando attach com o PID para anexar (e interromper) o programa em execução.
Para isso, você pode simplesmente iniciar o gdb sem argumentos.
No seguinte exemplo completo, você notará algumas coisas. Primeiro, eu anexo ao processo em execução, e ele me informa que está em alguma função chamada __nanosleep_nocancel(), o que não é muito surpreendente, já que chamei sleep() no meu código. De fato, pedir um backtrace mostra exatamente esta pilha de chamadas. Então eu digo finish algumas vezes para voltar até main().
$ gdb GNU gdb 6.8 Copyright (C) 2008 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 "i486-slackware-linux". (gdb) attach 3490 Attaching to process 3490 Reading symbols from /home/beej/hello...done. Reading symbols from /lib/libsafe.so.2...done. Loaded symbols for /lib/libsafe.so.2 Reading symbols from /lib/libc.so.6...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/libdl.so.2...done. Loaded symbols for /lib/libdl.so.2 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 0xb7eab21b in __nanosleep_nocancel () from /lib/libc.so.6 (gdb) backtrace #0 0xb7eab21b in __nanosleep_nocancel () from /lib/libc.so.6 #1 0xb7eab05f in sleep () from /lib/libc.so.6 #2 0x080483ab in main () at hello.c:10 (gdb) finish Run till exit from #0 0xb7eab21b in __nanosleep_nocancel () from /lib/libc.so.6 0xb7eab05f in sleep () from /lib/libc.so.6 (gdb) finish Run till exit from #0 0xb7eab05f in sleep () from /lib/libc.so.6 0x080483ab in main () at hello.c:10 10 sleep(1); (gdb) list 5 { 6 int i = 1; 7 8 while (i < 60) { 9 i++; 10 sleep(1); 11 } 12 13 return 0; 14 } (gdb) print i $1 = 19 (gdb) quit The program is running. Quit anyway (and detach it)? (y or n) y Detaching from program: /home/beej/hello, process 3490
Observe que quando volto para main(), eu imprimo o valor de i e é 19 — porque, neste caso, o programa está em execução há 19 segundos, e i é incrementado uma vez por segundo.
Depois de sairmos do depurador e desanexarmos do programa, o programa volta a ser executado normalmente.
Misture isso com set variable, acima, e você terá um grande poder!
Vamos dizer que você construa e execute um programa, e ele gere um coredump por algum motivo:
$ cc -g -o foo foo.c $ ./foo Segmentation fault (core dumped)
Isso significa que um arquivo core (com uma captura de memória do momento do crash) foi criado com o nome "core". Se você não estiver recebendo um arquivo core (ou seja, apenas diz "Segmentation fault" e nenhum arquivo core é criado), pode ser que seu ulimit esteja definido muito baixo; tente digitar ulimit -c unlimited no prompt do seu shell.
Você pode iniciar o gdb com a opção -c para especificar um arquivo core:
$ gdb -tui -c core foo
E, se estiver no modo TUI, você será recebido com uma tela de informações, informando por que o programa foi encerrado ("sinal 11, Segmentation fault"), e o destaque estará na linha problemática. (No modo de terminal simplificado, a linha problemática é impressa.)
Neste exemplo, eu imprimo a variável que está causando o problema. De fato, ela é NULL:
Mesmo que você não tenha todo o código-fonte, muitas vezes é útil obter um backtrace a partir do ponto em que o programa travou.
No modo TUI, você pode obter uma lista das janelas existentes com o comando info win. Em seguida, você pode alterar qual janela está em foco com o comando focus (ou fs). focus aceita como argumento um nome de janela, ou "prev" ou "next". Os nomes de janela válidos são "SRC" (janela de código-fonte), "CMD" (janela de comando), "REGS" (janela de registradores) e "ASM" (janela de assembly). Veja a próxima seção para saber como usar essas outras janelas.
Observe que quando a janela SRC está em foco, as teclas de seta moverão o código-fonte, mas quando a janela CMD está em foco, as teclas de seta selecionarão os comandos anteriores e seguintes no histórico de comandos. (Para que conste, os comandos para mover a janela SRC uma linha ou uma página são +, -, < e >.)
(gdb) info win SRC (36 lines) <has focus> CMD (18 lines) (gdb) fs next Focus set to CMD window. (gdb) info win SRC (36 lines) CMD (18 lines) <has focus> (gdb) fs SRC Focus set to SRC window. (gdb)
(Os nomes de janelas não distinguem maiúsculas de minúsculas.)
O comando winheight (ou wh) define a altura de uma janela específica, mas tive pouca sorte em fazer isso funcionar bem.
No modo TUI, o comando layout controla quais janelas você vê. Além disso, o tui reg permite controlar a janela de registradores e a abrirá se ainda não estiver aberta.
Os comandos são:
layout src | Layout padrão - código-fonte no topo, janela de comando na parte inferior |
layout asm | Semelhante ao layout "src", exceto que é exibida uma janela com assembly no topo |
layout split | Três janelas: código-fonte no topo, assembly no meio e comando na parte inferior |
layout reg | Abre a janela de registradores sobre o código-fonte ou assembly, o que quer que tenha sido aberto por último |
tui reg general | Mostra os registradores gerais |
tui reg float | Mostra os registradores de ponto flutuante |
tui reg system | Mostra os registradores do "sistema" |
tui reg next | Mostra a próxima página de registradores - isso é importante porque pode haver páginas de registradores que não estão nos conjuntos "general", "float" ou "system" |
Aqui está uma captura de tela interessante para aguçar seu apetite, mostrando o código fonte e o assembly no modo "split":
O código assembly vem em dois sabores em máquinas Intel: Intel e AT&T. Você pode definir qual deles aparece na janela de disassembly com set disassembly-flavor. Os valores válidos são "intel" e "att". Se você já tiver a janela de assembly aberta, terá que fechá-la e reabri-la (layout src seguido de layout split, por exemplo).
Para exibir registradoress no modo de console simples, digite info registers para os registradores inteiros, ou info all-registers para mostrar tudo.
Você está pensando, "Uau, isso é muito legal, mas eu poderia criar uma interface gráfica sensacional para essa coisa que funcionaria muito melhor! Como faço isso?"
O GDB suporta o que chama de "intérprete de interface de máquina", ou GDB/MI. O intérprete é selecionado na linha de comando do gdb com a opção --interpreter.
Basicamente, você irá iniciar o gdb e ler comandos e resultados para e dele (provavelmente usando pipes). Bastante simples.
Veja a documentação do GDB para todos os detalhes.
Os parâmetros dos comandos estão em itálico. Parâmetros opcionais estão entre colchetes. Todos os comandos podem ser abreviados até se tornarem ambíguos.
Esta lista é muito muito incompleta e mostra apenas coisas discutidas neste tutorial!
Comandos de Ajuda | |
help comando | Obter ajuda sobre um determinado comando |
apropos palavra-chave | Pesquisar ajuda para uma palavra-chave específica |
Iniciando e Encerrando | |
gdb [-tui] [-c core] [nome_do_executável] | (Comando Unix) Iniciar o gdb em um executável ou de forma independente; especifique "-tui" para iniciar a GUI TUI; especifique "-c" com o nome de um arquivo de core para ver onde ocorreu um travamento |
run [arg1] [arg2] [...] | Executar o programa atualmente carregado com os argumentos da linha de comando fornecidos |
quit | Sair do depurador |
file nome_do_executável | Carregar um arquivo executável pelo nome |
Breakpoints e Watchpoints | |
break localização | Definir um breakpoints em uma localização, número de linha ou arquivo (por exemplo, "main", "5" ou "hello.c:23") |
watch expressão | Interromper quando uma variável é escrita |
rwatch expressão | Interromper quando uma variável é lida |
awatch expressão | Interromper quando uma variável é escrita ou lida |
info break | Mostrar informações e números de breakpoint e watchpoint |
info watch | O mesmo que info break |
clear localização | Limpar um breakpoint de uma localização |
delete num | Excluir um breakpoint ou watchpoint pelo número |
Passo a Passo e Execução | |
next | Executar até a próxima linha desta função |
step | Entrar na função nesta linha, se possível |
stepi | Executar uma única instrução assembly |
continue | Continuar a execução a partir daqui |
CTRL-C | Parar a execução, onde quer que esteja |
finish | Executar até o final da função atual |
advance localização | Avançar para uma localização, número de linha ou arquivo (por exemplo, "algumafuncao", "5" ou "hello.c:23") |
jump localização | Igual a continue, exceto que salta para uma localização específica primeiro. |
Examinando e Modificando Variáveis | |
display expressão | Exibir o valor de uma variável ou expressão a cada passo do programa - a expressão deve fazer sentido no escopo atual |
info display | Mostrar uma lista de expressões atualmente sendo exibidas e seus números |
undisplay num | Parar de exibir uma expressão identificada pelo seu número (veja info display) |
print expressão | Imprimir o valor de uma variável ou expressão |
printf formatstr lista_de_expressões | Fazer alguma saída formatada com printf() por exemplo printf "i = %d, p = %s\n", i, p |
set variable expressão | Definir uma variável para um valor, por exemplo set variable x=20 |
set (expressão) | Funciona como set variable |
Comandos de Janela | |
info win | Mostra informações da janela atual |
focus nomedajanela | Define o foco para uma janela específica por nome ("SRC", "CMD", "ASM" ou "REG") ou por posição ("next" ou "prev") |
fs | Alias para focus |
layout tipo | Define o layout da janela ("src", "asm", "split" ou "reg") |
tui reg tipo | Define o layout da janela de registradores ("general", "float", "system" ou "next") |
winheight val | Define a altura da janela (ou um valor absoluto, ou um valor relativo precedido por "+" ou "-") |
wh | Alias para winheight |
set disassembly-flavor flavor | Define o estilo da disassembly. Em máquinas Intel, os valores válidos são intel e att |
Comandos Diversos | |
RETURN | Pressione RETURN para repetir o último comando |
backtrace | Mostra a stack atual |
bt | Alias para backtrace |
attach pid | Anexar a um processo em execução pelo seu PID |
info registers | Mostra registradores inteiros na tela |
info all-registers | Mostra todos os registradores na tela |
Direitos autorais 2009 Brian "Beej Jorgensen" Hall <beej@beej.us>