栈、栈帧和函数调用约定解析

目录

一.栈

二.栈帧

三.函数调用约定

1._cdecl (C调用约定)

2._stdcall(微软制定的标准调用约定)

3.x86 fastcall约定

4.x64 fastcall约定

5.linux下的函数调用


一.栈

内容后续补充...

这两篇文章挺不错的:

第一篇介绍了栈的运行原理以及push和pop指令的执行过程

汇编语言——寄存器(内存访问 ss栈段寄存器)

第二篇介绍了程序内存布局:

第5篇:C/C++ 内存布局与程序栈

通过程序内存布局可以得知,栈是从高地址向低地址增长的

简述一下push和pop指令的作用,以32位为例:

push eax:          //push指令实际上是先开辟空间(也就是调整栈指针)然后再将值压入栈中

esp=esp-4        //

mov [esp],eax

pop eax :           //pop指令先将栈顶的值取出来,然后调整栈指针

mov eax,[esp]   //

esp=esp+4

二.栈帧

后续补充...

三.函数调用约定

1._cdecl (C调用约定)

特点:

(1)调用方按照从右到左的顺序将函数参数压入栈中

(2)被调函数完成操作后,由调用方负责清除栈中参数

(3)无论有多少个参数,最左边的(第一个)参数总能轻易找到(在栈顶)

所以适用于参数数量可变的函数(如printf)

例子:

假设有一函数: void fun(int x, int y, int z);

当调用这个函数并且传参如下时:

fun(1,2,3);

主调函数中对应的汇编指令为:

push 3        //esp=esp-4

push 2        //esp=esp-4

push 1        //esp=esp-4

call fun       //调用函数后,清除参数进行栈平衡,所以esp=esp+12

add esp,12

 

被调函数对应汇编指令:

push ebp

mov ebp,esp

......

mov esp,ebp

pop ebp

ret

还有另一种形式(GNU编译器,gcc/g++利用这种技巧):

sub esp,12                //主调函数预先分配栈空间

mov [esp+8],3          //依次将参数入栈

mov [esp+4],2          //注意此时是根据与栈指针的偏移值入栈,并没有移动栈指针

mov [esp],1

call fun

add esp,12                //清除参数,栈平衡

无论是哪一种方式,栈顶的值都是最左边的(第一个)参数\

2._stdcall(微软制定的标准调用约定)

注意这里的标准是微软为自己制定的调用约定所起的名称,并非是真的"标准"

特点:

(1)与_cdecl一样,都是按照从右到左的顺序将参数入栈

(2)区别:函数执行结束时,由被调函数删除栈中的参数

(3)当函数参数个数固定不变时可以使用这种调用方式

仍采用_cdecl中介绍的例子:

主调函数对应汇编指令:

push 3        //esp=esp-4

push 2        //esp=esp-4

push 1        //esp=esp-4

call fun      

被调函数对应汇编指令:

push ebp               //保存原始帧指针

mov ebp,esp         //开辟新的栈帧

......                        //具体操作

mov esp,ebp        //恢复栈指针

pop ebp                //恢复帧指针

ret 12                    //即esp=esp+12,再ret   先调整栈指针,再取栈顶返回地址返回

//主函数中没有了add esp,12 这条指令,而是在fun函数结尾使用 ret 12(3*4=12)指令  

//ret 后跟的值与参数的大小之和一致

3.x86 fastcall约定

这是stdcall约定的一个变体,规定函数第1,2个参数分别由ecx,edx传参,其余参数传参方式和stdcall相同,并且也是由被调函数清除栈中参数

例子:

主调函数对应汇编指令:

push 3        //此时只有一个参数是压入栈的

mov edx,2

mov ecx,1

call fun      

被调函数对应汇编指令和_stdcall中的相同:

push ebp              

mov ebp,esp        

......                       

mov esp,ebp       

pop ebp               

ret 4                //只有一个参数是压入栈的,所以清除参数只需要清除一个int类型的空间

 

也就是说,只是传参时使用了ecx,edx这两个寄存器,其他参数依次入栈

4.x64 fastcall约定

与x32 fastcall类似,前4参数则先放入ecx、edx、r8、r9寄存器,剩余参数从右到左依次入栈,这里就不再赘述

5.linux下的函数调用

以上主要是windows下的函数调用约定,简单介绍一下linux下的函数调用约定

(1)x32 使用栈传递参数,参数从右到左入栈

(2)x64 _fastcall方式: 函数参数的 前 6 个参数是从左至右依次存放于 RDI,RSI,RDX,RCX,R8,R9 寄存器里面,剩下的参数通过栈传递,从右至左顺序入栈

你可能感兴趣的:(Reverse,安全,学习,linux,函数调用约定,栈/栈帧)