之前我一直以为,栈的增长方向是从低地址到高地址,今天发现linux kernel不是这样的,接下来我们就来看一下吧。
在说之前,需要指出的就是 eax是通用寄存器,esp是和堆栈相关的,栈顶指针寄存器,ebp是栈低指针寄存器。
片段一
pushl %eax
片段一表示的是把寄存器eax里的值,压入栈中。 其实 l 表示的是16位的寄存器和 eax (eax是16位的寄存器 ax是8位的寄存器)所对应。
片段二
subl $4, %esp
movl %eax, (%esp)
片段二中的第一条指令表示的是,首先把esp寄存器,也就是栈顶指针的值,先减去4,然后再把eax寄存器里的值,放入esp所指的地址处。
因此可以看出,push操作,栈顶往低的地址方向移动,所以栈的增长方向就是从高地址往低地址。同理pop操作和push相反。如下:
popl %eax
相当于下面两条指令
movl (%esp), %eax
addl $4, %esp
引申
我们都知道,cpu 做的事就是取指然后执行,这样不断的重复。
那么指令从哪里取呢?就是从cpu里的一个寄存器里,叫做ip,ip保存的是下一条cpu要执行指令的地址,cpu每执行完一条指令,ip就会自动+1。
那么函数调用时怎么实现的呢?
片段三
call 0x12345
这条指令就是执行函数的调用,但是这背后到底做了什么呢?对程序计数器有什么样的影响呢?我们接着往下看。
片段四
pushl %eip
movl $0x12345, %eip
其实片段三的执行函数调用的指令,就相当于片段四的这两条指令。
先把当前的eip指针压栈,然后把执行函数调用的入口地址,传给eip。
可以看出,函数调用打破了,cpu顺序取指,执行。
那么函数调用结束后,会怎么办呢?
片段五
popl %eip
当然是要恢复执行函数调用前的eip,这样cpu才可以接着从函数调用前的那个指令继续执行下去。
在这里还有一点需要注意的是,eip寄存器不能被直接修改,只能通过一些特殊指令间接的修改。