接着http://www.sacour.cn/post/1002.html的来,这篇最后说到不同编译器编译出来的代码是大相径庭的(虽然结果一样),那么今天就继续调试剩余的两个,一个是VB的,一个是Delphi的,源代码可以在1001.html中找到。

3.VB程序的调试

由于VB会把程序编译成解释型(http://zh.wikipedia.org/wiki/VB_%E4%BC%AA%E4%BB%A3%E7%A0%81)或者P-Code(http://en.wikipedia.org/wiki/Microsoft_P-Code),所以它的执行方式有点奇怪,它要通过MSVBVM60.DLL才可以解释运行,而一般VB程序的入口都是如下代码:

004011E0 >/$  68 24134000   push    00401324                         ;  (initial cpu selection)
004011E5  |.  E8 EEFFFFFF   call    <jmp.&MSVBVM60.#100>

这次示例程序也不例外,那么怎么调试它呢?如果你单步走的话你会发现这个程序一会儿在EXE领空,一会儿在DLL领空(http://www.unpack.cn/archiver/tid-46130.html),很是麻烦,有两种方法可以快速调试它。

本文来自www.sacour.cn 转载注明来源

第一种:由于VB程序是基于Unicode的,所以可以右键点击代码区,选择“Ultra String Reference”-“2 Find UNICODE”,然后找到对应的UNICODE字符,再移动到该段代码的第一句(1002.HTML中有提到,即表示为$的语句)下断点,运行程序。

第二种:右键点击代码区,选择“Analysis”-“Analysis code”,

然后按下Ctrl+M,右键点击“Search”-ascii中输入“vb6chs.dll”,然后Search,记下此字符出现的地址,然后回到代码窗口,按下Ctrl+G,进入此地址,往下翻找到类似

00401454     /2C184000      dd      工程1.0040182C                     ;  ASCII "Form"

的地址,然后其后面一个dd xxx.xxxxxx即为Form_Load函数。

找到以后,

0040145C     /FC144000      dd      工程1.004014FC

在这儿按Enter键,调到4014FC,

004014FC   . /E9 0F060000   jmp     00401B10

再按Enter键,到达Form_Load函数内,在此按F2,然后F9运行:

00401B10   > \55            push    ebp

 

接着就可以调试了,过程都很简单,1002已经说过了,这儿稍微列一下VB使用的函数:

00401B4F   .  FF52 04       call    dword ptr [edx+4]                ;  MSVBVM60.Zombie_AddRef

这个是用来处理对象引用(Object Reference)的函数

00401B7B   .  FF15 48104000 call    dword ptr [<&MSVBVM60.__vbaFixst>;  MSVBVM60.__vbaFixstrConstruct

这句是因为我使用了Dim x As String * 256这一固定长度字符串而出现的,它就是处理固定长度字串的函数。

继续往下走,可以看到:

00401C84   .  E8 5FFCFFFF   call    004018E8

这儿可以按F7进入

004018E8   $  A1 DC324000   mov     eax, dword ptr [4032DC]
004018ED   .  0BC0          or      eax, eax
004018EF   .  74 02         je      short 004018F3
004018F1   .  FFE0          jmp     eax
004018F3   >  68 D0184000   push    004018D0
004018F8   .  B8 60114000   mov     eax, <jmp.&MSVBVM60.DllFunctionC>
004018FD   .  FFD0          call    eax                              ;  <jmp.&MSVBVM60.DllFunctionCall>
004018FF   .- FFE0          jmp     eax                              ;  kernel32.GetModuleFileNameA

可以看到这就是VB调用函数的方法,可以跟进看看,跟进系统函数以后,可以使用菜单栏的Debug-Execute till return返回

而函数

00401C9D   .  FF15 5C104000 call    dword ptr [<&MSVBVM60.__vbaStrTo>;  MSVBVM60.__vbaStrToUnicode

则是把ANSI字串转为Unicode用的。

00401CA9   .  FF15 18104000 call    dword ptr [<&MSVBVM60.__vbaLsetF>;  MSVBVM60.__vbaLsetFixstr

则是对应String函数

00401D49   .  FF15 7C104000 call    dword ptr [<&MSVBVM60.__vbaStrCo>;  MSVBVM60.__vbaStrCopy

这个就是简单的“=”操作符

00401D50   .  FF15 14104000 call    dword ptr [<&MSVBVM60.#519>]     ;  MSVBVM60.rtcTrimBstr

 这个对应Trim函数

00401D10   .  FF15 28104000 call    dword ptr [<&MSVBVM60.#595>]     ;  MSVBVM60.rtcMsgBox

这个对应Msgbox函数

其他的调试方法都类似C++程序的方法,不过调试的时候应该多注意一下堆栈,很多信息都是在这里面表现出来的。

4.Delphi程序的调试

这次使用的是Delphi 7编译的程序,由于是命令行程序,而且只额外引用了一个Windows库,所以程序结构非常简单:

00407D14 >/$  55            push    ebp
00407D15  |.  8BEC          mov     ebp, esp
00407D17  |.  83C4 EC       add     esp, -14
00407D1A  |.  53            push    ebx
00407D1B  |.  56            push    esi
00407D1C  |.  33C0          xor     eax, eax
00407D1E  |.  8945 EC       mov     dword ptr [ebp-14], eax
00407D21  |.  A1 A8834000   mov     eax, dword ptr [4083A8]
00407D26  |.  C600 01       mov     byte ptr [eax], 1
00407D29  |.  B8 D47C4000   mov     eax, 00407CD4
00407D2E  |.  E8 3DC7FFFF   call    00404470
00407D33  |.  33C0          xor     eax, eax
00407D35  |.  55            push    ebp
00407D36  |.  68 A07D4000   push    00407DA0
00407D3B  |.  64:FF30       push    dword ptr fs:[eax]
00407D3E  |.  64:8920       mov     dword ptr fs:[eax], esp
00407D41  |.  BE 00010000   mov     esi, 100
00407D46  |.  8D4D EC       lea     ecx, dword ptr [ebp-14]
00407D49  |.  BA 00010000   mov     edx, 100
00407D4E  |.  33C0          xor     eax, eax
00407D50  |.  E8 BBBCFFFF   call    00403A10
00407D55  |.  8B45 EC       mov     eax, dword ptr [ebp-14]
00407D58  |.  E8 03BCFFFF   call    00403960
00407D5D  |.  8BD8          mov     ebx, eax
00407D5F  |.  56            push    esi                              ; /BufSize
00407D60  |.  53            push    ebx                              ; |PathBuffer
00407D61  |.  6A 00         push    0                                ; |hModule = NULL
00407D63  |.  E8 E4C7FFFF   call    <jmp.&kernel32.GetModuleFileName>; \GetModuleFileNameA
00407D68  |.  85C0          test    eax, eax
00407D6A  |.  75 13         jnz     short 00407D7F

00407D6C  |.  6A 00         push    0                                ; /Style = MB_OK|MB_APPLMODAL
00407D6E  |.  68 B07D4000   push    00407DB0                         ; |Title = ""
00407D73  |.  68 B47D4000   push    00407DB4                         ; |Text = "函数?,F7,"用失",B0,"?"
00407D78  |.  6A 00         push    0                                ; |hOwner = NULL
00407D7A  |.  E8 35C8FFFF   call    <jmp.&user32.MessageBoxA>        ; \MessageBoxA
00407D7F  |>  6A 06         push    6                                ; /Style = 6|MB_APPLMODAL
00407D81  |.  53            push    ebx                              ; |Title
00407D82  |.  53            push    ebx                              ; |Text
00407D83  |.  6A 00         push    0                                ; |hOwner = NULL
00407D85  |.  E8 2AC8FFFF   call    <jmp.&user32.MessageBoxA>        ; \MessageBoxA
00407D8A  |.  33C0          xor     eax, eax
00407D8C  |.  5A            pop     edx
00407D8D  |.  59            pop     ecx
00407D8E  |.  59            pop     ecx
00407D8F  |.  64:8910       mov     dword ptr fs:[eax], edx
00407D92  |.  68 A77D4000   push    00407DA7
00407D97  |>  8D45 EC       lea     eax, dword ptr [ebp-14]
00407D9A  |.  E8 75B8FFFF   call    00403614
00407D9F  \.  C3            retn

整个程序就是这个了,即使程序不是命令行模式的,Delphi的调试还是非常简单,基本就像这样平坦着到头,不过为啥要特别把它拎出来呢,因为这个正好可以体现不同编译器编译的不同,还记得C++程序怎么处理if语句的吗?

C++:

004012FD |. 837D F0 00 cmp dword ptr [ebp-10], 0
00401301 |. 75 26 jnz short 00401329

Delphi:

00407D68 |. 85C0 test eax, eax
00407D6A |. 75 13 jnz short 00407D7F


先看C++的,之前已经说过了,它先把eax移动到dword ptr [ebp-10]处,然后比较这个值是否为0(详见1001.HTML的叙述),cmp操作改变了标志位,这样下一句jnz就可以根据标志位来判断是否跳转了

再看Delphi的,Delphi的这句test eax,eax是什么意思呢,test指令是按位与运算(http://zh.wikipedia.org/wiki/%E9%80%BB%E8%BE%91%E4%B8%8E),这样如果eax=0的话,test eax,eax的值也为0,如果eax!=0的话,结果也就相应的不等于零了,而如果等于0(即jnz不跳转),则会执行对话框,弹出“函数调用出错!”的对话框,如果不等于0,jnz就会跳走,也就不会弹出“函数调用出错!”的对话框了。

 由于对一个立即数的操作要比寄存器操作慢,所以Delphi的这一优化还是很好的,既达到了相同的作用,又提高了效率。


 

差不多就写完了,暂时不写蛋疼的结束语,以后如果想到了什么再继续加。