【问题标题】:Understanding subtraction and multiplication in assembly code理解汇编代码中的减法和乘法
【发布时间】:2015-11-12 20:50:22
【问题描述】:

有人能解释一下反汇编代码中的这些步骤会做什么吗?我有一个大概的想法,但我仍然感到困惑。我知道前两条指令设置了堆栈,并且 eax 将是一个返回值,但仅此而已。

我正在寻找的是以下步骤的目的:

push %ebp - base stack frame pointer
mov %esp, %ebp - stack pointer
sub $0x10, %esp - subtracts 16 from ?
mov 0x8(%ebp), %eax - ?
imul 0xc(%ebp), %eax - multiply 12 and ?
mov %eax, -0x4(%ebp) - ?
mov -0x4(%ebp), %eax - puts -0x4(%ebp) not sure what that would be , into eax making it the return value?
leave
ret

【问题讨论】:

  • gcc -O0(默认)充满了嘈杂的加载/存储指令。使用gcc -Og -fverbose-asm(针对调试进行优化)以获得更易读的输出,从而使变量保持在寄存器中。
  • 先试着找一个汇编语言的教程。通过猜测学习汇编可能不是一种有效的方法。

标签: c assembly x86 disassembly


【解决方案1】:
; Standard prolog: stack frame setup
push ebp               ; save the old frame pointer
mov ebp, esp           ; set the frame pointer to the current top of the stack
sub esp, 0x10          ; make space for 16 bytes of local variables
; Do the stuff
mov eax, [ebp+8]       ; copy the first parameter in eax
imul eax, [ebp+0xc]    ; multiply eax with the second parameter
mov [ebp-4], eax       ; move the result to the first local variable
mov eax, [ebp-4]       ; move it back to eax (?) => set it as return value
; Standard cdecl epilog - clean up locals & return
leave                  ; restore the old frame pointer
                       ; same as: mov esp, ebp
                       ;          pop ebp
ret                    ; return

(很抱歉将其更改为 Intel 表示法,但 AT&T 语法对我来说似乎是一团乱麻,尤其是用于取消引用和偏移的可怕表示法1

要理解这一点,请参考这个方便的图表,该图表显示了在函数 prolog 之后的 x86 上的 cdecl 函数调用中堆栈通常的样子:

请记住,括号中的表达式是指针解引用操作。

本质上,这是对

的(相当幼稚的)翻译
int multiply(int a, int b) {
    //           \      \ &b == ebp+12
    //            \ &a == ebp+8
    int c = a*b;
    //   \    \ multiplication performed in eax
    //    \ &c == ebp-4
    return c;
    //   \ return value left in eax
}

(使用 cdecl 调用约定,调用者负责清理堆栈中的参数)

这可能是由禁用优化的编译器生成的。更紧凑的版本是:

mov eax, [esp+4]
imul eax, [esp+8]
ret

(因为一切都可以在没有局部变量的情况下完成,甚至不需要设置堆栈帧)


编辑

刚刚检查,您的代码与 gcc 在-O0 生成的完全匹配,而我的代码几乎与在-O3 生成的相同。


注意事项

  1. 记录:当你看到

    displacement(%register, %offset_register, multiplier)
    

    (除了%register之外的每个组件都是可选的)在AT&T语法中它实际上意味着

    [register + displacement + offset_register*multiplier]
    

    括号表示“取此处存储的值”。

    此外,几乎所有参数都在 AT&T 语法中交换(在 Intel 语法中,目标操作数在左侧,即 mov 读起来像赋值 - mov ebp, esp => ebp = esp)。

【讨论】:

  • AT&T 语法恕我直言并不可怕。我自己觉得它很可读。阅读 Intel 或 AT&T 语法对我来说需要付出同样多的努力。在您的笔记中,您缺少 offset 或此 [register + offset_register*multiplier] 中的位移。通常为了更清楚地理解这一点,我会说 offset[base_register + index_register*multiplier]offset 也可以描述为 displacement(术语可以互换)。 multiplier 也可以描述为 scalar 常量。
  • @MichaelPetch:哎呀,修复了注释。我对 AT&T 的抱怨是常见的:极端的视觉混乱(所有那些 %$ 都在意思已经很清楚的地方),并且偏移语法需要更多的知识和注意力才能正确解析(在英特尔语法中它读取作为一个“正常”的表达方式,而不必确切地记住什么字段意味着什么)。但当然,这确实是一个习惯问题。
  • 我同意。偏好风格的问题是一个主观的问题。我相信我第一次看到 AT&T 语法是在 80 年代 Sun 编译器对 Sparc 架构的支持。
  • @MichaelPetch:英特尔语法的最大问题是一些汇编程序将mov eax, symbol 视为负载(与mov eax, [symbol] 相同),而另一些则将其视为地址的mov-immediate(@ 987654346@,结果与lea eax, [symbol] 相同)。现在我更习惯于英特尔语法,我已经习惯了这个问题,并且不得不在某些 insns 中的内存操作数前面撒上BYTE PTR 或其他任何东西。我以前认为 AT&T 的语法要好得多,但现在我更喜欢 Intel。
  • 实际上,NASM/YASM 不允许您使用OFFSET symbol 来消除歧义并编写适用于 NASM 或 MASM 的代码。 nasm.us/doc/nasmdoc2.html#section-2.2.2 建议使用 %idefine 预处理器宏将 OFFSET 替换为空字符串以实现 MASM 兼容性。汇编程序之间的这种差异让我对 Intel 语法感到疯狂。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-24
相关资源
最近更新 更多