Stack
发表于 2006-01-22 07:40 AM 作者: zweily
注:这篇是笔记,由于原先贴的地方贴图不方便,于是就简单地写了几句,先搬过来,改天我会补充完整的。
IA-32的stack是由高地址向低地址延伸的,所以PUSH指令会将栈顶指针递减,POP指令将栈顶指针递增。
有2个寄存器与stack相关,分别为EBP和ESP。
EBP指向当前stack frame的栈底位置,按照IA-32的Manual上的称法,EBP是“Pointer to data on the stack”,其实应该是一个Base Pointer。由它决定了当前栈的起始位置。
ESP指向当前的栈顶,准确地说是指向当前栈里最顶端的那个数据。
PUSH指令会先将ESP递增(根据栈是2字节对齐还是4字节对齐,相应地加2或者加4),然后将要PUSH的数据放入栈中。POP指令执行相反的操作。
按照IA-32的Manual上对于过程调用(以call/ret方式实现)的说明,基本过程如下:
call
1. 将EIP(即过程返回地址)PUSH到stack上;
2. 将ESP的值存入EBP,即切换到新的stack;
3. 跳转到相应的地址执行被调用过程的指令;
ret
1. 将EBP的值存入ESP;
2. 将当前栈顶(即EBP所指位置)POP到EIP中;
3. 执行EIP地址的指令,也就是完成return;
从这个过程里,可以看到EIP的保存和切换到新stack的过程。但是这里有个问题,就是ret的时候,如何恢复原先的EBP呢?这个问题在IA-32的Manual上没有找到答案,那么就通过实际的程序来试验一下吧。
通过查看程序的汇编代码,发现新过程一开始总会执行下面的指令:
push ebp
mov ebp, esp
对应的过程结束部分有下面的指令:
mov esp, ebp
pop ebp
ret
这就对了嘛。而且也发现实际栈的EBP并不是像IA-32的Manual上说的那样指向保存的返回地址的,而是指向返回地址的下一项,也就是这里代码所显示的这样。
ok,过程调用的stack情况了解了,那么接下去看看关于参数传递的问题吧。
对于参数传递,可以有多种方式,比如通过通用寄存器来传递,或者用栈来传递。这里就讨论栈传递的方式。为了便于比较,这里就讨论c/c++ call convention和std call convention两种情况。
这里我就不讨论这两者的一些背景知识了,将注意力focus汇编指令如何实现这两种不同的方式。
前者是由调用者来负责参数的清理,而后者是由被调者来清理栈上的参数。
IA-32的RET指令可以带上一个参数,例如:
ret n ;ret的时候,将ESP + n
这个参数的作用就是为了给被调者用来清理堆栈上的参数的。
而对于c/c++ call的情况,被调者就ret,而调用者会在ret回来的地方执行下面指令:
add esp, n
这条指令就将栈顶指针指到正确的位置(即跳过了那些参数)。
那么,最后说一句为什么会需要调用者清理的这种情况呢?其实这种方式是为了对应于c中的可变长参数列表的情况,这时候只有调用者知道有多少参数,而被调者编译时不知道有多少参数传递进来了。
IA-32的stack是由高地址向低地址延伸的,所以PUSH指令会将栈顶指针递减,POP指令将栈顶指针递增。
有2个寄存器与stack相关,分别为EBP和ESP。
EBP指向当前stack frame的栈底位置,按照IA-32的Manual上的称法,EBP是“Pointer to data on the stack”,其实应该是一个Base Pointer。由它决定了当前栈的起始位置。
ESP指向当前的栈顶,准确地说是指向当前栈里最顶端的那个数据。
PUSH指令会先将ESP递增(根据栈是2字节对齐还是4字节对齐,相应地加2或者加4),然后将要PUSH的数据放入栈中。POP指令执行相反的操作。
按照IA-32的Manual上对于过程调用(以call/ret方式实现)的说明,基本过程如下:
call
1. 将EIP(即过程返回地址)PUSH到stack上;
2. 将ESP的值存入EBP,即切换到新的stack;
3. 跳转到相应的地址执行被调用过程的指令;
ret
1. 将EBP的值存入ESP;
2. 将当前栈顶(即EBP所指位置)POP到EIP中;
3. 执行EIP地址的指令,也就是完成return;
从这个过程里,可以看到EIP的保存和切换到新stack的过程。但是这里有个问题,就是ret的时候,如何恢复原先的EBP呢?这个问题在IA-32的Manual上没有找到答案,那么就通过实际的程序来试验一下吧。
通过查看程序的汇编代码,发现新过程一开始总会执行下面的指令:
push ebp
mov ebp, esp
对应的过程结束部分有下面的指令:
mov esp, ebp
pop ebp
ret
这就对了嘛。而且也发现实际栈的EBP并不是像IA-32的Manual上说的那样指向保存的返回地址的,而是指向返回地址的下一项,也就是这里代码所显示的这样。
ok,过程调用的stack情况了解了,那么接下去看看关于参数传递的问题吧。
对于参数传递,可以有多种方式,比如通过通用寄存器来传递,或者用栈来传递。这里就讨论栈传递的方式。为了便于比较,这里就讨论c/c++ call convention和std call convention两种情况。
这里我就不讨论这两者的一些背景知识了,将注意力focus汇编指令如何实现这两种不同的方式。
前者是由调用者来负责参数的清理,而后者是由被调者来清理栈上的参数。
IA-32的RET指令可以带上一个参数,例如:
ret n ;ret的时候,将ESP + n
这个参数的作用就是为了给被调者用来清理堆栈上的参数的。
而对于c/c++ call的情况,被调者就ret,而调用者会在ret回来的地方执行下面指令:
add esp, n
这条指令就将栈顶指针指到正确的位置(即跳过了那些参数)。
那么,最后说一句为什么会需要调用者清理的这种情况呢?其实这种方式是为了对应于c中的可变长参数列表的情况,这时候只有调用者知道有多少参数,而被调者编译时不知道有多少参数传递进来了。
评论总数 0
评论
发表评论 |
作者为 zweily 的最新文章
- ARM平台的对齐问题 (2006-08-13)
- System Call (2006-01-22)
- Stack (2006-01-22)
- ZWeily的小品文——《Something about TCP/IP》系列(1) (2006-01-22)
- ZWeily的小品文(六)C++入门教程(5) (2006-01-22)




