zepor

二进制旅程


<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>

页面列表

ITEM_HTML