gpt4 book ai didi

汇编:Y86 堆栈和调用、pushl/popl 和 ret 指令

转载 作者:行者123 更新时间:2023-12-04 22:17:30 25 4
gpt4 key购买 nike

除非我抄错了,上面的代码是同学在老师的帮助/更正下在类里面写在黑板上的:

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

如您所见,所有指令中都有一堆注释,我得到了(我认为)其中的大部分,让我感到困惑的是 call、pushl/popl 和 ret 指令。我不太明白它们,我也不明白堆栈发生了什么以及所有记录指向的位置。基本上,带有注释(//)的行上面没有写任何东西。

我了解这一切是如何运作的真的很重要,希望你们中的一些人可以对所有这些困惑情况有所了解。

关于我的评论的一些说明:
  • 0xsum: 这并不意味着地址是“sum”,这是不可能的。这只是一种理解我在说什么而不使用确切的内存地址的方法。
  • [sum = 0]: 这意味着在我们的 C 代码中,此时变量 sum 将被设置为 0。
  • i + 1 => i: 这意味着我们将 'i' 的值增加 1,而在下一行中,'i' 将实际表示增加的值。
  • 最佳答案

    我们来看一些代码:

    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 寄存器始终指向内存中的四个字节太高。请记住,堆栈指针在压入一个值后递减,因此它将指向压入的值,而不是下一个值。 (内存地址也很远,它类似于 0x600 而不是 0x20,因为这是声明 Stack 标签的地方。)

    关于汇编:Y86 堆栈和调用、pushl/popl 和 ret 指令,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1021935/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com