分享
 
 
 

VC环境下对函数调用的汇编分析【原创】

王朝vc·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

VC环境下对函数调用的汇编分析【原创】

前沿:对于我们平常编程中常出现一些细节,如__stdcall和__cdecl编译器如何为我们处理,函数中变量以及new出来的变量到底存放于哪些地方,等等一些列问题。本文将和大家一起分析程序执行的汇编语言,通过对此过程掌握使自己在开发中熟悉并优化自己的代码。作者:天衣有缝,联系邮件:waterpub@mail.csdn.net,MSN:waterpub_cn@hotmail.com,我的QQ群3226292,转载请保留完整文档。

1.环境:我使用的开发环境是vc7.1,其release单步调试需要对项目属性作如下修改:

“C++”--》“常规”--》“调试信息格式” 改为:“用于“编辑并继续”的程序数据库(/ZI)”

“C++”--》“优化”--》“优化” 改为:禁用(/Od)

如果你是vc6环境,可如下修改release版属性:

选中Win32 Release然后

Project-》setting-》C/C++ -》Category-》General

-》Optimization-》Disable(Debug)

-》Debug Info-》Program DataBase

-》Link---》Generate Debug Info打上钩

2.c语言代码,如下:

/***开始*****************************************************/

#include "stdafx.h"

int __cdecl add(int a, int b)

{

int c;

c = a + b;

return c;

}

int _tmain(int argc, _TCHAR* argv[])

{

int iResult = add(123, 456);

printf("\n************\n");

return 0;

}

/***结束*****************************************************/

3.程序分析过程,F10单步启动程序:

/***开始*****************************************************/

int _tmain(int argc, _TCHAR* argv[])

{

00401020 push ebp 建立堆栈帧

00401021 mov ebp,esp 存入栈基地址,运行后EBP = 0012FEE4

(表明默认堆栈大小约为1兆)

00401023 sub esp,44h 空出一块堆栈区,不知道是干什么的,可有高人指点?

×××××××××××××××××××××

在一篇vc6的汇编调试文章中看到main中变量存储于此堆栈中,但是我在vc7调试发现iresult地址根本不在堆栈范围之内,不知作何解释

×××××××××××××××××××××

00401026 push ebx 保护现场

00401027 push esi

00401028 push edi

int iResult = add(123, 456);

00401029 push 1C8h 参数入栈

0040102E push 7Bh

00401030 call add (401000h) 执行函数调用,按F11跳到本文蓝色处

×××××××××××××××××××××

call指令详解:

call指令是push和jmp的结合,先执行push eip将当前地址入栈(函数调用完毕需要用这个地址返回),然后调用jmp指令。因为普通指令无法对eip操作,所以在很多病毒程序中常有如下语句:

call @@get_eip

@@get_eip:

pop ebp ;取得eip

×××××××××××××××××××××

00401035 add esp,8 释放123和456变量所占堆栈

00401038 mov dword ptr [iResult],eax 从eax取出计算结果

printf("\n************\n"); 下面是一个函数的测试

0040103B push offset string "\n************\n" (4060FCh) 变量地址入栈

00401040 call printf (401051h) 执行call调用函数

00401045 add esp,4 变量地址出栈

return 0;

00401048 xor eax,eax 使eax为0,eax就是返回给操作系统的值

}

0040104A pop edi

0040104B pop esi

0040104C pop ebx 恢复现场

0040104D mov esp,ebp 平衡堆栈

0040104F pop ebp 释放堆栈帧

00401050 ret 返回操作系统调用处

函数定义:

int __cdecl add(int a, int b)

{

00401000 push ebp 建立堆栈帧

00401001 mov ebp,esp 存入栈基地址

00401003 sub esp,44h 开辟变量使用的堆栈区,供函数内部变量使用

执行前ESP = 0012FE84,执行后ESP = 0012FE40

×××××××××××××××××××××

此处可以打开内存0x0012FE8C,看到 7b 00 00 00 c8 01 00 00,这就是我们传入的123(0x0012fe8c处)和456(0x0012fe90处)变量

×××××××××××××××××××××

00401006 push ebx 保护现场

00401007 push esi

00401008 push edi

int c;

c = a + b;

00401009 mov eax,dword ptr [a] 第一个参数,也就是[ebp+8]

0040100C add eax,dword ptr [b] 第二个参数,也就是[ebp+c]

0040100F mov dword ptr [c],eax c变量在栈中,地址为0x0012fe80,就是变量堆栈区顶部

return c;

00401012 mov eax,dword ptr [c] 计算结果存入eax

}

00401015 pop edi 回复现场

00401016 pop esi

00401017 pop ebx

00401018 mov esp,ebp 平衡堆栈,回收变量堆栈区

0040101A pop ebp 释放堆栈帧

0040101B ret 回到调用地址,读者从这里转到粉红色处接着看

/***结束*****************************************************/

4.关于__cdecl和__stdcall:(vc项目默认调用方式为__cdecl)

我们将上面的add函数改为__stdcall形式,执行过程如下,我在和上面__cdecl调用不同的地方将作出标记:

/***开始*****************************************************/

int _tmain(int argc, _TCHAR* argv[])

{

00401020 push ebp

00401021 mov ebp,esp

00401023 sub esp,44h

00401026 push ebx

00401027 push esi

00401028 push edi

int iResult = add(123, 456);

00401029 push 1C8h

0040102E push 7Bh

00401030 call add (401000h)

注意,这句话后面没有了add esp,8,原因:__stdcall调用方式的入栈参数在函数内部已经释放了,所以这句话也就不需要了。

00401035 mov dword ptr [iResult],eax

printf("\n************\n");

00401038 push offset string "\n************\n" (4060FCh)

0040103D call printf (40104Eh)

00401042 add esp,4

return 0;

00401045 xor eax,eax

}

00401047 pop edi

00401048 pop esi

00401049 pop ebx

0040104A mov esp,ebp

0040104C pop ebp

0040104D ret

函数部分:

int __stdcall add(int a, int b)

{

00401000 push ebp

00401001 mov ebp,esp

00401003 sub esp,44h

00401006 push ebx

00401007 push esi

00401008 push edi

int c;

c = a + b;

00401009 mov eax,dword ptr [a]

0040100C add eax,dword ptr [b]

0040100F mov dword ptr [c],eax

return c;

00401012 mov eax,dword ptr [c]

}

00401015 pop edi

00401016 pop esi

00401017 pop ebx

00401018 mov esp,ebp

0040101A pop ebp

0040101B ret 8

这句话就是__stdcall调用方式的入栈参数

在函数内部释放的语句!

/***结束*****************************************************/

说明:对于函数的传值还是传址,大家在此之后自行分析,相信初学者可以看到很多细节方面的咚咚

5.全局变量的初始化:

#include "stdafx.h"

const char szName[]= "http://blog.csdn.net/waterpub";

class CTestClass

{

public:

CTestClass()

{

printf("CTestClass::CTestClass()\n");

}

~CTestClass()

{

printf("CTestClass::~CTestClass()\n");

}

};

const CTestClass tobject;

int _tmain(int argc, _TCHAR* argv[])

{

printf("\n************\n");

return 0;

}

在这个程序中szname如何初始化的,为何没有执行到对应的反汇编语句?

因为main函数开始执行的时候,szname已经初始化了,所有我们运行不到这个地方。当用户启动这个exe程序的时候,进入C/C++ 运行时库代码(CRTStartup),由它初始化静态变量及全局变量,然后再转入main函数。

我们现在在上面绿色部分设置一个断点,然后运行,程序断在此处。按(Ctrl+Alt+C :VC7.1的快捷键,vc6有相应的菜单项),点到最下面调用函数,从此可以看出:程序启动时由操作系统执行“mainCRTStartup”函数,简化的代码我贴在下面了,一些很直观的英文没有翻译:

/***开始*****************************************************/

int WinMainCRTStartup(void)

{

int initret;

int mainret;

OSVERSIONINFOA *posvi;

int managedapp;

posvi = (OSVERSIONINFOA *)_alloca(sizeof(OSVERSIONINFOA));//用这个函数避免了全局存储缓冲区的分配运行时检测

//操作系统版本相关的一些代码

posvi->dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);

(void)GetVersionExA(posvi);

_osplatform = posvi->dwPlatformId;

_winmajor = posvi->dwMajorVersion;

_winminor = posvi->dwMinorVersion;

_osver = (posvi->dwBuildNumber) & 0x07fff;

if ( _osplatform != VER_PLATFORM_WIN32_NT )

_osver |= 0x08000;

_winver = (_winmajor << 8) + _winminor;

//是否为托管程序

managedapp = check_managed_app();

#ifdef _MT

if ( !_heap_init(1) ) /* 多线程用此方式初始化堆 */

#else /* _MT */

if ( !_heap_init(0) ) /* 单线程用此方式初始化堆 */

#endif /* _MT */

fast_error_exit(_RT_HEAPINIT); /* write message and die */

#ifdef _MT

if( !_mtinit() ) /* initialize multi-thread */

fast_error_exit(_RT_THREAD); /* write message and die */

#endif /* _MT */

/*

* Initialize the Runtime Checks stuff

*/

#ifdef _RTC

_RTC_Initialize(); // 初始化runtime

#endif /* _RTC */

/*

* 下面是剩余的初始化代码(包括我的程序的全局变量的初始化就在这里了,呵呵)

* 初始化完毕调用 main 或 WinMain(在try里面执行的)

*/

__try {

if ( _ioinit() < 0 ) /* io初始化,应该是输入输出吧,不太清楚 */

_amsg_exit(_RT_LOWIOINIT);

#ifdef WPRFLAG

/* get wide cmd line info */

_wcmdln = (wchar_t *)__crtGetCommandLineW(); //取得运行参数的字符串

/* get wide environ info */

_wenvptr = (wchar_t *)__crtGetEnvironmentStringsW(); //取得环境变量的字符串

if ( _wsetargv() < 0 )

_amsg_exit(_RT_SPACEARG);

if ( _wsetenvp() < 0 )

_amsg_exit(_RT_SPACEENV);

#else /* WPRFLAG */

/* get cmd line info */

_acmdln = (char *)GetCommandLineA();

/* get environ info */

_aenvptr = (char *)__crtGetEnvironmentStringsA();

if ( _setargv() < 0 )

_amsg_exit(_RT_SPACEARG);

if ( _setenvp() < 0 )

_amsg_exit(_RT_SPACEENV);

#endif /* WPRFLAG */

initret = _cinit(TRUE); /* 全局变量初始化,找的就是这里!!! */

if (initret != 0)

_amsg_exit(initret);

#ifdef _WINMAIN_

StartupInfo.dwFlags = 0;

GetStartupInfo( &StartupInfo );

#ifdef WPRFLAG

lpszCommandLine = _wwincmdln();

mainret = wWinMain(

#else /* WPRFLAG */

lpszCommandLine = _wincmdln();

mainret = WinMain(

#endif /* WPRFLAG */

GetModuleHandleA(NULL),

NULL,

lpszCommandLine,

StartupInfo.dwFlags & STARTF_USESHOWWINDOW

? StartupInfo.wShowWindow

: SW_SHOWDEFAULT

);

#else /* _WINMAIN_ */

#ifdef WPRFLAG

__winitenv = _wenviron;

mainret = wmain(__argc, __wargv, _wenviron);

#else /* WPRFLAG */

__initenv = _environ;

mainret = main(__argc, __argv, _environ);

// 就在这里调用了main,因为运行时代码在exe文件中,所以可以把main函数拿来调用(跟普通的函数没什么区别了,如果你看了win32汇编就知道main或winmain名字都不是定死的了)!

#endif /* WPRFLAG */

#endif /* _WINMAIN_ */

if ( !managedapp )

exit(mainret);

_cexit();

}

__except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) ) // 异常就到这里来了,比如丢失了dll文件

{

/*

* Should never reach here

*/

mainret = GetExceptionCode();

if ( !managedapp )

_exit(mainret);

_c_exit();

} /* end of try - except */

return mainret;

}

/***结束*****************************************************/

6.win32的启动过程

既然console的程序我们分析出来了,win32的又有什么区别呢?区别还是有的(启动程序的核心代码都在crt0.c文件中),上面我把具体的分析方法阐述了一下,win32的分析就留给大家做好啦,:)

7.错误可能也有的,或者可以写的更好,但本菜也只有这个水平了,贻笑大方,悉请高手不吝指正。文章可能随时修改,如果你有什么问题或好的想法,到我的blog(http://blog.csdn.net/waterpub)上留言,不要在这里留了,我来的少,:)

8.深圳南山科技园科技工业大厦 2005-02-22 17:00:00

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有