Tutorial de depuração do GDB para iniciantes

Tutorial de depuração do GDB para iniciantes

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
Tutorial de depuração do GDB para iniciantes

Requisitos de software e convenções usadas

Requisitos de software e convenções de linha de comando Linux
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…