栈
<p>[TOC]</p>
<h1>介绍</h1>
<p>栈在整个内存模型中如下图所示</p>
<p><img src="https://pic.imgdb.cn/item/63a0953fb1fccdcd36524ca2.png" alt="" /> </p>
<p>栈的位置位于高地址,kernel之下,它从高地址到低地址增长,是一种先入后出的数据结构。最常用的栈控制指令为push和pop。push为入栈指令,会让当前栈顶指针esp/rsp减小,而pop指令为出栈指令,会让esp/rsp增加。</p>
<p>而栈在进程中的作用有 </p>
<ol>
<li>暂时保存函数内的局部变量</li>
<li>调用函数时传递参数</li>
<li>保存函数返回后的地址</li>
</ol>
<h1>理解栈的作用</h1>
<p>栈的作用在32位程序中体现比较明显,我们可以编写一个32位程序来分析栈的作用</p>
<pre><code>int func1(int a, int b) {
return a + b;
}
int main() {
int a = 4, b = 5;
a = func1(a, b);
return 0;
}
// gcc stack.c -o stack -fno-stack-protector -no-pie -fno-pic -m32</code></pre>
<p>将断点设置在main函数,指令 <code>disass</code> 指令查看main函数的反汇编</p>
<p><img src="https://pic.imgdb.cn/item/63a1a4b0b1fccdcd36dd1381.png" alt="" /> </p>
<p>main函数先将esp减去0x10,为了给局部变量a和b提供空间,然后通过mov给局部变量a和b赋值</p>
<p>调用func1函数之前先将两个参数按照从右到左的顺序入栈,而call指令相当于 <code>push main+12;jmp func1</code> ,也就是将call指令下一条指令地址压入栈内,为了配合func1的ret指令使用,所以跳转去func1时的栈模型为</p>
<p><img src="https://pic.imgdb.cn/item/63a1a5c4b1fccdcd36dec2ed.png" alt="" /> </p>
<p>这里就体现了栈的三个作用</p>
<h1>栈帧</h1>
<p>栈帧的概念是记录函数调用的各种信息,例如参数、局部变量、函数返回地址</p>
<p><img src="https://pic.imgdb.cn/item/63a1c8e4b1fccdcd362d6c7a.png" alt="" /> </p>
<p>每个栈帧都有两个重要寄存器来记录,一个是栈底指针ebp和栈顶指针esp,可以通过ebp来定位栈帧内的所有内容,因为ebp是不变的,但是栈顶esp是动态变化的,比如上面那个例子里对于局部变量a和b的定位都是用ebp</p>
<p>而为了可以恢复上一个栈帧的环境,所以在一开始会有两条指令</p>
<pre><code>push ebp ; 保存上一个栈帧的ebp
mov ebp, esp ; 修改ebp为当前栈帧的栈底</code></pre>
<p>将上一个栈帧的ebp值保存到栈里,并赋值ebp新栈帧的栈底地址</p>
<p>然后在fun1函数快结束的时候有一条指令</p>
<pre><code>pop ebp</code></pre>
<p>恢复上一个栈帧的ebp,这种情况只有函数栈没有局部变量的时候会用到,有局部变量用到的是</p>
<pre><code>leave</code></pre>
<p>它相当于</p>
<pre><code>mov esp, ebp
pop ebp</code></pre>
<p>作用是恢复栈顶指针,再恢复上一个栈帧的ebp</p>