我用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
之前被清除:返回零。