Как разобрать одну единственную функцию с помощью objdump?

89

В моей системе установлен двоичный файл, и я хотел бы посмотреть, как выполняется разборка данной функции. Предпочтительно использовать objdump, но также будут приемлемы другие решения.

Из этих вопросов я узнал, что мог бы дизассемблировать часть кода, если бы мне были известны только граничные адреса. Из этого ответа я узнал, как превратить мои разделенные символы отладки обратно в один файл.

Но даже работая с этим единственным файлом и даже дизассемблируя весь код (то есть без начального или конечного адреса, но с обычным -dпараметром objdump), я все равно нигде не вижу этого символа. Это имеет смысл, поскольку рассматриваемая функция статична и не экспортируется. Тем не менее, valgrindон сообщит имя функции, поэтому его нужно где-то хранить.

Глядя на детали разделов отладки, я обнаружил, что это имя упоминается в .debug_strразделе, но я не знаю инструмента, который мог бы превратить это в диапазон адресов.

MvG
источник
2
Небольшое примечание: если функция отмечена static, она может быть встроена компилятором в его сайты вызовов. Это может означать, что на самом деле может не быть никакой функции для разборки как таковой . Если вы можете найти символы для других функций, но не для функции, которую ищете, это сильный намек на то, что функция была встроена. Valgrind может по-прежнему ссылаться на исходную предварительно встроенную функцию, потому что отладочная информация файла ELF хранит источник каждой отдельной инструкции, даже если инструкции перемещены в другое место.
Дэвидг 01
@davidg: верно, но поскольку в данном случае сработал ответ Тома, похоже, что это не так. Тем не менее, знаете ли вы способ, например, аннотировать ассемблерный код информацией о том, откуда пришла каждая инструкция?
MvG 01
1
Приятно слышать! addr2lineбудет принимать ПК / IP-адреса stdinи распечатывать соответствующие строки исходного кода. Точно так же objdump -lбудет смешивать objdump со строками исходного текста; хотя для высокооптимизированного кода с тяжелым встраиванием результаты любой программы не всегда особенно полезны.
Дэвидг 01

Ответы:

86

Я бы посоветовал использовать gdb как самый простой подход. Вы даже можете сделать это однострочно, например:

gdb -batch -ex 'file /bin/ls' -ex 'disassemble main'
Том Троми
источник
4
+1 недокументированная функция! -ex 'command'не в man gdb!? Но на самом деле он указан в документации по gdb . Также для других подобные вещи /bin/lsмогут быть удалены, поэтому, если эта точная команда ничего не отображает, попробуйте другой объект! Также можно указать файл / объект как аргумент без слов; например,gdb -batch -ex 'disassemble main' /bin/ls
hoc_age
3
Страница руководства не является окончательной. Долгое время он не поддерживался, но теперь я думаю, что он генерируется из основных документов. Также "gdb --help" теперь более полный.
Том Троми
7
gdb /bin/ls -batch -ex 'disassemble main'тоже работает
stefanct
1
Если вы используете column -ts$'\t'для фильтрации вывода GDB, у вас будут хорошо выровнены необработанные байты и исходные столбцы. Кроме того, -ex 'set disassembly-flavor intel'перед другими -exs приведет к синтаксису сборки Intel.
Руслан
Я позвонил, disassemble fnиспользуя метод, описанный выше. Но кажется, что когда в двоичном файле есть несколько функций с одинаковым именем, дизассемблируется только одна. Можно ли их все разобрать, или я должен разбирать их по необработанному адресу?
TheAhmad
26

gdb disassemble/rsдля отображения исходных и необработанных байтов

В этом формате он действительно приближается к objdump -Sвыводу:

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

Скомпилировать и разобрать

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

Разборка:

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.

Проверено на Ubuntu 16.04, GDB 7.11.1.

objdump + awk обходные пути

Распечатайте абзац, как указано здесь: /unix/82944/how-to-grep-for-text-in-a-file-and-display-the-paragraph-that-has-the -текст

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

например:

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

дает только:

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   

При использовании -Sя не думаю, что существует отказоустойчивый способ, поскольку комментарии к коду могут содержать любую возможную последовательность ... Но следующее работает почти всегда:

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

адаптировано из: Как выбрать строки между двумя шаблонами маркеров, которые могут встречаться несколько раз с awk / sed

Ответы на рассылку

В списке рассылки 2010 года есть ветка, в которой говорится, что это невозможно: https://sourceware.org/ml/binutils/2010-04/msg00445.html

Помимо gdbобходного пути, предложенного Томом, они также комментируют другой (худший) обходной путь компиляции, при -ffunction-sectionкотором помещается одна функция в раздел, а затем выполняется дамп раздела.

Николас Клифтон дал ему WONTFIX https://sourceware.org/ml/binutils/2015-07/msg00004.html , вероятно, потому, что обходной путь GDB охватывает этот вариант использования.

Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
Подход gdb отлично работает с разделяемыми библиотеками и объектными файлами.
Том Троми
16

Разберите одну единственную функцию с помощью Objdump

У меня есть два решения:

1. На основе командной строки

Этот метод отлично работает и дополняет простой. Я использую objdump с -d флага и трубами его через AWK . Вывод в разобранном виде выглядит как

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

Для начала я начну с описания вывода objdump. Раздел или функция отделена пустой строкой. Поэтому изменение FS (разделитель полей) на новую строку и RS (разделитель записей) на двойной перевод строки позволяет вам легко искать рекомендуемую функцию, поскольку ее просто найти в поле $ 1!

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

Конечно, вы можете заменить main любой другой функцией, которую хотите распечатать.

2. Bash Script

Я написал небольшой сценарий на bash для решения этой проблемы. Вставьте и скопируйте его и сохраните, например, как файл 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

Измените x-доступ и вызовите его, например:

chmod +x dasm
./dasm test main

Это намного быстрее, чем вызов GDB со скриптом. Кроме того, использование objdump не загружает библиотеки в память и, следовательно, безопаснее!


Виталий Фадеев запрограммировал автозаполнение для этого скрипта, что действительно приятно и ускоряет набор текста.

Скрипт можно найти здесь .

abu_bua
источник
Кажется, это зависит от того, быстрее objdumpили gdbбыстрее. Поскольку огромный двоичный файл (Firefox libxul.so) objdumpзанимает вечность, я отменил его через час, а это gdbзаймет меньше минуты.
Саймон
5

Чтобы упростить использование awk для анализа вывода objdump относительно других ответов:

objdump -d filename | sed '/<functionName>:/,/^$/!d'
fcr
источник
4

Это работает так же, как решение gdb (в том, что оно сдвигает смещения в сторону нуля), за исключением того, что оно не тормозит (выполняет работу примерно за 5 мс на моем ПК, тогда как решение gdb занимает около 150 мс):

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
источник
Я не могу сейчас протестировать, но с нетерпением жду, когда дойду до этого. Не могли бы вы уточнить аспект «смещения смещения к нулю»? Я не видел этого явного в ответах gdb здесь, и я хотел бы услышать немного больше о том, что на самом деле там происходит и почему.
MvG 07
По сути, это выглядит так, как будто функция, на которую вы нацелены (что и awkделает первая ), была единственной функцией в объектном файле, то есть даже если функция начинается, скажем 0x2d, с второго awk, она сдвинется в сторону 0x00(вычитая 0x2dот адреса каждой инструкции), что полезно, потому что ассемблерный код часто делает ссылки относительно начала функции, и если функция начинается с 0, вам не нужно делать вычитания в уме. Код awk мог бы быть лучше, но, по крайней мере, он выполняет свою работу и достаточно эффективен.
PSkocik 07
Оглядываясь назад, кажется, что компиляция с помощью -ffunction-sections- это более простой способ убедиться, что каждая функция начинается с 0.
PSkocik
4

Если у вас установлена ​​последняя версия binutils (2.32+), это очень просто.

При переходе --disassemble=SYMBOLк objdump будет дизассемблирована только указанная функция. Не нужно передавать начальный и конечный адреса.

LLVM objdump также имеет аналогичную опцию ( --disassemble-symbols).

Лео Лам
источник
Спасибо. Журнал изменений для binutils 2.32, 2 февраля 2019 г.: lists.gnu.org/archive/html/info-gnu/2019-02/msg00000.html " Параметр Objdump --disassemble теперь может принимать параметр, указывающий начальный символ для разборки. Дизассемблирование продолжится от этого символа до следующего символа или до конца функции »
osgx
3

Завершение Bash для ./dasm

Полные имена символов для этого решения (версия на языке D):

  • Набрав, dasm testа затем нажав TabTab, вы получите список всех функций.
  • При вводе dasm test mи последующем нажатии будут показаны TabTab все функции, начинающиеся с m , или, если существует только одна функция, она будет заполнена автоматически.

Файл /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
Виталий Фадеев
источник