除非我将其复制错误,否则上面的代码是由学生在课堂上写的,并由老师帮助/更正:
int array[100], sum, i;
void ini() {
for(i = 0; i < 100; i++)
array[i] = i;
}
int main() {
ini();
sum = 0;
for(i = 0; i < 100; i++)
sum += array[i];
}
.pos 0
irmovl Stack, %esp
rrmovl Stack, %ebp
jmp main
array:
.pos 430
sum: .long 0
i: .long 0
main:
call ini //
irmovl $0, %eax // %eax = 0
irmovl sum, %esi // %esi = 0xsum
rmmovl %eax, 0(%esi) // 0(%esi) = %eax <=> 0(0xsum) = 0 [sum = 0]
rmmovl %eax, 4(%esi) // 4(%esi) = %eax <=> 4(0xsum) = 0 [i = 0]
compare:
irmovl $100, %ebx // %ebx = 100
subl %eax, %ebx // %ebx = %ebx - %eax <=> %ebx = 100 - i
jle finish // Jumps to "finish" if SF=1 pr ZF=0
mrmovl 0(%esi), %edx // %edx = 0(%esi) <=> %edx = 0(0xsum) = sum
addl %eax, %edx // %edx = %edx + %eax <=> %edx = sum + i => sum
rmmovl %edx, 0($esi) // 0(%esi) = %edx <=> 0(0xsum) = sum
irmovl $1, %ecx // %ecx = 1
addl %ecx, %eax // %eax = %eax + %ecx <=> %eax = i + 1 => i
rmmovl %eax, 4(%esi) // 4($esi) = %eax <=> 4(0xsum) = i
jmp compare // Jumps unconditionally to "compare"
ini:
pushl %ebp //
rrmovl %esp, %ebp //
pushl %ebx //
pushl %eax //
irmovl $0, %eax // %eax = 0
rmmovl %eax, -8(%ebp) //
ini_compare:
irmovl $100, %ecx // %ecx = 100
subl %eax, %ecx // %ecx = %ecx - %eax <=> %ecx = 100 - i
jle ini_finish // Jumps to "ini_finish" if SF=1 pr ZF=0
rrmovl %eax, %ebx // %ebx = %eax <=> %ebx = i
addl %eax, $ebx // %ebx = %ebx + %eax <=> %ebx = i + i = 2i
addl %ebx, %ebx // %ebx = %ebx + %ebx <=> %ecx = 2i + 2i = 4i
rmmovl %eax, array(%ebx) // array(%ebx) = %eax <=> array(0x4i) = i
irmovl %1, %ecx // %ecx = 1
addl %ecx, %eax // %eax = %eax + %ecx <=> %eax = i + 1 => i
rmmovl %eax, -8(%ebp) //
jmp ini_compare // Jumps unconditionally to "ini_compare"
ini_finish:
irmovl $4, %ebx //
addl %ebx, %esp //
popl %ebx //
popl %ebp //
ret //
.pos 600
Stack .long 0
正如你所看到的,所有说明中都有很多评论,而且我(其中)大多数都是这些评论,令我困惑的是电话,pushl / popl和ret指令。我不太了解它们,我也不明白堆栈发生了什么以及所有记录都指向了什么。基本上,带有注释(//)的行没有写入任何内容。
我非常了解这一切是如何运作的,希望你们中的一些人可以对所有这些混乱有所了解。
关于我的评论的一些注释:
答案 0 :(得分:13)
让我们看看一些代码:
main:
call ini
这会将指令指针的值推送到堆栈(以便稍后可以返回到代码中的这个位置),并跳转到ini标签的地址。 'ret'指令使用存储在堆栈中的值从子例程返回。
以下是子程序的初始化顺序。它将一些寄存器的值保存在堆栈中,并通过将堆栈指针(esp)复制到基址指针寄存器(ebp)来设置堆栈帧。如果子例程具有局部变量,则堆栈指针递减以为堆栈上的变量腾出空间,并且基指针用于访问堆栈帧中的局部变量。在示例中,唯一的局部变量是(未使用的)返回值。
push指令使用要推送的数据大小递减堆栈指针(esp),然后将值存储在该地址。 pop指令执行相反的操作,首先获取值,然后递增堆栈指针。 (请注意,堆栈向下增长,因此堆栈指针地址在堆栈增长时会降低。)
ini:
pushl %ebp // save ebp on the stack
rrmovl %esp, %ebp // ebp = esp (create stack frame)
pushl %ebx // save ebx on the stack
pushl %eax // push eax on the stack (only to decrement stack pointer)
irmovl $0, %eax // eax = 0
rmmovl %eax, -8(%ebp) // store eax at ebp-8 (clear return value)
代码遵循标准模式,因此当没有局部变量时,它看起来有点尴尬,并且有一个未使用的返回值。如果存在局部变量,则使用减法来递减堆栈指针而不是推送eax。
以下是子程序的退出顺序。它将堆栈恢复到创建堆栈帧之前的位置,然后返回到调用子例程的代码。
ini_finish:
irmovl $4, %ebx // ebx = 4
addl %ebx, %esp // esp += ebx (remove stack frame)
popl %ebx // restore ebx from stack
popl %ebp // restore ebp from stack
ret // get return address from stack and jump there
回应你的意见:
推送并弹出ebx寄存器以保留其值。编译器显然总是将这些代码放在那里,可能是因为寄存器是非常常用的,而不是在这段代码中。同样,总是通过将esp复制到ebp来创建堆栈帧,即使它不是真的需要。
推送eax的指令只是递减堆栈指针。它以小的减量方式完成,因为它比减去堆栈指针更短更快。它保留的空间用于返回值,即使没有使用返回值,编译器显然也会这样做。
在图中,esp寄存器始终将4个字节指向内存过高。请记住,在按下一个值后,堆栈指针会递减,因此它将指向推送的值,而不是下一个值。 (内存地址也是关闭的,它类似于0x600而不是0x20,因为它是声明Stack标签的地方。)