分类目录

展开|收起

看你喜欢

(1) (1) (42) (1) (1) (1) (16) (2) (1) (1) (4) (1) (2) (7) (4) (1) (1) (1) (1) (3) (1) (5) (1) (1) (1) (1) (1) (2) (1) (4) (4) (3) (1) (1) (2) (1) (37) (2) (1) (5) (3) (1) (4) (1) (1) (11) (3) (1) (9) (3) (1) (23) (2) (1) (2) (1) (1) (1) (1)

最新精华

函数调用汇编分析【2】- 函数调用规范(2)

2.2 C和Pascal调用规范

有了前面栈帧的基本概念,再来讨论x86的函数调用规范就很好理解了,基本上有两种调用规范:C和Pascal调用规范。二者的栈帧概念是一样的,区别在于参数入栈的次序和函数调用时栈指针的调整方式,下面分别作下比较。

(1) Pascal调用规范

  • 函数参数从左到右依次入栈

假设调用函数func(A,B,C),那么caller中汇编代码如下:

push A
push B
push C
call func
  • 函数返回时,栈指针的调整由callee来实现。解释如下:

比如前面caller调用已经push 3个参数入栈,函数调用完了,实际这3个参数就没有用了,就需要把栈指针(即esp的值)加上12(假设每个参数占4字节),这里的意思就是这个加12的操作由callee来实现。 具体的实现是采用汇编指令ret x,即ret后面带一个数值,这里就应该是12。这样不仅能函数返回,还能调整栈指针。

注:如果不进行栈指针调整,显然经过多次函数调用后就会产生栈溢出,肯定就会触发系统异常。

(2) C调用规范

  • 函数参数从右到左依次入栈

假设调用函数func(A,B,C),那么caller中汇编代码如下:

push C
push B
push A
call func
  • 函数返回时,栈指针的调整由caller来实现。如下:
push C
push B
push A
call func
add esp,0CH    // esp=esp+12

关于栈帧的具体情况,后文的例子中有详细的说明。 顺便说一个有趣的事情,正是由于C调用规范是caller来调整栈指针,所以实现可变参数函数时容易实现,因为caller调用的时候实际已经知道参数的数目和大小,这样就很容易进行栈指针调整。而如果在callee中调整,就必须借助额外的一个参数来告诉callee栈指针要调整的大小,等于是多一个参数入栈,因为可变参数时callee在编译时是无法知道实际调用时传入多少个参数的。如果是固定参数函数,则callee在编译时是知道如何调整的。这个事情导致C调用规范自认为比Pascal调用规范强。

参考资料:

Gnu Assembly Language Manual
标题: Gnu Assembly Language Manual (939 次点击)
文件: Gnu-Assembly-Language-Manual.pdf
大小: 114 kB
描述:

  打分:5.0/5 (共1人投票)
(浏览总计: 43 次)
Add Comment Register



发表回复

  

  

  

您可以使用这些HTML标签

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>