จะถอดฟังก์ชั่นเดียวโดยใช้ objdump ได้อย่างไร?


91

ฉันได้ติดตั้งไบนารีไว้ในระบบของฉันและต้องการดูการแยกชิ้นส่วนของฟังก์ชันที่กำหนด ควรใช้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อาจถูกปล้นดังนั้นหากคำสั่งที่แน่นอนนั้นไม่แสดงอะไรเลยให้ลองใช้วัตถุอื่น! ยังสามารถระบุ file / object เป็นอาร์กิวเมนต์ bareword; เช่นgdb -batch -ex 'disassemble main' /bin/ls
hoc_age

3
หน้าคนไม่ชัดเจน มันไม่ได้รับการดูแลเป็นเวลานาน แต่ตอนนี้ฉันคิดว่ามันสร้างขึ้นจากเอกสารหลัก นอกจากนี้ "gdb --help" ก็สมบูรณ์มากขึ้นแล้วด้วย
Tom Tromey

7
gdb /bin/ls -batch -ex 'disassemble main'ได้ผลเช่นกัน
stefanct

1
หากคุณใช้column -ts$'\t'เพื่อกรองเอาต์พุต GDB คุณจะมีไบต์ดิบและคอลัมน์ซอร์สที่อยู่ในแนวเดียวกัน นอกจากนี้-ex 'set disassembly-flavor intel'ก่อนหน้า-exs อื่น ๆจะส่งผลให้ไวยากรณ์ประกอบของ Intel
Ruslan

ฉันเรียกdisassemble fnโดยใช้วิธีการด้านบน แต่ดูเหมือนว่าเมื่อมีหลายฟังก์ชันที่มีชื่อเดียวกันในไฟล์ไบนารีจะมีเพียงฟังก์ชันเดียวเท่านั้นที่ถูกถอดประกอบ เป็นไปได้ไหมที่จะถอดแยกชิ้นส่วนทั้งหมดหรือฉันควรแยกชิ้นส่วนตามที่อยู่ดิบ
TheAhmad

28

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วิธีแก้ปัญหาที่ Tom เสนอแล้วพวกเขายังแสดงความคิดเห็นเกี่ยวกับวิธีแก้ปัญหาอื่น (แย่กว่า) ของการรวบรวม-ffunction-sectionซึ่งทำให้หนึ่งฟังก์ชันต่อส่วนแล้วทิ้งส่วนนั้น

Nicolas Clifton ให้ WONTFIX https://sourceware.org/ml/binutils/2015-07/msg00004.htmlซึ่งอาจเป็นเพราะวิธีแก้ปัญหาของ GDB ครอบคลุมกรณีการใช้งานนั้น


วิธี gdb ทำงานได้ดีกับไลบรารีที่แบ่งใช้และไฟล์อ็อบเจ็กต์
Tom Tromey

16

ถอด One Single Function โดยใช้ 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 (Field Separator) เป็นบรรทัดใหม่และRS (ตัวคั่นเร็กคอร์ด) เป็นบรรทัดใหม่สองครั้งช่วยให้คุณค้นหาฟังก์ชันที่แนะนำได้อย่างง่ายดายเนื่องจากหาได้ง่ายในฟิลด์ $ 1!

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

แน่นอนคุณสามารถแทนที่main ได้ด้วยฟังก์ชันอื่น ๆ ที่คุณต้องการพิมพ์ได้

2. สคริปต์ทุบตี

ฉันได้เขียนสคริปต์ทุบตีเล็ก ๆ สำหรับปัญหานี้ วางและคัดลอกและบันทึกเป็นไฟล์เช่น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-accessและเรียกใช้ด้วยเช่น:

chmod +x dasm
./dasm test main

นี้เป็นมากเร็วกว่าการเรียก gdb กับสคริปต์ ข้างวิธีการใช้ objdump จะไม่โหลดไลบรารีลงในหน่วยความจำดังนั้นจึงปลอดภัยกว่า!


Vitaly Fadeevโปรแกรมการเติมข้อความอัตโนมัติให้กับสคริปต์นี้ซึ่งเป็นคุณสมบัติที่ดีและเพิ่มความเร็วในการพิมพ์

สคริปต์ที่สามารถพบได้ที่นี่


ดูเหมือนว่าจะขึ้นobjdumpหรือgdbเร็วกว่า สำหรับไบนารีขนาดใหญ่ (libxul.so) ของ Firefox objdumpจะใช้เวลาตลอดไปฉันจึงยกเลิกหลังจากนั้นหนึ่งชั่วโมงในขณะที่gdbใช้เวลาไม่ถึงหนึ่งนาที
Simon

6

หากคุณมี binutils ล่าสุด (2.32+) สิ่งนี้ง่ายมาก

การส่งผ่าน--disassemble=SYMBOLไปยัง objdump จะแยกเฉพาะฟังก์ชันที่ระบุ ไม่จำเป็นต้องผ่านที่อยู่เริ่มต้นและที่อยู่ปลายทาง

LLVM objdump ยังมีตัวเลือกที่คล้ายกัน ( --disassemble-symbols)


ขอขอบคุณ. การเปลี่ยนแปลงสำหรับ binutils 2.32, 02 กุมภาพันธ์ 2019: lists.gnu.org/archive/html/info-gnu/2019-02/msg00000.html " อ็อพชัน Objdump's --disassemble สามารถรับพารามิเตอร์ได้แล้วโดยระบุสัญลักษณ์เริ่มต้นสำหรับการถอดชิ้นส่วนการถอดชิ้นส่วน จะต่อจากสัญลักษณ์นี้ไปจนถึงสัญลักษณ์ถัดไปหรือจุดสิ้นสุดของฟังก์ชัน "
osgx

5

เพื่อลดความซับซ้อนในการใช้ awk สำหรับการแยกวิเคราะห์เอาต์พุตของ objdump เมื่อเทียบกับคำตอบอื่น ๆ :

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

4

วิธีนี้ใช้งานได้เหมือนกับโซลูชัน gdb (ซึ่งจะเปลี่ยนการชดเชยเป็นศูนย์) ยกเว้นว่าจะไม่ล้าหลัง (ทำให้งานเสร็จในประมาณ 5ms บนพีซีของฉันในขณะที่โซลูชัน gdb ใช้เวลาประมาณ 150ms):

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ทำ) เป็นฟังก์ชันเดียวในไฟล์ออบเจ็กต์นั่นคือแม้ว่าฟังก์ชันจะเริ่มต้นที่คำว่า0x2dawk ที่สองจะเลื่อนไปทาง0x00(โดยการลบ0x2dจากที่อยู่ของแต่ละคำสั่ง) ซึ่งมีประโยชน์เนื่องจากรหัสแอสเซมบลีมักทำให้การอ้างอิงสัมพันธ์กับจุดเริ่มต้นของฟังก์ชันและหากฟังก์ชันเริ่มต้นที่ 0 คุณไม่จำเป็นต้องทำการลบในหัวของคุณ รหัส awk อาจดีกว่า แต่อย่างน้อยมันก็ทำงานได้ดีและมีประสิทธิภาพพอสมควร
PSkocik

เมื่อมองย้อนกลับไปดูเหมือนว่าการรวบรวม-ffunction-sectionsเป็นวิธีที่ง่ายกว่าในการตรวจสอบให้แน่ใจว่าแต่ละฟังก์ชันเริ่มต้นที่ 0
PSkocik

3

Bash เสร็จสิ้นสำหรับ ./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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.