如何使用 objdump 反汇编一个函数?

Posted

技术标签:

【中文标题】如何使用 objdump 反汇编一个函数?【英文标题】:How to disassemble one single function using objdump? 【发布时间】:2014-05-11 05:52:42 【问题描述】:

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

从this questions 我了解到,如果我只知道边界地址,我可能能够反汇编部分代码。从this answer,我学会了如何将拆分调试符号转换回单个文件。

但即使在单个文件上操作,甚至反汇编所有代码(即没有开始或停止地址,但将-d 参数转换为objdump),我仍然在任何地方都看不到那个符号。就所讨论的函数是静态的而言,这是有道理的,因此它不会被导出。尽管如此,valgrind 将报告函数名称,因此它必须存储在某个地方。

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

【问题讨论】:

一个小注解:如果一个函数被标记为static,它可能被编译器内联到它的调用站点。这可能意味着实际上可能没有任何要反汇编的函数,本身。如果您可以发现其他函数的符号,但不能发现您要查找的函数,这强烈暗示该函数已被内联。 Valgrind 可能仍然引用原始的预内联函数,因为 ELF 文件调试信息存储了每个单独指令的来源,即使指令被移到其他地方。 @davidg:是的,但由于汤姆的回答在这种情况下有效,因此情况似乎并非如此。不过,您是否知道一种方法,例如用每条指令来自哪里的信息来注释汇编代码? 很高兴听到! addr2line 将接受来自stdin 的 PC/IP 并打印出它们对应的源代码行。同样,objdump -l 会将 objdump 与源代码行混合在一起;尽管对于具有大量内联的高度优化的代码,这两个程序的结果并不总是特别有用。 【参考方案1】:

不完全按照您的要求,但如果您使用 GCC 从源代码编译 C 或 C++ 程序,您可以添加 a function attribute 将其放入二进制文件的自定义命名部分:

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

然后您可以要求objdump 仅显示带有-jdisasm 的命名部分中的函数。

【讨论】:

【参考方案2】:

只需使用objdump -d filename | awk '/<funcname>/,/^$/'

【讨论】:

这个问题有八个现有的答案,包括一个接受了 94 票的答案。您确定尚未提供您的答案吗?如果不是,为什么有人会更喜欢您的方法而不是提议的现有方法?您是否正在利用新功能?是否存在更适合您的方法的场景? 好吧,感谢您的 cmets。我还没有看到其他答案,只是离开了我的。同时,我并不打算要求投票。 但是,您的解决方案与其他解决方案相比有什么优势?你可以edit这些细节到你的答案中吗?这看起来很像sed 解决方案。为什么使用awk 而不是sed【参考方案3】:

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

【讨论】:

我在问题中写道,objdump -d 没有显示该功能,大概是因为它是静态的。 抱歉,我不知道为什么。【参考方案4】:

使用 Objdump 反汇编单个函数

我有两个解决方案:

1。基于命令行

这种方法效果很好,而且很简单。我使用带有 -d 标志的 objdump 并通过 awk pipe 它。反汇编的输出看起来像

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

首先,我从 objdump 输出的描述开始。 sectionfunction 用空行分隔。因此,将 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

这比使用脚本调用 gdb 快很多。除了使用 objdump 的方式之外,它不会将库加载到内存中,因此更安全!


Vitaly Fadeev 为这个脚本编写了一个自动补全功能,这确实是一个不错的功能并且可以加快打字速度。

脚本可以在here找到。

【讨论】:

这似乎取决于objdumpgdb 是否更快。对于一个巨大的二进制文件(Firefox 的 libxul.so)objdump 需要很长时间,我在一小时后取消了它,而 gdb 需要不到一分钟。【参考方案5】:

如果你有一个最近的 binutils (2.32+),这很简单。

--disassemble=SYMBOL 传递给objdump 将只反汇编指定的函数。无需传递起始地址和结束地址。

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

【讨论】:

谢谢。 binutils 2.32 的更新日志,2019 年 2 月 2 日:lists.gnu.org/archive/html/info-gnu/2019-02/msg00000.html“Objdump 的 --disassemble 选项现在可以带一个参数,指定反汇编的起始符号。反汇编将从这个符号继续到下一个符号或结尾功能。"【参考方案6】:

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 解决方法

打印以下段落: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 时,我认为没有防故障方法,因为代码 cmets 可能包含任何可能的序列...但以下几乎一直有效:

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

改编自:How to select lines between two marker patterns which may occur multiple times with awk/sed

邮件列表回复

邮件列表中有一个 2010 年的帖子说这是不可能的:https://sourceware.org/ml/binutils/2010-04/msg00445.html

除了 Tom 提出的 gdb 解决方法之外,他们还评论了另一种(更糟糕的)使用 -ffunction-section 编译的解决方法,它在每个部分放置一个函数,然后转储部分。

Nicolas Clifton 给了它一个 WONTFIX https://sourceware.org/ml/binutils/2015-07/msg00004.html,可能是因为 GDB 解决方法涵盖了该用例。

【讨论】:

gdb 方法适用于共享库和目标文件。【参考方案7】:

./dasm 的 Bash 完成

完整的符号名称到this solution(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

【讨论】:

【参考方案8】:

为了简化使用 awk 来解析 objdump 相对于其他答案的输出:

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

【讨论】:

【参考方案9】:

这就像 gdb 解决方案一样工作(因为它将偏移量移向零),除了它不滞后(在我的 PC 上完成工作大约需要 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 答案中没有看到这一点,我想更多地了解那里实际发生的情况以及原因。 它基本上使它看起来好像你的目标函数(这是第一个 awk 所做的)是目标文件中的唯一函数,也就是说,即使函数开始于,比如说0x2d,第二个 awk 会将其移向 0x00(通过从每条指令的地址中减去 0x2d),这很有用,因为汇编代码通常会相对于函数的开头进行引用,如果函数开始在 0 时,您不必在脑海中进行减法运算。 awk 代码可能会更好,但至少它可以完成这项工作并且相当高效。 回想起来,使用-ffunction-sections 编译似乎是确保每个函数从 0 开始的更简单方法。【参考方案10】:

我建议使用 gdb 作为最简单的方法。您甚至可以将其作为单线进行,例如:

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

【讨论】:

+1 未记录的功能! -ex 'command' 不在man gdb 中!?但实际上在gdb docs 中列出。同样对于其他人来说,/bin/ls 之类的东西可能会被删除,所以如果该命令没有显示任何内容,请尝试另一个对象!还可以将文件/对象指定为裸词参数;例如,gdb -batch -ex 'disassemble main' /bin/ls 手册页不是确定的。很长一段时间没有真正维护它,但现在我认为它是从主文档生成的。 “gdb --help”现在也更完整了。 gdb /bin/ls -batch -ex 'disassemble main' 也可以 如果您使用column -ts$'\t' 过滤 GDB 输出,您将可以很好地对齐原始字节和源列。此外,-ex 'set disassembly-flavor intel' 在其他 -exs 之前将导致 Intel 汇编语法。 我使用上面的方法调用了disassemble fn。但似乎当二进制文件中有多个同名函数时,只有一个被反汇编。是否可以全部反汇编,或者我应该根据原始地址进行反汇编?

以上是关于如何使用 objdump 反汇编一个函数?的主要内容,如果未能解决你的问题,请参考以下文章

dll文件如何反汇编成源码,C++语言编写

反汇编arm-linux-objdump 就能

如何反汇编原始 16 位 x86 机器代码?

C之bssdata存储位置区分,使用objdump -t反汇编查看变量所处存储位置

了解反汇编程序:查看添加使用了多少字节

带注释的可执行文件反汇编