Comment démonter une seule fonction en utilisant objdump?

89

J'ai un binaire installé sur mon système et j'aimerais voir le démontage d'une fonction donnée. Utiliser de préférence objdump, mais d'autres solutions seraient également acceptables.

À partir de ces questions, j'ai appris que je pourrais peut-être démonter une partie du code si je ne connais que les adresses des limites. À partir de cette réponse, j'ai appris à reconvertir mes symboles de débogage fractionnés en un seul fichier.

Mais même en opérant sur ce seul fichier, et même en désassemblant tout le code (c'est-à-dire sans adresse de démarrage ou d'arrêt, mais -dparamètre clair à objdump), je ne vois toujours ce symbole nulle part. Ce qui a du sens dans la mesure où la fonction en question est statique, donc elle n'est pas exportée. Néanmoins, valgrindrapportera le nom de la fonction, il doit donc être stocké quelque part.

En regardant les détails des sections de débogage, je trouve ce nom mentionné dans la .debug_strsection, mais je ne connais pas d'outil qui puisse transformer cela en une plage d'adresses.

MvG
la source
2
Une remarque secondaire mineure: si une fonction est marquée static, elle peut être intégrée par le compilateur dans ses sites d'appels. Cela peut signifier qu'il n'y a peut-être aucune fonction à démonter en soi . Si vous pouvez repérer des symboles pour d'autres fonctions, mais pas la fonction que vous recherchez, cela indique clairement que la fonction a été incorporée. Valgrind peut toujours faire référence à la fonction pré-intégrée d'origine car les informations de débogage du fichier ELF stockent l'origine de chaque instruction individuelle, même si les instructions sont déplacées ailleurs.
davidg
@davidg: vrai, mais comme la réponse de Tom a fonctionné dans ce cas, cela ne semble pas être le cas. Néanmoins, connaissez-vous un moyen, par exemple, d'annoter le code d'assemblage avec ces informations sur l'origine de chaque instruction?
MvG
1
Bon à entendre! addr2lineacceptera les PC / IP de stdinet imprimera leurs lignes de code source correspondantes. De même, objdump -lmélangera l'objdump avec les lignes source; cependant, pour un code hautement optimisé avec une insertion lourde, les résultats de l'un ou l'autre programme ne sont pas toujours particulièrement utiles.
davidg

Réponses:

86

Je suggérerais d'utiliser gdb comme approche la plus simple. Vous pouvez même le faire en une seule ligne, comme:

gdb -batch -ex 'file /bin/ls' -ex 'disassemble main'
Tom Tromey
la source
4
+1 fonctionnalité non documentée! -ex 'command'n'est pas man gdbdedans!? Mais est en fait répertorié dans la documentation gdb . Aussi pour d'autres, des trucs comme /bin/lspeuvent être supprimés, donc si cette commande exacte n'affiche rien, essayez un autre objet! Peut également spécifier un fichier / objet comme argument bareword; par exemple,gdb -batch -ex 'disassemble main' /bin/ls
hoc_age
3
La page de manuel n'est pas définitive. Pendant longtemps, ce n'était pas vraiment maintenu, mais maintenant je pense qu'il est généré à partir de la documentation principale. «Gdb --help» est également plus complet maintenant.
Tom Tromey
7
gdb /bin/ls -batch -ex 'disassemble main'fonctionne aussi
stefanct
1
Si vous utilisez column -ts$'\t'pour filtrer la sortie GDB, vous aurez les octets bruts et les colonnes source bien alignés. En outre, -ex 'set disassembly-flavor intel'avant que d'autres -exs entraînent une syntaxe d'assemblage Intel.
Ruslan
J'ai appelé en disassemble fnutilisant la méthode ci-dessus. Mais il semble que lorsqu'il y a plusieurs fonctions avec le même nom dans le fichier binaire, une seule est démontée. Est-il possible de tous les démonter ou dois-je les démonter en fonction de l'adresse brute?
TheAhmad
26

gdb disassemble/rspour afficher également les octets source et bruts

Avec ce format, il se rapproche vraiment de la objdump -Ssortie:

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

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

Compiler et démonter

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

Démontage:

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.

Testé sur Ubuntu 16.04, GDB 7.11.1.

objdump + awk solutions de contournement

Imprimez le paragraphe comme indiqué sur: /unix/82944/how-to-grep-for-text-in-a-file-and-display-the- paragraph- that - has- the -texte

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

par exemple:

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

donne juste:

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   

Lors de l'utilisation -S, je ne pense pas qu'il existe un moyen infaillible, car les commentaires de code pourraient contenir n'importe quelle séquence possible ... Mais ce qui suit fonctionne presque tout le temps:

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

adapté de: Comment sélectionner des lignes entre deux motifs de marqueurs qui peuvent se produire plusieurs fois avec awk / sed

Réponses à la liste de diffusion

Il y a un fil de discussion 2010 sur la liste de diffusion qui dit que ce n'est pas possible: https://sourceware.org/ml/binutils/2010-04/msg00445.html

Outre la gdbsolution de contournement proposée par Tom, ils commentent également une autre (pire) solution de contournement de compilation avec -ffunction-sectionlaquelle met une fonction par section, puis vidage de la section.

Nicolas Clifton lui a donné un WONTFIX https://sourceware.org/ml/binutils/2015-07/msg00004.html , probablement parce que la solution de contournement GDB couvre ce cas d'utilisation.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
L'approche gdb fonctionne très bien sur les bibliothèques partagées et les fichiers objets.
Tom Tromey
16

Démonter une seule fonction à l'aide d'Objdump

J'ai deux solutions:

1. Basé sur la ligne de commande

Cette méthode fonctionne parfaitement et en plus une méthode simple. J'utilise objdump avec le -d drapeau et conduite à travers awk . La sortie démontée ressemble à

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

Pour commencer, je commence par la description de la sortie objdump. Une section ou une fonction est séparée par une ligne vide. Par conséquent, changer le FS (Field Separator) en newline et le RS (Record Separator) en double newline vous permet de rechercher facilement votre fonction recommandée, car il s'agit simplement de trouver dans le champ $ 1!

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

Bien sûr, vous pouvez remplacer main par toute autre fonction que vous souhaitez imprimer.

2. Script Bash

J'ai écrit un petit script bash pour ce numéro. Collez-le et copiez-le et enregistrez-le comme par exemple un fichier 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

Modifiez l' accès x et invoquez-le avec par exemple:

chmod +x dasm
./dasm test main

C'est beaucoup plus rapide que d'appeler gdb avec un script. De plus, utiliser objdump ne chargera pas les bibliothèques en mémoire et est donc plus sûr!


Vitaly Fadeev a programmé une auto-complétion de ce script, ce qui est vraiment une fonctionnalité intéressante et accélère la saisie.

Le script peut être trouvé ici .

abu_bua
la source
Il semble que cela dépend si objdumpou gdbest plus rapide. Pour un énorme binaire (libxul.so de Firefox) objdumpprend une éternité, je l'ai annulé après une heure, alors qu'il gdbprend moins d'une minute.
Simon
5

Pour simplifier l'utilisation de awk pour analyser la sortie d'objdump par rapport aux autres réponses:

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

Cela fonctionne comme la solution gdb (en ce sens qu'elle décale les décalages vers zéro) sauf que ce n'est pas laggy (fait le travail en environ 5 ms sur mon PC alors que la solution gdb prend environ 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
la source
Je ne peux pas tester pour le moment, mais j'attends avec impatience quand j'y reviendrai. Pouvez-vous nous en dire un peu plus sur l'aspect «décalage vers zéro»? Je n'ai pas vu cela explicite dans les réponses gdb ici, et j'aimerais en savoir un peu plus sur ce qui se passe réellement là-bas et pourquoi.
MvG
Cela donne essentiellement l'impression que la fonction que vous ciblez (ce que fait la première awk) était la seule fonction du fichier objet, c'est-à-dire que même si la fonction commence à, disons 0x2d, le deuxième awk la déplacera vers 0x00(en soustrayant 0x2dà partir de l'adresse de chaque instruction), ce qui est utile car le code d'assemblage fait souvent des références relatives au début de la fonction et si la fonction commence à 0, vous n'avez pas à faire les soustractions dans votre tête. Le code awk pourrait être meilleur, mais au moins il fait le travail et est assez efficace.
PSkocik
Rétrospectivement, il semble que la compilation avec -ffunction-sectionsest un moyen plus simple de s'assurer que chaque fonction commence à 0.
PSkocik
4

Si vous avez un binutils très récent (2.32+), c'est très simple.

Passer --disassemble=SYMBOLà objdump ne démontera que la fonction spécifiée. Pas besoin de passer l'adresse de début et l'adresse de fin.

LLVM objdump a également une option similaire ( --disassemble-symbols).

Léo Lam
la source
Merci. Changelog for binutils 2.32, 02 février 2019: lists.gnu.org/archive/html/info-gnu/2019-02/msg00000.html " L'option --disassemble d'Objdump peut désormais prendre un paramètre, spécifiant le symbole de départ pour le démontage. Démontage continuera à partir de ce symbole jusqu'au symbole suivant ou à la fin de la fonction. "
osgx
3

Achèvement de Bash pour ./dasm

Noms des symboles complets pour cette solution (version D lang):

  • En tapant dasm testpuis en appuyant sur TabTab, vous obtiendrez une liste de toutes les fonctions.
  • En tapant dasm test mpuis en appuyant sur TabTab toutes les fonctions commençant par m seront affichées, ou dans le cas où une seule fonction existe, elle sera complétée automatiquement.

Fichier /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
la source