Tutorial de depuração do GDB para iniciantes
- 1985
- 258
- Robert Wunsch DVM
Você já pode ser versado em depurar scripts Bash (veja como depurar scripts Bash se você ainda não estiver familiarizado com a depuração da Bash), mas como depurar C ou C++? Vamos explorar.
O GDB é um utilitário de depuração Linux de longa data e abrangente, que levaria muitos anos para aprender se você quisesse conhecer bem a ferramenta. No entanto, mesmo para iniciantes, a ferramenta pode ser muito poderosa e útil quando se trata de depurar C ou C++.
Por exemplo, se você é um engenheiro de controle de qualidade e gostaria de depurar um programa C e binário em que sua equipe está trabalhando e trava, você pode usar o GDB para obter um backtrace (uma lista de funções chamadas - como uma árvore - que eventualmente levou ao acidente). Ou, se você é um desenvolvedor C ou C ++ e acabou de introduzir um bug no seu código, pode usar o GDB para depurar variáveis, código e mais! Vamos mergulhar!
Neste tutorial, você aprenderá:
- Como instalar e usar o utilitário gdb na linha de comando em bash
- Como fazer depuração básica do GDB usando o console do GDB e o prompt
- Saiba mais sobre a saída detalhada do GDB produz
Requisitos de software e convenções usadas
Categoria | Requisitos, convenções ou versão de software usada |
---|---|
Sistema | Independente da distribuição Linux |
Programas | Linhas de comando Bash e GDB, sistema baseado em Linux |
Outro | O utilitário GDB pode ser instalado usando os comandos fornecidos abaixo |
Convenções | # - requer que o Linux -Commands seja executado com privilégios de raiz diretamente como usuário root ou por uso de sudo comando$-exige que o Linux-Commands seja executado como um usuário não privilegiado regular |
Configurando o GDB e um programa de teste
Para este artigo, veremos um pequeno teste.c
Programa na linguagem de desenvolvimento C, que introduz um erro de divisão por zero no código. O código é um pouco mais longo do que o necessário na vida real (algumas linhas faria e nenhum uso de função seria necessário), mas isso foi feito de propósito para destacar como os nomes de funções podem ser vistos claramente dentro do gdb ao depurar a depuração.
Vamos primeiro instalar as ferramentas que exigiremos usando Instalação sudo apt
(ou Instalação sudo yum
Se você usar uma distribuição baseada em Red Hat):
sudo apt install gdb Build-essencial GCC
O Construção-essencial
e GCC
vão ajudá -lo a compilar o teste.c
C Programa em seu sistema.
Em seguida, vamos definir o teste.c
script como segue (você pode copiar e colar o seguinte em seu editor favorito e salvar o arquivo como teste.c
):
int real_calc (int a, int b) int c; c = a/b; retornar 0; int calc () int a; int b; a = 13; b = 0; real_calc (a, b); retornar 0; int main () calc (); retornar 0;
Algumas notas sobre este script: você pode ver que quando o principal
função será iniciada (o principal
A função é sempre a função principal e a primeira chamada quando você inicia o binário compilado, isso faz parte do padrão C), ela imediatamente chama a função Calc
, que por sua vez chamam atual_calc
Depois de definir algumas variáveis a
e b
para 13
e 0
respectivamente.
Executando nosso script e configurando os principais despejos
Vamos agora compilar este script usando GCC
e executar o mesmo:
Teste $ GCC -GGDB.teste C -O.out $ ./teste.Exceção de ponto flutuante (núcleo despejado)
O -GGDB
opção para GCC
garantirá que nossa sessão de depuração usando o GDB seja amigável; adiciona informações de depuração específicas do GDB ao teste.fora
binário. Nomeamos este arquivo binário de saída usando o -o
opção para GCC
, E como entrada, temos nosso script teste.c
.
Quando executamos o script, recebemos imediatamente uma mensagem enigmática Exceção de ponto flutuante (núcleo despejado)
. A parte que estamos interessados no momento é o núcleo despejado
mensagem. Se você não vir esta mensagem (ou se vê a mensagem, mas não conseguir localizar o arquivo principal), poderá configurar melhor dumping do núcleo da seguinte forma:
se ! Grep -qi 'kernel.Core_pattern ' /etc /sysctl.conf; Então Sudo sh -c 'echo "kernel.core_pattern = núcleo.%p.%você.%s.%e.%t ">> /etc /sysctl.conf 'sudo sysctl -p fi ulimit -C Unlimited
Aqui estamos primeiro certificando -se de que não há padrão de núcleo do kernel Linux (núcleo.Core_pattern
) Configuração feita ainda em /etc/sysctl.conf
(O arquivo de configuração para definir variáveis do sistema no Ubuntu e em outros sistemas operacionais) e - não foi encontrado nenhum padrão de núcleo existente - adicione um prático padrão de nome de arquivo central (essencial.%p.%você.%s.%e.%t
) para o mesmo arquivo.
O sysctl -p
comando (a ser executado como raiz, daí o sudo
) Próximo a seguir, o arquivo é imediatamente recarregado sem exigir uma reinicialização. Para mais informações sobre o padrão principal, você pode ver o Nomeação de arquivos de despejo principal seção que pode ser acessada usando o Man núcleo
comando.
finalmente, o Ulimit -C Unlimited
o comando simplesmente define o tamanho máximo do arquivo principal como ilimitado
Para esta sessão. Esta configuração é não persistente entre os reinicialização. Para torná -lo permanente, você pode fazer:
sudo bash -c "gato < /etc/security/limits.conf * soft core unlimited * hard core unlimited EOF
Que vai adicionar * Soft Core Unlimited
e * núcleo duro ilimitado
para /etc/segurança/limites.conf
, garantindo que não haja limites para despejos de núcleo.
Quando você agora reexecira o teste.fora
arquivo você deve ver o núcleo despejado
mensagem e você poderá ver um arquivo principal (com o padrão principal especificado), como segue:
$ ls núcleo.1341870.1000.8.teste.fora.1598867712 Teste.C Teste.fora
Vamos examinar a seguir os metadados do arquivo principal:
$ FILE CORE.1341870.1000.8.teste.fora.1598867712 núcleo.1341870.1000.8.teste.fora.1598867712: ELF Arquivo Core LSB de 64 bits, x86-64, versão 1 (SYSV), estilo SVR4, de './teste.OUT ', UID real: 1000, UID efetivo: 1000, GID real: 1000, GID efetivo: 1000, Execfn:'./teste.fora ', plataforma:' x86_64 '
Podemos ver que este é um arquivo principal de 64 bits, qual ID de usuário estava em uso, qual era a plataforma e, finalmente, qual executável foi usado. Também podemos ver no nome do arquivo (.8.
) que foi um sinal 8 que encerrou o programa. O sinal 8 é Sigfpe, uma exceção de ponto flutuante. O GDB mais tarde nos mostrará que esta é uma exceção aritmética.
Usando o GDB para analisar o depósito de núcleo
Vamos abrir o arquivo principal com o GDB e assumir por um segundo que não sabemos o que aconteceu (se você é um desenvolvedor experiente, você já pode ter visto o bug real na fonte!):
$ GDB ./teste.fora ./essencial.1341870.1000.8.teste.fora.1598867712 GNU GDB (Ubuntu 9.1-0ubuntu1) 9.1 Copyright (C) 2020 Free Software Foundation, Inc. Licença GPLV3+: GNU GPL Versão 3 ou mais tarde Este é um software livre: você está livre para alterar e redistribuí -lo. Não há garantia, na medida permitida por lei. Digite "Mostrar cópia" e "Mostrar garantia" para obter detalhes. Este GDB foi configurado como "x86_64-linux-gnu". Digite "Show Configuration" para detalhes de configuração. Para instruções de relatório de bugs, consulte: . Encontre o manual do GDB e outros recursos de documentação online em: . Para obter ajuda, digite "Ajuda". Digite "Appropos Word" para procurar comandos relacionados a "word"… lendo símbolos de ./teste.fora… [novo LWP 1341870] O núcleo foi gerado por './teste.fora'. Programa encerrado com sinalizador sigfpe, exceção aritmética. #0 0x000056468844813b em real_calc (a = 13, b = 0) no teste.C: 3 3 C = A/B; (GDB)
Como você pode ver, na primeira linha que chamamos GDB
Com a primeira opção, nosso binário e como segunda opção, o arquivo principal. Simplesmente lembre -se binário e núcleo. Em seguida, vemos o GDB inicializar e somos apresentados com algumas informações.
Se você vir um Aviso: tamanho inesperado da seção
.Reg-XState/1341870 'no arquivo principal.'Ou mensagem semelhante, você pode ignorá -la por enquanto.
Vemos que o dump do núcleo foi gerado por teste.fora
e foram informados de que o sinal era um sigfpe, exceção aritmética. Ótimo; Já sabemos que algo está errado com nossa matemática, e talvez não com nosso código!
Em seguida, vemos o quadro (por favor pense sobre um quadro
como um procedimento
no código por enquanto) em que o programa terminou: quadro #0
. GDB adiciona todos os tipos de informações úteis a isso: o endereço de memória, o nome do procedimento real_calc
, quais eram nossos valores variáveis, e mesmo em uma linha (3
) de qual arquivo (teste.c
) o problema aconteceu.
Em seguida, vemos a linha de código (linha 3
) novamente, desta vez com o código real (c = a/b;
) daquela linha incluída. Finalmente somos apresentados com um prompt de GDB.
O problema provavelmente já está muito claro até agora; nós fizemos c = a/b
, ou com variáveis preenchidas C = 13/0
. Mas o ser humano não pode se dividir por zero, e um computador também não pode. Como ninguém disse a um computador como se dividir por zero, ocorreu uma exceção, uma exceção aritmética, uma exceção / erro de ponto flutuante.
Traslamento
Então, vamos ver o que mais podemos descobrir sobre o GDB. Vejamos alguns comandos básicos. O punho é o que você mais provavelmente usará com mais frequência: bt
:
(GDB) BT #0 0x000056468844813b em real_calc (a = 13, b = 0) em teste.C: 3 #1 0x0000564688448171 em calc () no teste.C: 12 #2 0x000056468844818a em main () no teste.C: 17
Este comando é uma abreviação para Backtrace
e basicamente nos dá um traço do estado atual (procedimento após procedimento chamado) do programa. Pense nisso como uma ordem inversa de coisas que aconteceram; quadro #0
(o primeiro quadro) é a última função que estava sendo executada pelo programa quando travou e quadro #2
foi o primeiro quadro chamado quando o programa foi iniciado.
Podemos assim analisar o que aconteceu: o programa começou e principal()
foi chamado automaticamente. Próximo, principal()
chamado calc ()
(e podemos confirmar isso no código -fonte acima) e finalmente calc ()
chamado real_calc
E lá as coisas deram errado.
Bem, podemos ver cada linha em que algo aconteceu. Por exemplo, o real_calc ()
A função foi chamada da linha 12 em teste.c
. Observe que não é calc ()
que foi chamado da linha 12, mas sim real_calc ()
o que faz sentido; teste.C acabou executando para a linha 12 até onde calc ()
a função está preocupada, pois é aqui que o calc ()
função chamada real_calc ()
.
Dica do usuário elétrico: se você usar vários threads, poderá usar o comando Tópico Aplique todo o BT
Para obter um backtrace para todos os tópicos que estavam em execução quando o programa travou!
Inspeção de quadros
Se quisermos, podemos inspecionar cada quadro, o código -fonte correspondente (se estiver disponível) e cada variável passo a passo:
(GDB) F 2 #2 0x000055FA2323318A em main () no teste.C: 17 17 Calc (); (GDB) Lista 12 real_calc (a, b); 13 retornar 0; 14 15 16 int main () 17 calc (); 18 retorno 0; 19 (gdb) p a sem símbolo "a" no contexto atual.
Aqui 'pularemos' quadro 2 usando o f 2
comando. f
é uma mão curta para o quadro
comando. Em seguida, listamos o código -fonte usando o lista
comando e finalmente tente imprimir (usando o p
comando taquigrafia) o valor do a
variável, que falha, como neste momento a
ainda não foi definido neste ponto do código; Observe que estamos trabalhando na linha 17 na função principal()
, e o contexto real em que existia dentro dos limites desta função/quadro.
Observe que a função de exibição do código -fonte, incluindo parte do código -fonte exibido nas saídas anteriores acima, está disponível apenas se o código -fonte real estiver disponível.
Aqui, imediatamente também vemos um Gotcha; Se o código -fonte for diferente, o código do qual o binário foi compilado pode ser facilmente enganado; A saída pode mostrar fonte não aplicável / alterada. GDB faz não Verifique se existe uma correspondência de revisão do código -fonte! É, portanto, de suma importância que você use exatamente a mesma revisão do código -fonte da qual seu binário foi compilado.
Uma alternativa é não usar o código -fonte e simplesmente depurar uma situação específica em uma função específica, usando uma revisão mais recente do código -fonte. Isso geralmente acontece para desenvolvedores e depuradores avançados que provavelmente não precisam de muitas pistas sobre onde o problema pode estar em uma determinada função e com valores variáveis fornecidos.
Vamos a seguir o quadro 1:
(GDB) F 1 #1 0x000055FA23233171 em calc () no teste.c: 12 12 real_calc (a, b); (GDB) Lista 7 int calc () 8 int a; 9 int b; 10 a = 13; 11 b = 0; 12 real_calc (a, b); 13 retornar 0; 14 15 16 int main ()
Aqui podemos ver novamente muitas informações sendo produzidas pelo GDB, que ajudarão o desenvolvedor a depurar a questão em questão. Já que estamos agora em Calc
(na linha 12), e já inicializamos e subsequentemente definimos as variáveis a
e b
para 13
e 0
respectivamente, agora podemos imprimir seus valores:
(GDB) P A $ 1 = 13 (GDB) P B $ 2 = 0 (GDB) P C Sem símbolo "C" no contexto atual. (GDB) P A/B Divisão por zero
Observe que quando tentamos imprimir o valor de c
, ainda falha como novamente c
não está definido até este ponto (os desenvolvedores podem falar sobre 'neste contexto') ainda.
Finalmente, olhamos em quadro #0
, Nosso quadro de travamento:
(GDB) F 0 #0 0x000055FA2323313B em real_calc (a = 13, b = 0) em teste.C: 3 3 C = A/B; (GDB) P A $ 3 = 13 (GDB) P B $ 4 = 0 (GDB) P C $ 5 = 22010
Tudo auto -evidente, exceto pelo valor relatado para c
. Observe que tínhamos definido a variável c
, mas ainda não havia dado um valor inicial. Como tal c
é realmente indefinido (e não foi preenchido pela equação c = a/b
No entanto, como aquele falhou) e o valor resultante provavelmente foi lido em algum espaço de endereço para o qual a variável c
foi atribuído (e esse espaço de memória ainda não foi inicializado/limpo).
Conclusão
Ótimo. Conseguimos depurar um dump central para um programa C e apoiamos o básico da depuração do GDB nesse meio tempo. Se você é um engenheiro de controle de qualidade, ou um desenvolvedor júnior, e você entendeu e aprendeu tudo neste tutorial bem, você já está um pouco à frente da maioria dos engenheiros de controle de qualidade e, potencialmente, outros desenvolvedores ao seu redor.
E da próxima vez que você assistir Star Trek e Capitão Janeway ou Capitão Picard querem 'despejar o núcleo', você fará um sorriso mais amplo, com certeza. Desfrute de depuração do seu próximo núcleo despejado e deixe um comentário abaixo com suas aventuras de depuração.
Tutoriais do Linux relacionados:
- Uma introdução à automação, ferramentas e técnicas do Linux
- Coisas para instalar no Ubuntu 20.04
- Mastering Bash Script Loops
- Coisas para fazer depois de instalar o Ubuntu 20.04 fossa focal linux
- Mint 20: Melhor que o Ubuntu e o Microsoft Windows?
- Ubuntu 20.04 Guia
- Coisas para instalar no Ubuntu 22.04
- Loops aninhados em scripts de basquete
- Como fazer bota dupla kali linux e windows 10
- Sistema Linux Hung? Como escapar para a linha de comando e…