打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
标 题:
      使用CDB和NTSD开始调试旅程
      介绍
      在软件开发和维护的过程中,调试是最有具有价值的技能之一,它几乎被用在产品生命周期的每个阶段。首先,开发人员很明显会遇到很多错误。这些错误多种多样,有逻辑错误、语法错误、编译器方面的错误等等。其次,当测试更多的高级的情况或当软件在和其他环境交互的时候,质量保证部门人员可能会遇到困难。最后,产品发布之后,公司必须对它提供一些支持。顾客拿到软件产品之后,调试并没有结束,错误通常都会升级,并被返回公司等待调试。
      这篇教程的目的
      这篇教程仅仅是关于调试的一个介绍。这篇是”教程 #1”,如果反馈好的话,我会继续写。有许多复杂的调试技术以及问题,以至于我们不知道从哪里开始着手。这篇教程从最初开始帮助你了解调试是怎么一回事。我希望把高级调试的世界展现给初级和中级程序员,而不是简单的重新编译,或者简单的MessageBox和Printf调试。
      调试器和操作系统
      你可以到下面这个网站下载最新的微软提供的调试器。
      http://www.microsoft.com/whdc/ddk/debugging
      CDB,NTSD和Windbg
      这篇文章讨论的情况一般都是Windows2000以及更高版本。我们将要谈论的这三个调试器是CDB,NTSD和WinDbg。Windows2000以及更高版本一般系统内都内置NTSD!所以,如果你想快速调试,就不需要另外再安装软件了。
      那么这又有什么不一样呢?文档上说”NTSD不需要控制台支持,而CDB需要”。这是真的,NTSD确实不需要,而CDB需要。然而,我发现还有更多的不同之处。第一,老版本的NTSD不支持PDB符号文件,它们只支持DBG符号文件!我还发现NTSD不支持符号服务器,而CDB支持。老版本的NTSD不能创建内存dump,还有其他一些问题,比如NTSD只支持2种断点命令。NTSD相对于CDB的一个优势就是它不需要控制台。
      当你正在调试用户态服务或者登陆到系统之前的进程时,不需要控制台窗口这个特点是非常重要的。如果没有用户登陆到系统,你就不能创建控制台窗口。你可以设置一个命令选项-d,让NTSD和已经连接上的内核调试器通信(CDB也有相同的选项)。这样就能通过内核调试器来调试系统启动期间的进程。当你已经可以用内核调试器调试进程,用户态调试器能给你更多的灵活性。这已经超出了介绍章节的范围,只要消化这个概念就行了。
      除了很少的一些差异,WinDbg和CDB几乎一样。WinDbg是GUI程序,CDB是控制台程序,这是第一个不同。WinDbg还支持内核调试和源码级调试。
      Visual C++调试器
      我不使用这个调试器,并且我不推荐使用它。第一个原因就是它非常消耗资源。它加载的非常慢,并且包含了很多没用的东西,以至于成为累赘。第二个原因就是安装完这个调试器后,你需要重启,我一般都是在没有安装调试器的机器上工作。并且VC++非常大,安装费时。
      Windows 9x/ME
      在Windows 9x/ME的机器上,我们该怎么办呢?你可以使用WinDbg。对所有系统,和调试有关的API都是一样的,因此Windbg能运行在Windows 9x/ME上。我唯一的担心就是WinDbg会检测当前系统是否Windows 9x并且会不允许调试。我最近发现事实上不会这样。剩下的问题就是,最新的WinDbg是MSI安装包,并且不允许安装在Windows 9x系统上。我们可以在NT系统上安装,然后共享这个目录或者拷贝到CD上共享。这会有一些影响,比如若NT和9x系统放置的数据在内存的不同地方,你就不能使用所有的!xxx命令。那么符号能使用吗?是的,PDB可以使用。但是当设置了ba r1 xxxxx之后,单步走非常慢。这篇文章的内容不包括Windows 9x/ME。
      设置环境
      开始调试前,这是非常重要的一步。把系统配置成你喜欢的状态,并且包含所有你需要的工具。
      符号和符号服务器
      符号是调试操作中很重要的一步。你可以从微软的一个地址下载相应操作系统的所有符号。问题是,你需要很大的硬盘空间,并且不如你在一台机器上调试许多操作系统,这可能会非常麻烦。
      为了适应这个需要,微软提供了”符号服务器”这个功能。这会帮助你得到正确的符号。符号服务器的地址http://msdl.microsoft.com/download/symbols。如果你把符号路径设置为这个地址,调试器将会自动你需要的系统符号。你自己的程序的符号需要你自己设置。
      映像文件执行选项
      这是注册表中的一个位置,当一个程序开始运行,这个注册表位置会自动将调试器附加到程序。这个注册表位置如下:
      HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options
      在这个键下,你只要创建一个子键,取名为你想要调试的程序,比如”myapplication.exe”。如果你以前没有使用过这个功能,可能会有一个默认键值”Your Application Here”或类似的值,重命名它即可。
      这个键下的一个值是”Debugger”,你可以在这里设置需要开启的调试器,”Your Application Here”下面默认的这个值为”ntsd -d”。你不能使用这个,除非已经有内核调试器附加在系统上,所以要去掉”-d”选项。
      注意:使用”-d”选项,并且当前没有内核调试器附加在系统上,每次启动程序都有可能导致系统锁死。必须非常小心。如果内核调试器已经设置好了,你可以使用”g”命令解锁系统。
      另外一个值为”GlobalFlags”。这是另外一个可以用于调试的工具,然而它超出了本篇的范围。如果你想知道更多,可以看”gflags.exe”。
      内核调试设置
      如果需要内核调试,首先你需要以调试模式启动系统。虽然在系统属性里面可以用GUI的方式设置,我还是建议直接编辑boot.ini文件。在C:\盘找到boot.ini文件,它是一个隐藏的系统文件。
      小心:不正确的编辑这个文件将会让你无法启动。
      这个启动文件内容类似下面:
      timeout=30
      default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
      [operating systems]
      multi(0)disk(0)rdisk(0)partition(1)\WINDOWS.0=
      'Microsoft Windows XP Professional' /fastdetect
      我将复制Operating Systems下面的第一行:
      timeout=30
      default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
      [operating systems]
      multi(0)disk(0)rdisk(0)partition(1)\WINDOWS.0=
      'Microsoft Windows XP Professional' /fastdetect
      multi(0)disk(0)rdisk(0)partition(1)\WINDOWS.0=
      'Microsoft Windows XP Professional' 
      /fastdetect /debug /debugport=COM1 /baudrate=115200
      复制的这行包含了你的设置。/debug,然后是/debugport=port,最后是/baudrate=baudrate。调试端口是你需要使用的串行端口,这是你需要设置的硬件,你还需要另外一台机器来完成设置。除了使用COM端口,你还可以使用IEEE 1394,这个速度会更快一些。
      当你再次启动后,选择”Debugger Enabled”选项来启动调试模式。
      (其实我们一般都用虚拟机来完成这部分的设置,具体可以在论坛上搜,有很多教程。)
      环境变量
      我一般会把_NT_SYMBOL_PATH环境变量设置为微软的符号服务器和我自己的符号目录。你可以在     系统属性->高级->环境变量  里面设置这个值。
      默认调试器
      当有任何崩溃出现在系统上,将会调用缺省调试器。默认情况下,它被设置为”Doctor Watson”。这个注册表键在如下位置: 
      HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug
      我会把”Auto”设为1,”Debugger”设为需要的调试器。
      汇编
      我十分推荐你学习汇编语言。这篇教程不会给你展示源码级调试,因为我从来不这样做并且我也不会。源码级调试带来的问题是,你不会总是有源代码,并且有时候从源代码看不出问题在哪。如果你了解系统的组成,你可以很容易的逆向系统来找到你需要的信息,这个源码级调试不能带给你的。
      我讨厌源码级调试的另外一个原因是,如果源码和符号不符合,调试器将会给你错误的信息。这意味着如果你为你的项目创建了多个版本,你不得不找到和你正在调试的程序符合的版本。
      让我们开始吧
      这篇教程是第一部分,如果你们喜欢,我们写更多,并且会更深入。这篇将会解决两个简单的用户态编程问题。
      发布版本的符号
      首先,怎样为发布版程序创建符号呢?很简单,创建一个make文件。
      我一般使用的编译选项如下:
      /nologo /MD /W3 /Oxs /Zi /I '..\..\inc' /D 'WIN32' /D '_WINDOWS' 
      /Fr$(OBJDIR)\\ /Fo$(OBJDIR)\\ /Fd$(OBJDIR)\\ /c
      我一般使用的链接选项如下:
      /nologo /subsystem:console 
      /out:$(TARGETDIR)\$(TARGET)/pdb:<YourProjectName>.pdb 
      /debug /debugtype:both 
      /LIBPATH:'..\..\..\bin\lib'
      这将会为你的工程创建.pdb文件。当然,根据VC++7的介绍,他们已经放弃使用.DBG(因此/debugtype:both在这个编译器上可能会出现错误)。.DBG是.PDB的简单版本并且它不包含源码信息,精确的符号察看。它甚至不包含参数或其他一些东西。如果你还在使用这样的编译器,你需要做下面的工作:
      rebase -b 0x00100000 -x $(TARGETDIR) -a $(TARGETDIR)\$(TARGET)
      -b选项后面是重定位的新的可执行文件的内存地址。如果你使用默认的Visual Studio方式创建文件,那可能会比这个更小一点,不过,你不会获得符号。产生的代码是一样的,并且会有相应的优化。不同的是,这些文件会更有用,不论你在哪里使用,你都能得到符号信息。
      记住,最棒的调试时机总是在你还没有重新生成可执行文件之前。一旦你不得不重新生成可执行文件,你必须知道,你已经改变了这个文件在内存中的位置。你也可能改变了文件执行的速度。如果你需要重现这个错误,这将是非常重要的!如果需要4天才能引起这个错误,那该怎么办呢?如果能的话,最好在发生的时候就去处理它。
      简单的Access Violation错误
      我们来看一个简单的”Access Violation”错误,这很常见。解决这个问题可以分为三步。
      1.  谁触发了这个访问?哪个模块?
      2.  它要访问哪里?这块内存在哪?
      3.  为什么它要访问这块内存?它要干什么?
      这些是一般情况下解决这个问题的方法,其中第2条又是最重要的。然而,解决1和3的问题可以帮助解决2的问题。
      我创建了一个简单的崩溃的程序。我已经把我的默认调试器设置为CDB,并且我运行了这个程序。我也为这个可执行文件创建了符号并把_NT_SYMBOL_PATH设置为微软的符号服务器。
      当我们运行这个程序,我们将会看到下面的情况:
      C:\programs\DirectX\Games\src\Games\temp\bin>temp
      Microsoft (R) Windows Debugger  Version 6.3.0005.1
      Copyright (c) Microsoft Corporation. All rights reserved.
      *** wait with pending attach
      Symbol search path is: 
      SRV*c:\symbols*http://msdl.microsoft.com/download/symbols
      Executable search path is:
      ModLoad: 00400000 00404000   C:\programs\DirectX\Games\src\Games\temp\bin\temp.e
      xe
      ModLoad: 77f50000 77ff7000   C:\WINDOWS.0\System32\ntdll.dll
      ModLoad: 77e60000 77f46000   C:\WINDOWS.0\system32\kernel32.dll
      ModLoad: 77c10000 77c63000   C:\WINDOWS.0\system32\MSVCRT.dll
      ModLoad: 77dd0000 77e5d000   C:\WINDOWS.0\system32\ADVAPI32.DLL
      ModLoad: 78000000 78086000   C:\WINDOWS.0\system32\RPCRT4.dll
      (ee8.c38): Access violation - code c0000005 (!!! second chance !!!)
      eax=00000000 ebx=7ffdf000 ecx=00001000 edx=00320608 esi=77c5aca0 edi=77f944a8
      eip=77c3f10b esp=0012fb0c ebp=0012fd60 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      MSVCRT!_output+0x18:
      77c3f10b 8a18             mov     bl,[eax]                ds:0023:00000000=??
      0:000>
      第一个要注意的就是,这个错误发生在MSVCRT.DLL当中。这很明显,因为调试器给我们显示了类似的信息,格式为<module>!<nearest symbol>+offset。这意味着最近的符号是_output,并且我们运行到了里面的+18h处。因此我们假设自己正处于_output函数当中。
      (ee8.c38): Access violation - code c0000005 (!!! second chance !!!)
      eax=00000000 ebx=7ffdf000 ecx=00001000 edx=00320608 esi=77c5aca0 edi=77f944a8
      eip=77c3f10b esp=0012fb0c ebp=0012fd60 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      MSVCRT!_output+0x18:
      77c3f10b 8a18             mov     bl,[eax]                ds:0023:00000000=??
      0:000>
      如果我们想验证这个信息,我们应该怎么做?
      <0:000> x *!
      start    end        module name
      00400000 00404000   temp         (deferred)
      77c10000 77c63000   MSVCRT       (pdb symbols)  
      c:\symbols\msvcrt.pdb\3D6DD5921\msvcrt.pdb
      77dd0000 77e5d000   ADVAPI32     (deferred)
      77e60000 77f46000   kernel32     (deferred)
      77f50000 77ff7000   ntdll        (deferred)
      78000000 78086000   RPCRT4       (deferred)
      这个命令展示所有模块的列表以及它们的开始和结束位置。我们的错误地址是77c3f10b,77c10000<=77c3f10b<=77c63000,因此可以确认错误发生在MSVCRT。下面我们来确定这个地址在哪。
      有几种方法可以完成,我们可以反汇编代码,找出这个地址,还可以看栈回溯。首先我们来看看_output函数的反汇编代码。
      0:000> u MSVCRT!_output
      MSVCRT!_output:
      77c3f0f3 55               push    ebp
      77c3f0f4 8bec             mov     ebp,esp
      77c3f0f6 81ec50020000     sub     esp,0x250
      77c3f0fc 33c0             xor     eax,eax
      77c3f0fe 8945d8           mov     [ebp-0x28],eax
      77c3f101 8945f0           mov     [ebp-0x10],eax
      77c3f104 8945ec           mov     [ebp-0x14],eax
      77c3f107 8b450c           mov     eax,[ebp+0xc]
      0:000> u
      MSVCRT!_output+0x17:
      77c3f10a 53               push    ebx
      77c3f10b 8a18             mov     bl,[eax]
      即使你不懂汇编,你也会发现一些东西。首先,我们可以发现这个内存地址在EAX当中。它是CPU的一个寄存器,但是我们可以把它看作一个变量。环绕EAX的[]符号相当于C语言中的*MyPointer。这意味着我们正在引用EAX指向的地址。那么EAX又是怎么来的?EAX来自[EBP+0Ch],你可以把它看作DWORD *EBP,EAX=EBP[3].这是因为在汇编语言中,没有类型。EAX是一个32位寄存器,EBP+12相当于一个DWORD指针加3。
      下面我们可以看到MOV EBP,ESP。ESP是栈指针。参数都是压入栈中,返回地址和局部变量都是在栈中。ESP指向栈的位置。在内存中,一个C函数调用约定的存放如下:
      [Parameter n]
      ...
      [Parameter 2]
      [Parameter 1]
      [Return Address]
      并且,我们看到了PUSH  EBP。PUSH就是把某个东西压入栈,因此我们在栈中保存了EBP以前的值。此时,栈的状态如下:
      [Parameter n]
      ...
      [Parameter 2]
      [Parameter 1]
      [Return Address]
      [Previous EBP]
      然后我们又把EBP设为ESP,我们可以把它看作是一个指针,栈就相当于一个DWORD类型的数组。因此,栈中各个变量与EBP的对应如下:
      [Parameter n]     ==  [EBP + n*4 + 4] (The formula)
      ...
      [Parameter 2]     ==  [EBP + 12]
      [Parameter 1]     ==  [EBP + 8]
      [Return Address]  ==  [EBP + 4]
      [Previous EBP]    ==  [EBP + 0]
      对于我们这个例子来说,我们的变量是_output的第二个参数。那么,下面该怎么办呢?我们来反汇编调用函数!我们知道EBP+4指向返回地址,或者我们也可以得到栈回溯。
      0:000> kb
      ChildEBP RetAddr  Args to Child
      0012fd60 77c3e68d 77c5aca0 00000000 0012fdb0 MSVCRT!_output+0x18
      0012fda4 0040102f 00000000 00000000 00403010 MSVCRT!printf+0x35
      0012ff4c 00401125 00000001 00323d70 00322ca8 temp!main+0x2f
      0012ffc0 77e814c7 77f944a8 00000007 7ffdf000 temp!mainCRTStartup+0xe3
      0012fff0 00000000 00401042 00000000 78746341 kernel32!BaseProcessStart+0x23
      0:000>
      “KB”命令能得到栈回溯。我们不会总是得到完整的栈回溯,我们会在更加深入的教程中讲解这点。在这篇简单的教程里,我们假定我们得到了完整的栈回溯。我们注意到,这个函数是printf,并且printf调用_output。我们来反汇编printf,注意我们不用每次都反汇编整个函数,将其分割成几段即可。有时,我们能从栈回溯中很简单就找到错误发生点,而这些函数也都非常简单,我们可以非常轻松的跟踪他们。
      0:000> u MSVCRT!_output
      MSVCRT!_output:
      77c3f0f3 55               push    ebp
      77c3f0f4 8bec             mov     ebp,esp
      77c3f0f6 81ec50020000     sub     esp,0x250
      77c3f0fc 33c0             xor     eax,eax
      77c3f0fe 8945d8           mov     [ebp-0x28],eax
      77c3f101 8945f0           mov     [ebp-0x10],eax
      77c3f104 8945ec           mov     [ebp-0x14],eax
      77c3f107 8b450c           mov     eax,[ebp+0xc]
      0:000> u
      MSVCRT!_output+0x17:
      77c3f10a 53               push    ebx
      77c3f10b 8a18             mov     bl,[eax]
      77c3f10d 33c9             xor     ecx,ecx
      77c3f10f 84db             test    bl,bl
      77c3f111 0f8445070000     je      MSVCRT!_output+0x769 (77c3
      77c3f117 56               push    esi
      77c3f118 57               push    edi
      77c3f119 8bf8             mov     edi,eax
      0:000> u MSVCRT!printf
      MSVCRT!printf:
      77c3e658 6a10             push    0x10
      77c3e65a 68e046c177       push    0x77c146e0
      77c3e65f e8606effff       call    MSVCRT!_SEH_prolog (77c354
      77c3e664 bea0acc577       mov     esi,0x77c5aca0
      77c3e669 56               push    esi
      77c3e66a 6a01             push    0x1
      77c3e66c e8bdadffff       call    MSVCRT!_lock_file2 (77c394
      77c3e671 59               pop     ecx
      0:000> u
      MSVCRT!printf+0x1a:
      77c3e672 59               pop     ecx
      77c3e673 8365fc00         and     dword ptr [ebp-0x4],0x0
      77c3e677 56               push    esi
      77c3e678 e8c7140000       call    MSVCRT!_stbuf (77c3fb44)
      77c3e67d 8945e4           mov     [ebp-0x1c],eax
      77c3e680 8d450c           lea     eax,[ebp+0xc]
      77c3e683 50               push    eax
      77c3e684 ff7508           push    dword ptr [ebp+0x8]
      0:000> u
      MSVCRT!printf+0x2f:
      77c3e687 56               push    esi
      77c3e688 e8660a0000       call    MSVCRT!_output (77c3f0f3)
      _output的第二个参数是[EBP+8],并且有PUSH  EBP和MOV  EBP,ESP,因此这和我之前说的情况一样。但是也不总是这样的,要视具体情况而定,我们慢慢再深入讲解。
      因此,我们可以确定printf的第一个参数在内存中的哪个地方,并且printf是我们的程序发出的调用。从错误信息,我们知道EAX是0,因此我们在对一个空指针解引用。
      77c3f10b 8a18    mov bl,[eax]   ds:0023:00000000=??
      下面是我们写的代码:
      int main(int argc, char *argv[])
      {  
      char *TheLastParameter[100];
      sprintf(*TheLastParameter, 'The last parameter is %s', argv[argc]);
      printf(*TheLastParameter);
      return 0;
      }
      这段代码有很多问题,然后因为空指针,错误发生在printf。奇怪的是,并没有在sprintf()处出现错误。那么,我们该怎样只用KB命令解决这个问题呢?
      0:000> kb
      ChildEBP RetAddr  Args to Child
      0012fd60 77c3e68d 77c5aca0 00000000 0012fdb0 MSVCRT!_output+0x18
      0012fda4 0040102f 00000000 00000000 00403010 MSVCRT!printf+0x35
      0012ff4c 00401125 00000001 00323d70 00322ca8 temp!main+0x2f
      0012ffc0 77e814c7 77f944a8 00000007 7ffdf000 temp!mainCRTStartup+0xe3
      0012fff0 00000000 00401042 00000000 78746341 kernel32!BaseProcessStart+0x23
      0:000>
      我们有符号和栈回溯,第一个参数是0,并且我们调用了这个函数。这是很简单的情况,我会试着讲述一些技巧,让你发现问题的所在之处。知道栈是如何组成的,在内存中的状态,这对你找出问题所在之处非常有帮助,因为仅仅一个”kb”命令不会让你一眼就能看出问题。
      不按预期运行的程序
      这也是一个很常见的错误。你运行了程序,但是你并没有看到正确的输出,或者总是有一些错误信息。这个常见的问题有可能非常容易解决,也可能很复杂。那么解决这种问题的步骤如何呢?
      1.  什么东西没有工作? 
      2.  哪些API或模块可能会和这个问题有关?
      3.  什么原因导致这些API不正常的工作?
      这些步骤不是必须的。下面让我们来看一个例子,关于打开文件的。
      HANDLE hFile;
      DWORD dwWritten;
      hFile = CreateFile('c:\MyFile.txt', GENERIC_READ, 
      0, NULL, OPEN_EXISTING, 0, NULL);
      if(hFile != INVALID_HANDLE_VALUE)
      {
      WriteFile(hFile, 'Test', strlen('Test'), &dwWritten, NULL);
      CloseHandle(hFile);
      }
      这是我们的代码。你可能会想使用GetLastError(),然后重新编译,并将错误打印出来。但是,你并不需要这样做,在这个例子中非常简单。下面我们来看看,打开调试器,并且会停在这个函数。有符号的帮助,一切都很简单,CreateFile是一个导出的符号,我们总是能断在这个函数上。
      C:\programs\DirectX\Games\src\Games\temp\bin>cdb temp
      Microsoft (R) Windows Debugger  Version 6.3.0005.1
      Copyright (c) Microsoft Corporation. All rights reserved.
      CommandLine: temp
      Symbol search path is: 
      SRV*c:\symbols*http://msdl.microsoft.com/download/symbols
      Executable search path is:
      ModLoad: 00400000 00404000   temp.exe
      ModLoad: 77f50000 77ff7000   ntdll.dll
      ModLoad: 77e60000 77f46000   C:\WINDOWS.0\system32\kernel32.dll
      ModLoad: 77c10000 77c63000   C:\WINDOWS.0\system32\MSVCRT.dll
      (2a0.94): Break instruction exception - code 80000003 (first chance)
      eax=00241eb4 ebx=7ffdf000 ecx=00000004 edx=77f51310 esi=00241eb4 edi=00241f48
      eip=77f75a58 esp=0012fb38 ebp=0012fc2c iopl=0         nv up ei pl nz na pe nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
      ntdll!DbgBreakPoint:
      77f75a58 cc               int     3
      0:000> bp temp!main
      0:000> g
      我们在main函数上下一个断点,然后使用”g”命令让其运行。当我们到达断点,用”p”命令单步走,直到CreateFile函数。
      Breakpoint 0 hit
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401000 esp=0012ff50 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main:
      00401000 51               push    ecx
      0:000> p
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401001 esp=0012ff4c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x1:
      00401001 56               push    esi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401002 esp=0012ff48 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x2:
      00401002 57               push    edi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401003 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x3:
      00401003 33ff             xor     edi,edi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401005 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x5:
      00401005 57               push    edi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401006 esp=0012ff40 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x6:
      00401006 57               push    edi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401007 esp=0012ff3c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x7:
      00401007 6a03             push    0x3
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401009 esp=0012ff38 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x9:
      00401009 57               push    edi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=0040100a esp=0012ff34 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0xa:
      0040100a 57               push    edi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=0040100b esp=0012ff30 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0xb:
      0040100b 6800000080       push    0x80000000
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401010 esp=0012ff2c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x10:
      00401010 6810304000       push    0x403010
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401015 esp=0012ff28 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x15:
      00401015 ff1504204000 call dword ptr [temp!_imp__CreateFileA (00402004)]{kernel3
      2!CreateFileA (77e7b476)} ds:0023:00402004=77e7b476
      0:000> p
      eax=ffffffff ebx=7ffdf000 ecx=77f939e3 edx=00000002 esi=00000000 edi=00000000
      eip=0040101b esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei ng nz na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000286
      temp!main+0x1b:
      0040101b 8bf0             mov     esi,eax
      在调用CreateFile函数之后,EAX中将存储返回值。我们注意到其值为ffffffff,也就是”Invalid Handle Value”,我们还想知道GetLastError的值,它存储在fs:34这个位置。FS是TEB选择子,我们可以把它dump出来。
      0:000> dd fs:34
      0038:00000034  00000002 00000000 00000000 00000000
      0038:00000044  00000000 00000000 00000000 00000000
      0038:00000054  00000000 00000000 00000000 00000000
      0038:00000064  00000000 00000000 00000000 00000000
      0038:00000074  00000000 00000000 00000000 00000000
      0038:00000084  00000000 00000000 00000000 00000000
      0038:00000094  00000000 00000000 00000000 00000000
      0038:000000a4  00000000 00000000 00000000 00000000
      CDB还有一种更快速的方式能做到这点,!gle:
      0:000> !gle
      LastErrorValue: (Win32) 0x2 (2) - The system cannot find the file specified.
      LastStatusValue: (NTSTATUS) 0xc0000034 - Object Name not found.
      0:000>
      找不到对应的文件。那么问题出在哪呢?我们需要进一步调试,我们来看看传递给CreateFile的参数。
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401010 esp=0012ff2c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x10:
      00401010 6810304000       push    0x403010
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401015 esp=0012ff28 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x15:
      00401015 ff1504204000 call dword ptr [temp!_imp__CreateFileA 
      (00402004)]{kernel32!CreateFileA (77e7b476)} ds:0023:00402004=77e7b476
      幸运的是,这是内存中的一个常量,它不太可能会改变,因为我们并没有运行到离CreateFile太远。
      然后,我们可以使用”da”,”dc”,“du”命令。”da”命令打印出ANSI字符串,”du”打印出Unicode字符串,”dc”和”dd”类似,不过它是打印出所有的字符,包括不可显示的。我们知道这是一个ANSI字符串,”da”命令:
      0:000> da 403010
      00403010  'c:MyFile.txt'
      0:000>
      我们看到这是错误的字符串,我们应该用c:\\MyFile.txt来代替它。
      因此,我们找到并更正这个错误,但是,我们还是不能写入。我们再进一步调试。
      重新载入一遍。
      C:\programs\DirectX\Games\src\Games\temp\bin>cdb temp
      Microsoft (R) Windows Debugger  Version 6.3.0005.1
      Copyright (c) Microsoft Corporation. All rights reserved.
      CommandLine: temp
      Symbol search path is: 
      SRV*c:\symbols*http://msdl.microsoft.com/download/symbols
      Executable search path is:
      ModLoad: 00400000 00404000   temp.exe
      ModLoad: 77f50000 77ff7000   ntdll.dll
      ModLoad: 77e60000 77f46000   C:\WINDOWS.0\system32\kernel32.dll
      ModLoad: 77c10000 77c63000   C:\WINDOWS.0\system32\MSVCRT.dll
      (80c.c94): Break instruction exception - code 80000003 (first chance)
      eax=00241eb4 ebx=7ffdf000 ecx=00000004 edx=77f51310 esi=00241eb4 edi=00241f48
      eip=77f75a58 esp=0012fb38 ebp=0012fc2c iopl=0         nv up ei pl nz na pe nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
      ntdll!DbgBreakPoint:
      77f75a58 cc               int     3
      0:000> bp temp!main
      0:000> g
      Breakpoint 0 hit
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401000 esp=0012ff50 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main:
      00401000 51               push    ecx
      0:000> p
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401001 esp=0012ff4c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x1:
      00401001 56               push    esi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401002 esp=0012ff48 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x2:
      00401002 57               push    edi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401003 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x3:
      00401003 33ff             xor     edi,edi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401005 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x5:
      00401005 57               push    edi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401006 esp=0012ff40 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x6:
      00401006 57               push    edi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401007 esp=0012ff3c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x7:
      00401007 6a03             push    0x3
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401009 esp=0012ff38 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x9:
      00401009 57               push    edi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=0040100a esp=0012ff34 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0xa:
      0040100a 57               push    edi
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=0040100b esp=0012ff30 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0xb:
      0040100b 6800000080       push    0x80000000
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401010 esp=0012ff2c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x10:
      00401010 6810304000       push    0x403010
      0:000>
      eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
      eip=00401015 esp=0012ff28 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
      temp!main+0x15:
      00401015 ff1504204000 call dword ptr [temp!_imp__CreateFileA (00402004)]{kernel3
      2!CreateFileA (77e7b476)} ds:0023:00402004=77e7b476
      0:000>
      eax=000007e8 ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=00000000 edi=00000000
      eip=0040101b esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei ng nz ac pe cy
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000293
      temp!main+0x1b:
      0040101b 8bf0             mov     esi,eax
      0:000> p
      走到这里,我们看到EAX是一个有效的handle,继续。
      eax=000007e8 ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000
      eip=0040101d esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei ng nz ac pe cy
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000293
      temp!main+0x1d:
      0040101d 83feff           cmp     esi,0xffffffff
      0:000>
      eax=000007e8 ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000
      eip=00401020 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy
      cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213
      temp!main+0x20:
      00401020 741b             jz      temp!main+0x3d (0040103d)            
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
带你感受破解的几个境界
NOP 指令作用
【代码真相】函数调用 堆栈 转载 - liangxiufei - 博客园
PECompact 2.x -> Jeremy Collake完美脱壳去校验 - 『 我为...
Unlocker 工作原理分析
[外挂学习]Jim's游戏外挂学习笔记3——继续找当前地图数据和所处坐标存放的地址(原创)...
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服