关于 函数调用 参数不匹配的简单分析

前段时间朋友问我,如果函数的调用者在调用该函数的时候,其定义(指参数)和实际在库里,
或者是目标文件里不一致会出什么问题呢?
我们先将这个问题分为下面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这个函数。
虽然不知道关于异常处理的细节,但是有一点就是程序已经异常了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值