函数调用


我们知道函数本身其实就是多条指令的合集,只不过这多个指令的合集可以被多次重复调用

我们学过两个指令 JMP和CALL指令,都是跳转到对应目标地址继续执行下一条指令,但JMP和CALl的区别是什么?

jmp只能修改eip的值,要跳回去还要再加一个jmp来实现回跳的功能,但如果有多个地方要使用这个函数,那这时,jmp就无法使用了
call是在同时修改eip的同时,还把call指令的下一条地址压入栈中,再ret返回时,取出栈中的地址放入eip中,这时不管什么地方调用都不会出现问题

参数传递

在x86模式下,我们要给一个函数传递参数可以用到8个通用寄存器,但是如果我们要传递的参数不止8个呢?

这时,就要使用我们的堆栈传参

// 使用堆栈完成10个数相加
_asm{
	push 1
	push 2
	push 3
	push 4
	push 5
	push 6
	push 7
	push 8
	push 10
	push 11
	call 0x4183E1
	add eax,DWORD PTR DS:[esp+28] // 第十个参数
	add eax,DWORD PTR DS:[esp+24] // 第九个参数
	add eax,DWORD PTR DS:[esp+20] // 第八个参数
	add eax,DWORD PTR DS:[esp+1c] // 第七个参数
	add eax,DWORD PTR DS:[esp+18] // 第六个参数
	add eax,DWORD PTR DS:[esp+14] // 第五个参数
	add eax,DWORD PTR DS:[esp+10] // 第四个参数
	add eax,DWORD PTR DS:[esp+c] // 第三个参数
	add eax,DWORD PTR DS:[esp+8] // 第二个参数
	add eax,DWORD PTR DS:[esp+4] // 第一个参数
	ret
}

堆栈平衡

什么是堆栈平衡?

如果通过堆栈传递参数了,那么在函数执行完毕后,要平衡参数导致的堆栈变化,要在RET这条指令之前,ESP指向的是我们压入栈中的地址

_asm{
	...
	call 0x12345678
	...
	// 函数调用后有使用堆栈的操作
	mov eax,1
	push eax
	ret	// 堆栈不平衡
}

修改后

// 在函数执行完成后(外平栈)
_asm{
	...
	call 0x12345678
	add esp,4 // 堆栈平衡
	// 函数调用后有使用堆栈的操作
	mov eax,1
	push eax
	ret
	...
}

// // 在函数执行里面(内平栈)
_asm{
	...
	call 0x12345678
	...
	// 函数调用后有使用堆栈的操作
	mov eax,1
	push eax
	ret 4 // 堆栈平衡
	...
}

EBP寻址

esp寻址的弊端

我们在往堆栈中存入参数时,可以根据esp来加上偏移来取出参数,但是这种通过esp来寻址的方式并不友好,前面有几个push就要加多个对应的偏移来获取,可以采取另一种寻址方式EBP寻址

在这里插入图片描述

_asm{
	...
	push 1
	push 2
	call 0x12345678
	...
	push ebp 	// 保留原来ebp的值
	mov ebp,esp
	sub esp,1c 	// 提升esp
	mov eax,dword ptr ss:[ebp + 8] // 第二个参数
	add eax,dword ptr ss:[ebp + c] // 第一个参数
	mov esp,ebp	// 把ebp的值给到esp (还原esp)
	pop ebp		// 还原ebp
	ret
	...
}

代码测试

// 测试函数调用对应的堆栈关系
#include <iostream>

int test(int a, int b, int c) {
	int e = 0;
	int d = 2;
	e = a + 1;
	d = c + 1;

	return e;
}


int main() {
	printf("begin\n");
	int a = 0;
	a = test(1, 2, 3);
	printf("end\n");
	return 0;
}

编译生成.exe程序后,我们放到x64dbg中进行分析

在这里插入图片描述
这里就是我们main函数的执行位置了

现在我们来逐行分析一下每条c++对应的汇编指令都作了什么

0000000140011710 <testfunc.main>    | push rbp                                     | 保存rbp的位置
0000000140011712                    | sub rsp,70                                   | 提升堆栈
0000000140011716                    | lea rbp,qword ptr ss:[rsp+20]                | [ss:[rsp+20]]:__scrt_release_startup_lock+D
000000014001171B                    | lea rcx,qword ptr ds:[<"begin\n"...>]        |  ds:[0000000140018BB0]:"begin\n"获取数据段中的字符串
0000000140011722                    | call testfunc.14001114A                      | 执行printf函数
0000000140011727                    | nop                                          | 字节对齐
0000000140011728                    | mov dword ptr ss:[rbp],0                     | 向栈中rbp地址位置存入0
000000014001172F                    | mov r8d,3                                    | main.cpp:16, r8d:&"ALLUSERSPROFILE=C:\\ProgramData"
0000000140011735                    | mov edx,2                                    | edx:&"C:\\Users\\BananaLi\\Desktop\\testfunc.exe"
000000014001173A                    | mov ecx,1                                    |
000000014001173F                    | call testfunc.140011104                      | test()
0000000140011744                    | mov dword ptr ss:[rbp],eax                   |
0000000140011747                    | lea rcx,qword ptr ds:[<"end\n"...>]          | ds:[0000000140018BB8]:"end\n"获取数据段中的字符串
000000014001174E                    | call testfunc.14001114A                      | 执行printf函数
0000000140011753                    | nop                                          |
0000000140011754                    | xor eax,eax                                  | main.cpp:18
0000000140011756                    | lea rsp,qword ptr ss:[rbp+50]                | main.cpp:19
000000014001175A                    | pop rbp                                      |
000000014001175B                    | ret                                          |

test()函数

注:在64位环境中,函数的参数由rcx,rdx,r8,r9,这4个通过寄存器传递,剩余的参数用栈

0000000140011640 <testfunc.int __cd | mov dword ptr ss:[rsp+18],r8d                | main.cpp:3, [dword ptr ss:[rsp+18]]:&"ALLUSERSPROFILE=C:\\ProgramData"
0000000140011645                    | mov dword ptr ss:[rsp+10],edx                | [dword ptr ss:[rsp+10]]:&"C:\\Users\\BananaLi\\Desktop\\testfunc.exe"
0000000140011649                    | mov dword ptr ss:[rsp+8],ecx                 |
000000014001164D                    | push rbp                                     | 保存栈底
000000014001164E                    | sub rsp,50                                   | 提升栈顶
0000000140011652                    | mov rbp,rsp                                  | 把原先栈顶的位置变成栈底
0000000140011655                    | mov dword ptr ss:[rbp],0                     | main.cpp:4
000000014001165C                    | mov dword ptr ss:[rbp+4],2                   | main.cpp:5
0000000140011663                    | mov eax,dword ptr ss:[rbp+60]                | main.cpp:6
0000000140011666                    | inc eax                                      |
0000000140011668                    | mov dword ptr ss:[rbp],eax                   |
000000014001166B                    | mov eax,dword ptr ss:[rbp+70]                | main.cpp:7
000000014001166E                    | inc eax                                      |
0000000140011670                    | mov dword ptr ss:[rbp+4],eax                 |
0000000140011673                    | mov eax,dword ptr ss:[rbp]                   | main.cpp:9
0000000140011676                    | lea rsp,qword ptr ss:[rbp+50]                | main.cpp:10
000000014001167A                    | pop rbp                                      |
000000014001167B                    | ret                                          |

堆栈图

1.原始堆栈
在这里插入图片描述
2.对应test传入的3个参数传入后
在这里插入图片描述
3.保存栈底指针
在这里插入图片描述
4.提升栈空间,给test()函数局部变量使用

在这里插入图片描述
5.使rsp=rbp

在这里插入图片描述
6.保存局部变量

	int e = 0;
	int d = 2;

在这里插入图片描述
7.取第一个参数1,并赋值给eax
在这里插入图片描述
8.eax自增1,并在局部变量e的位置保存

	e = a + 1;

在这里插入图片描述
9.取test()第3个参数,自增1后,存入d的局部变量位置

	d = c + 1;

在这里插入图片描述
10.eax存入要返回的值(e),还原rsp,rbp

	return e;

在这里插入图片描述

Logo

欢迎加入我们的广州开发者社区,与优秀的开发者共同成长!

更多推荐