函数名修饰和调用规则 (四)

2014-11-24 12:03:58 · 作者: · 浏览: 2
...);
};

int A::function1 (int a,int b)
{
return a+b;
}


int A::function2(int a,...)
{
va_list ap;

va_start(ap,a);

int i;
int result = 0;

for(i = 0 ; i < a ; i ++)
{
result += va_arg(ap,int);
}

return result;
}

void callee()
{
A a;
a.function1 (1,2);
a.function2(3,1,2,3);
}

// 下面这段汇编代码是原文章的,我觉得有问题,还是自己反汇编看看

//函数function1调用0401C1D
push 200401C1F
push 100401C21
lea ecx,[ebp-8]00401C24
call function1
// 注意,这里this没有被入栈
//函数function2调用00401C29
push 300401C2B
push 200401C2D
push 100401C2F
push 300401C31
lea eax,[ebp-8]
这里引入this指针00401C34
push eax00401C35
call function200401C3A
add esp,14h

以下代码是我修改分析的:

上面的C++代码,必须包含 stdarg.h ,提供动态参数头文件

int A::function1 (int a,int b) //
{
004113A0 push ebp
004113A1 mov ebp,esp
004113A3 sub esp,0CCh
004113A9 push ebx
004113AA push esi
004113AB push edi
004113AC push ecx
004113AD lea edi,[ebp-0CCh]
004113B3 mov ecx,33h
004113B8 mov eax,0CCCCCCCCh
004113BD rep stos dword ptr es:[edi]
004113BF pop ecx
004113C0 mov dword ptr [ebp-8],ecx
return a+b;
004113C3 mov eax,dword ptr [a]
004113C6 add eax,dword ptr [b]
}

004113C9 pop edi
004113CA pop esi
004113CB pop ebx
004113CC mov esp,ebp
004113CE pop ebp
004113CF ret 8

void callee()
{
00411460 push ebp
00411461 mov ebp,esp
00411463 sub esp,0CCh
00411469 push ebx
0041146A push esi
0041146B push edi
0041146C lea edi,[ebp-0CCh]
00411472 mov ecx,33h
00411477 mov eax,0CCCCCCCCh
0041147C rep stos dword ptr es:[edi]
A a;
a.function1 (1,2);
0041147E push 2 // 参数 2 入栈
00411480 push 1 // 参数 1 入栈
00411482 lea ecx,[a] // this 指针 ----> ECX
00411485 call A::function1 (411050h)
a.function2(3,1,2,3);
0041148A push 3
0041148C push 2
0041148E push 1
00411490 push 3
00411492 lea eax,[a] // 这里 this 指针入栈了,对照 callee 对 function1 的调用,

00411495 push eax // 对 this 的处理是不同的
00411496 call A::function2 (411122h) // 此处调用者自己没有恢复堆栈

// 由于上面的入栈顺序可知,在 function 2中 当保存ebp 后(打开stack frame后),堆栈的状态如下.
ebp // 保存的 EBP 的值, 且 此时ebp指向该处
RetAddr // 返回地址
this指针 // 入栈的 this 指针
参数 3 // 下面是入栈的参数, 从右向左入栈
参数 1
参数 2
参数 3


0041149B add esp,14h // 此处调用者自己恢复堆栈

//.............下面的汇编代码是 检查堆栈和恢复 callee 堆栈的操作,不再写了
}

可见,对于参数个数固定情况下,它类似于stdcall,不定时则类似cdecl

naked call 调用约定
这是一个很少见的调用约定,一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不能用return返

回返回值,只能用插入汇编返回结果。这一般用于实模式驱动程序设计,假设定义一个求和的加法程序,可以定义为:

__declspec(naked) int add(int a,int b)
{
__asm mov eax,a
__asm add eax,b
__asm ret
}

注意,这个函数没有显式的return返回值,返回通过修改eax寄存器实现,而且连退出函数的ret指令都必须显式插入。

上面代码被翻译成汇编以后变成:

mov eax,[ebp+8]
add eax,[ebp+12]
ret 8

注意这个修饰是和__stdcall及cdecl结合使用的,前面是它和cdecl结合使用的代码,对于和stdcall结合的代码,则变成:

__declspec(naked) int __stdcall function(int a,int b)
{
__asm mov eax,a
__asm add eax,b
__asm ret 8 //注意后面的8
}