第六部分 函数的工作原理一

        写这篇文章得益于我最近钱林松、赵海旭写的新书《C++反汇编与逆向分析》,一直希望学习相关的技术,但是一直没有开始,看到此书后,激发起了我以前的兴趣,最近一直在读这本书,今日将我了解到的知识分享给大家。


首先让我们来看一段最简单的代码

#include<stdio.h>


void func(int first, int second)
{
}


int main()
{
int first = 1,second =2;
func(first,second);
return 0;
}

通过VC6++ 在WIN7 上编译得到的汇编代码如下:

PUBLIC	_func
;	COMDAT _func
_TEXT	SEGMENT
_func	PROC NEAR					; COMDAT

; 4    : {

	push	ebp		 ;保存调用者的栈底  即main函数的栈底
	mov	ebp, esp	;更新栈顶指针,将当前的栈顶赋予ebp
	sub	esp, 64					; 00000040H	 //预留 64个字节用于保存临时变量
	push	ebx	 ;保存寄存器的值
	push	esi	 ;保存寄存器的值
	push	edi	 ;保存寄存器的值
	lea	edi, DWORD PTR [ebp-64]		;取当前临时变量空间的最低地址
	mov	ecx, 16					; 00000010H  		//设置ecx 16
	mov	eax, -858993460				; ccccccccH
	rep stosd		;初始化局部变量空间

; 5    : }

	pop	edi		;还原寄存器
	pop	esi		;还原寄存器
	pop	ebx		;还原寄存器
	mov	esp, ebp	;还原 esp
	pop	ebp			;弹出调用者的栈底 即main函数的栈底
	ret	0			
_func	ENDP
_TEXT	ENDS
PUBLIC	_main
EXTRN	__chkesp:NEAR
;	COMDAT _main
_TEXT	SEGMENT
_first$ = -4
_second$ = -8
_main	PROC NEAR					; COMDAT

; 8    : {

	push	ebp			
	mov	ebp, esp
	sub	esp, 72					; 00000048H
	push	ebx
	push	esi
	push	edi
	lea	edi, DWORD PTR [ebp-72]
	mov	ecx, 18					; 00000012H
	mov	eax, -858993460				; ccccccccH
	rep stosd

; 9    : 	int first = 1,second =2;

	mov	DWORD PTR _first$[ebp], 1		;初始化  first
	mov	DWORD PTR _second$[ebp], 2		;初始化 second

; 10   : 	func(first,second);

	mov	eax, DWORD PTR _second$[ebp]
	push	eax			;将第二个参数入栈
	mov	ecx, DWORD PTR _first$[ebp]
	push	ecx			;将第一个参数入栈
	call	_func		;调用func函数
	add	esp, 8			;平衡栈空间  前面有两次push操作

; 11   : 	return 0;

	xor	eax, eax

; 12   : }

	pop	edi		;还原寄存器
	pop	esi		;还原寄存器
	pop	ebx		;还原寄存器
	add	esp, 72					; 00000048H	平衡栈空间
	cmp	ebp, esp		;栈平衡检测 
	call	__chkesp	;栈平衡检测函数
	mov	esp, ebp		;还原 esp
	pop	ebp				;弹出调用者的栈底 注意main函数不是第一个函数,它也是被其他函数调用的,有兴趣的读者请自行研究
	ret	0
_main	ENDP
_TEXT	ENDS
END



首先 我们


知识补充

(1)ESP:栈指针寄存器(extendedstackpointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
(2)EBP:基址指针寄存器(extendedbasepointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。


上图为本测试实例的栈空间



细心的读者会发现 汇编代码中   add esp, 8  ;修正esp的操作

为什么要做这部操作呢,因为在调用func 函数之前,main 函数中还做了两次push 的入栈操作,将first second 两个参数压入栈中。呵呵可能有些读者已经发现了,这就是所谓的 _cdecl 函数调用,由调用者清栈的操作,我们平时所用的变参的函数必须由这种方式实现,因为被调函数是无法清除到底有多少参数。还有其他的调用方式,如 _stdcall, _fastcall, thiscall 等。这里给大家一个链接http://en.wikipedia.org/wiki/X86_calling_conventions#Caller_clean-up


下面给大家一个_stdcall的例子

#include<stdio.h>


void _stdcall func(int first, int second)
{
}


int main()
{
	int first = 1,second =2;
	func(first,second);
	return 0;
}
; 4    : {

	push	ebp
	mov	ebp, esp
	sub	esp, 64					; 00000040H


; 5    : }

	mov	esp, ebp
	pop	ebp
	ret	8


; 10   : 	func(first,second);

	mov	eax, DWORD PTR _second$[ebp]
	push	eax
	mov	ecx, DWORD PTR _first$[ebp]
	push	ecx
	call	_func@8

; 11   : 	return 0;

	xor	eax, eax

通过观察上面的汇编代码main函数在调用func后,没有做平衡栈的操作,在func函数中 ret 8 这个指令做了平衡栈的操作。

本例中没有给出读者函数是如何返回返回值的,读者可以自行实验,其实如果是简单的变量,是通过寄存器返回的,如果是数组,结构体呢??



PS:技术拙劣,如有错误请指教




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值