如何使用objdump分解单个函数?

117

我在我的系统上安装了一个二进制文件,并想查看给定函数的反汇编代码。最好使用objdump,但其他解决方案也可以接受。

这个问题中,我了解到如果我只知道边界地址,就可以对代码的一部分进行反汇编。从这个答案中,我了解到如何将我的拆分调试符号转换回单个文件。

但是,即使在单个文件上操作,即使反汇编所有代码(即不带任何起始或停止地址,而是使用纯-d参数到objdump),我仍然没有看到该符号出现在哪里。这是有道理的,因为涉及到的函数是静态的,所以它没有被导出。尽管如此,valgrind将报告函数名称,因此它必须存储在某个地方。

查看调试部分的详细信息,我发现该名称在.debug_str部分中提到,但我不知道可以将其转换为地址范围的工具。


2
一个小的侧面说明:如果一个函数被标记为“static”,编译器可能会将它内联到其调用点中。这可能意味着实际上没有任何函数需要反汇编。如果您可以找到其他函数的符号,但找不到您要查找的函数,则这是一个强烈的提示该函数已被内联。Valgrind仍然可以引用原始的预内联函数,因为ELF文件调试信息存储了每个单独指令来自哪里,即使指令被移动到其他地方。 - davidg
@davidg:没错,但由于Tom的答案在这种情况下有效,这似乎不是问题。尽管如此,您是否知道一种方法,例如使用哪个信息来注释汇编代码每个指令来自哪里? - MvG
1
很高兴听到这个消息!addr2line将从stdin接受PCs/IPs并打印出它们对应的源代码行。同样,objdump -l将混合objdump和源代码行;但是对于高度优化且具有大量内联的代码,任何一个程序的结果都不总是特别有用。 - davidg
12个回答

109

我建议使用gdb作为最简单的方法。你甚至可以像这样一行代码完成:

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

4
+1个未公开的功能!-ex '命令'不在man gdb中?但实际上在gdb文档中有列出。另外,对于其他人,像/ bin / ls这样的东西可能会被剥离,因此如果该命令未显示任何内容,请尝试其他对象!还可以将文件/对象指定为裸字参数;例如,gdb -batch -ex 'disassemble main' /bin/ls - hoc_age
3
手册并非权威参考。长期以来,它没有真正进行维护,但现在我认为它是从主要文档生成的。同时,“gdb --help”现在也更加完整了。 - Tom Tromey
12
gdb /bin/ls -batch -ex 'disassemble main'同样有效。 (说明:该命令用于在GDB中以批处理模式下反汇编/bin/ls二进制文件中的main函数) - stefanct
2
如果您使用 column -ts$'\t' 来过滤 GDB 输出,您将会得到原始字节和源代码列对齐的效果。此外,在其他 -ex 之前加上 -ex 'set disassembly-flavor intel' 将会得到 Intel 汇编语法。 - Ruslan
我使用上述方法调用了 disassemble fn。但是当二进制文件中存在多个同名函数时,似乎只有一个被反汇编了。是否可以将它们全部反汇编,或者我应该根据原始地址进行反汇编? - TheAhmad
只是补充一下,如果你的命名空间中有一个符号,你需要将其名称用单引号括起来:gdb -batch -ex 'file binary' -ex "disassemble 'namespace::function'"否则我会得到混淆的错误“在类或命名空间“namespace”中没有类型“function”。”。如果我使用双引号,我会得到同样令人困惑的错误“你不能在没有要调试的进程的情况下这么做。”。 - Simon

44

如果您使用的是非常新的binutils(2.32+),那么这非常简单。

在objdump中传递--disassemble=SYMBOL,将仅反汇编指定函数。无需传递起始地址和结束地址。

LLVM objdump也有类似的选项(--disassemble-symbols)。


感谢您。binutils 2.32的更改日志,2019年2月2日:https://lists.gnu.org/archive/html/info-gnu/2019-02/msg00000.html "Objdump的--disassemble选项现在可以带一个参数,指定反汇编的起始符号。反汇编将从该符号继续到下一个符号或函数的结尾。" - osgx
1
适用于ARM gcc工具链9-2020-q2-update。 - personal_cloud

41

使用gdb的disassemble/rs命令可以同时显示源代码和二进制码

使用这种格式,输出结果与objdump -S几乎相同:

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

主函数.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 解决方法

打印如下段落:https://unix.stackexchange.com/questions/82944/how-to-grep-for-text-in-a-file-and-display-the-paragraph-that-has-the-text

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

除了Tom提出的gdb解决方法外,他们还评论了另一个(更糟糕的)解决方法,即使用-ffunction-section进行编译,将每个函数放入一个单独的section中,然后对该section进行转储。

Nicolas Clifton表示WONTFIX:https://sourceware.org/ml/binutils/2015-07/msg00004.html,很可能是因为GDB解决方法可以覆盖该用例。


gdb方法在共享库和目标文件上运行良好。 - Tom Tromey
我认为现在可能是“/rm”而不是“/rs”。 - cbr
在GDB shell上使用help disas命令可以解释差异。在GDB 13.1和Ubuntu 23.04中,使用/m命令结束的帮助信息为:"这个修饰符在实践中没有证明其有用,并且已被/s修饰符取代。" - Ciro Santilli OurBigBook.com
谢谢你的纠正。我原以为我在使用一个较新的GDB,但事实上,我运行的容器里面的GDB似乎是旧版本的,没有/s路径。 - cbr

17

使用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脚本

我为此问题编写了一个小型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-access 并使用例如以下方式调用:

chmod +x dasm
./dasm test main

使用objdump比使用脚本调用gdb要快得多。此外,使用objdump的方法不会将库加载到内存中,因此更安全!


Vitaly Fadeev为这个脚本编写了自动完成功能,这是一个非常好的特性,可以加快打字速度。

该脚本可以在这里找到。


似乎取决于objdumpgdb哪个更快。对于一个巨大的二进制文件(Firefox' libxul.so),objdump需要很长时间,我在一个小时后取消了它,而gdb只需要不到一分钟。 - Simon

5
为了更好地使用awk解析objdump的输出结果,相对于其他答案,我们可以进行简化:
objdump -d filename | sed '/<functionName>:/,/^$/!d'

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代码可能还可以改进,但至少它能够完成工作并且相当有效。 - Petr Skocik
回顾起来,似乎使用“-ffunction-sections”编译是确保每个函数从0开始的更简单方法。 - Petr Skocik

3

针对 ./dasm 的Bash自动完成

完成符号名称此解决方案(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

1

虽然不完全符合您的要求,但如果您正在使用GCC从源代码编译C或C++程序,则可以添加函数属性将其放置在二进制文件中的自定义命名部分:

extern __attribute__((noinline, section("disasm"))) void foo() {}

然后您可以使用objdump -jdisasm命令,只显示该命名部分中的函数。

0

对于那些想要查看带有调试符号的二进制文件汇编代码的人来说,可以参考man页面:

objdump -d <binary> --disassemble=<symbol>

这应该就可以了。不需要使用gdb或其他任何工具。


0

也许这很容易实现:
objdump -d libxxx.so | grep -A 50 func_name_to_be_searched


我在问题中写道,objdump -d没有显示该函数,可能是由于它是静态的。 - MvG
抱歉,我不确定原因。 - galian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接