Por que o GDB precisa do executável e do core dump?

11

Estou depurando usando core dumps, e observe que o gdb precisa que você forneça o executável e o core dump. Por que é isso? Se o core dump contiver toda a memória que o processo usa, o executável não está contido no core dump? Talvez não haja garantia de que todo o exe seja carregado na memória (os executáveis ​​individuais geralmente não são tão grandes assim) ou talvez o dump do núcleo não contenha toda a memória relevante, afinal? É para os símbolos (talvez eles não sejam carregados na memória normalmente)?


fonte
1
O executável contém as informações de símbolos, como observado na documentação gdb ...
Thomas Dickey
1
Surpreendentemente, nenhuma resposta (exceto a que acabei de adicionar) menciona o formato DWARF
Basile Starynkevitch

Respostas:

15

O core dump é apenas o dump da pegada de memória de seus programas; se você souber onde estava tudo, poderá usá-lo.

Você usa o executável porque explica onde (em termos de endereços lógicos) as coisas estão localizadas na memória, ou seja, o arquivo principal.

Se você usar um comando, objdumpele fará o dump dos metadados sobre o objeto executável que você está investigando. Usando um objeto executável chamado a.out como exemplo.

objdump -h a.outdespeja apenas as informações do cabeçalho, você verá seções nomeadas, por exemplo. .data ou .bss ou .text (há muito mais). Eles informam ao carregador do kernel onde, no objeto, várias seções podem ser encontradas e onde, no espaço de endereço do processo, a seção deve ser carregada e, para algumas seções (por exemplo, .data .text), o que deve ser carregado. (a seção .bss não contém nenhum dado no arquivo, mas se refere à quantidade de memória a ser reservada no processo para dados não inicializados, é preenchida com zeros).

O layout do arquivo de objeto executável está em conformidade com um ELF padrão.

objdump -x a.out - despeja tudo

Se o objeto executável ainda contiver suas tabelas de símbolos (ele não foi retirado - man stripe você costumava -ggerar geração de depuração para gcc assumir a compilação da fonte de CA), poderá examinar o conteúdo principal por nomes de símbolos, por exemplo, se você tivesse uma variável / buffer chamado inputLine no seu código-fonte, você pode usar esse nome gdbpara examinar seu conteúdo. isto é gdb, saberia o deslocamento desde o início do segmento de dados inicializado de seus programas onde o inputLine inicia e o tamanho dessa variável.

Leitura adicional Artigo 1 , Artigo 2 e para a especificação Executable and Linking Format (ELF) .


Atualizar após o comentário @mirabilos abaixo.

Mas se usar a tabela de símbolos como em

$ gdb --batch -s a.out -c core -q -ex "x buf1"

Produz

 0x601060 <buf1>:    0x72617453

e depois não usar a tabela de símbolos e examinar o endereço diretamente em,

$ gdb --batch -c core -q -ex "x 0x601060"

Produz

0x601060:   0x72617453

Examinei a memória diretamente sem usar a tabela de símbolos no segundo comando.


Vejo também que a resposta de @ user580082 adiciona mais explicações e fará uma votação positiva.

X Tian
fonte
6
Nunca ouvi falar em "seção básica da pilha". .bss é (historicamente) "bloco iniciado pelo símbolo" e praticamente "dados unitizados", enquanto .data é "dados inicializados" e texto (não .code) é usado para armazenar o código da máquina. Não há seção de pilha em um binário, pois as pilhas são criadas em tempo de execução.
Jlliagre
“Se você sabe onde estava tudo, poderia simplesmente usar isso” também não é verdade, porque nem tudo no programa está necessariamente incluído na pegada.
mirabilos
1
@ jlliagre você está correto, por engano chamei .text .code (porque estava pensando em uma explicação enquanto escrevia a resposta) - atualizado. Por engano, pensei em bss incorretamente por nome e atualizei minha resposta, mas evitei * Bloco iniciado por Symbol, pois não acho que isso realmente agregue à equação, e expliquei que ele é usado como dados não inicializados, que eram nossos entendimento comum. Obrigado. Agradecemos seu comentário para corrigir esta postagem.
X Tian
4

O arquivo principal é uma captura instantânea da imagem da pilha, mapeamentos de memória e registros no momento do encerramento do processo. O conteúdo pode ser manipulado conforme fornecido na página principal do manual . Por padrão, mapeamentos privados, mapeamentos compartilhados e informações de cabeçalho ELF são despejados no arquivo principal.

Chegando à sua pergunta , o motivo pelo qual o gdb requer executável é que ele não simula a execução. Ao ler e interpretar as instruções binárias, como o valgrind, ele se torna o pai do processo, a fim de controlar o comportamento do processo durante a execução. Tempo. Ele usa o arquivo principal para determinar os mapeamentos de memória e o estado do processo do processador durante a falha.

No Linux, os processos pai podem obter informações adicionais sobre seus filhos, em particular a capacidade de rastreá-los, permitindo que o depurador acesse informações de baixo nível do processo, como ler / gravar sua memória, registros, alterar os mapeamentos de sinais, interromper sua execução etc.

Você entenderá o requisito do executável, apesar de ter o arquivo principal mais uma vez depois de ler como funciona qualquer depurador.

enzima
fonte
1

(além de outras boas respostas)

Nos modernos sistemas Linux (e em muitos sistemas Unix), as informações de depuração (incluindo metadados sobre os tipos de símbolos, localização do código-fonte, tipo de variáveis, etc etc ....) estão no formato DWARF e ficam dentro do executável do ELF ( ou bibliotecas compartilhadas ELF) quando compiladas com alguma -gopção. Eu recomendo compilar programas para serem depurados -g3 -O0e, talvez, -fno-inlinese estiver usando um GCC recente ; no entanto, com o GCC você pode até compilar informações de otimização e depuração, por exemplo, com -O2 -g1, embora as informações de depuração possam, nesse caso, ser um pouco "imprecisas" (isso pode ajudar um pouco a capturar alguns Heisenbugs impertinentes ).

É bastante sensato evitar colocar essas informações nos arquivos principais , porque você pode ter muitos arquivos principais diferentes (imagine um software amplamente usado com muitos usuários fazendo relatórios de erros, a maioria deles com um coredespejo) para o mesmo executável. Os arquivos core (5) também são despejados pelo kernel, que não deve se importar com a existência de seções DWARF nos executáveis elf (5) (porque essas seções não são mapeadas no espaço de endereço virtual do processo com falha que despejou o núcleo em algum sinal ( 7) ). Existe até a possibilidade de colocar as informações de depuração em arquivos separados (fora do executável).

BTW, GDB pode ser dolorosamente usado para descarregar um core debug para executáveis sem qualquer informação de depuração. Mas você praticamente depura no nível do código da máquina (não no nível simbólico fornecido pelas linguagens de programação e seus compiladores).

Basile Starynkevitch
fonte