Jak zdemontować jedną funkcję za pomocą objdump?

89

Mam zainstalowany plik binarny w moim systemie i chciałbym przyjrzeć się dezasemblacji danej funkcji. Preferowane użycie objdump, ale inne rozwiązania również byłyby dopuszczalne.

Z tych pytań dowiedziałem się, że mógłbym zdemontować część kodu, jeśli znam tylko adresy graniczne. Z tej odpowiedzi nauczyłem się, jak zamienić moje podzielone symbole debugowania z powrotem w pojedynczy plik.

Ale nawet działając na tym pojedynczym pliku, a nawet deasemblując cały kod (tj. Bez adresu początkowego lub końcowego, ale zwykły -dparametr do objdump), nadal nie widzę nigdzie tego symbolu. Ma to sens, o ile dana funkcja jest statyczna, więc nie jest eksportowana. Niemniej jednak valgrindzgłosi nazwę funkcji, więc musi być gdzieś przechowywana.

Patrząc na szczegóły sekcji debugowania, znalazłem tę nazwę wymienioną w .debug_strsekcji, ale nie znam narzędzia, które może przekształcić to w zakres adresów.

MvG
źródło
2
Drobna uwaga dodatkowa: jeśli funkcja jest zaznaczona static, może zostać wstawiona przez kompilator do witryn wywołań. Może to oznaczać, że nie może w rzeczywistości być dowolna funkcja demontować, per se . Jeśli możesz znaleźć symbole innych funkcji, ale nie funkcji, której szukasz, jest to silna wskazówka, że ​​funkcja została wbudowana. Valgrind może nadal odwoływać się do oryginalnej, wstępnie wstawionej funkcji, ponieważ informacje debugowania pliku ELF przechowują skąd pochodzi każda indywidualna instrukcja, nawet jeśli instrukcje są przenoszone w inne miejsce.
davidg
@davidg: prawda, ale ponieważ odpowiedź Toma zadziałała w tym przypadku, wydaje się, że tak nie jest. Niemniej jednak, czy znasz sposób, aby np. Dodać adnotację do kodu asemblera z informacją, skąd pochodzi każda instrukcja?
MvG
1
Dobrze to słyszeć! addr2linezaakceptuje komputery / adresy IP zi stdinwydrukuje odpowiednie linie kodu źródłowego. Podobnie, objdump -lbędzie mieszać objdump z liniami źródłowymi; chociaż dla wysoce zoptymalizowanego kodu z ciężkim wstawianiem, wyniki obu programów nie zawsze są szczególnie pomocne.
davidg

Odpowiedzi:

86

Sugerowałbym użycie gdb jako najprostszego podejścia. Możesz to zrobić nawet jako linijkę, na przykład:

gdb -batch -ex 'file /bin/ls' -ex 'disassemble main'
Tom Tromey
źródło
4
+1 nieudokumentowana funkcja! -ex 'command'nie ma w man gdb!? Ale w rzeczywistości jest wymieniony w dokumentacji gdb . Również w przypadku innych rzeczy takie jak /bin/lsmogą zostać usunięte, więc jeśli to dokładne polecenie nic nie wyświetla, spróbuj innego obiektu! Może również określić plik / obiekt jako argument gołego słowa; np.gdb -batch -ex 'disassemble main' /bin/ls
hoc_age
3
Strona podręcznika nie jest ostateczna. Przez długi czas tak naprawdę nie był obsługiwany, ale teraz myślę, że jest generowany z głównych dokumentów. Również "gdb --help" jest teraz bardziej kompletne.
Tom Tromey,
7
gdb /bin/ls -batch -ex 'disassemble main'działa również
stefanct
1
Jeśli użyjesz column -ts$'\t'do filtrowania danych wyjściowych GDB, uzyskasz ładnie wyrównane surowe bajty i kolumny źródłowe. Ponadto, -ex 'set disassembly-flavor intel'zanim inne -exs spowodują składnię zespołu Intel.
Ruslan
Zadzwoniłem disassemble fnza pomocą powyższej metody. Ale wydaje się, że gdy w pliku binarnym znajduje się wiele funkcji o tej samej nazwie, tylko jedna jest dezasemblowana. Czy można je wszystkie zdemontować, czy powinienem je zdemontować na podstawie surowego adresu?
TheAhmad
26

gdb, disassemble/rsaby wyświetlić również bajty źródłowe i surowe

W tym formacie bardzo zbliża się do objdump -Swyniku:

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;
}

Skompiluj i zdemontuj

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

Demontaż:

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.

Testowano na Ubuntu 16.04, GDB 7.11.1.

objdump + awk obejścia

Wydrukuj akapit, jak wspomniano na: /unix/82944/how-to-grep-for-text-in-a-file-and-display-the-paragraph-that-has-the -tekst

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

na przykład:

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

daje tylko:

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   

Podczas używania -Snie wydaje mi się, aby istniał sposób odporny na awarie, ponieważ komentarze do kodu mogą zawierać dowolną możliwą sekwencję ... Ale poniższe działa prawie cały czas:

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

zaadaptowano z: Jak zaznaczyć linie między dwoma wzorami znaczników, które mogą wystąpić wielokrotnie w awk / sed

Odpowiedzi na listy mailingowe

Na liście mailingowej z 2010 roku jest wątek, który mówi, że to niemożliwe: https://sourceware.org/ml/binutils/2010-04/msg00445.html

Oprócz gdbobejścia zaproponowanego przez Toma, komentują również inne (gorsze) obejście kompilacji, w -ffunction-sectionktórym jedna funkcja jest umieszczana na sekcję, a następnie zrzucana jest sekcja.

Nicolas Clifton dał mu WONTFIX https://sourceware.org/ml/binutils/2015-07/msg00004.html , prawdopodobnie dlatego, że obejście GDB obejmuje ten przypadek użycia.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
Podejście gdb działa dobrze w przypadku bibliotek współdzielonych i plików obiektów.
Tom Tromey
16

Zdemontuj jedną funkcję za pomocą Objdump

Mam dwa rozwiązania:

1. Oparte na linii poleceń

Ta metoda działa doskonale i dodatkowo prosta. Używam objdump z flagą -d i przesyłam go potokiem przez awk . Wygląda jak zdemontowane wyjście

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

Na początek zacznę od opisu wyniku objdump. Sekcji lub funkcja jest oddzielona od pustej linii. Dlatego zmiana FS (Separator pól) na znak nowej linii i RS (Separator rekordów) na podwójny znak nowej linii umożliwia łatwe wyszukiwanie zalecanej funkcji, ponieważ można ją po prostu znaleźć w polu $ 1!

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

Oczywiście możesz zastąpić main dowolną inną funkcją, którą chciałbyś wydrukować.

2. Bash Script

Napisałem mały skrypt basha dla tego wydania. Wklej go, skopiuj i zapisz np. Jako plik 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

Zmień x-access i wywołaj go np:

chmod +x dasm
./dasm test main

Jest to znacznie szybsze niż wywołanie gdb za pomocą skryptu. Poza tym użycie objdump nie załaduje bibliotek do pamięci i dlatego jest bezpieczniejsze!


Witalij Fadeev zaprogramował automatyczne uzupełnianie tego skryptu, co jest naprawdę fajną funkcją i przyspiesza pisanie.

Skrypt można znaleźć tutaj .

abu_bua
źródło
Wydaje się, że zależy to od tego, objdumpczy gdbjest szybsze. Ponieważ ogromny plik binarny (libxul.so Firefoksa) objdumptrwa wiecznie, anulowałem go po godzinie, a gdbzajmuje mniej niż minutę.
Simon
5

Aby uprościć użycie awk do analizowania wyniku objdump względem innych odpowiedzi:

objdump -d filename | sed '/<functionName>:/,/^$/!d'
fcr
źródło
4

Działa to tak samo, jak rozwiązanie gdb (w tym, że przesuwa przesunięcia w kierunku zera), z wyjątkiem tego, że nie jest opóźnione (wykonuje pracę w około 5 ms na moim komputerze, podczas gdy rozwiązanie gdb zajmuje około 150 ms):

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
źródło
Nie mogę teraz testować, ale nie mogę się doczekać, kiedy to zrobię. Czy możesz trochę rozwinąć aspekt „przesunięcia przesunięć w kierunku zera”? Nie widziałem tego jednoznacznie w odpowiedziach gdb tutaj i chciałbym usłyszeć trochę więcej o tym, co się tam właściwie dzieje i dlaczego.
MvG
Zasadniczo sprawia to, że wygląda na to, że funkcja, na którą celujesz (co awkrobi pierwsza ) była jedyną funkcją w pliku obiektowym, to znaczy nawet jeśli funkcja zaczyna się na, powiedzmy 0x2d, drugi awk przesunie ją w kierunku 0x00(odejmując 0x2dz adresu każdej instrukcji), co jest przydatne, ponieważ kod asemblera często odwołuje się do początku funkcji i jeśli funkcja zaczyna się od 0, nie musisz robić odejmowań w głowie. Kod awk mógłby być lepszy, ale przynajmniej spełnia swoje zadanie i jest dość wydajny.
PSkocik,
Z perspektywy czasu wydaje się, że kompilacja z -ffunction-sectionsto łatwiejszy sposób na upewnienie się, że każda funkcja zaczyna się od 0.
PSkocik
4

Jeśli masz bardzo niedawne binutils (2.32+), jest to bardzo proste.

Przekazanie --disassemble=SYMBOLdo objdump spowoduje dezasemblację tylko określonej funkcji. Nie ma potrzeby podawania adresu początkowego i końcowego.

LLVM objdump ma również podobną opcję ( --disassemble-symbols).

Léo Lam
źródło
Dziękuję Ci. Dziennik zmian dla binutils 2.32, 02 lutego 2019: lists.gnu.org/archive/html/info-gnu/2019-02/msg00000.html " Opcja --disassemble Objdumpa może teraz przyjmować parametr określający początkowy symbol demontażu. Demontaż będzie kontynuowany od tego symbolu do następnego symbolu lub do końca funkcji.
osgx
3

Uzupełnienie Bash dla ./dasm

Pełne nazwy symboli dla tego rozwiązania (wersja D lang):

  • Wpisując, dasm testa następnie naciskając TabTab, otrzymasz listę wszystkich funkcji.
  • Wpisanie, dasm test ma następnie naciśnięcie spowoduje wyświetlenie TabTab wszystkich funkcji zaczynających się od m , lub jeśli istnieje tylko jedna funkcja, zostanie ona automatycznie uzupełniona.

Plik /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
Witalij Fadejew
źródło