gpt4 book ai didi

assembly - assembly `push` 、 `pop` 、 `call` 和 `ret` 操作的幕后到底发生了什么?

转载 作者:行者123 更新时间:2023-12-04 01:37:09 24 4
gpt4 key购买 nike

我试图详细了解如何实现一个健壮的堆栈/注册机(我猜是一种混合机): How to simulate a call stack in JavaScript using only a single array 。那里的答案表明:

const memory = [
8, // initial program counter
0,
0,
0,
0,
0,
0,
0,
"push",
1,
"push",
2,
"add",
"push",
3,
"add",
"print",
"exit",
0,
0,
]

在其他资源中,我看到它们都 只有 像调用 pushpopcallret 一样低级。他们没有展示这些是如何实现的。这是另一个 example :

enter image description here

但是,如果我要为整个计算机编写一个模拟器,包括 pushpopcallret 指令,那么这些指令到底是什么样的?他们如何知道内存中的哪个位置可以自由存储下一个 push ?当您调用 pop 时,内存究竟发生了什么?堆栈上的内容如何实际传递给使用 call 调用的函数?等等。基本上,这些东西是如何在引擎盖下工作的。我知道它们是 implemented in the hardware ,但是如果您要在代码中实现它们(仅使用 memory 数组,如上述问题),它们会是什么样子?

最佳答案

堆栈指针是一个 CPU 寄存器(此处为 %esp ),必须对其进行初始化以引用用作堆栈的内存区域。这个区域需要是可变的,并且足够大以便程序调用传递参数。

这个堆栈指针寄存器然后是 push/pop 操作的引用:

  • 推送操作递减堆栈指针并将值写入该地址,并且,
  • Pop 操作读取堆栈指针所指的值,然后递增堆栈指针。

  • 堆栈指针(以及其他寄存器)在进程调用 main 之前被初始化。这要么由操作系统完成,要么由调用 __startcrt0.o 中的 main 完成。

    一个合适的模拟器必须模拟一些操作系统行为和/或 __start 行为,例如在调用 main 之前,将有效的初始值放入堆栈指针寄存器。

    How do they know which place in memory is free to store the next push?



    堆栈指针是引用:它的值表示堆栈上第一项的地址。

    What happens exactly to the memory when you call pop?



    内存没有任何变化,只是堆栈指针被更新(寄存器的值发生变化,因此它指向的位置被移动),以便内存现在被视为可用(例如,用于另一次推送)。堆栈指针以下的内存被认为是空闲堆栈区域,堆栈指针及其上方的内存被视为正在使用的堆栈区域。

    How do the things on the stack actually get passed to the function being called with call?



    调用函数(调用者)和被调用函数(被调用者)都可以访问 CPU 堆栈寄存器。

    被调用者知道栈上有返回地址和参数。返回地址在栈顶——栈指针直接指向它。因此堆栈指针的地址+ 4 然后指向参数#1,+8 指向参数#2,等等。

    模拟 CPU 的模拟器将模拟内存和 CPU 寄存器,例如堆栈指针和程序计数器(又名指令指针)。指令指针保存下一条要执行的指令的地址,它是处理器管理控制流的方式。

    为了模拟推送指令,模拟器将减少模拟的堆栈指针寄存器,然后在该堆栈指针中保存的地址写入要推送的值,并且还增加程序计数器寄存器以准备执行下一个顺序指令遵循 push
    模拟器对 pop 执行相反的操作:它从模拟堆栈指针寄存器引用的位置处的模拟内存读取,然后递增该模拟寄存器。它还将增加程序计数器,为执行 pop 之后的下一条顺序指令做准备。
    call 指令具有推送的行为,其中推送的值是模拟程序计数器 - 修改为引用 call 指令之后的指令 - 这是返回地址:当被调用者被调用时恢复调用者的位置完全的。与 pushpop 指令不同的是,调用指令还会更改模拟程序计数器寄存器,以便执行的下一条指令是被调用函数的第一条指令。
    ret 指令具有 pop 的行为,但会弹出到模拟程序计数器中,因此这会改变控制流,以便下一条要执行的指令返回调用者 - 原始调用之后的一条指令。

    希望您能看到 pushpop 是如何对偶的,就像 callret 一样。

    最后一件事,如果你想在不指定压入值的情况下分配堆栈内存,我们可以从堆栈指针中减去——并且要释放堆栈内存而不弹出,我们可以添加到堆栈指针中。 addl 后面的这条 call plus 指令就是这样做的——它弹出最初推送的两个参数,回收两次推送(值(value)),而不检索它们的值。

    关于assembly - assembly `push` 、 `pop` 、 `call` 和 `ret` 操作的幕后到底发生了什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59096539/

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