Wie zerlege ich eine einzelne Funktion mit objdump?

89

Ich habe eine Binärdatei auf meinem System installiert und möchte die Demontage einer bestimmten Funktion untersuchen. Bevorzugt verwenden objdump, aber auch andere Lösungen wären akzeptabel.

Aus diesen Fragen habe ich gelernt, dass ich möglicherweise einen Teil des Codes zerlegen kann, wenn ich nur die Grenzadressen kenne. Aus dieser Antwort habe ich gelernt, wie ich meine geteilten Debug-Symbole wieder in eine einzige Datei umwandle.

Aber selbst wenn ich mit dieser einzelnen Datei arbeite und sogar den gesamten Code zerlege (dh ohne Start- oder Stoppadresse, aber mit einfachen -dParametern objdump), sehe ich dieses Symbol immer noch nirgendwo. Dies ist insofern sinnvoll, als die betreffende Funktion statisch ist und daher nicht exportiert wird. Trotzdem valgrindwird der Funktionsname gemeldet, so dass er irgendwo gespeichert werden muss.

Wenn ich mir die Details der Debug-Abschnitte ansehe, finde ich diesen Namen im .debug_strAbschnitt erwähnt, aber ich kenne kein Tool, das dies in einen Adressbereich verwandeln kann.

MvG
quelle
2
Eine kleine Randnotiz: Wenn eine Funktion markiert ist static, wird sie möglicherweise vom Compiler in ihre Aufrufseiten eingefügt. Dies kann bedeuten , dass es nicht wirklich jede Funktion zu zerlegen sein kann, per se . Wenn Sie Symbole für andere Funktionen erkennen können, jedoch nicht für die gesuchte Funktion, ist dies ein starker Hinweis darauf, dass die Funktion eingebunden wurde. Valgrind verweist möglicherweise weiterhin auf die ursprüngliche vorinline-Funktion, da in den Debugging-Informationen der ELF-Datei gespeichert wird, woher jeder einzelne Befehl stammt, selbst wenn die Befehle an einen anderen Ort verschoben werden.
Davidg
@davidg: stimmt, aber da die Antwort von Tom in diesem Fall funktioniert hat, scheint dies nicht der Fall zu sein. Kennen Sie dennoch eine Möglichkeit, z. B. Assembler-Code mit den Informationen zu versehen, aus denen die einzelnen Anweisungen stammen?
MvG
1
Gut zu hören! addr2lineakzeptiert PCs / IPs von stdinund druckt die entsprechenden Quellcodezeilen aus. In ähnlicher Weise objdump -lwird der objdump mit Quellzeilen gemischt; Für hochoptimierten Code mit starkem Inlining sind die Ergebnisse beider Programme jedoch nicht immer besonders hilfreich.
Davidg

Antworten:

86

Ich würde vorschlagen, gdb als einfachsten Ansatz zu verwenden. Sie können es sogar als Einzeiler tun, wie:

gdb -batch -ex 'file /bin/ls' -ex 'disassemble main'
Tom Tromey
quelle
4
+1 undokumentierte Funktion! -ex 'command'ist nicht in man gdb!? Ist aber in der Tat in GDB-Dokumenten aufgeführt . Auch für andere /bin/lskönnten Dinge wie entfernt werden. Wenn dieser genaue Befehl also nichts anzeigt, versuchen Sie es mit einem anderen Objekt! Kann auch Datei / Objekt als Bareword-Argument angeben. zBgdb -batch -ex 'disassemble main' /bin/ls
hoc_age
3
Die Manpage ist nicht endgültig. Lange Zeit wurde es nicht wirklich gewartet, aber jetzt denke ich, dass es aus den Hauptdokumenten generiert wurde. Auch "gdb --help" ist jetzt auch vollständiger.
Tom Tromey
7
gdb /bin/ls -batch -ex 'disassemble main'funktioniert auch
stefanct
1
Wenn Sie column -ts$'\t'die GDB-Ausgabe filtern, sind die Rohbytes und Quellenspalten gut ausgerichtet. Auch -ex 'set disassembly-flavor intel'vor anderen -exs wird in Inteler Montage Syntax führen.
Ruslan
Ich habe oben disassemble fnmit der Methode angerufen . Es scheint jedoch, dass, wenn die Binärdatei mehrere Funktionen mit demselben Namen enthält, nur eine zerlegt wird. Ist es möglich, alle zu zerlegen, oder sollte ich sie anhand der Rohadresse zerlegen?
TheAhmad
26

gdb disassemble/rs, um auch Quell- und Rohbytes anzuzeigen

Mit diesem Format kommt es der objdump -SAusgabe sehr nahe :

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

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

Kompilieren und zerlegen

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

Demontage:

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.

Getestet unter Ubuntu 16.04, GDB 7.11.1.

objdump + awk Workarounds

Drucken Sie den Absatz wie folgt aus: /unix/82944/how-to-grep-for-text-in-a-file-and-display-the-paragraph-that-has-the -Text

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

z.B:

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

gibt nur:

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   

Bei der Verwendung -Sgibt es meines Erachtens keinen ausfallsicheren Weg, da die Codekommentare eine mögliche Reihenfolge enthalten könnten ... Aber das Folgende funktioniert fast immer:

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

angepasst von: So wählen Sie Linien zwischen zwei Markierungsmustern aus, die bei awk / sed mehrfach auftreten können

Antworten auf die Mailingliste

Auf der Mailingliste befindet sich ein Thread von 2010, der besagt, dass dies nicht möglich ist: https://sourceware.org/ml/binutils/2010-04/msg00445.html

Neben der gdbvon Tom vorgeschlagenen Problemumgehung kommentieren sie auch eine andere (schlechtere) Problemumgehung beim Kompilieren, mit -ffunction-sectionder eine Funktion pro Abschnitt erstellt und dann der Abschnitt ausgegeben wird.

Nicolas Clifton gab ihm ein WONTFIX https://sourceware.org/ml/binutils/2015-07/msg00004.html , wahrscheinlich weil die GDB- Problemumgehung diesen Anwendungsfall abdeckt.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
Der GDB-Ansatz funktioniert gut für gemeinsam genutzte Bibliotheken und Objektdateien.
Tom Tromey
16

Zerlegen Sie eine einzelne Funktion mit Objdump

Ich habe zwei Lösungen:

1. Kommandozeilenbasiert

Diese Methode funktioniert perfekt und zusätzlich eine einfache. Ich benutze objdump mit dem Flag -d und leite es durch awk . Die zerlegte Ausgabe sieht aus wie

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

Zunächst beginne ich mit der Beschreibung der objdump-Ausgabe. Ein Abschnitt oder eine Funktion wird durch eine leere Zeile getrennt. Wenn Sie also den FS (Field Separator) in Newline und den RS (Record Separator) in Double Newline ändern, können Sie leicht nach Ihrer empfohlenen Funktion suchen, da diese einfach im $ 1-Feld zu finden ist!

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

Natürlich können Sie main durch jede andere Funktion ersetzen, die Sie drucken möchten.

2. Bash Script

Ich habe ein kleines Bash-Skript für dieses Problem geschrieben. Fügen Sie es ein, kopieren Sie es und speichern Sie es als zB Dasm- Datei.

#!/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

Ändern Sie den x-access und rufen Sie ihn auf mit zB:

chmod +x dasm
./dasm test main

Dies ist viel schneller als das Aufrufen von gdb mit einem Skript. Neben der Art und Weise mit objdump wird nicht die Bibliotheken in den Speicher geladen werden und ist daher sicherer!


Vitaly Fadeev hat eine automatische Vervollständigung für dieses Skript programmiert, was wirklich eine nette Funktion ist und das Tippen beschleunigt.

Das Skript finden Sie hier .

abu_bua
quelle
Es scheint, es hängt davon ab, ob objdumpoder gdbist schneller. Für eine riesige Binärdatei (Firefox 'libxul.so) objdumpdauert es ewig, ich habe sie nach einer Stunde abgebrochen, während sie gdbweniger als eine Minute dauert.
Simon
5

So vereinfachen Sie die Verwendung von awk zum Parsen der Ausgabe von objdump im Vergleich zu anderen Antworten:

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

Dies funktioniert genau wie die GDB-Lösung (da sie die Offsets gegen Null verschiebt), nur dass sie nicht verzögert ist (erledigt die Arbeit auf meinem PC in ca. 5 ms, während die GDB-Lösung ca. 150 ms dauert):

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
quelle
Ich kann gerade nicht testen, aber ich freue mich darauf, wenn ich dazu komme. Können Sie den Aspekt „Verschiebungen in Richtung Null“ etwas näher erläutern? Ich habe dies in den GDB-Antworten hier nicht explizit gesehen, und ich würde gerne ein bisschen mehr darüber hören, was dort tatsächlich vor sich geht und warum.
MvG
Im Grunde sieht es so aus, als ob die Funktion, auf die Sie abzielen (was die erste awktut), die einzige Funktion in der Objektdatei ist, das heißt, selbst wenn die Funktion beispielsweise bei 0x2dder zweiten awk beginnt, wird sie in Richtung verschoben 0x00(durch Subtrahieren) 0x2dvon der Adresse jeder Anweisung), was nützlich ist, da der Assembler-Code häufig Verweise auf den Start der Funktion enthält und wenn die Funktion bei 0 beginnt, müssen Sie die Subtraktionen nicht in Ihrem Kopf durchführen. Der awk-Code könnte besser sein, macht aber zumindest den Job und ist ziemlich effizient.
PSkocik
Rückblickend scheint das Kompilieren mit -ffunction-sectionseine einfachere Möglichkeit zu sein, um sicherzustellen, dass jede Funktion bei 0 beginnt.
PSkocik
4

Wenn Sie über sehr aktuelle Binutils (2.32+) verfügen, ist dies sehr einfach.

Wenn Sie --disassemble=SYMBOLan objdump übergeben, wird nur die angegebene Funktion zerlegt. Die Startadresse und die Endadresse müssen nicht übergeben werden.

LLVM objdump hat auch eine ähnliche Option ( --disassemble-symbols).

Léo Lam
quelle
Vielen Dank. Änderungsprotokoll für binutils 2.32, 02. Februar 2019: lists.gnu.org/archive/html/info-gnu/2019-02/msg00000.html " Die Option --disassemble von Objdump kann jetzt einen Parameter annehmen, der das Startsymbol für die Demontage angibt. Demontage wird von diesem Symbol bis zum nächsten Symbol oder dem Ende der Funktion
fortgesetzt
3

Bash-Abschluss für ./dasm

Vollständige Symbolnamen für diese Lösung (D lang-Version):

  • Wenn Sie tippen dasm testund dann drücken TabTab, erhalten Sie eine Liste aller Funktionen.
  • Durch Eingabe dasm test mund anschließendes Drücken werden TabTab alle Funktionen angezeigt, die mit m beginnen. Wenn nur eine Funktion vorhanden ist, wird diese automatisch vervollständigt.

Datei /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
quelle