汇编眼中的函数
裸函数
裸函数是一种不使用函数预定义的函数框架(prologue)和函数结束的代码(epilogue)的函数,需要自己处理函数入口和出口的任务,还有保持堆栈的平衡等等
-
定义方式:
返回值类型 _declspec(naked) 函数名 (参数列表)
返回值类型 _declspec(naked) 函数名 (参数列表) { __asm { PUSH EBP //保存old_EBP,保持堆栈平衡 MOV EBP,ESP //抬高栈底 SUB ESP,0X40 //开辟缓冲区 PUSH EBX //保存易变寄存器 PUSH ESI PUSH EDI LEA EDI,DWORD PTR DS:[EBP - 0X40] //填充缓冲区 MOV EAX,0XCCCCCCCC MOV ECX,0X10 REP STOSD //函数内语句 POP EDI //弹出易变寄存器 POP ESI POP EBX MOV ESP,EBP //降低栈顶 POP EBP //恢复old_EBP RET //跳出函数 } }
函数调用
在汇编语言中,函数调用涉及到将控制权从一个函数转移至另一个函数。通常,函数调用包括以下步骤:
- 参数传递: 将参数传递给被调用函数。参数可以通过寄存器、堆栈或其他机制传递。
- 调用: 使用
call
指令将程序的控制权转移到被调用的函数。 - 函数体执行: 被调用函数的代码开始执行,处理传递的参数,执行函数体内的指令。
- 返回: 使用
ret
指令将控制权返回到调用函数的下一条指令.
PUSH DWORD PTR DS:[EBP - 0X4] //传递参数
PUSH 0X1 //传递参数
CALL FUN1 //调用函数
ADD ESP,0X8 //清理栈区的参数
MOV DWORPD PTR DS:[EBP - 0X8] //获取返回值
XOR EAX //清零寄存器
局部变量
- 局部变量反汇编识别: [EBP - XXX] (
__fastcall
调用约定的函数,参数通过ECX和EDX传递进函数内之后,也会把参数存储在EBP-XXX) - 局部变量是通过占空间保存的
- 因为局部变量使用栈空间进行存储,所以进入函数后的第一件事就是开辟函数中的局部变量所需的栈空间(缓冲区).这时函数中的局部变量就有了存储的内存空间,在函数结尾处执行释放栈空间的操作.因此局部变量是有生命周期的,他的生命周期在进入函数体的时候开始,在函数执行结束的时候终止.
PUSH EBP //保存旧的栈底指针
MOV EBP,ESP //设置新的栈底指针
SUB ESP,0X40 //开辟缓冲区
PUSH EBX //保存易变寄存器
PUSH ESI
PUSH EDI
LEA EDI,DWORD PTR DS:[EBP - 0X40] //填充缓冲区
MOV ECX,0X10
MOV EAX,0XCCCCCCCC
REP STOSD
MOV DWORD PTR DS:[EBP - 0X4],0X1 //创建变量
MOV DWORD PTR DS:[EBP - 0X4],0XA
POP EDI //恢复易变寄存器
POP ESI
POP EBX
MOV ESP,EBP //恢复栈顶
POP EBP //恢复旧的栈底指针
RET //返回
返回值
在x86架构中,通常使用 eax
寄存器来存储函数的返回值。
//函数调用--------------------------------------------
CALL fun1 //函数调用
MOV DWORD PTR DS:[EBP - 0X4],EAX //获取返回值
XOR EAX //清零寄存器
//设置返回值-------------------------------------------
PUSH EBP //保存旧的栈底指针
MOV EBP,ESP //设置新的栈底指针
SUB ESP,0X40 //开辟缓冲区
PUSH EBX //保存易变寄存器
PUSH PSI
PUSH EDI
MOV EAX,0XCCCCCCCC //填充缓冲区
MOV ECX,0X10
LEA EDI,DWORD PTR DS:[EBP - 0X40]
REP STOSD
MOV DWORD PTR DS:[EBP - 0X4],0X1 //int a = 1;
MOV DWORD PTR DS:[EBP - 0X8],0X2 //int b = 2;
MOV EAX,DWORD PTR DS:[EBP - 0X4] //b= a + b;
ADD EAX,DWORD PTR DS:[EBP - 0X8]
MOV DWORD PTR DS:[EBP - 0X8],EAX
MOV EAX,DWORD PTR DS:[EBP - 0X8] //return b;
POP EDI //恢复易变寄存器
POP ESI
POP EBX
MOV ESP,EBP //恢复栈顶
POP EBP //恢复旧的栈底指针
RET //返回
-
参数
在汇编语言中,参数的传递方式取决于所使用的体系结构和编程规范。通常,参数可以通过寄存器、堆栈或者一些其他机制来传递。以下是一些常见的参数传递方式:
- 寄存器传递: 一些寄存器用于传递函数的参数。不同的体系结构和编程规范可能会指定不同的寄存器用于参数传递。在x86架构中,
eax
、ebx
、ecx
、edx
寄存器通常被用于参数传递。 - 堆栈传递: 参数可以通过将它们推送(push)到堆栈上来传递。被调用函数可以通过弹出(pop)来获取这些参数。在x86架构中,参数通常从右向左入栈,因此第一个参数会被放在最后入栈的位置。
int a = Fun2(1,2); //函数调用 push 2 //传递第二个参数 PUSH 1 //传递第一个参数 CALL Fun2 //调用函数 ADD EBP,0X8 //清理栈区参数 MOV DWORD PTR DS:[EBP - 0X4],EAX //接收返回值 XOR EAX,EAX //清零寄存器 int Fun2(int a,int b) { int c = a + b; return c; } //函数体内部 PUSH EBP //保存旧的栈底指针 MOV EBP,ESP //设置新的栈底指针 SUB ESP,0X40 //开辟缓冲区 PUSH EBX //保存易变寄存器 PUSH ESI PUSH EDI LEA EDI,DWORD PTR DS:[EBP - 0X40] //填充缓冲区 MOV EAX,0XCCCCCCCC MOV ECX,0X10 REP STOSD MOV EAX,DWORD PTR DS:[EBP + 0X8] //int a = 参数一 + 参数二 ADD EAX,DWORD PTR DS:[EBP + 0XC] MOV DWORD PTR DS:[EBP - 0X4] MOV EAX,DWORD PTR DS:[EBP - 0X4] //设置返回值 POP EDI //恢复易变寄存器 POP ESI POP EBX MOV ESP,EBP //降低栈顶 POP EBP //获取旧的栈底指针 RET //返回
- 寄存器传递: 一些寄存器用于传递函数的参数。不同的体系结构和编程规范可能会指定不同的寄存器用于参数传递。在x86架构中,