Làm thế nào để tháo rời một chức năng duy nhất bằng objdump?


90

Tôi đã cài đặt một tệp nhị phân trên hệ thống của mình và muốn xem xét việc tháo rời một hàm nhất định. Tốt hơn là sử dụng objdump, nhưng các giải pháp khác cũng sẽ được chấp nhận.

Từ những câu hỏi này, tôi đã học được rằng tôi có thể tháo rời một phần của mã nếu tôi chỉ biết địa chỉ ranh giới. Từ câu trả lời này, tôi đã học được cách chuyển các ký hiệu gỡ lỗi đã tách của mình trở lại thành một tệp duy nhất.

Nhưng ngay cả khi hoạt động trên một tệp duy nhất đó, và thậm chí tháo rời tất cả mã (tức là không có địa chỉ bắt đầu hoặc địa chỉ dừng, mà là -dtham số đơn giản objdump), tôi vẫn không thấy biểu tượng đó ở đâu. Điều này có ý nghĩa trong chừng mực hàm được đề cập là tĩnh, vì vậy nó không được xuất. Tuy nhiên, valgrindsẽ báo cáo tên hàm, vì vậy nó phải được lưu trữ ở đâu đó.

Nhìn vào chi tiết của các phần gỡ lỗi, tôi thấy tên đó được đề cập trong .debug_strphần đó, nhưng tôi không biết một công cụ có thể biến điều này thành một dải địa chỉ.


2
Một lưu ý nhỏ: Nếu một hàm được đánh dấu static, nó có thể được trình biên dịch đưa vào các site gọi của nó. Điều này có nghĩa có thể không thực sự có bất kỳ chức năng để tháo rời, cho mỗi gia nhập . Nếu bạn có thể phát hiện các ký hiệu cho các chức năng khác, nhưng không phải là chức năng bạn đang tìm kiếm, thì đây là một gợi ý mạnh mẽ rằng chức năng đã được nội tuyến. Valgrind vẫn có thể tham chiếu đến hàm được gạch trước ban đầu vì thông tin gỡ lỗi tệp ELF lưu trữ nơi bắt nguồn của từng lệnh riêng lẻ, ngay cả khi các hướng dẫn được chuyển đi nơi khác.
davidg

@davidg: đúng, nhưng vì câu trả lời của Tom có ​​tác dụng trong trường hợp này, nên điều này có vẻ không đúng. Tuy nhiên, bạn có biết một cách để chú thích mã hợp ngữ với thông tin về nơi xuất phát của mỗi lệnh không?
MvG

1
Tốt để nghe! addr2linesẽ chấp nhận PC / IP từ stdinvà in ra các dòng mã nguồn tương ứng của chúng. Tương tự như vậy, objdump -lsẽ trộn objdump với các dòng nguồn; mặc dù đối với mã được tối ưu hóa cao với nhiều nội tuyến, kết quả của một trong hai chương trình không phải lúc nào cũng đặc biệt hữu ích.
davidg

Câu trả lời:


87

Tôi sẽ đề nghị sử dụng gdb là cách tiếp cận đơn giản nhất. Bạn thậm chí có thể làm điều đó như một lớp lót, như:

gdb -batch -ex 'file /bin/ls' -ex 'disassemble main'

4
+1 tính năng không có giấy tờ! -ex 'command'không có trong man gdb!? Nhưng trên thực tế được liệt kê trong tài liệu gdb . Ngoài ra đối với những người khác, những thứ như /bin/lscó thể bị loại bỏ, vì vậy nếu lệnh chính xác đó không hiển thị gì, hãy thử đối tượng khác! Cũng có thể chỉ định tệp / đối tượng làm đối số từ trần; ví dụ:gdb -batch -ex 'disassemble main' /bin/ls
hoc_age

3
Trang người đàn ông không phải là dứt khoát. Trong một thời gian dài, nó không thực sự được duy trì, nhưng bây giờ tôi nghĩ rằng nó được tạo ra từ các tài liệu chính. Ngoài ra, "gdb --help" cũng đã hoàn thiện hơn.
Tom Tromey

7
gdb /bin/ls -batch -ex 'disassemble main'hoạt động tốt
stefanct

1
Nếu bạn sử dụng column -ts$'\t'để lọc đầu ra GDB, bạn sẽ có các byte thô và cột nguồn được căn chỉnh phù hợp. Ngoài ra, -ex 'set disassembly-flavor intel'trước các -exs khác sẽ dẫn đến cú pháp hợp ngữ Intel.
Ruslan

Tôi đã gọi disassemble fnbằng cách sử dụng phương pháp ở trên. Nhưng có vẻ như khi có nhiều hàm có cùng tên trong tệp nhị phân, chỉ có một hàm được tháo rời. Có thể tháo rời tất cả chúng hay tôi nên tháo rời chúng dựa trên địa chỉ thô?
TheAhmad

26

gdb disassemble/rsđể hiển thị nguồn và byte thô

Với định dạng này, nó thực sự gần với objdump -Sđầu ra:

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

C chính

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

Biên dịch và tháo rời

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

Tháo rời:

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.

Đã thử nghiệm trên Ubuntu 16.04, GDB 7.11.1.

objdump + awk giải pháp

In đoạn văn như đã đề cập tại: /unix/82944/how-to-grep-for-text-in-a-file-and-display-the-paragraph-that-has-the -bản văn

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

ví dụ:

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

chỉ đưa ra:

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   

Khi sử dụng -S, tôi không nghĩ rằng có cách chống lỗi, vì các bình luận mã có thể chứa bất kỳ trình tự nào có thể xảy ra ... Nhưng những cách sau hầu như hoạt động mọi lúc:

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

phỏng theo: Cách chọn các dòng giữa hai mẫu điểm đánh dấu có thể xảy ra nhiều lần với awk / sed

Thư trả lời danh sách gửi thư

Có một chuỗi năm 2010 trong danh sách gửi thư cho biết không thể thực hiện được: https://sourceware.org/ml/binutils/2010-04/msg00445.html

Bên cạnh gdbcách giải quyết do Tom đề xuất, họ cũng nhận xét về một cách giải quyết khác (tệ hơn) là biên dịch với cách -ffunction-sectionđặt một chức năng cho mỗi phần và sau đó kết xuất phần đó.

Nicolas Clifton đã cung cấp cho nó một WONTFIX https://sourceware.org/ml/binutils/2015-07/msg00004.html , có thể là do giải pháp GDB bao gồm trường hợp sử dụng đó.


Phương pháp gdb hoạt động tốt trên các thư viện được chia sẻ và tệp đối tượng.
Tom Tromey

16

Tháo rời một chức năng duy nhất sử dụng objdump

Tôi có hai giải pháp:

1. Dựa trên dòng lệnh

Phương pháp này hoạt động hoàn hảo và bổ sung một phương pháp đơn giản. Tôi sử dụng objdump với -d cờ và ống nó thông qua awk . Đầu ra được tháo rời trông giống như

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

Để bắt đầu, tôi bắt đầu với phần mô tả đầu ra của objdump. Một phần hoặc chức năng được phân tách bằng một dòng trống. Do đó, việc thay đổi FS (Bộ phân tách trường) thành dòng mới và RS (Bộ phân tách bản ghi) thành hai dòng mới cho phép bạn dễ dàng tìm kiếm hàm được đề xuất của mình, vì bạn chỉ cần tìm trong trường $ 1!

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

Tất nhiên bạn có thể thay thế main bằng bất kỳ chức năng nào khác mà bạn muốn in.

2. Tập lệnh Bash

Tôi đã viết một kịch bản bash nhỏ cho vấn đề này. Dán và sao chép nó và lưu nó như tệp 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

Thay đổi x-access và gọi nó bằng ví dụ:

chmod +x dasm
./dasm test main

Đây là nhiều nhanh hơn so với cách gọi gdb với một kịch bản. Bên cạnh đó cách sử dụng objdump sẽ không tải các thư viện vào bộ nhớ và do đó an toàn hơn!


Vitaly Fadeev đã lập trình tự động hoàn thành tập lệnh này, đây thực sự là một tính năng hay và tăng tốc độ nhập.

Kịch bản có thể được tìm thấy ở đây .


Có vẻ như nó phụ thuộc nếu objdumphoặc gdbnhanh hơn. Đối với một tệp nhị phân khổng lồ (Firefox 'libxul.so) objdumpmất vĩnh viễn, tôi đã hủy nó sau một giờ, trong khi chỉ gdbmất chưa đầy một phút.
Simon

5

Để đơn giản hóa việc sử dụng awk để phân tích cú pháp đầu ra của objdump so với các câu trả lời khác:

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

5

Nếu bạn có binutils rất gần đây (2.32+), điều này rất đơn giản.

Chuyển --disassemble=SYMBOLđến objdump sẽ chỉ tháo rời chức năng đã chỉ định. Không cần phải chuyển địa chỉ bắt đầu và địa chỉ kết thúc.

LLVM objdump cũng có một tùy chọn tương tự ( --disassemble-symbols).


Cảm ơn bạn. Changelog cho binutils 2.32, ngày 02 tháng 2 năm 2019: list.gnu.org/archive/html/info-gnu/2019-02/msg00000.html " Tùy chọn --disassemble của objdump hiện có thể nhận một tham số, chỉ định biểu tượng bắt đầu để tháo gỡ. Disassembly sẽ tiếp tục từ biểu tượng này cho đến biểu tượng tiếp theo hoặc kết thúc hàm. "
osgx

4

Điều này hoạt động giống như giải pháp gdb (ở chỗ nó chuyển các hiệu số về 0) ngoại trừ việc nó không bị lag (hoàn thành công việc trong khoảng 5ms trên PC của tôi trong khi giải pháp gdb mất khoảng 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 }'

Tôi không thể kiểm tra ngay bây giờ, nhưng tôi mong đợi khi tôi hoàn thành điều này. Bạn có thể giải thích một chút về khía cạnh "dịch chuyển bù về phía không" không? Tôi không thấy điều này rõ ràng trong các câu trả lời gdb ở đây và tôi muốn nghe thêm một chút về những gì thực sự đang diễn ra ở đó và tại sao.
MvG

Về cơ bản, nó làm cho nó trông như thể hàm mà bạn nhắm mục tiêu (đó là những gì cái đầu tiên awklàm) là hàm duy nhất trong tệp đối tượng, nghĩa là, ngay cả khi hàm bắt đầu ở, chẳng hạn 0x2d, awk thứ hai sẽ chuyển nó sang 0x00(bằng cách trừ 0x2dtừ địa chỉ của mỗi lệnh), điều này rất hữu ích vì mã hợp ngữ thường tạo tham chiếu liên quan đến phần bắt đầu của hàm và nếu hàm bắt đầu từ 0, bạn không cần phải thực hiện các phép trừ trong đầu. Mã awk có thể tốt hơn nhưng ít nhất nó thực hiện công việc và khá hiệu quả.
PSkocik

Nhìn lại, có vẻ như biên dịch -ffunction-sectionslà một cách dễ dàng hơn để đảm bảo mỗi chức năng bắt đầu bằng 0.
PSkocik

3

Hoàn thành bash cho ./dasm

Hoàn thành các tên ký hiệu cho giải pháp này (phiên bản D lang):

  • Bằng cách gõ dasm testvà sau đó nhấn TabTab, bạn sẽ nhận được danh sách tất cả các chức năng.
  • Bằng cách nhập dasm test mvà sau đó nhấn TabTab tất cả các chức năng bắt đầu bằng m sẽ được hiển thị hoặc trong trường hợp chỉ tồn tại một chức năng, nó sẽ tự động hoàn thành.

Tệp tin /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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.