VC6php单步调试方法出现转到00404876 push ebp这种汇编语言而无法php单步调试方法

2254人阅读
VC环境下对函数调用的汇编分析【原创】
前沿:对于我们平常编程中常出现一些细节,如__stdcall和__cdecl编译器如何为我们处理,函数中变量以及new出来的变量到底存放于哪些地方,等等一些列问题。本文将和大家一起分析程序执行的汇编语言,通过对此过程掌握使自己在开发中熟悉并优化自己的代码。作者:天衣有缝,联系邮件:,MSN:,我的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){&&c = a +&}
int _tmain(int argc, _TCHAR* argv[]){&int iResult = add(123, 456);&printf("/n************/n");
&return 0;}
/***结束*****************************************************/
3.程序分析过程,F10单步启动程序:
/***开始*****************************************************/int _tmain(int argc, _TCHAR* argv[]){& push&&&&&&& ebp&&&&&&&&&&&&& 建立堆栈帧& mov&&&&&&&& ebp,esp&&&&&&&&& 存入栈基地址,运行后EBP = 0012FEE4
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& (表明默认堆栈大小约为1兆)& sub&&&&&&&& esp,44h&&&&&&&&& 空出一块堆栈区,不知道是干什么的,可有高人指点?
×××××××××××××××××××××
在一篇vc6的汇编调试文章中看到main中变量存储于此堆栈中,但是我在vc7调试发现iresult地址根本不在堆栈范围之内,不知作何解释
×××××××××××××××××××××& push&&&&&&& ebx&&&&&&&&&&&&& 保护现场& push&&&&&&& esi& & push&&&&&&& edi& &int iResult = add(123, 456);& push&&&&&&& 1C8h&&&&&&&&&&&& 参数入栈0040102E& push&&&&&&& 7Bh& & call&&&&&&& add (401000h)&&& 执行函数调用,按F11跳到本文蓝色处
×××××××××××××××××××××
call指令详解:
call指令是push和jmp的结合,先执行push eip将当前地址入栈(函数调用完毕需要用这个地址返回),然后调用jmp指令。因为普通指令无法对eip操作,所以在很多病毒程序中常有如下语句:
&&&&&&&call&&&&&&&&&&&&@@get_eip@@get_eip: && & & &pop &取得eip
×××××××××××××××××××××& add&&&&&&&& esp,8&&&&&&&&&&& 释放123和456变量所占堆栈& mov&&&&&&&& dword ptr [iResult],eax&&&&&&&&&& 从eax取出计算结果&printf("/n************/n");&&&&&&&&&& 下面是一个函数的测试0040103B& push&&&&&&& offset string "/n************/n" (4060FCh)&&&&&&& 变量地址入栈& call&&&&&&& printf (401051h) 执行call调用函数& add&&&&&&&& esp,4&&&&&&&&&&& 变量地址出栈
&return 0;& xor&&&&&&&& eax,eax&&&&&&&&& 使eax为0,eax就是返回给操作系统的值}0040104A& pop&&&&&&&& edi& 0040104B& pop&&&&&&&& esi& 0040104C& pop&&&&&&&& ebx&&&&&&&&&&&&&&恢复现场0040104D& mov&&&&&&&& esp,ebp&&&&&&&&& 平衡堆栈0040104F& pop&&&&&&&& ebp&&&&&&&&&&&&& 释放堆栈帧& ret&&&&&&&&&&&&&&&&&&&&&&&&& 返回操作系统调用处
函数定义:
&int __cdecl add(int a, int b){& push&&&&&&& ebp&&&&&&&& 建立堆栈帧& mov&&&&&&&& ebp,esp&&&& 存入栈基地址& sub&&&&&&&& esp,44h&&&& 开辟变量使用的堆栈区,供函数内部变量使用
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 执行前ESP = 0012FE84,执行后ESP = 0012FE40
×××××××××××××××××××××
此处可以打开内存0x0012FE8C,看到 7b 00 00 00 c8 01 00 00,这就是我们传入的123(0x0012fe8c处)和456(0x0012fe90处)变量×××××××××××××××××××××& push&&&&&&& ebx&&&&&&&& 保护现场& push&&&&&&& esi& & push&&&&&&& edi& &&c = a +& mov&&&&&&&& eax,dword ptr [a]&&&第一个参数,也就是[ebp+8]0040100C& add&&&&&&&& eax,dword ptr [b]&&&第二个参数,也就是[ebp+c]0040100F& mov&&&&&&&& dword ptr [c],eax&&&c变量在栈中,地址为0x0012fe80,就是变量堆栈区顶部&& mov&&&&&&&& eax,dword ptr [c]&& 计算结果存入eax}& pop&&&&&&&& edi&&&&&&& 回复现场& pop&&&&&&&& esi& & pop&&&&&&&& ebx& & mov&&&&&&&& esp,ebp&&& 平衡堆栈,回收变量堆栈区0040101A& pop&&&&&&&& ebp&&&&&&&&释放堆栈帧0040101B& ret&&&&&&&&&&&&&&&&&&& 回到调用地址,读者从这里转到粉红色处接着看
/***结束*****************************************************/
4.关于__cdecl和__stdcall:(vc项目默认调用方式为__cdecl)
我们将上面的add函数改为__stdcall形式,执行过程如下,我在和上面__cdecl调用不同的地方将作出标记:
/***开始*****************************************************/
int _tmain(int argc, _TCHAR* argv[]){& push&&&&&&& ebp& & mov&&&&&&&& ebp,esp & sub&&&&&&&& esp,44h & push&&&&&&& ebx& & push&&&&&&& esi& & push&&&&&&& edi& &int iResult = add(123, 456);& push&&&&&&& 1C8h 0040102E& push&&&&&&& 7Bh& & call&&&&&&& add (401000h)
注意,这句话后面没有了add&&esp,8,原因:__stdcall调用方式的入栈参数在函数内部已经释放了,所以这句话也就不需要了。& mov&&&&&&&& dword ptr [iResult],eax &printf("/n************/n");& push&&&&&&& offset string "/n************/n" (4060FCh) 0040103D& call&&&&&&& printf (40104Eh) & add&&&&&&&& esp,4
&return 0;& xor&&&&&&&& eax,eax }& pop&&&&&&&& edi& & pop&&&&&&&& esi& & pop&&&&&&&& ebx& 0040104A& mov&&&&&&&& esp,ebp 0040104C& pop&&&&&&&& ebp& 0040104D& ret&&&&&
函数部分:
int __stdcall add(int a, int b){& push&&&&&&& ebp& & mov&&&&&&&& ebp,esp & sub&&&&&&&& esp,44h & push&&&&&&& ebx& & push&&&&&&& esi& & push&&&&&&& edi& &&c = a +& mov&&&&&&&& eax,dword ptr [a] 0040100C& add&&&&&&&& eax,dword ptr [b] 0040100F& mov&&&&&&&& dword ptr [c],eax && mov&&&&&&&& eax,dword ptr [c] }& pop&&&&&&&& edi& & pop&&&&&&&& esi& & pop&&&&&&&& ebx& & mov&&&&&&&& esp,ebp 0040101A& pop&&&&&&&& ebp& 0040101B& ret&&&&&&&& 8&&&
这句话就是__stdcall调用方式的入栈参数
在函数内部释放的语句!
/***结束*****************************************************/
说明:对于函数的传值还是传址,大家在此之后自行分析,相信初学者可以看到很多细节方面的咚咚
5.全局变量的初始化:
#include "stdafx.h"
const char szName[]= "";class CTestClass{public:&CTestClass()&{&&printf("CTestClass::CTestClass()/n");&}&~CTestClass()&{&&printf("CTestClass::~CTestClass()/n");&}};
const CTestC
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)
{&&&OSVERSIONINFOA *&&posvi = (OSVERSIONINFOA *)_alloca(sizeof(OSVERSIONINFOA));//用这个函数避免了全局存储缓冲区的分配运行时检测
&//操作系统版本相关的一些代码&posvi-&dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);&(void)GetVersionExA(posvi);&_osplatform = posvi-&dwPlatformId;&_winmajor = posvi-&dwMajorV&_winminor = posvi-&dwMinorV&_osver = (posvi-&dwBuildNumber) & 0x07&if ( _osplatform != VER_PLATFORM_WIN32_NT )&&_osver |= 0x08000;&_winver = (_winmajor && 8) + _&//是否为托管程序&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里面执行的)&*/
&&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 = _&&mainret = wmain(__argc, __wargv, _wenviron);#else& /* WPRFLAG */&&__initenv = _&&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 */
/***结束*****************************************************/
&6.win32的启动过程
既然console的程序我们分析出来了,win32的又有什么区别呢?区别还是有的(启动程序的核心代码都在crt0.c文件中),上面我把具体的分析方法阐述了一下,win32的分析就留给大家做好啦,:)
&7.错误可能也有的,或者可以写的更好,但本菜也只有这个水平了,贻笑大方,悉请高手不吝指正。文章可能随时修改,如果你有什么问题或好的想法,到我的blog()上留言,不要在这里留了,我来的少,:)
&8.深圳南山科技园科技工业大厦&&&&&& & 17:00:00
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:89348次
积分:1354
积分:1354
排名:第18122名
原创:41篇
评论:32条
(3)(1)(2)(1)(1)(2)(3)(10)(16)(2)}

我要回帖

更多关于 单步调试 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信