Como desmontar uma única função usando objdump?


91

Tenho um binário instalado em meu sistema e gostaria de ver a desmontagem de uma determinada função. De preferência usando objdump, mas outras soluções também seriam aceitáveis.

Com essas perguntas , aprendi que posso desmontar parte do código se apenas souber os endereços de limite. Com essa resposta , aprendi como transformar meus símbolos de depuração de divisão de volta em um único arquivo.

Mas mesmo operando naquele único arquivo, e até mesmo desmontando todo o código (ou seja, sem endereço de início ou parada, mas -dparâmetro simples para objdump), ainda não vejo esse símbolo em lugar nenhum. O que faz sentido na medida em que a função em questão é estática, portanto, não é exportada. No entanto, valgrindrelatará o nome da função, portanto, ele deve ser armazenado em algum lugar.

Olhando os detalhes das seções de depuração, encontro esse nome mencionado na .debug_strseção, mas não conheço uma ferramenta que possa transformar isso em um intervalo de endereços.


2
Uma observação secundária: se uma função estiver marcada static, ela pode ser embutida pelo compilador em seus sites de chamada. Isso pode significar que pode não haver nenhuma função para desmontar, por si só . Se você puder localizar símbolos para outras funções, mas não para a função que está procurando, esta é uma forte dica de que a função foi incorporada. Valgrind ainda pode fazer referência à função pré-embutida original porque as informações de depuração do arquivo ELF armazenam de onde cada instrução individual se originou, mesmo se as instruções forem movidas para outro lugar.
Davidg

@davidg: verdadeiro, mas como a resposta de Tom funcionou neste caso, não parece ser o caso. No entanto, você conhece uma maneira de, por exemplo, anotar o código assembly com as informações de onde cada instrução veio?
MvG de

1
Bom ouvir! addr2lineaceitará PCs / IPs de stdine imprimirá suas linhas de código-fonte correspondentes. Da mesma forma, objdump -lirá misturar o objdump com linhas de origem; embora para código altamente otimizado com inlining pesado, os resultados de qualquer programa nem sempre são particularmente úteis.
davidg de

Respostas:


87

Eu sugeriria usar gdb como a abordagem mais simples. Você pode até fazer uma linha, como:

gdb -batch -ex 'file /bin/ls' -ex 'disassemble main'

4
+1 recurso não documentado! -ex 'command'não está dentro man gdb!? Mas na verdade está listado na documentação do gdb . Também para outros, coisas como /bin/lspodem ser removidas, então se esse comando exato não exibir nada, tente outro objeto! Também pode especificar arquivo / objeto como argumento de bareword; por exemplo,gdb -batch -ex 'disassemble main' /bin/ls
hoc_age

3
A página do manual não é definitiva. Por muito tempo não foi realmente mantido, mas agora acho que é gerado a partir dos documentos principais. Além disso, "gdb --help" está mais completo agora.
Tom Tromey de

7
gdb /bin/ls -batch -ex 'disassemble main'também funciona
stefanct

1
Se você usar column -ts$'\t'para filtrar a saída GDB, terá os bytes brutos e as colunas de origem bem alinhados. Além disso, -ex 'set disassembly-flavor intel'antes de outros -exs resultarão na sintaxe de montagem da Intel.
Ruslan

Liguei disassemble fnusando o método acima. Mas parece que quando há várias funções com o mesmo nome no arquivo binário, apenas uma é desmontada. É possível desmontar todos eles ou devo desmontá-los com base no endereço bruto?
TheAhmad

28

gdb disassemble/rspara mostrar os bytes originais e brutos também

Com este formato, fica muito próximo da objdump -Ssaída:

gdb -batch -ex "disassemble/rs $FUNCTION" "$EXECUTABLE"

main.c

#include <assert.h>

int myfunc(int i) {
    i = i + 2;
    i = i * 2;
    return i;
}

int main(void) {
    assert(myfunc(1) == 6);
    assert(myfunc(2) == 8);
    return 0;
}

Compilar e desmontar

gcc -O0 -ggdb3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
gdb -batch -ex "disassemble/rs myfunc" main.out

Desmontagem:

Dump of assembler code for function myfunc:
main.c:
3       int myfunc(int i) {
   0x0000000000001135 <+0>:     55      push   %rbp
   0x0000000000001136 <+1>:     48 89 e5        mov    %rsp,%rbp
   0x0000000000001139 <+4>:     89 7d fc        mov    %edi,-0x4(%rbp)

4           i = i + 2;
   0x000000000000113c <+7>:     83 45 fc 02     addl   $0x2,-0x4(%rbp)

5           i = i * 2;
   0x0000000000001140 <+11>:    d1 65 fc        shll   -0x4(%rbp)

6           return i;
   0x0000000000001143 <+14>:    8b 45 fc        mov    -0x4(%rbp),%eax

7       }
   0x0000000000001146 <+17>:    5d      pop    %rbp
   0x0000000000001147 <+18>:    c3      retq   
End of assembler dump.

Testado em Ubuntu 16.04, GDB 7.11.1.

objdump + soluções alternativas do awk

Imprima o parágrafo conforme mencionado em: /unix/82944/how-to-grep-for-text-in-a-file-and-display-the-paragraph-that-has-the -texto

objdump -d main.out | awk -v RS= '/^[[:xdigit:]]+ <FUNCTION>/'

por exemplo:

objdump -d main.out | awk -v RS= '/^[[:xdigit:]]+ <myfunc>/'

dá apenas:

0000000000001135 <myfunc>:
    1135:   55                      push   %rbp
    1136:   48 89 e5                mov    %rsp,%rbp
    1139:   89 7d fc                mov    %edi,-0x4(%rbp)
    113c:   83 45 fc 02             addl   $0x2,-0x4(%rbp)
    1140:   d1 65 fc                shll   -0x4(%rbp)
    1143:   8b 45 fc                mov    -0x4(%rbp),%eax
    1146:   5d                      pop    %rbp
    1147:   c3                      retq   

Ao usar -S, acho que não há uma maneira à prova de falhas, pois os comentários do código podem conter qualquer sequência possível ... Mas o seguinte funciona quase o tempo todo:

objdump -S main.out | awk '/^[[:xdigit:]]+ <FUNCTION>:$/{flag=1;next}/^[[:xdigit:]]+ <.*>:$/{flag=0}flag'

adaptado de: Como selecionar linhas entre dois padrões de marcadores que podem ocorrer várias vezes com awk / sed

Respostas da lista de discussão

Há um tópico de 2010 na lista de discussão que diz que não é possível: https://sourceware.org/ml/binutils/2010-04/msg00445.html

Além da gdbsolução alternativa proposta por Tom, eles também comentam sobre outra (pior) solução alternativa de compilar, com a -ffunction-sectionqual coloca uma função por seção e, em seguida, despeja a seção.

Nicolas Clifton deu a ele um WONTFIX https://sourceware.org/ml/binutils/2015-07/msg00004.html , provavelmente porque a solução alternativa GDB cobre esse caso de uso.


A abordagem gdb funciona bem em bibliotecas compartilhadas e arquivos de objetos.
Tom Tromey

16

Desmonte uma única função usando Objdump

Tenho duas soluções:

1. Baseado em linha de comando

Este método funciona perfeitamente e mais simples. Eu uso objdump com o -d bandeira e tubulação -lo através de awk . A saída desmontada parece

000000000000068a <main>:
68a:    55                      push   %rbp
68b:    48 89 e5                mov    %rsp,%rbp
68e:    48 83 ec 20             sub    $0x20,%rsp

Para começar, começo com a descrição da saída do objdump. Uma seção ou função é separada por uma linha vazia. Portanto, alterar o FS (Separador de campo) para nova linha e o RS (Separador de registro) para duas vezes de nova linha permite que você pesquise facilmente a função recomendada, já que é simplesmente encontrar dentro do campo $ 1!

objdump -d name_of_your_obj_file | awk -F"\n" -v RS="\n\n" '$1 ~ /main/'

Claro que você pode substituir main por qualquer outra função que gostaria de imprimir.

2. Bash Script

Eu escrevi um pequeno script bash para esse problema. Cole e copie e salve como, por exemplo um arquivo dasm .

#!/bin/bash
# Author: abu
# filename: dasm
# Description: puts disassembled objectfile to std-out

if [ $# = 2 ]; then
        sstrg="^[[:xdigit:]]{2,}+.*<$2>:$"
        objdump -d $1 | awk -F"\n" -v RS="\n\n" '$1 ~ /'"$sstrg"'/'
elif [ $# = 1 ]; then
        objdump -d $1 | awk -F"\n" -v RS="\n\n" '{ print $1 }'
else
    echo "You have to add argument(s)"
    echo "Usage:   "$0 " arg1 arg2"  
    echo "Description: print disassembled label to std-out"
    echo "             arg1: name of object file"
    echo "             arg2: name of function to be disassembled"
    echo "         "$0 " arg1    ... print labels and their rel. addresses" 
fi

Mudar o x-access e invoque-o com, por exemplo:

chmod +x dasm
./dasm test main

Isso é muito mais rápido do que invocar gdb com um script. Além da maneira como o objdump não carrega as bibliotecas na memória, é mais seguro!


Vitaly Fadeev programou um preenchimento automático para este script, que é realmente um recurso interessante e acelera a digitação.

O script pode ser encontrado aqui .


Parece que depende se objdumpou gdbé mais rápido. Para um binário enorme (libxul.so do Firefox) objdumpdemora uma eternidade, cancelei depois de uma hora, enquanto gdbleva menos de um minuto.
Simon

6

Se você tiver um binutils muito recente (2.32+), isso é muito simples.

Passar --disassemble=SYMBOLpara objdump desmontará apenas a função especificada. Não há necessidade de passar o endereço inicial e o endereço final.

O objdump do LLVM também tem uma opção semelhante ( --disassemble-symbols).


Obrigado. Log de alterações para binutils 2.32, 02 de fevereiro de 2019: lists.gnu.org/archive/html/info-gnu/2019-02/msg00000.html " A opção --disassemble do Objdump agora pode ter um parâmetro, especificando o símbolo inicial para desmontagem. Desmontagem continuará a partir deste símbolo até o próximo símbolo ou o final da função. "
osgx

5

Para simplificar o uso de awk para analisar a saída de objdump em relação a outras respostas:

objdump -d filename | sed '/<functionName>:/,/^$/!d'

4

Isso funciona exatamente como a solução gdb (no sentido de que muda os deslocamentos para zero), exceto que não é lento (faz o trabalho em cerca de 5ms no meu PC, enquanto a solução gdb leva cerca de 150ms):

objdump_func:

#!/bin/sh
# $1 -- function name; rest -- object files
fn=$1; shift 1
exec objdump -d "$@" | 
awk " /^[[:xdigit:]].*<$fn>/,/^\$/ { print \$0 }" |
awk -F: -F' '  'NR==1 {  offset=strtonum("0x"$1); print $0; } 
                NR!=1 {  split($0,a,":"); rhs=a[2]; n=strtonum("0x"$1); $1=sprintf("%x", n-offset); printf "%4s:%s\n", $1,rhs }'

Não posso testar agora, mas estou ansioso para ver isso. Você pode falar um pouco sobre o aspecto “deslocamento de deslocamento para zero”? Eu não vi isso explícito nas respostas do gdb aqui, e gostaria de ouvir um pouco mais sobre o que está realmente acontecendo lá e por quê.
MvG de

Basicamente, faz com que pareça que a função que você almeja (que é o que o primeiro awkfaz) era a única função no arquivo de objeto, ou seja, mesmo se a função começar em, digamos 0x2d, o segundo awk a deslocará para 0x00(subtraindo 0x2ddo endereço de cada instrução), o que é útil porque o código assembly geralmente faz referências relativas ao início da função e se a função começa em 0, você não precisa fazer as subtrações em sua cabeça. O código awk poderia ser melhor, mas pelo menos ele faz o trabalho e é bastante eficiente.
PSkocik

Em retrospecto, parece que compilar -ffunction-sectionsé uma maneira mais fácil de garantir que cada função comece em 0.
PSkocik

3

Conclusão Bash para ./dasm

Nomes de símbolos completos para esta solução (versão D lang):

  • Digitando dasm test e pressionandoTabTab , você obterá uma lista de todas as funções.
  • Digitando dasm test me pressionando TabTab todas as funções, começando com m serão mostradas, ou no caso de existir apenas uma função, ela será completada automaticamente.

Arquivo /etc/bash_completion.d/dasm:

# bash completion for dasm
_dasm()
{
    local cur=${COMP_WORDS[COMP_CWORD]}

    if [[ $COMP_CWORD -eq 1 ]] ; then
    # files
    COMPREPLY=( $( command ls *.o -F 2>/dev/null | grep "^$cur" ) )

    elif [[ $COMP_CWORD -eq 2 ]] ; then
    # functions
    OBJFILE=${COMP_WORDS[COMP_CWORD-1]}

    COMPREPLY=( $( command nm --demangle=dlang $OBJFILE | grep " W " | cut -d " " -f 3 | tr "()" "  " | grep "$cur" ) )

    else
    COMPREPLY=($(compgen -W "" -- "$cur"));
    fi
}

complete -F _dasm dasm
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.