【问题标题】:assembly x86 'decompiling' [closed]程序集 x86 '反编译' [关闭]
【发布时间】:2025-12-22 10:45:16
【问题描述】:

我无法理解这个汇编 x86 代码(AT&T 表示法)。我需要能够理解它(编写编译为该代码的 C++ 函数)并在考试中解决类似的练习。 你能向我解释一下哪个部分做什么以及约定是什么?

f:
    pushl %ebp ; 1
    movl %esp, %ebp; 2
    pushl %ebx ; 3
    subl $36, %esp; 4
    movl 8(%ebp), %edx ; 5
    movl 12(%ebp), %eax ; 6
    movl (%eax), %eax ; 7
    movl %edx, 8(%esp) ; 8
    leal 16(%ebp), %edx ; 9
    movl %edx, 4(%esp) ; 10
    movl %eax, (%esp) ; 11
    call f; 12
    movl %eax, -12(%ebp) ; 13
    movl 16(%ebp), %edx ; 14
    movl 12(%ebp), %eax ; 15
    movl %edx, (%eax) ; 16
    movl 12(%ebp), %eax ; 17
    movl (%eax), %edx ; 18
    movl -12(%ebp), %eax ; 19
    movl %edx, 8(%esp) ; 20
    leal 8(%ebp), %edx ; 21
    movl %edx, 4(%esp) ; 22
    movl %eax, (%esp) ; 23
    call f; 24
    movl %eax, %ebx; 25
    movl 16(%ebp), %edx ; 26
    movl -12(%ebp), %eax ; 27
    movl %edx, 8(%esp) ; 28
    movl 12(%ebp), %edx ; 29
    movl %edx, 4(%esp) ; 30
    movl %eax, (%esp) ; 31
    call f; 32
    movl %eax, %edx; 33
    movl 16(%ebp), %eax ; 34
    movl %edx, 8(%esp) ; 35
    leal 8(%ebp), %edx ; 36
    movl %edx, 4(%esp) ; 37
    movl %eax, (%esp) ; 38
    call f; 39
    movl %ebx, 8(%esp) ; 40
    leal -12(%ebp), %edx ; 41
    movl %edx, 4(%esp) ; 42
    movl %eax, (%esp) ; 43
    call f; 44
    addl $36, %esp; 45
    popl %ebx ; 46
    popl %ebp ; 47
    ret; 48

没有跳转,但是有几个'call f',是不是意味着有死循环?

【问题讨论】:

  • 这显然是您真正应该在寻求帮助之前发布“您的最佳尝试”的情况之一,因为这将告诉我们您需要帮助的级别。
  • 到目前为止你是怎么翻译的? (顺便说一句,在调试器中逐步执行将回答您的第二个问题......)
  • @Adriano:我能想到很多这样的例子是不可行的。即使在这种情况下,也可能需要很长时间才能得出结论。
  • 您只需要快速查看前 12 条指令,就可以确定它将耗尽堆栈空间并崩溃! :-)
  • @MatsPetersson 是的,当然编译后的代码会很快真的很复杂,但是:1)这是一个考试文本。 2)我没有看到任何试图了解发生了什么的尝试......

标签: c++ assembly x86 decompiling


【解决方案1】:

以下内容可以帮助您入门。

步骤 1. 将代码分成逻辑块。寻找识别逻辑块的关键是堆栈序言和结尾代码、函数调用、分支语句和由分支语句标识的地址。

第 2 步。记下每个块在做什么。

例如...

f:
    pushl %ebp
    movl %esp, %ebp      ; Create the stack frame
    pushl %ebx           ; and save non-volatile register EBX
    subl $36, %esp       ; Carve space for 9 32-bit words on the stack

    ; Notes: 8(%ebp) is the address for the 1st parameter
    ;       12(%ebp) is the address for the 2nd parameter
    ;       16(%ebp) is the address for the 3rd parameter
    ;
    ; Anything addresses as -#(%ebp) will be a stack variable
    ; local to this function.
    ;
    ; Anything addressed as #(%esp) will be used to pass parameters
    ; to the sub-function.  The advantage of doing it this way is that
    ; parameters passed to the sub-function do not have to be popped
    ; after every call to a sub-function.

    movl 8(%ebp), %edx         ; EDX = 1st parameter
    movl 12(%ebp), %eax        ; EAX = 2nd parameter
    movl (%eax), %eax          ;       The 2nd parameter is a pointer!
    movl %edx, 8(%esp)         ; Pass EDX as 3rd parameter to sub-function
    leal 16(%ebp), %edx        ; EDX = address of 3rd parameter to this function
    movl %edx, 4(%esp)         ;       Passing it as 2nd parameter to sub-function
    movl %eax, (%esp)          ; Pass EAX as 3rd parameter to sub-function
    call f                     ; Call sub-function
    movl %eax, -12(%ebp)       ; Save return value to local stack variable

    ; More Notes:
    ; I am guessing that this bit of decompiled code was an object file.
    ; Experience has shown me that when the address sub-functions used by
    ; CALL are all the same (and match the address of the calling function)
    ; this is often due to decompiling an object file as opposed to an
    ; executable.  If however, the sub-function address truly is '0xf', then
    ; this will be a recursive routine that will blow the stack as there is
    ; no exit condition.

    movl 16(%ebp), %edx    ; EDX: 3rd parameter passed to function
                           ;      likely modified by previous CALL
    movl 12(%ebp), %eax    ; EAX: 2nd parameter passed to function
    movl %edx, (%eax)      ; Save EDX to the location pointed to by the 2nd parameter
    movl 12(%ebp), %eax    ; EAX: 2nd parameter passed to function (recall it's a ptr)
    movl (%eax), %edx      ;    ... and so on ...
    movl -12(%ebp), %eax
    movl %edx, 8(%esp)
    leal 8(%ebp), %edx)
    movl %edx, 4(%esp)
    movl %eax, (%esp)
    call f
    movl %eax, %ebx

    movl 16(%ebp), %edx
    movl -12(%ebp), %eax
    movl %edx, 8(%esp)
    movl 12(%ebp), %edx
    movl %edx, 4(%esp)
    movl %eax, (%esp)
    call f
    movl %eax, %edx

    movl 16(%ebp), %eax
    movl %edx, 8(%esp)
    leal 8(%ebp), %edx
    movl %edx, 4(%esp)
    movl %eax, (%esp)
    call f
    movl %ebx, 8(%esp)

    leal -12(%ebp), %edx
    movl %edx, 4(%esp)
    movl %eax, (%esp)
    call f

    addl $36, %esp             ; Reclaim that carved stack space
    popl %ebx                  ; Restore the non-volatile register EBX
    popl %ebp                  ; Restore to the caller's stack frame
    ret                        ; Return

剩下的留给你。我希望这对您有所帮助。

【讨论】:

    【解决方案2】:

    这个函数f 是一个没有终止递归的递归函数。类似的东西

    void f(int a, int b, int c)
    {
        f(a,b,c);
        //....
    }
    

    停止评估反汇编,因为在任何高级语言中获得如此糟糕的代码是不值得的。

    【讨论】:

    • 不一定。它可能是反汇编的目标代码,而不是反汇编的可执行文件。有三个项目支持这一点。首先是被反汇编的函数的低数字地址。其次是从它调用的所有子函数都使用与调用函数相同的地址这一事实。第三,被调用的子函数似乎并不都采用相同数量的参数。
    • @Sparky 对你的评论的一些注释: 1.什么低地址?问题中根本没有地址。 2. 这叫递归。 3.函数调用自身时,不决定取多少参数,除非是可变参数。但是如果没有函数内部的分支,这是不可能的。
    • 函数开始的标签是'f'——一个不明确的标签。是被解释为字符串还是地址。我将它解释为一个十六进制值——一个地址。我遇到的大多数反编译代码通常没有该范围内的地址,除非它是一个目标文件。如果它是一个目标文件,那么 CALL 指令中显示的地址是一种在链接时要解析的存根。如果我错了,它确实是一个递归函数,那么它就是一个格式不正确的函数。
    • 我从未见过非 32 位对齐的 32 位机器的函数地址。我确信您没有看到冒号前的地址。当您在反汇编列表中有一个地址时,您会在每一行中使用目标代码字节获得它。我从未在反汇编中看到冒号前的地址。因此f: 是标签的名称而不是地址。
    【解决方案3】:

    我找到了解决方案:

    int f (int i, int* j, int k) {
        int n = f(*j, &k, i);
        *j = k;
        f( f(n, &i, *j), &n, f(k, &i, f(n, j, k)) );
        return 0;
    }
    

    编译我的代码时
    g++ -m32 -S a.cpp

    我得到以下汇编代码:
    _Z1fiPii:
    .LFB971:
    .cfi_startproc
    .cfi_personality 0,__gxx_personality_v0
    .cfi_lsda 0,.LLSDA971
    pushl %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl %esp, %ebp
    .cfi_def_cfa_register 5
    pushl %ebx
    低于 $36, %esp
    .cfi_offset 3, -12
    movl 8(%ebp), %edx
    movl 12(%ebp), %eax
    movl (%eax), %eax
    movl %edx, 8(%esp)
    leal 16(%ebp), %edx
    movl %edx, 4(%esp)
    movl %eax, (%esp)
    .LEHB0:
    致电_Z1fiPii
    movl %eax, -12(%ebp)
    movl 16(%ebp), %edx
    movl 12(%ebp), %eax
    movl %edx, (%eax)
    movl 16(%ebp), %edx
    movl -12(%ebp), %eax
    movl %edx, 8(%esp)
    movl 12(%ebp), %edx
    movl %edx, 4(%esp)
    movl %eax, (%esp)
    致电_Z1fiPii
    movl 16(%ebp), %edx
    movl %eax, 8(%esp)
    leal 8(%ebp), %eax
    movl %eax, 4(%esp)
    movl %edx, (%esp)
    致电_Z1fiPii
    movl %eax, %ebx
    movl 12(%ebp), %eax
    movl (%eax), %edx
    movl -12(%ebp), %eax
    movl %edx, 8(%esp)
    leal 8(%ebp), %ecx
    movl %ecx, 4(%esp)
    movl %eax, (%esp)
    致电_Z1fiPii
    movl %ebx, 8(%esp)
    leal -12(%ebp), %edx
    movl %edx, 4(%esp)
    movl %eax, (%esp)
    致电_Z1fiPii
    .LEHE0:
    movl $0, %eax
    jmp .L5
    .L4:
    movl %eax, (%esp)
    .LEHB1:
    调用_Unwind_Resume
    .LEHE1:
    .L5:
    加 $36, %esp
    流行 %ebx
    .cfi_restore 3
    popl %ebp
    .cfi_restore 5
    .cfi_def_cfa 4, 4

    .cfi_endproc

    这个和之前贴的一样吗?

    【讨论】: