objdump
Autor: Rafael | Publicado: 2009-02-25T00:27:00-03:00

No post passado, disse que era extremamente difícil depurar um código compilado em C e que roda no simulador do ArchC. Mas como é possível depurar, se o código que roda no simulador é em linguagem de montagem, e o simulador não irá ler informações de debug e sair imprimindo as linhas do código C? Se tivermos o endereço da instrução, podemos facilmente obter em que linha de código C o programa está. Essa conversão "endereço da instrução" -> "linha de código" é muito útil também quando qualquer programa imprime um erro fatal e finaliza dizendo apenas o endereço com a instrução ofensiva, ou imprime um "backtrace" (olhando na pilha os endereços de retorno e portanto oferecendo um histórico de como chegamos até a função atual onde o erro ocorre) com diversos endereços na pilha, mas sem informação de qual linha de código estão as chamadas de função. Isto ocorre, por exemplo, quando você compila um programa com a flag -D_FORTIFY_SOURCE=2 (nos gcc atuais isso é o padrão quando usado -O2). Esta flag liga um verificador de buffer overflow. Quando você escreve em um campo de uma estrutura e ultrapassa seu tamanho (geralmente com uma função de cópia de string), por exemplo, o programa é encerrado com estes amigáveis dizeres:overflow detected ***: ./programa terminated ======= Backtrace: ========= /lib/libc.so.6(__fortify_fail+0x48)[0x22b6f8] /lib/libc.so.6[0x229810] /lib/libc.so.6[0x228f08] /lib/libc.so.6(_IO_default_xsputn+0xc8)[0x19f608] /lib/libc.so.6(__printf_fp+0x14c2)[0x177d92] /lib/libc.so.6(_IO_vfprintf+0x3ca)[0x171a0a] /lib/libc.so.6(__vsprintf_chk+0xa7)[0x228fb7] /lib/libc.so.6(__sprintf_chk+0x2d)[0x228efd]  ./programa[0x8048db7]No caso, se olharmos o backtrace, percebemos que quem chamou o sprintf() com um buffer pequeno (que sofreu overflow) foi uma função dentro de "./programa", endereço 0x8048db7. Bom, isso aparentemente não diz nada. Nestes casos, se tivermos o programa compilado com símbolos de debug, uma opção é usar o objdump. readelf e objdump são programas correlatos que extraem informações de arquivos objeto ELF (executáveis, relocáveis ou dinâmicos). readelf é uma versão que não usa a libbfd, e com uma formatação muito melhor. Entretanto, para disassembly, o objdump deve ser usado, pois só ele oferece essa opção. Assim, usamos o parâmetro "-S" denominado "intermix source and assembly". Esta opção irá não só realizar o disassembly, mas também irá imprimir as linhas correlatas do código quando informações de debug DWARF estiverem disponíveis no arquivo objeto (ou seja, quando você compilou com gcc -g e não usou a infeliz opção -strip em um ligador, que ocasionaria a remoção destas informações).objdump -S ./programa &amp;> logA saída produzida geralmente é bem extensa (pois imprime todo o código e todas as instruções), por isso costumo salvar em um arquivo separado. Você pode alternativamente usar qualquer outro método mais sofisticado para tratar a saída e encontrar o endereço que queremos. Analisando o arquivo log, onde salvei a saída, encontraremos algo como:struct buf {  char campo[10];} buffer;sprintf(buffer.campo, "1234567890"); ...    8048db7:     ea000019        b 0x44c ; sprintfOu seja, o objdump mistura as linhas do código C com as instruções relacionadas e o mais importante é que sempre fornece o endereço das instruções.Agora temos o código relacionado às instruções nas redondezas daquele endereço com a instrução que chama por sprintf (a instrução b é um branch no ARM, como um call no x86). Agora fica fácil: um grep  recursivo procurando por esta string (as linhas de código c) na árvore do fonte encontrará o arquivo .c específico, e lá você usará um find e encontrará a linha específica, e provavelmente também o motivo do bug.