了解x86汇编程序



我用C编写了一个程序,它只是把所有的整数加起来作为输入。例如,如果输入为4,则输出为4+3+2+1=10。

我在理解这个程序的汇编x86版本时遇到了一点麻烦。

所有的评论都是我自己写的,请告诉我哪些是对的,哪些是错的,以及你会如何描述每一行的作用。通过你的评论,我将能够吸收更深入的理解什么是cpu到底做,因为目前我不能说我完全理解这里发生了什么。不管怎样,就是这样。欢迎发表意见。

.LC0:
.byte 0x25,0x64,0x0 ; 2 digits / integers that our program will output
main:
pushl %ebp ; we save %ebp for later usage
movl %esp,%ebp ; we set register %ebp to point to the stack frame
subl $12,%esp ; subtracts 18 bytes from the stack pointer (esp). This allocates 18 bytes of space on the stack to be used for variables.
movl $0,-12(%ebp)
leal -4(%ebp),%eax ; subtracks -4 from the memory address of ebp and stores it at register eax
pushl %eax ; we store register eax for later usage
pushl $.LC0
call __isoc99_scanf ; reads from io port / waiting for key input
movl $1,-8(%ebp)
leal 8(%esp),%esp ; adds +8 to stack pointer memory address
.L2:
movl -4(%ebp),%edx
cmpl -8(%ebp),%edx ; compares our input number with an incremented number
jl .L3 ; if incremented number is equal or bigger than input number goto .L3
movl -8(%ebp),%edx
addl %edx,-12(%ebp)
incl -8(%ebp)
jmp .L2 ; loop / another addition to our input
.L3:
pushl -12(%ebp)
pushl $.LC0 ; we push the argument to print function
call printf ; prints result on screen
xorl %eax,%eax ; sets %eax to zero
leave ; leave copies the frame pointer to the stack point and releases the stack space formerly used by a procedure for its local variables. leave pops the old frame pointer into (E)BP, thus restoring the caller's frame.
ret ; returns to address located on the top of the stack```

你的分析基本上是正确的,只有一些小错误,我将设法指出来。我冒昧地把你的评论打断了,这样你就可以不滚动阅读了。

注意,如果你打算把编译器输出作为手工编写的程序集传递出去,你的老师很可能会发现这一点。不要在作业中这样作弊。

作为初始注释:您的函数似乎有三个变量。这些存储在-4(%ebp),-8(%ebp)-12(%ebp)。这也是为什么C编译器发出代码将堆栈指针减少12,为这些变量分配足够的存储空间。

.byte 0x25,0x64,0x0 ; 2 digits / integers that our program will output

这是传递给scanf的字符串"%d"

subl $12,%esp ; subtracts 18 bytes from the stack pointer (esp).
; This allocates 18 bytes of space on the stack to
; be used for variables.

注意,美元符号仅仅表示一个直接操作数。与其他汇编程序不同,它不表示AT&T语法中的十六进制数。正如您已经在.byte指令中看到的那样,这是用0x前缀完成的。

leal -4(%ebp),%eax ; subtracks -4 from the memory address of ebp
; and stores it at register eax

这个解释令人困惑。leal指令接受一个内存操作数,并将该操作数的有效地址存储到寄存器操作数中。因此,在本例中,它计算-4(%ebp)的地址(即ebp加上4的内容)并将其存储到eax中。所以是eax = ebp + 4。这是用来获取scanf使用的一块堆栈内存的地址。

pushl %eax ; we store register eax for later usage
pushl $.LC0

这不会保存eax以供以后使用。相反,eax被作为下一个scanf调用的参数压入堆栈。同样,下面的指令将字符串.LC0的地址压入堆栈,准备一个类似scanf("%d", &x)的scanf调用,其中x-4(%ebp)的变量。

call __isoc99_scanf ; reads from io port / waiting for key input

这只是对C99标准变体中的scanf函数的调用。glibc有一些逻辑,可以根据您选择的C标准版本将对scanf的调用重定向到不同的函数,因此它为-std=c99选择了这个奇怪的命名符号。

注意scanf是一个libc函数。它不执行任何端口IO,而是可能要求操作系统提供额外的输入,也可能不要求。这些输入来自哪里取决于附加到您的流程的标准输入的内容。

leal 8(%esp),%esp ; adds +8 to stack pointer memory address

就像其他通用寄存器一样,堆栈指针没有内存地址。然而,它确实有一个值,表示一个地址。因此,更准确的说法可能是"将堆栈指针增加8,将参数从堆栈中弹出"。

.L2:
movl -4(%ebp),%edx
cmpl -8(%ebp),%edx ; compares our input number with an incremented number
jl .L3 ; if incremented number is equal or bigger
;than input number goto .L3
movl -8(%ebp),%edx
addl %edx,-12(%ebp)
incl -8(%ebp)
jmp .L2 ; loop / another addition to our input
.L3:

试着找出这个循环的作用。一种简单的方法是将每条指令写成伪代码,然后进行重构,直到看起来合理为止。虽然如果你从你写的C代码中得到这个,你可能已经知道它是做什么的。

pushl -12(%ebp)
pushl $.LC0 ; we push the argument to print function
call printf ; prints result on screen

再一次,这类似于printf("%d", z),其中z-12(%ebp)的变量。

ret ; returns to address located on the top of the stack

说"从函数返回"可能更准确。这是因为leave刚刚清除了堆栈帧,所以堆栈的顶部保存着返回地址。从函数返回的值是按照调用约定在eax中保存的值。这也是为什么eax之前被清除:返回零。

最新更新