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.

MvG
fonte
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'
Tom Tromey
fonte
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.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fonte
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 .

abu_bua
fonte
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).

Léo Lam
fonte
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'
fcr
fonte
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 }'
PSkocik
fonte
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
Vitaly Fadeev
fonte