堆栈和寄存器如何在汇编程序中工作?

use*_*525 2 assembly

我知道有EBP,ESP,EAX等,并使用这些寄存器,堆栈和所有堆叠.如果某个寄存器(即EBP)是堆栈并且ESP和其他寄存器堆叠在EBP之上以堆叠在EBP上,我会感到困惑.

或者堆栈只是内存分配(边界)的可视化,以更好地理解内存,寄存器是真正的内存.

令我困惑的是当main函数调用函数时:

在main中,在调用函数之前,函数的任何参数都从EAX推送到ESP,然后对函数进行"调用",该函数将返回地址(main中"call"之后的下一个地址)推送到堆栈(我认为返回地址)用函数的参数堆叠在ESP上,以便在调用函数时堆叠在EBP上.我认为这是错误的?),然后将EIP移动到函数的开头.

然后当调用该函数时,EBP被推(再次?这是因为在函数内部,EBP什么都没有?但是EBP寄存器是否已经包含了一些来自main的值?)并且ESP值被推送到EBP(这这就是为什么我认为EBP是堆栈.此时所有东西都叠加在EBP上不是吗?)然后,ESP是"sub"的,具有一些值,为函数的局部变量提供空间.(当ESP在函数入口处被推入EBP时,ESP是否具有ESP值?或者它是否已清空?)

在函数结束时,函数会"离开"和"退出",它会删除函数的堆栈帧(EBP?或ESP?或者只是"堆栈帧",既不是EBP也不是ESP?如果它删除了EBP或ESP, EBP for main会发生什么?我读到EBP是从堆栈指针重新初始化但堆栈指针何时被推到堆栈上?)然后"ret",EIP移动到返回地址,该地址在执行之前被推入"main"功能上的"呼叫".

所以这一切都让我感到困惑,因为我不确定"堆栈"是一个特定的寄存器还是一个灵活的内存边界,以便更好地理解.而且我不确定堆栈指针在堆栈上的位置和时间.

old*_*mer 6

"堆栈"只是记忆.在处理器的某个地方,你有一个"堆栈指针".关于堆栈的事情是你不关心它在内存中的确切位置,一切都是相对于堆栈指针,堆栈指针加上或减去一些内存位置.

希望/假设您的堆栈有足够的空间来执行您的程序需要做的事情(这是另一个主题).所以在这方面,堆栈只是一堆内存,它不仅仅是一个寄存器的数据.

可以认为堆栈实际上是一堆东西,实际上是堆栈的内存位置,但也许是一堆索引卡,您可以在其上编写各种内容.图片通常有帮助.

[     ]  <- sp
Run Code Online (Sandbox Code Playgroud)

我不记得x86的细节,一些处理器的堆栈指针指向堆栈"顶部"的当前项.和其他处理器堆栈指针指向第一个空闲位置.我将选择一种方法并使用它运行,然后根据需要进行调整.此外,一些处理器堆栈自然地"向下"增长,这意味着当您向堆栈添加内容时,堆栈指针地址会变小.有些堆叠会长大,但这种情况不太常见,但是如果你的堆叠的便条卡堆叠在一些桌子上而不是反向重力并且它们通过某种力量推入天花板会更有意义.

所以我们在准备调用函数之前有上面的图片.假设堆栈指针指向堆栈的顶部,我们不关心堆栈顶部的人或者什么,除了它是我们不应该触摸的某些数据,堆栈的另一个属性,一个堆栈指针的一面是公平的游戏,堆栈指针的另一面是你不应该触摸的某人的数据,除非它是你自己的数据.当你调用另一个函数时,确保堆栈指针使堆栈指针指向堆栈的顶部,推送不会破坏任何东西.

所以我们想要向函数传递两个参数,我们以相反的顺序推送它们,这样当函数被调用时它们看起来更自然,这是任意的并且基于编译器调用约定.只要规则始终相同,无论什么顺序都无关紧要,反之亦然,尽管如此.

fun( a, b);
Run Code Online (Sandbox Code Playgroud)

在我们推b之前

[stuff] <-sp
Run Code Online (Sandbox Code Playgroud)

在我们推b之后

[  b  ] <- sp
[stuff] 
Run Code Online (Sandbox Code Playgroud)

其中每个[item]是一些固定大小的堆栈上的一个内存位置,现在假定为32位,但它可以是64位.

然后我们推了一个

[  a  ] <- sp
[  b  ] 
[stuff] 
Run Code Online (Sandbox Code Playgroud)

我们准备调用该函数,因此假设一个调用将返回地址放在堆栈上

打电话给我

[retadd] <- sp
[  a  ] 
[  b  ] 
[stuff] 
Run Code Online (Sandbox Code Playgroud)

所以现在在相对于堆栈指针的fun函数中,我们可以解决堆栈中的各种项目:

[retadd] <- sp + 0
[  a  ]  <- sp + 4
[  b  ]  <- sp + 8
[stuff]  <- sp + 12
Run Code Online (Sandbox Code Playgroud)

在这个例子中假设一个32位宽的堆栈.

堆栈帧通常不是必需的,它们有助于使代码更具可读性,因此编译器人员更容易调试,但它只是刻录寄存器(根据您的体系结构,可能是也可能不是通用的).但这是图片的工作原理

push fp since we are going to modify it we don't want to mess up the callers fp register
fp = sp;  (Frame pointer (ebp) = stack pointer (esp));


[  fp ]  <- sp + 0  <- fp + 0
[retadd] <- sp + 4  <- fp + 4
[  a  ]  <- sp + 8  <- fp + 8
[  b  ]  <- sp + 12 <- fp + 12
[stuff]  <- sp + 16 <- fp + 16
Run Code Online (Sandbox Code Playgroud)

因此,如果我想访问传递给我的函数的第一个参数,我可以在fp + 8的内存地址访问它.

现在说我想要有两个局部变量,它们通常在堆栈上,所以我需要为那些空间腾出空间,我可以推送虚拟数据或只是修改堆栈指针,无论我最终如何

[  x  ]  <- sp + 0  <- fp - 8
[  x  ]  <- sp + 4  <- fp - 4
[  fp ]  <- sp + 8  <- fp + 0
[retadd] <- sp + 12 <- fp + 4
[  a  ]  <- sp + 16 <- fp + 8
[  b  ]  <- sp + 20 <- fp + 12
[stuff]  <- sp + 24 <- fp + 16
Run Code Online (Sandbox Code Playgroud)

现在帧指针开始变得非常有意义了,因为我用堆栈指针捣乱了我的参数相对于堆栈指针的位置也被混淆了,第一个参数曾经是sp + 8现在它在sp + 16,编译器或程序员必须在函数的每个点跟踪它,以便知道一切都在哪里,非常可行,但有时候不是这样做的.

但即使我们弄乱了堆栈指针,帧指针也不会移动; 我们没有碰它,所以我们的第一个参数仍然是fp + 8.当堆栈添加和删除东西时,或者即使它没有触及来自初始保存和设置的帧指针,只要我们可以访问传递的参数和局部变量整个功能中已知的偏移量.

在返回之前,我们将堆栈指针重新调整到它指向帧指针的位置

[  fp ]  <- sp + 0  <- fp + 0
[retadd] <- sp + 4  <- fp + 4
[  a  ]  <- sp + 8  <- fp + 8
[  b  ]  <- sp + 12 <- fp + 12
[stuff]  <- sp + 16 <- fp + 16
Run Code Online (Sandbox Code Playgroud)

然后我们弹出帧指针以恢复调用者帧指针,这样它们就不会搞砸其余的功能

[retadd] <- sp + 0
[  a  ]  <- sp + 4
[  b  ]  <- sp + 8
[stuff]  <- sp + 12
Run Code Online (Sandbox Code Playgroud)

然后我们从使用堆栈指针指向的地址的函数返回

[  a  ]  <- sp + 0
[  b  ]  <- sp + 4
[stuff]  <- sp + 8
Run Code Online (Sandbox Code Playgroud)

然后调用函数将堆栈清理到它开始调用之前的状态

[stuff]  <- sp + 0
Run Code Online (Sandbox Code Playgroud)

有许多网页和书籍谈论堆栈基础知识,太多不足以提及.