x86堆栈是钟乳石

2021-05-08 00:17:30

一旦一些程序员对堆在x86上的方向混淆了,我不止一次注意到"堆栈&#34的顶部;和#34;堆栈的底部"吝啬的。看来,这种混乱是由人们习惯于思考堆栈的基本不匹配引起的,并且在X86上的堆栈实际上表现出[1]。

回到基础知识。堆栈类比有时会向新生用一堆板计算。您将板推到堆栈上并从堆叠中弹出一盘。堆栈的顶部是推动时的下一盘的位置,以及在弹出时拍板的地方。

在计算机中,堆栈通常是特殊处理的内存区域。在抽象的意义上,类比适用 - 通过将数据放在堆栈的顶部,并通过从堆栈的顶部置于堆栈的顶部来推送数据。请注意,此并不是解决堆栈顶部位于内存中的问题的问题。

这里介绍了混乱的来源。英特尔' s x86架构将其堆栈&#34置于"它从某些地址开始,并将降至较低的地址。在这里'它看起来如何:

所以,当我们说"堆栈的顶部"在X86上,我们实际上是堆栈占用的内存区域中的最低地址。这对某些人来说可能是不自然的[2]。只要我们坚定地保持上面的图表,我们应该没问题。

虽然我们'在它,让' s看x86装配编程映射的一些常见习语如何在该图形表示。

X86架构保留一个用于使用堆栈 - ESP(扩展堆栈指针)的特殊寄存器。按定义,eSP始终指向堆栈的顶部:

在此图中,地址0x9080abcc是堆栈的顶部。位于它的词是一些" foo"而esp包含0x9080abcc的地址 - 换句话说,指向它。

要将新数据推到堆栈上,我们使用推送指令[3]。什么推动是第一次递减4,然后将其操作数存储在eSP点。所以这:

以前的图表作为起点,并假设EAX保持尊重0xdeadbeef,推动堆栈后将如下所示:

同样,POP指令取下堆叠顶部的值,并将其放置在其操作数中,然后增加堆栈指针。换句话说,这是:

因此,再次,以前的图表(按下推送后)作为起点,Pop EAX将执行以下操作:

值0xdeadbeef将被写入EAX。请注意,0xDeadBeef还留在地址0x9080abc8,因为我们没有什么可以覆盖它。

在从C生成的汇编代码时,您会发现很多有趣的模式。也许最识别的模式是参数使用堆栈传递到函数的方式,局部变量在堆栈上分配的方式[4]。

int foobar(int a,int b,int c){int xx = a + 2; int yy = b + 3; int zz = c + 4; int = xx + yy + zz;返回xx * yy * zz + sum;} int main(){返回foobar(77,88,99);}

传递给Foobar的参数和该功能的局部变量以及一些其他数据,当调用foobar时,将存储在堆栈上。堆栈上的这组数据被称为此功能的帧。就在返回声明之前,foobar的堆栈框如下所示:

通过呼叫功能将绿色数据按压到堆叠上,并通过Foobar本身推出蓝色。

为Foobar生成以下装配列表。我评论了很容易理解:

_Foobar:; EBP必须在呼叫中保留。自从 ;这个功能改变了它,它必须是;保存。 ;推ebp;从现在开始,EBP指向当前堆栈;函数的框架; MOV EBP,ESP;为局部变量置于堆栈上的空间; Sub ESP,16; eax< a。 EAX + = 2.然后在XX中存储EAX; MOV EAX,DWORD PTR [EBP + 8]添加EAX,2 MOV DWORD PTR [EBP-4],EAX; eax< b。 eax + = 3.然后在yy储存eax; MOV EAX,DWORD PTR [EBP + 12]添加EAX,3 MOV DWORD PTR [EBP-8],EAX; eax< c。 EAX + = 4.然后在ZZ存储EAX; MOV EAX,DWORD PTR [EBP + 16]添加EAX,4 MOV DWORD PTR [EBP-12],EAX;添加xx + yy + zz并存放在一起; MOV EAX,DWORD PTR [EBP-8] MOV EDX,DWORD PTR [EBP-4] LEA EAX,[EDX + EAX] ADD EAX,DWORD PTR [EBP-12] MOV DWORD PTR [EBP-16],EAX;将最终结果计算为EAX;留在那里直到回来; MOV EAX,DWORD PTR [EBP-4] IMUL EAX,DWORD PTR [EBP-8] IMUL EAX,DWORD PTR [EBP-12]添加EAX,DWORD PTR [EBP-16];这里的假说明等同于:; ; MOVES ESP,EBP; POP EBP; ;它清理分配的当地人并恢复; ebp。 ;离开et.

由于ESP随着函数执行而导致的,因此使用EBP(基本指针,也称为其他架构中的帧指针)用作相对于哪个函数参数和当地人可以找到的方便锚。参数在堆栈中的EBP上方(因此访问它们时的正偏移),而当地人在堆栈中的EBP下方。

它不应帮助一些在线资源错误地称之为堆栈的顶部"底部"。此处呈现的版本是X86中的正确之一,因为它依赖于英特尔' s x86架构手册中定义的术语。

您可以尝试通过查看内存以底部顶部和高地址的低地址查看内存来解决混淆。 虽然这确实使堆叠运动更加自然,但它也意味着增加一些内存地址将在图形表示中将其缩小,这可能更加反向直观。 有几条指令x86在&#34中定义;推动家庭" 我' m展示推动以来是最简单,最普遍适用的推动。 当然,这仅适用于某些呼叫约定和架构。 在其他方面,一些参数在寄存器中传递。