跳转到内容

x86 反汇编/分支示例

来自维基教科书,开放的书籍,构建开放的世界

示例:参数数量

[编辑 | 编辑源代码]

此函数接受哪些参数?它使用什么调用约定?它返回什么类型的值?编写此函数的完整 C 语言原型。假设所有值都是无符号值。

 push ebp
 mov ebp, esp
 mov eax, 0
 mov ecx, [ebp + 8]
 cmp ecx, 0
 jne _Label_1
 inc eax
 jmp _Label_2
 :_Label_1
 dec eax
 : _Label_2
 mov ecx, [ebp + 12]
 cmp ecx, 0
 jne _Label_3
 inc eax
 : _Label_3
 mov esp, ebp
 pop ebp
 ret

此函数在 [ebp + 8] 和 [ebp + 12] 处的堆栈上访问参数。这两个值都被加载到 ecx 中,因此我们可以假设它们是 4 字节值。此函数不清理自己的堆栈,并且值不是在寄存器中传递的,所以我们知道该函数是 CDECL。eax 中的返回值是 4 字节值,我们被告知假设所有值都是无符号的。将所有这些组合在一起,我们可以构建函数原型

 unsigned int CDECL MyFunction(unsigned int param1, unsigned int param2);

示例:识别分支结构

[编辑 | 编辑源代码]

此函数中有多少个独立的分支结构?它们是什么类型?你能根据这些分支的结构,给_Label_1、_Label_2 和_Label_3 更具描述性的名称吗?

 push ebp
 mov ebp, esp
 mov eax, 0
 mov ecx, [ebp + 8]
 cmp ecx, 0
 jne _Label_1
 inc eax
 jmp _Label_2
 :_Label_1
 dec eax
 : _Label_2
 mov ecx, [ebp + 12]
 cmp ecx, 0
 jne _Label_3
 inc eax
 : _Label_3
 mov esp, ebp
 pop ebp
 ret

此函数中有多少个独立的分支结构?剥离掉入口和退出序列后,我们剩下的代码如下:

 mov ecx, [ebp + 8]
 cmp ecx, 0
 jne _Label_1
 inc eax
 jmp _Label_2
 :_Label_1
 dec eax
 : _Label_2
 mov ecx, [ebp + 12]
 cmp ecx, 0
 jne _Label_3
 inc eax
 : _Label_3

观察代码,我们看到 2 个cmp 语句。第一个 cmp 语句将 ecx 与零比较。如果 ecx 不为零,我们就跳转到 _Label_1,递减 eax,然后直接执行到 _Label_2。如果 ecx 为零,我们就递增 eax,直接跳转到 _Label_2。写出一些伪代码,我们对第一部分有以下结果:

if(ecx doesnt equal 0) goto _Label_1
eax++;
goto _Label_2
:_Label_1
eax--;
:_Label_2

由于 _Label_2 发生在这个结构的末尾,我们可以将它重命名为更具描述性的名称,例如 "End_of_Branch_1" 或 "Branch_1_End"。第一个比较测试 ecx 与 0 的关系,然后在不等于时跳转。我们可以反转条件,说 _Label_1 是一个else

 if(ecx == 0) ;ecx is param1 here
 {
    eax++;
 }
 else
 {
    eax--;
 }

因此,我们可以将 _Label_1 重命名为其他具描述性的名称,例如 "Else_1"。Branch_1_End (_Label_2) 之后代码块的其余部分如下:

 mov ecx, [ebp + 12]
 cmp ecx, 0
 jne _Label_3
 inc eax
 : _Label_3

我们可以立即看到 _Label_3 是此分支结构的末尾,因此我们可以立即将其称为 "Branch_2_End" 或其他名称。在这里,我们再次将 ecx 与 0 进行比较,如果不相等,我们就跳转到块的末尾。但是,如果它等于零,我们就递增 eax,然后从分支的底部退出。我们可以看到此分支结构中没有else 块,因此我们不需要反转条件。我们可以直接写一个if 语句

 if(ecx == 0) ;ecx is param2 here
 {
    eax++;
 }

示例:转换为 C 语言

[编辑 | 编辑源代码]

编写此函数的等效 C 语言代码。假设所有参数和返回值都是无符号值。

 push ebp
 mov ebp, esp
 mov eax, 0
 mov ecx, [ebp + 8]
 cmp ecx, 0
 jne _Label_1
 inc eax
 jne _Label_2
 :_Label_1
 dec eax
 : _Label_2
 mov ecx, [ebp + 12]
 cmp ecx, 0
 jne _Label_3
 inc eax
 : _Label_3
 mov esp, ebp
 pop ebp
 ret

从答案 1 中的 C 语言函数原型和答案 2 中的条件块开始,我们可以将一个伪代码函数拼凑起来,不包含变量声明或返回值

 unsigned int CDECL MyFunction(unsigned int param1, unsigned int param2)
 {
    if(param1 == 0)
    {
       eax++;
    }
    else
    {
       eax--;
    }
    if(param2 == 0)
    {
       eax++;
    }
 }

现在,我们只需要创建一个变量来存储 eax 中的值,我们将它称为 "a",并将其声明为register 类型

 unsigned int CDECL MyFunction(unsigned int param1, unsigned int param2)
 {
    register unsigned int a = 0;
    if(param1 == 0)
    {
       a++;
    }
    else
    {
       a--;
    }
    if(param2 == 0)
    {
       a++;
    }
    return a;
 }

当然,此函数不是一个特别有用的函数,但至少我们知道它在做什么。

华夏公益教科书