使用windbg进行简单的debug c++代码

本文介绍了如何使用Windbg调试C++代码,通过讲解eax、esp、ebp寄存器的作用,展示了如何查看调用栈、分析函数参数和局部变量。在调试过程中,详细阐述了如何通过 ebp 寻找参数和局部变量的位置,并通过实例展示了如何跟踪函数调用。

了解eax, esp, ebp的作用(x86上是exx, x64上是rxx)

1. eax -- 数据寄存器,用来存储/读取数据,做累加的操作

2. esp 指针寄存器- 堆栈指针,里面寸的都是指针,需要调用dd来查看内容

3. ebp 指针寄存器 - 基址寄存器,每段函数进入后都会有如下指令集:

00414330  push        ebp  <-- 存当前函数上层函数的基指针
00414331  mov         ebp,esp <-- 存当前的堆栈指针, 传入的参数都都在这个esp里,由于下面还会有push操作,所以把esp存入ebp中,ebp成为当前函数的基指针,非常重要,此时ebp以上,为传入的参数,以下为临时变量的指针
00414333  sub         esp,0D8h  <--开辟一段内存,用于储存临时/局部变量
00414339  push        ebx  <--几个寄存器值先入站
0041433A  push        esi  
0041433B  push        edi  
0041433C  lea         edi,[ebp-0D8h] <--循环
00414342  mov         ecx,36h 
00414347  mov         eax,0CCCCCCCCh <--debug版本将栈上所有的数据置为该值,用于检查当前变量是否初始化


首先调用kn来查看当前call stack和frame


0:000> kn
  *** Stack trace for last set context - .thread/.cxr resets it
 # ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0012fc3c 102afdd2 kernel32!RaiseException+0x52
01 0012fc7c 00414385 msvcr90d!CxxThrowException+0x52
02 0012fd70 004143f0 ConsoleCpp1!Test22+0x55 [f:\codes\cpp\consolecpp1\consolecpp1\consolecpp1.cpp @ 669] <--准备查看此stack上传入的参数以及临时变量
03 0012fe58 004144ee ConsoleCpp1!Test11+0x30 [f:\codes\cpp\consolecpp1\consolecpp1\consolecpp1.cpp @ 674]
04 0012ff68 00415268 ConsoleCpp1!wmain+0xae [f:\codes\cpp\consolecpp1\consolecpp1\consolecpp1.cpp @ 684]
05 0012ffb8 004150af ConsoleCpp1!__tmainCRTStartup+0x1a8 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 583]
06 0012ffc0 7c81776f ConsoleCpp1!wmainCRTStartup+0xf [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 403]
07 0012fff0 00000000 kernel32!RegisterWaitForInputIdle+0x49


使用.frame /r #来查看frame上的register的情况

0:000> .frame /r 2
02 0012fd70 004143f0 ConsoleCpp1!Test22+0x55 [f:\codes\cpp\consolecpp1\consolecpp1\consolecpp1.cpp @ 669]
eax=0012fbec ebx=7ffd6000 ecx=00000000 edx=00000003 esi=0012fc7c edi=0012fc7c
eip=00414385 esp=0012fc84 ebp=0012fd70iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
ConsoleCpp1!Test22+0x55:
00414385 8b45f8          mov     eax,dword ptr [ebp-8] ss:0023:0012fd68=00008de6


查看当前ebp的值,来查找传入的参数args->arg1和args->arg2,还有results(此处我也不是很确定,传说参数是从ebp+8开始是第一个参数,因为ebp+4存的是返回地址,ebp存的是上层ebp的值),此处查询得之两个参数为0x6554, 0x2892, 注意这两个数都是16进制

0:000> dd 0012fd70 + 8
0012fd78  00006554 00002892 0012ff5c 0012fe64
0012fd88  7ffd6000 cccccccc cccccccc cccccccc
0012fd98  cccccccc cccccccc cccccccc cccccccc
0012fda8  cccccccc cccccccc cccccccc cccccccc
0012fdb8  cccccccc cccccccc cccccccc cccccccc
0012fdc8  cccccccc cccccccc cccccccc cccccccc
0012fdd8  cccccccc cccccccc cccccccc cccccccc
0012fde8  cccccccc cccccccc cccccccc cccccccc


接下来寻找当前frame上的局部变量result,此处不知为何多出一个值为cccccccc的临时变量,查到第二个局部变量的时候才找到results的值0x6554 + 0x2892 = 0x8de6

0:000> dd 0012fd70 -4
0012fd6c  cccccccc 0012fe58 004143f0 00006554
0012fd7c  00002892 0012ff5c 0012fe64 7ffd6000
0012fd8c  cccccccc cccccccc cccccccc cccccccc
0012fd9c  cccccccc cccccccc cccccccc cccccccc
0012fdac  cccccccc cccccccc cccccccc cccccccc
0012fdbc  cccccccc cccccccc cccccccc cccccccc
0012fdcc  cccccccc cccccccc cccccccc cccccccc
0012fddc  cccccccc cccccccc cccccccc cccccccc


0:000> dd 0012fd70  - 8 
0012fd68  00008de6 cccccccc 0012fe58 004143f0
0012fd78  00006554 00002892 0012ff5c 0012fe64
0012fd88  7ffd6000 cccccccc cccccccc cccccccc
0012fd98  cccccccc cccccccc cccccccc cccccccc
0012fda8  cccccccc cccccccc cccccccc cccccccc
0012fdb8  cccccccc cccccccc cccccccc cccccccc
0012fdc8  cccccccc cccccccc cccccccc cccccccc
0012fdd8  cccccccc cccccccc cccccccc cccccccc


从此处dump文件的寄存器使用情况可以得知,vs2008编译器的局部变量存储位置就是ebp-8, ebp - 14, ebp-20以此类推,每次用8 byte的数据,这是dbg版本为了在变量前后都加上前后标志用以检查越界,这就可以解释为什么ebp-4是cccccccc了


CrashCPP!Test:
012b1c80 55              push    ebp
012b1c81 8bec            mov     ebp,esp
012b1c83 81ece4000000    sub     esp,0E4h
012b1c89 53              push    ebx
012b1c8a 56              push    esi
012b1c8b 57              push    edi
012b1c8c 8dbd1cffffff    lea     edi,[ebp-0E4h]
012b1c92 b939000000      mov     ecx,39h
012b1c97 b8cccccccc      mov     eax,0CCCCCCCCh
012b1c9c f3ab            rep stos dword ptr es:[edi]
012b1c9e 8b4508          mov     eax,dword ptr [ebp+8]
012b1ca1 8b08            mov     ecx,dword ptr [eax]
012b1ca3 894df8          mov     dword ptr [ebp-8],ecx
012b1ca6 8b4508          mov     eax,dword ptr [ebp+8]
012b1ca9 8b4804          mov     ecx,dword ptr [eax+4]
012b1cac 894dec          mov     dword ptr [ebp-14h],ecx
012b1caf 8b45f8          mov     eax,dword ptr [ebp-8]
012b1cb2 0345ec          add     eax,dword ptr [ebp-14h]
012b1cb5 8945e0          mov     dword ptr [ebp-20h],eax
012b1cb8 837df800        cmp     dword ptr [ebp-8],0
012b1cbc 752b            jne     CrashCPP!Test+0x69 (012b1ce9)
012b1cbe 8bf4            mov     esi,esp
012b1cc0 ff15d4922b01    call    dword ptr [CrashCPP!_imp__getchar (012b92d4)]
012b1cc6 3bf4            cmp     esi,esp
012b1cc8 e88cf4ffff      call    CrashCPP!ILT+340(__RTC_CheckEsp) (012b1159)
012b1ccd 8bf4            mov     esi,esp
012b1ccf 6a00            push    0
012b1cd1 6a00            push    0
012b1cd3 6a00            push    0
012b1cd5 6803000080      push    80000003h
012b1cda ff15fc912b01    call    dword ptr [CrashCPP!_imp__RaiseException (012b91fc)]
012b1ce0 3bf4            cmp     esi,esp



到此,本轮的debug完成,下面是完整的c++代码和汇编代码,另外附上资料原文和连接



/*
 * main
 */

class MyArgs
{
public:
	int arg1;
	int arg2;
	MyArgs()
	{
		srand(time(NULL));
		arg1 = rand();
		arg2 = rand();
	}
};

int Test22(int a, int b)
{
	int result = a+b;
	if(result%2==0)
	{
		throw 1;
	}
	return result;
}

void Test11(MyArgs* arg)
{
	int result = Test22(arg->arg1, arg->arg2);
	printf("Result: %d", result);
}



int _tmain(int argc, _TCHAR* argv[])
{
	getchar();
	MyArgs* arg = new MyArgs();
	Test11(arg);
    return 0;
}

int Test22(int a, int b)
{
00414330  push        ebp  
00414331  mov         ebp,esp 
00414333  sub         esp,0D8h 
00414339  push        ebx  
0041433A  push        esi  
0041433B  push        edi  
0041433C  lea         edi,[ebp-0D8h] 
00414342  mov         ecx,36h 
00414347  mov         eax,0CCCCCCCCh 
0041434C  rep stos    dword ptr es:[edi] 
	int result = a+b;
0041434E  mov         eax,dword ptr [a] 
00414351  add         eax,dword ptr [b] 
00414354  mov         dword ptr [result],eax 
	if(result%2==0)
00414357  mov         eax,dword ptr [result] 
0041435A  and         eax,80000001h 
0041435F  jns         Test22+36h (414366h) 
00414361  dec         eax  
00414362  or          eax,0FFFFFFFEh 
00414365  inc         eax  
00414366  test        eax,eax 
00414368  jne         Test22+55h (414385h) 
	{
		throw 1;
0041436A  mov         dword ptr [ebp-0D4h],1 
00414374  push        offset __TI1H (41C320h) 
00414379  lea         eax,[ebp-0D4h] 
0041437F  push        eax  
00414380  call        @ILT+415(__CxxThrowException@8) (4111A4h) 
	}
	return result;
00414385  mov         eax,dword ptr [result] 
}
00414388  pop         edi  
00414389  pop         esi  
0041438A  pop         ebx  
0041438B  add         esp,0D8h 
00414391  cmp         ebp,esp 
00414393  call        @ILT+575(__RTC_CheckEsp) (411244h) 
00414398  mov         esp,ebp 
0041439A  pop         ebp  
0041439B  ret              

void Test11(MyArgs* arg)
{
004143C0  push        ebp  
004143C1  mov         ebp,esp 
004143C3  sub         esp,0CCh 
004143C9  push        ebx  
004143CA  push        esi  
004143CB  push        edi  
004143CC  lea         edi,[ebp-0CCh] 
004143D2  mov         ecx,33h 
004143D7  mov         eax,0CCCCCCCCh 
004143DC  rep stos    dword ptr es:[edi] 
	int result = Test22(arg->arg1, arg->arg2);
004143DE  mov         eax,dword ptr [arg] 
004143E1  mov         ecx,dword ptr [eax+4] 
004143E4  push        ecx  
004143E5  mov         edx,dword ptr [arg] 
004143E8  mov         eax,dword ptr [edx] 
004143EA  push        eax  
004143EB  call        Test22 (41127Bh) 
004143F0  add         esp,8 
004143F3  mov         dword ptr [result],eax 
	printf("Result: %d", result);
004143F6  mov         esi,esp 
004143F8  mov         eax,dword ptr [result] 
004143FB  push        eax  
004143FC  push        offset string "Result: %d" (41AD14h) 
00414401  call        dword ptr [__imp__printf (41E324h)] 
00414407  add         esp,8 
0041440A  cmp         esi,esp 
0041440C  call        @ILT+575(__RTC_CheckEsp) (411244h) 
}
00414411  pop         edi  
00414412  pop         esi  
00414413  pop         ebx  
00414414  add         esp,0CCh 
0041441A  cmp         ebp,esp 
0041441C  call        @ILT+575(__RTC_CheckEsp) (411244h) 
00414421  mov         esp,ebp 
00414423  pop         ebp  
00414424  ret        

int _tmain(int argc, _TCHAR* argv[])
{
00414440  push        ebp  
00414441  mov         ebp,esp 
00414443  push        0FFFFFFFFh 
00414445  push        offset __ehhandler$_wmain (41808Eh) 
0041444A  mov         eax,dword ptr fs:[00000000h] 
00414450  push        eax  
00414451  sub         esp,0E8h 
00414457  push        ebx  
00414458  push        esi  
00414459  push        edi  
0041445A  lea         edi,[ebp-0F4h] 
00414460  mov         ecx,3Ah 
00414465  mov         eax,0CCCCCCCCh 
0041446A  rep stos    dword ptr es:[edi] 
0041446C  mov         eax,dword ptr [___security_cookie (41D04Ch)] 
00414471  xor         eax,ebp 
00414473  push        eax  
00414474  lea         eax,[ebp-0Ch] 
00414477  mov         dword ptr fs:[00000000h],eax 
	getchar();
0041447D  mov         esi,esp 
0041447F  call        dword ptr [MSVCR90D_NULL_THUNK_DATA (41E328h)] 
00414485  cmp         esi,esp 
00414487  call        @ILT+575(__RTC_CheckEsp) (411244h) 
	MyArgs* arg = new MyArgs();
0041448C  push        8    
0041448E  call        operator new (4112C1h) 
00414493  add         esp,4 
00414496  mov         dword ptr [ebp-0E0h],eax 
0041449C  mov         dword ptr [ebp-4],0 
004144A3  cmp         dword ptr [ebp-0E0h],0 
004144AA  je          wmain+7Fh (4144BFh) 
004144AC  mov         ecx,dword ptr [ebp-0E0h] 
004144B2  call        MyArgs::MyArgs (4110F0h) 
004144B7  mov         dword ptr [ebp-0F4h],eax 
004144BD  jmp         wmain+89h (4144C9h) 
004144BF  mov         dword ptr [ebp-0F4h],0 
004144C9  mov         eax,dword ptr [ebp-0F4h] 
004144CF  mov         dword ptr [ebp-0ECh],eax 
004144D5  mov         dword ptr [ebp-4],0FFFFFFFFh 
004144DC  mov         ecx,dword ptr [ebp-0ECh] 
004144E2  mov         dword ptr [ebp-14h],ecx 
	Test11(arg);
004144E5  mov         eax,dword ptr [ebp-14h] 
004144E8  push        eax  
004144E9  call        Test11 (41128Fh) 
004144EE  add         esp,4 
    return 0;
004144F1  xor         eax,eax 
}
004144F3  mov         ecx,dword ptr [ebp-0Ch] 
004144F6  mov         dword ptr fs:[0],ecx 
004144FD  pop         ecx  
004144FE  pop         edi  
004144FF  pop         esi  
00414500  pop         ebx  
00414501  add         esp,0F4h 
00414507  cmp         ebp,esp 
00414509  call        @ILT+575(__RTC_CheckEsp) (411244h) 
0041450E  mov         esp,ebp 
00414510  pop         ebp  
00414511  ret           

http://shitouer.cn/2012/12/method-called/

C++反汇编代码分析–函数调用

12.13.2012, C++深度剖析, by fowler.

代码如下:
#include “stdlib.h”

int sum(int a,int b,int m,int n)
{

return a+b;

}

void main()
{

int result = sum(1,2,3,4);

system(“pause”);

}

有四个参数的sum函数,接着在main方法中调用sum函数。在debug环境下,单步调试如下:

11:   void main()
12:   {
00401060   push        ebp

;保存ebp,执行这句之前,ESP = 0012FF4C EBP = 0012FF88

;执行后,ESP = 0012FF48 EBP = 0012FF88,ESP减小,EBP不变
00401061   mov         ebp,esp

;将esp放入ebp中,此时ebp和esp相同,即执行后ESP = 0012FF48 EBP = 0012FF48

;原EBP值已经被压栈(位于栈顶),而新的EBP又恰恰指向栈顶。
;此时EBP寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原EBP入栈后的栈顶),
;从该地址为基准,向上(栈底方向)能获取返回地址、参数值(假如main中有参数,“获取参数值”会比较容易理解,

;不过在看下边的sum函数调用时会有体会的),向下(栈顶方向)能获取函数局部变量值,
;而该地址处又存储着上一层函数调用时的EBP值!
00401063   sub         esp,44h

;把esp往上移动一个范围
;等于在栈中空出一片空间来存局部变量
;执行这句后ESP = 0012FF04 EBP = 0012FF48

00401066   push        ebx
00401067   push        esi
00401068   push        edi

;保存三个寄存器的值
00401069   lea         edi,[ebp-44h]

;把ebp-44h加载到edi中,目的是保存局部变量的区域
0040106C   mov         ecx,11h
00401071   mov         eax,0CCCCCCCCh
00401076   rep stos    dword ptr [edi]

;从ebp-44h开始的区域初始化成全部0CCCCCCCCh,就是int3断点,初始化局部变量空间

;REP           ;CX不等于0 ,则重复执行字符串指令

;格式: STOS OPRD

;功能: 把AL(字节)或AX(字)中的数据存储到DI为目的串地址指针所寻址的存储器单元中去.指针DI将根据DF的值进行自动

;调整. 其中OPRD为目的串符号地址.

 

;以上的语句就是在栈中开辟一块空间放局部变量
;然后把这块空间都初始化为0CCCCCCCCh,就是int3断点,一个中断指令。
;因为局部变量不可能被执行,执行了就会出错,这时候发生中断提示开发者。
13:       int result = sum(1,2,3,4);
00401078   push        4
0040107A   push        3
0040107C   push        2
0040107E   push        1

;各个参数入栈,注意查看寄存器ESP值的变化

;亦可以看到参数入栈的顺序,从右到左

;变化为:ESP = 0012FEF8–>ESP = 0012FEF4–>ESP = 0012FEF0–>ESP = 0012FEEC–>ESP = 0012FEE8
00401080   call        @ILT+15(boxer) (00401014)

;调用sum函数,可以按F11跟进

;注:f10(step over),单步调试,遇到函数调用,直接执行,不会进入函数内部

;f11(step into),单步调试,遇到函数调用,会进入函数内部

;shift+f11(step out),进入函数内部后,想从函数内部跳出,用此快捷方式

;ctrl+f10(run to cursor),呵呵,看英语注释就应该知道是什么意思了,不再解释
00401085   add         esp,10h

;调用完函数后恢复/释放栈,执行后ESP = 0012FEF8,与sum函数的参数入栈前的数值一致

00401088   mov         dword ptr [ebp-4],eax

;将结果存放在result中,原因详看最后有关ss的注释
14:       system(“pause”);
0040108B   push        offset string “pause” (00422f6c)
00401090   call        system (0040eed0)
00401095   add   esp ,4

;有关system(“pause”)的处理,此处不讨论

15:   }
00401098   pop         edi
00401099   pop         esi
0040109A   pop         ebx

;恢复原来寄存器的值,怎么“吃”进去,怎么“吐”出来
0040109B   add         esp,44h

;恢复ESP,对应上边的sub esp,44h
0040109E   cmp         ebp,esp

;检查esp是否正常,不正常就进入下边的call里面debug
004010A0   call        __chkesp (004010b0)

;处理可能出现的堆栈异常,如果有的话,就会陷入debug
004010A5   mov         esp,ebp
004010A7   pop         ebp

;恢复原来的esp和ebp,让上一个调用函数正常使用
004010A8   ret

;将返回地址存入eip,转移流程

 

;如果函数有返回值,返回值将放在eax返回(这就是很多软件给秒杀爆破的原因了,因为eax的返回值是可以改的)

—————————————————————————————————————————–

;以上即是主函数调用的反汇编过程,下边来看调用sum函数的过程:

;上边有说在00401080   call        @ILT+15(boxer) (00401014)这一句处,用f11单步调试,f11后如下句:

00401014   jmp         sum (00401020)

;即跳转到sum函数的代码段中,再f11如下:

6:    int sum(int a,int b,int m,int n)
7:    {
00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,40h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-40h]
0040102C   mov         ecx,10h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]

;可见,上边几乎与主函数调用相同,每一步不再赘述,可对照上边主函数调用的注释
8:        return a+b;
00401038   mov         eax,dword ptr [ebp+8]

;取第一个参数放在eax
0040103B   add         eax,dword ptr [ebp+0Ch]

;取第二个参数,与eax中的数值相加并存在eax中
9:    }
0040103E   pop         edi
0040103F   pop         esi
00401040   pop         ebx
00401041   mov         esp,ebp
00401043   pop         ebp
00401044   ret
;收尾操作,比前边只是少了检查esp操作罢了

 

有关ss部分的注释:

;一般而言,ss:[ebp+4]处为返回地址
;ss:[ebp+8]处为第一个参数值(这里是a),ss:[ebp+0Ch]处为第二个参数(这里是b,这里8+4=12=0Ch)
;ss:[ebp-4]处为第一个局部变量(如main中的result),ss:[ebp]处为上一层EBP值
;ebp和函数返回值是32位,所以占4个字节


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值