Як розібрати одну єдину функцію за допомогою objdump?


90

У моїй системі встановлено двійковий файл, і я хотів би поглянути на розбирання заданої функції. Бажано використовувати objdump, але також можуть бути прийняті й інші рішення.

З цих питань я дізнався, що, можливо, я зможу розібрати частину коду, якщо знаю лише адреси меж. З цієї відповіді я дізнався, як перетворити мої розділені символи налагодження назад в один файл.

Але навіть оперуючи цим єдиним файлом і навіть розбираючи весь код (тобто без адреси запуску чи зупинки, але простий -dпараметр для objdump), я все одно ніде не бачу цього символу. Що має сенс, оскільки функція, про яку йде мова, є статичною, тому вона не експортується. Тим не менше, valgrindбуде повідомляти назву функції, тому її потрібно десь зберігати.

Переглядаючи деталі розділів налагодження, я знаходжу це ім’я, згадане в цьому .debug_strрозділі, але я не знаю інструменту, який може перетворити це на діапазон адрес.


2
Незначне побічне зауваження: Якщо функцію позначено static, компілятор може вбудувати її на сайти викликів. Це може означати , що не може бути на Насправді будь-яка функція демонтує, самі по собі . Якщо ви можете помітити символи для інших функцій, але не функції, яку ви шукаєте, це сильний натяк на те, що функція була вбудована. Valgrind все ще може посилатися на оригінальну попередньо вбудовану функцію, оскільки інформація про налагодження файлу ELF зберігає, звідки походить кожна окрема інструкція, навіть якщо інструкції переміщені в інше місце.
davidg

@davidg: правда, але оскільки відповідь Тома спрацювала у цьому випадку, схоже, це не так. Тим не менше, чи знаєте ви спосіб, наприклад, анотувати код збірки такою інформацією про те, звідки походить кожна інструкція?
MvG

1
Приємно чути! addr2lineприйме ПК / IP-адреси stdinта роздрукує відповідні рядки вихідного коду. Подібним чином, objdump -lбуде змішувати objdump з вихідними рядками; хоча для високооптимізованого коду з інтенсивним вбудовуванням результати будь-якої програми не завжди особливо корисні.
davidg

Відповіді:


87

Я б запропонував використовувати 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 + обхідні шляхи

Роздрукуйте абзац, як згадано за посиланням: /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-сценарій для цього випуску. Вставте та скопіюйте його та збережіть як, наприклад, файл дасми .

#!/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 не завантажує бібліотеки в пам'ять і, отже, безпечніше!


Віталій Фадєєв запрограмував автозаповнення цього сценарію, що є дійсно приємною функцією та пришвидшує набір тексту.

Сценарій можна знайти тут .


Здається, це залежить, objdumpчи gdbце швидше. Для величезного двійкового файлу (Firefox 'libxul.so) objdumpпотрібно вічно, я скасував його через годину, а gdbзаймає менше хвилини.
Саймон

5

Щоб спростити використання awk для аналізу вихідних даних objdump щодо інших відповідей:

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

5

Якщо у вас є нещодавні binutils (2.32+), це дуже просто.

Перехід --disassemble=SYMBOLдо objdump розбере лише вказану функцію. Не потрібно передавати початкову та кінцеву адреси.

LJVM objdump також має подібний параметр ( --disassemble-symbols).


Дякую. Журнал змін для binutils 2.32, 02 лютого 2019: lists.gnu.org/archive/html/info-gnu/2019-02/msg00000.html " Опція Objdump --disassemble тепер може приймати параметр, вказуючи початковий символ для розбирання. Розбирання продовжиться від цього символу до наступного символу або кінця функції. "
osgx

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

Зараз я не можу протестувати, але я з нетерпінням чекаю, коли до цього підійду. Чи можете Ви дещо детальніше розглянути аспект «зміщення зміщення до нуля»? Я не бачив цього явного у відповідях на gdb тут, і хотів би почути трохи більше про те, що насправді там відбувається і чому.
MvG

В основному це робить вигляд, ніби функція, на яку ви націлюєтесь (це те, що awkробить перша ), є єдиною функцією у об’єктному файлі, тобто навіть якщо функція починається, скажімо 0x2d, другий awk змістить її в бік 0x00(шляхом віднімання 0x2dвід адреси кожної інструкції), що корисно, оскільки код збірки часто робить посилання щодо початку функції, і якщо функція починається з 0, вам не потрібно робити віднімання в голові. Код awk може бути кращим, але принаймні він робить свою роботу і є досить ефективним.
PSkocik

В ретроспективі здається, що компіляція з -ffunction-sections- це простіший спосіб переконатися, що кожна функція починається з 0.
PSkocik,

3

Завершення башу для ./dasm

Повні імена символів для цього рішення (версія D lang):

  • Набравши 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.