【问题标题】:Compiling C to 32-bit assembly with GCC doesn't match a book使用 GCC 将 C 编译为 32 位程序集与一本书不匹配
【发布时间】:2021-01-14 11:45:14
【问题描述】:

我一直在尝试将这个 C 程序编译为汇编,但它没有正常工作。

我正在阅读 Dennis Yurichev 初学者的逆向工程,但我没有得到相同的输出。它是一个简单的 hello world 语句。我正在尝试获得 32 位输出

#include <stdio.h>
int main()
{
     printf("hello, world\n");
     return 0;
}

这就是书中所说的输出应该是什么

   main proc near
    var_10 = dword ptr -10h
    push ebp
    mov ebp, esp
    and esp, 0FFFFFFF0h
    sub esp, 10h
    mov eax, offset aHelloWorld ; "hello, world\n"
    mov [esp+10h+var_10], eax
    call _printf
    mov eax, 0
    leave
    retn
    main endp

这里是步骤;

  1. 将print语句编译成32bit(我目前运行的是64bit pc)

    • gcc -m32 hello_world.c -o hello_world
  2. 使用gdb反汇编

    • gdb 文件
    • 设置拆解风格的intel
    • 设置架构 i386:intel 反汇编主程序

我明白了;


    lea    ecx,[esp+0x4]
    and    esp,0xfffffff0
    push   DWORD PTR [ecx-0x4]
    push   ebp
    mov    ebp,esp
    push   ebx
    push   ecx
    call   0x565561d5 <__x86.get_pc_thunk.ax>
    add    eax,0x2e53
    sub    esp,0xc
    lea    edx,[eax-0x1ff8]
    push   edx
    mov    ebx,eax
    call   0x56556030 <puts@plt>
    add    esp,0x10
    mov    eax,0x0
    lea    esp,[ebp-0x8]
    pop    ecx
    pop    ebx
    pop    ebp
    lea    esp,[ecx-0x4]
    ret 

我也用过

objdump -D -M i386,intel hello_world> hello_world.txt

ndisasm -b32 hello_world > hello_world.txt

但这些都不起作用。我只是不知道出了什么问题。我需要帮助。看着你彼得·科德斯 ^^

【问题讨论】:

  • 为什么您希望您的编译器产生与其他人(不同)编译器相同的输出?
  • @EOF 实际上 gcc 给出了该代码
  • 您是否在与作者相同的操作系统上使用相同版本的相同编译器、相同的优化标志?如果有任何不同,即使是编译器版本,那么您的结果也不会完全匹配。
  • 编译器从输入语言以输出语言创建功能等效的代码。没有理由期望任何两个或相同的选项具有不同的选项(或者与 gcc 一样,即使相同版本也是如此)产生完全相同的输出。为了理解这本书,只需阅读本书并使用书中的任何内容。
  • 感谢您的回复。我知道会有一些差异,因为编译器不同,但我没想到会有那么多。添加了更多说明。我也完全忘记了优化。我仍然希望至少能看到字符串,但什么也没有……

标签: c assembly gcc x86 objdump


【解决方案1】:

这本书的输出看起来像 MSVC,而不是 GCC。 GCC 绝对不会发出 main proc 因为那是 MASM 语法,而不是有效的 GAS 语法。而且它不会做像var_10 = dword ptr -10h这样的事情。
(即使是这样,您也不会在 反汇编 中看到汇编时常量定义,只能在本书建议您查看的编译器的 asm 输出中看到。gcc -S -masm=intel 输出。@987654321 @)

因此存在很多差异,因为您使用的是不同的编译器。即使是现代版本的 MSVC(在 Godbolt 编译器资源管理器上)也会产生一些不同的 asm,例如不费心将 ESP 对齐 16,可能是因为更现代的 Windows 版本或 CRT 启动代码已经这样做了?


此外,您的 GCC 默认会生成 PIE 可执行文件,因此请使用 -fno-pie -no-pie。 32 位 PIE 在效率和易于理解方面都很糟糕。 How do i get rid of call __x86.get_pc_thunk.ax。 (也可以32-bit absolute addresses no longer allowed in x86-64 Linux? 了解有关 PIE 可执行文件的更多信息,主要关注 64 位代码)

main 序言中额外的笨拙堆栈对齐是 GCC8 为不需要 alloca 的函数优化的。但是,当您不启用优化时,即使是当前的 GCC10 也会发出完整的未优化版本:(。 Why is gcc generating an extra return address?Trying to understand gcc's complicated stack-alignment at the top of main that copies the return address

将 printf 优化为 puts:参见 How to get the gcc compiler to not optimize a standard library function call like printf?-O2 optimizes printf("%s\n", str) to puts(str)gcc -fno-builtin-printf 是避免这种情况发生的一种方法,或者只是习惯它。 GCC 甚至在 -O0 上进行了一些优化,而其他编译器只在更高的优化级别上进行。


MSVC 19.10 像这样 (on the Godbolt compiler explorer) 编译您的函数,禁用优化(默认,无编译器选项)。

_main   PROC
        push    ebp
        mov     ebp, esp
        push    OFFSET $SG4501
        call    _printf
        add     esp, 4
        xor     eax, eax
        pop     ebp
        ret     0
_main   ENDP

_DATA   SEGMENT
$SG4501 DB        'hello, world', 0aH, 00H

GCC10.2 在序言中仍然使用了过于复杂的堆栈对齐舞蹈。

.LC0:
        .string "hello, world"
main:
        lea     ecx, [esp+4]
        and     esp, -16
        push    DWORD PTR [ecx-4]
        push    ebp
        mov     ebp, esp
        push    ecx
        sub     esp, 4
# end of function prologue, I think.
        sub     esp, 12                  # make sure arg will be 16-byte aligned
        push    OFFSET FLAT:.LC0         # push a pointer
        call    puts
        add     esp, 16                  # pop the arg-passing space
        mov     eax, 0                   # return 0

        mov     ecx, DWORD PTR [ebp-4]   # undo stack alignment.
        leave
        lea     esp, [ecx-4]
        ret

是的,这非常低效。如果您将函数调用为 main 以外的任何名称,则它已经假定 ESP 在函数入口处对齐 16:

# GCC10.2 -m32 -O0
.LC0:
        .string "hello, world"
foo:
        push    ebp
        mov     ebp, esp
        sub     esp, 8            # reach a 16-byte boundary, assuming ESP%16 = 12 on entry
#
        sub     esp, 12                   
        push    OFFSET FLAT:.LC0
        call    puts
        add     esp, 16
        mov     eax, 0
        leave
        ret

所以它仍然没有结合两个sub 指令,但你确实告诉它不要优化,所以预计会出现脑死亡代码。例如,请参阅Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?

【讨论】:

  • 首先我需要提到的是,在编译器方面我是一个菜鸟,而在汇编方面我不是一个菜鸟。在书中,gcc 和 msvc 都有 32 位和 64 位输出。我相信他使用 gcc -S -masm=intel 进行 32 位输出。我没有尝试使用它,因为我对 nasm 比对 masm 更熟悉。您是说 gcc 正在向文件添加额外的噪音。
  • 你是个天才先生。你可以关闭这个问题
  • @opobtdfs:您可以通过单击其中一个答案的向上/向下投票箭头下的“接受”复选框来做到这一点。
【解决方案2】:

首先你编译而不是反编译。

在没有优化的情况下编译时会产生很多噪音。如果您使用优化进行编译,您将获得与您拥有的几乎相同的更小的代码(为了防止从 printf 更改为 puts 您需要删除 '\n' https://godbolt.org/z/cs4qe9):

.LC0:
        .string "hello, world"
main:
        lea     ecx, [esp+4]
        and     esp, -16
        push    DWORD PTR [ecx-4]
        push    ebp
        mov     ebp, esp
        push    ecx
        sub     esp, 16
        push    OFFSET FLAT:.LC0
        call    puts
        mov     ecx, DWORD PTR [ebp-4]
        add     esp, 16
        xor     eax, eax
        leave
        lea     esp, [ecx-4]
        ret

https://godbolt.org/z/xMqo33

【讨论】:

  • 这与问题中的任何一个程序集 sn-ps 都不相同。
  • @EOF 几乎相同
  • 你对我原来的评论的回复是什么...?
  • 只使用 IDA 会更好吗?
【解决方案3】:

我的 GCC 会非常热切地将对 printf 的呼叫转换为 puts!我没有设法找到会使编译器不执行此操作的命令行选项。 IE。该程序具有相同的外部行为,但机器代码是

#include <stdio.h>
int main(void)
{
     puts("hello, world");
}

因此,您将很难获得与书中完全相同的程序集,因为该书中的程序集调用printf 而不是puts

【讨论】:

  • 如果你删除\n,它会发出 printf。
  • 试试-fno-builtin-printf
猜你喜欢
  • 2011-02-21
  • 2016-08-06
  • 2011-09-08
  • 2015-10-27
  • 1970-01-01
  • 1970-01-01
  • 2011-12-22
  • 2013-12-02
相关资源
最近更新 更多