前段时间朋友问我,如果函数的调用者在调用该函数的时候,其定义(指参数)和实际在库里,
或者是目标文件里不一致会出什么问题呢?
我们先将这个问题分为下面2个子问题:
1. 编译和连接是否正常?
2. 运行是否正常?
第一个问题比较容易回答,编译应该都是没有问题的。对于连接来说,如果是对于C,那么没有问题;如果是C++,会出现错误。
(为什么?因为C++有函数重载的特性,所以在连接的时候他会对参数进行匹配)
第二个问题就不好说了,因为必须从汇编代码里找答案,所以主要是看编译器,当然也要看你代码是怎么写的。不同的编译器很肯能会导致不同的结果。
我们先看看下面的测试样例。
语言环境:C
编译环境:VC++ 6.0
处理器:X86
main.c
#include <stdio.h>
extern int func_a(int a);
int main(void)
{
return func_a(100);
}
lib.c
#include <stdio.h>
int func_a(int a, int b)
{
int c;
c = a + b;
printf("You are calling [%s], a [%d], b [%d], a+b [%d]/n", "func_a", a, b, c);
return c;
}
使用Release模式,运行结果如下:
You are calling [func_a], a [100], b [4198693], a+b [4198793]
Press any key to continue
我们知道VC给我产生的机器码是通过堆栈来传递参数的,那么这个b [4198693]是从堆栈来的,通过多次的运行,发现他
每次显示的值是固定的,那么为什么是固定的?
我们知道在在func_a返回的时候,有一个平衡堆栈的过程,或者在func_a函数内部实现(StdCall 规范),或者在调用者部分实现(C 规范)。那么
Q:这个堆栈在这种异常情况下,是否可以得到正常的平衡呢?
A:可以保证,
004010A8 push 64h
004010AF add esp,4
如果是2个参数相应的add esp,4这里的4会变成8,这个是由编译器来完成的,VC使用的是C规范,所以这部分代码在调用者里。
通过OllyDbg的分析:
下面这3行代码对应于main函数,
00401030 /$ 6A 64 push 64
00401032 |? E8 C9FFFFFF call 00401000
00401037 | 83C4 04 add esp, 4
下面这些对应于func_a
00401000 /$ 8B4424 04 mov eax, dword ptr [esp+4]
00401004 |. 8B4C24 08 mov ecx, dword ptr [esp+8]
00401008 |? 56 push esi
00401009 |? 8D3408 lea esi, dword ptr [eax+ecx]
0040100C |. 56 push esi
0040100D |? 51 push ecx
0040100E |? 50 push eax
0040100F |? 68 60604000 push 00406060 ; ASCII "func_a"
00401014 |? 68 30604000 push 00406030 ; ASCII "You are calling [%s], a [%d], b [%d], a+b [%d]",LF
00401019 |? E8 22000000 call 00401040
0040101E |? 83C4 14 add esp, 14
00401021 |. 8BC6 mov eax, esi
00401023 |? 5E pop esi
00401024 |. C3 retn
00401025 |? 90 nop
00401026 |. 90 nop
00401027 /. 90 nop
当运行到func_a的领空的时候,栈顶[ESP]在0013FF7C
0013FF7C 00401037 caller.00401037
0013FF80 00000064
0013FF84 00401125 caller.00401125
而
00401000 /$ 8B4424 04 mov eax, dword ptr [esp+4]
00401004 |. 8B4C24 08 mov ecx, dword ptr [esp+8]
这2行就是取参数a和b,esp+4是a,我们看到是64h。
而b是[esp+8],这个不得了,是main函数的调用者的返回地址(有些朋友可能知道,其实main并不是PE真正的入口,编译环境会加一些引导代码在前面的)。
所以我们看到的这个b [4198693]就等于00401125h
这里说下call这条汇编,其实call A可以简单理解成 push [EIP+4]; jmp A;他保存了函数返回地址后,实现跳转。
综合来说,这个环境运行是正常的,但是如果我对b进行了写操作,那情况就大不一样了,main返回的时候EIP来到你写的这个值,导致内存访问异常,这是
一个典型的堆栈溢出的例子。
例如我把代码改成下面这样:
int func_a(int a, int* b)
{
int c;
*b=200;
c = a + (*b);
printf("You are calling [%s], a [%d], b [%d], a+b [%d]/n", "func_a", a, b, c);
return c;
}
编译后生成:
00401000 /$ 8B4C24 04 mov ecx, dword ptr [esp+4]
00401004 |. 8B4424 08 mov eax, dword ptr [esp+8]
00401008 |? 56 push esi
00401009 |? 8DB1 C8000000 lea esi, dword ptr [ecx+C8]
0040100F |? C700 C8000000 mov dword ptr [eax], 0C8
00401015 |? 56 push esi
00401016 |? 50 push eax
00401017 |. 51 push ecx
00401018 |? 68 60604000 push 00406060 ; ASCII "func_a"
0040101D |? 68 30604000 push 00406030 ; ASCII "You are calling [%s], a [%d], b [%d], a+b [%d]",LF
00401022 |? E8 19000000 call 00401040
00401027 /. 83C4 14 add esp, 14
这里多了个
0040100F |? C700 C8000000 mov dword ptr [eax], 0C8
实际通过Ollydbg调试发现和我上面说的不太一样。
运行完0040100F的时候,直接跳到了下面这个代码,感觉像是异常处理:
7C90E47C > 8B4C24 04 mov ecx, dword ptr [esp+4]
7C90E480 8B1C24 mov ebx, dword ptr [esp]
通过Ollydbg我们知道这里是ntdll的领空,通过VC++ 6.0里带的工具Depends
我们知道其对应的函数是,KiUserExceptionDispatcher这个函数。
虽然不知道关于异常处理的细节,但是有一点就是程序已经异常了。