大神洗礼第四讲——函数相关及编程技巧

Author:bakari       Date:2012.11.2

1、参数传递问题:

< 1 >、堆栈传参

< 2 >、寄存器传参(利用通用寄存器进行函数参数传递的方法)

< 3 >、全局变量或静态变量传参

2、 Call Convention(函数调用约定)

< 1 >、_cdecl

a、 参数从右向左压入堆栈

b、 函数被调用者修改堆栈

c、 在win32应用程序里,宏APIENTRY,WINAPI,都表示_stdcall,非常常见.

d、 C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。

< 2 >、_stdcall

a、 压栈方式与_cdecl一样,与之不一样的是堆栈的平衡不是由函数调用者完成,而是自身完成,在退出时自己清空堆栈。

b、 此种方式在函数返回是以 ret 8 指令来平衡堆栈,此处:ret 8 = add esp , 8。

< 3 >、上两种方式最为常用,此外还有fastcall ,thiscall, naked call,_pascal等 _pascal 入栈方式是从左到右。

下面通过一些例子来深入理解。

3、 跟踪汇编代码看函数参数的调用机制 我们看这样一个简单的函数,

编译器翻译的汇编指令如下:

< 1 >、void Test1(){} 

 1 void Test1()
 2  {
 3  013516E0  push        ebp  
 4  013516E1  mov         ebp,esp 
 5  013516E3  sub         esp,0C0h 
 6  013516E9  push        ebx  
 7  013516EA  push        esi  
 8  013516EB  push        edi  
 9  013516EC  lea         edi,[ebp-0C0h] 
10  013516F2  mov         ecx,30h ;每次移四个字节,30h * 4 = 0C0h
11  013516F7  mov         eax,0CCCCCCCCh 
12  013516FC  rep stos    dword ptr es:[edi] ;将开辟的内存赋值为cc
13  }
14  013516FE  pop         edi  
15  013516FF  pop         esi  
16  01351700  pop         ebx  
17  01351701  mov         esp,ebp 
18  01351703  pop         ebp  
19  01351704  ret    ;--->编译器默认为_cdecl   
20  

 据此画出内存布局图为:

< 2 >、有参数的情况

 1 int _cdecl Test (int i, int j)
 2  {
 3      return j + i ++;
 4  }
 5  
 6  int _cdecl Test (int i, int j)
 7  {
 8  01311690  push        ebp  
 9  01311691  mov         ebp,esp 
10  01311693  sub         esp,0C4h 
11  01311699  push        ebx  
12  0131169A  push        esi  
13  0131169B  push        edi  
14  0131169C  lea         edi,[ebp-0C4h] 
15  013116A2  mov         ecx,31h ;31h * 4 = 0C4h
16  013116A7  mov         eax,0CCCCCCCCh 
17  013116AC  rep stos    dword ptr es:[edi] ;和上面无参的类型一样
18      return j + i ++;
19  013116AE  mov         eax,dword ptr [j] 
20  013116B1  add         eax,dword ptr [i] 
21  013116B4  mov         dword ptr [ebp-0C4h],eax 
22  013116BA  mov         ecx,dword ptr [i] 
23  013116BD  add         ecx,1 
24  013116C0  mov         dword ptr [i],ecx 
25  013116C3  mov         eax,dword ptr [ebp-0C4h] 
26  }
27  013116C9  pop         edi  
28  013116CA  pop         esi  
29  013116CB  pop         ebx  
30  013116CC  mov         esp,ebp 
31  013116CE  pop         ebp  
32  013116CF  ret    
33  
34  00D21D88  push        2    ;在调用函数之前先将参数从右往左压入堆栈
35  00D21D8A  mov         eax,dword ptr [i] 
36  00D21D8D  push        eax  
37  00D21D8E  call        Test (0D211F4h) ;函数调用
38  00D21D93  add         esp,8           ;平衡堆栈
39  00D21D96  mov         dword ptr [i],eax 
40  

内存布布局如下:

4、 编写裸函数(不让系统加汇编的代码,而是人为的加上去)

比如:

 1 int  _declspec (naked) MyFunc()
 2  {
 3      _asm {
 4          push ebp
 5          mov ebp, esp
 6          sub esp, 0C0h
 7          push ebx
 8          push esi
 9          push edi
10          lea edi, dword ptr [ebp - 0C0h]
11          mov ecx, 30h
12          mov eax, 0cccccccch
13          rep stos dword ptr [edi]
14      }
15  
16      _asm {
17          mov eax, 8      ;返回值为8
18      }
19  
20      _asm {
21          pop edi
22          pop esi
23          pop ebx
24          mov esp, ebp  ;平衡堆栈
25          pop ebp
26          ret            ;记得一定要返回
27      }
28  }
29  

在main函数调用的结果printf("%d\n", MyFunc());

练习:

< 1 >、无参数的情况(不在堆栈上展开)

 1 void _declspec (naked) BlankFunc(void)
 2  {
 3      _asm {
 4          push ebp
 5          mov ebp, esp
 6          pushad
 7          popad
 8          mov esp, ebp
 9          pop ebp
10      }
11  }
12  

< 2 >、有参数的情况(在堆栈上展开)

 1 int Nest (int a, int b)
 2  {
 3      int nValue_1 = a;
 4      int nValue_2 = b;
 5      return nValue_1 + nValue_2;
 6  }
 7  int _declspec (naked) myFunc(int a, int b)
 8  {
 9      _asm {
10          push ebp
11          mov ebp, esp
12          sub esp, 8
13          push edi
14          push ebx
15          push ecx
16          lea edi, dword ptr [ebp - 8]
17          mov ecx, 2
18          mov eax, 0cccccccch
19          rep stos dword ptr [edi]
20      }
21      _asm {
22          mov eax, dword ptr[ebp + 8]  //有了上面的知识,这里就不难理解了。
23          mov dword ptr [ebp - 4], eax
24          mov ebx, dword ptr[ebp + 12] 
25          mov dword ptr [ebp - 8], ebx
26          add eax, ebx
27      }
28      _asm {
29          pop edi
30          pop ebx
31          pop ecx
32          mov esp, ebp
33          pop ebp
34          ret
35      }
36  }
37  

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java帮帮-微信公众号-技术文章全总结

【选择题】Java基础测试八(16道)

【选择题】Java基础测试八(16道) 101.下面哪个流类属于面向字符的输入流( D ) A)BufferedWriter ...

53760
来自专栏积累沉淀

JDK动态代理的底层实现原理

JDK动态代理的底层实现原理      动态代理是许多框架底层实现的基础,比如Spirng的AOP等,其实弄清楚了动态代理的实现原理,它就没那么神奇了,下面就来...

60270
来自专栏java思维导图

HashMap为什么是线程不安全的?

一直以来只是知道HashMap是线程不安全的,但是到底HashMap为什么线程不安全,多线程并发的时候在什么情况下可能出现问题?

24220
来自专栏java一日一条

java面试小题系列(一)

10130
来自专栏微信公众号:Java团长

Java堆和栈的区别

在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作...

26130
来自专栏進无尽的文章

编码篇-iOS程序中的内存分配 栈区堆区全局区等相关知识

在计算机的系统中,运行的应用程序中的数据都是保存在内存中,不同类型的数据,保存的内存区域不同。内存区域大致可以分为:栈区、堆区、全局区(静态区)、文字常量区、程...

22120
来自专栏JadePeng的技术博客

Javascript \x 反斜杠x 16进制 编解码

decode('\x5f\x63\x68\x61\x6e\x67\x65\x49\x74\x65\x6d\x43\x72\x6f\x73\x73\x4c\x61...

16530
来自专栏Java爬坑系列

你不可不知的Java引用类型之——Reference源码解析

reference指代引用对象本身,referent指代reference引用的对象,下文介绍会以reference,referent形式出现。

12210
来自专栏刘君君

JVM Specification notes 1 -Jvm Structure

摘要: Jvm Structure 正文: Java 虚拟机结构 Class文件格式 数据类型 原始类型(基本类型) 数值类型{整数[byte8 short1...

38070
来自专栏公众号_薛勤的博客

Java虚拟机性能监测工具Visual VM与OQL对象查询语言

Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具,它集成了多种性能统计工具的功能,使用 Visual VM 可以代替jstat、jmap、...

28330

扫码关注云+社区

领取腾讯云代金券