body {font-family: Verdana;font-size: 10.8pt; color:#000000; background-color:#FFFFFF}
table {font-family: Verdana;font-size: 10.8pt;text-decoration:none}
table.frame {font-family: Verdana;font-size: 10.8pt;text-decoration:none;border-collapse: collapse; border-bottom-style:dotted; border-bottom-width:1px}
div.hr{border-bottom:1px dotted #C0C0C0; width:75%;height:1px;}
td.leftframe {font-family: Verdana;font-size: 11px;text-decoration:none;border-collapse: collapse; width:169px; overflow:hidden}
td.topleftframe {font-family: Verdana;font-size: 10px;text-decoration:none;border-collapse: collapse; padding-top:4px; padding-bottom:4px }
td.mainframe {padding:2px; border-left:1px dotted #C0C0C0; border-top:1px dotted #C0C0C0; font-family: Verdana;font-size: 10.8px;text-decoration:none;border-collapse: collapse;width:590px; overflow:hidden; }
td.nav {font-family: Verdana;font-size: 11px;text-decoration:none;border-collapse: collapse }
a:link { font-family: Verdana,"宋体"; color:#0000FF; text-decoration:none}
a:visited { font-family: Verdana,"宋体"; color:#000080; text-decoration:none}
a:hover { font-family: Verdana,"宋体"; text-decoration:underline; color:#0000FF }
a:active { font-family: Verdana,"宋体"; color:#FF0000; text-decoration:none}
div.nav { font-size: 11px;font-family: Verdana,"宋体"; text-decoration:none; margin-left:2em; margin-right:2em}
div.leftnav {overflow:hidden;width:169px;left-margin:0px;right-margin:0px;font-size: 11px;font-family: Verdana,"宋体"; padding-left:3px; padding-right:3px}
div.copyright { font-family: Verdana; font-size: 10px; text-decoration: none }
div.author { font-family: Verdana; font-size: 10px; text-decoration: none }
div.archon { font-family: Verdana; font-size: 10px; text-decoration: none }
pre.prog { font-family: "Courier";font-size: 10px; background-color: #EEEEEE;}
pre.code { font-family: "Courier";font-size: 10px; background-color: #EEEEEE;}
h1 { font-size: 200%; font-family: Georgia; text-decoration: none }
h1.title {border-style:dotted; border-width:1px;;background-color: #EEEEEE;margin: 3px; color:#000080}
h2 {font-family: "Georgia";font-size: 175%;text-decoration: none;}
h3 {font-family: "Georgia";font-size: 150%;text-decoration:none}
h4 {font-family: "Verdana";font-size: 125%;text-decoration: none;}
h5 { font-family: Verdana; font-size: 125%; text-decoration: none; color:#999999 }
div.article {
font-family: "Verdana";
font-size: 10.8pt;
scrollbar-face-color: #eeeeee;
scrollbar-shadow-color: #9F9F9F;
scrollbar-highlight-color: #eeeeee;
scrollbar-3dlight-color: #9F9F9F;
scrollbar-darkshadow-color: #FFFFFF;
scrollbar-track-color: #FFFFFF;
scrollbar-arrow-color: #000000;
background-color: #FFFFFF;
width: 574px; margin-left:8px; margin-right:8px
}
input.text { font-family: Verdana; font-size: 10px; text-decoration: none; border-style:
dotted; border-width: 1px;height: 18px }
input.button { font-family: Verdana; font-size: 10px; text-decoration: none; border-style:
solid; border-width: 1px;height: 18px }
关键词: 函数调用,calling convention, C语言实现
函数调用的底层机制
作者: apf::detrox
这是一篇介绍C语言中的函数调用是如何用实现的文章。写给那些对C语言各种行为的底层实现感兴趣人的入门级文章。如果你是C语言或者汇编、底层技术的老鸟或是对这个问题不感兴趣,那么这篇文章只会耽误您的时间,您大可不必阅读他。当然如果前辈们愿意为我指出不足,我将十分感谢您的指导,并对耽误您宝贵的时间致歉。
好了,废话少说!要研究这个问题,让我们先打开VC++吧。最好是6.0的,:-P。(什么你没有VC++,倒!....赶快装一个!@#$,要快!)
首先,让我们在VC++里建立一个Win32 Console Application项目,并建立主文件fun.c。并输入以下内容。
int fun(int a, int b) {
a = 0x4455;
b = 0x6677;
return a + b;
}
int main() {
fun(0x8899,0x1100);
return 0;
}
之后,最关键的是在项目设置里关闭优化功能。也就是把Project->Setting->C/C++->Optimizations选为Disabled。编译器的优化在分析底层实现时大多数情况不太受欢迎。
按键盘上的F10键,进入单步调试模式(Step Over)。看到你的main函数左侧有个黄色的小箭头了吗?那个就是程序即将执行的语句。按Alt + 8。打开反编译窗口,看到汇编语句了吗?是不是想这个样子
==> 00401078 push 1100h
0040107D push 8899h
00401082 call @ILT+5(fun) (0040100a)
00401087 add esp,8
看到两个PUSH指令了吗?再看看后面的数字,不正是我们要传递的参数吗。奇怪阿?我们明明是先传递的0x8899怎么反倒先push 1100h呢?呵呵,这个现象就叫Calling conversion。究竟是何方神圣,我在后面会详细的给你解释的。先别着急。随后的Call指令的作用就是开始调用函数了。
接下来关掉反汇编窗口,在源代码窗口按F11(Step Into)进入函数体。当看到那个黄色的小箭头指向函数名的时候再调出反汇编窗口(Alt+8)。你会看到类似下面的代码:
1: int fun(int a, int b) {
00401000 push ebp
00401001 mov ebp,esp
00401003 sub esp,40h
00401006 push ebx
00401007 push esi
00401008 push edi
00401009 lea edi,[ebp-40h]
0040100C mov ecx,10h
00401011 mov eax,0CCCCCCCCh
00401016 rep stos dword ptr [edi]
2: a = 0x4455;
00401018 mov dword ptr [ebp+8],4455h
3: b = 0x6677;
0040101F mov dword ptr [ebp+0Ch],6677h
4: return a + b;
00401026 mov eax,dword ptr [ebp+8]
00401029 add eax,dword ptr [ebp+0Ch]
5: }
0040102C pop edi
0040102D pop esi
0040102E pop ebx
0040102F mov esp,ebp
00401031 pop ebp
00401032 ret
VC++就是好,还在难懂的汇编语句前加入了C语言的源代码。不过同时也有不少我们不需要的代码。因此,你只需要关心红色的部分就可以了。
奇怪阿?不是参数都用push传递了吗?怎么没看到被pop出来?问题其实是这样,当你调用Call进入函数的时候Call背着你做了一件事。call把它下一条语句的地址push进了堆栈。(旁人: 什么!这是为什么?)原因很简单,因为函数调用完了,要用ret返回。而ret怎么知道返回哪里呢?对了, ret指令pop了call指令push给他的地址(搞清楚这个关系哦),然后返回到了这个地址。call和ret配合的如此绝妙,一个PUSH一个POP肯定不会让堆栈不平衡的(老外叫no stack unwinding)。现在明白了,如果你来个pop eax,那eax里面是什么?当然是ret要用的返回地址了。好啦,你要是pop eax就等于抢了ret要用的东西了。不论曾程序流程和道德标准上你做的都不对 :-P。
可是怎么在函数体里使用参数呢?问题其实并不难,既然参数在堆栈里我们就可以使用esp(堆栈指针)来访问了。不过,我相信你也想到了。esp是个经常变化的值。一旦,函数里出现pop或push他就会变化。这样很不容易定位参数的于内存中的位置。因此,我们需要一个不会变化的东西作为访问参数的基准。看看函数体的开头部分:
00401000 push ebp
00401001 mov ebp,esp
先用push ebp保存了原来ebp的值再把esp的值给ebp。原来ebp就是用来做基准的。也难怪他被称为ebp(Base Pointer)。很自然ret返回前的pop ebp就是恢复原来ebp的数值喽。当然一定要恢复,因为函数里也可以调用函数嘛。每个函数都用ebp,自然要保证使用完后完璧归赵了。现在当函数执行到 mov ebp, esp后堆栈应该变成这个样子了。
/-------------------\ Higher Address
| 参数2: 0x1100h |
+-----------------+
| 参数1: 0x8899h |
+-----------------+
| 函数返回地址 |
| 0x00401087 |
+-----------------+
| ebp |
\-------------------/ Lower Address
本文地址:http://com.8s8s.com/it/it1217.htm