WinDbg学习笔记(转)

最近项目老大要求我在windows登录过程中嵌入智能卡登录验证,需要对gina.dll动手术。花了3天学习消化几个巨人的代码,站在他们的肩膀上,到现在能够显示自己的登录对话框,能够验证用户名密码并登录成功。但在其后调用WlxStartApplication()时失败, GetLastError()返回0。这下没辙了,仅凭简单的MessageBox无法有效的调试。Microsoft的网站上提供了一个在 Windows2000下用WinDbg调试Gina的方法(http://support.microsoft.com/kb/260901/zh-cn)。没法,硬着头皮学学WinDbg吧。

符号文件——Windows 应用程序调试必备

一、何谓符号文件?

符号文件(Symbol Files)是一个数据信息文件,它包含了应用程序二进制文件(比如:EXE、DLL等)调试信息,专门用来作调试之用,最终生成的可执行文件在运行时并不需要这个符号文件,但你的程序中所有的变量信息都记录在这个文件中。所以调试应用程序时,这个文件是非常重要的。用 Visual C++ 和 WinDbg 调试程序时都要用到这个文件。
在 Windows 系统中,符号文件以 .pdb 为扩展名,比如:每个 Windows 操作系统下有一个 GDI32.dll 文件,编译器在编译该 DLL 的时候会产生一个 GDI32.pdb 文件,一旦你拥有了这个 PDB 文件,那么便可以用它来调试并跟踪到 GDI32.dll 内部。该文件和二进制文件的编译版本密切相关,比如修改了 DLL 的输出函数,再编译该 DLL,那么原先的 PDB 文件就过时了,不能再用老的 PDB 文件来做调试工作,而必须使用最新的 PDB 文件版本。
Visual C++ 编译代码后会在 Debug 或者 Release 目录下生成一个 PDB 文件。一般情况下,符号文件包括以下的数据信息:

   1. 全局变量(Global variables);
   2. 局部变量(Local variables);
   3. 函数名和它们的入口地址(Function names and the addresses of their entry points);
   4. FPO 数据(Frame Pointer Omission):Frame Pointer 是一种用来在调用堆栈(Call stack)中找到下一个将要被调用的函数的数据结构源代码的行序号(Source-line numbers);

二、如何得到和安装符号文件?

   1. 先确定你的操作系统(OS)版本;
   2. 到微软网站下载相应的符号文件;
   3. 安装符号文件,对于符号文件的安装位置没有特贝要求,可以安装在任何目录中;
   4. 设置环境变量,使得调试工具(比如:Visual C++、WinDbg、Ntsd、DrWatson 等)能找到符号文件;

安装符号文件的注意事项:

如果是手动安装符号文件,有一点很重要,那就是宿主机(Hostt Computer)上的符号文件必须与目标机器(Target Computer)上的 Windows 版本相匹配。
这里所谓的宿主机指的是运行调试会话的机器,在典型的双系统调试会话环境中,宿主机可以是连接到目标机器的任何机器。目标机器指的是发生软件组件、系统服务、应用程序或操作系统运行失败的机器。也即是需要被调试的机器,它是调试会话关注的焦点。目标机器可以近在咫尺,也可以位于完全不同的地方。有时我们也将目标机器称之为——被调试者(debuggee),那么与之对应,宿主机则可以称为调试者(debugger)。

三、在 Visual C++ 使用符号文件的方法

在 Visual C++ 6.0 中的使用方法:

   1. 打开 Visual C++ 6.0 的 Workspace 文件(*.dsw);
   2. 进入 Tools 菜单,选择 Options 菜单项 (Tools->Options);
   3. 单击 Directoties 标签;
   4. 在 “Show directories for”下拉列表中选择 “Executable files”;
   5. 将符号文件的路径添加到 “Directories” 路径列表中;
   6. 单击    OK 完成;

在 Visual C++ .NET 2003 中的使用方法:

   1. 打开 Visual C++ .NET 的项目文件(*.vcproj);
   2. 在解决方案管理器中选中要使用符号文件的项目;
   3. 单击右键进入项目属性对话框;
   4. 选择“配置属性”中的“调试”;
   5. 在与“调试”对应的“操作”选项中有一个“符号路径”,在此添加符号文件的路径即可;
   6. 单击    “确定” 完成;

四、如何产生 Release 版本二进制文件对应的 PDB 文件?

在 Visual C++ 6.0 中的方法:

   1. 打开 Visual C++ 6.0 的 Workspace 文件(*.dsw);
   2. 进入 Project 菜单,选择 Settings 菜单项 (Project->Settings),打开项目设置对话框;
   3. 在 “Settings for”列表中选择项目的 Release 配置;
   4. 单击“C/C++”标签;
   5. 在“Category”下拉列表框中选择“General”选项;
   6. 在“Debug info”下拉列表框中选择调试信息格式(具体选项参见图一),在此不必禁用任何优化选项;
   7. 单击“Link”标签;
   8. 在“Category”下拉列表框中选择“Debug”选项;
   9. 选中“Debug info”复选框,然后选择需要的链接调试类型(具体选项参见图一);
10. 不要选择“Separate types”复选框;
11. 在“Project options”编辑框的最后添加如下指令:/opt:ref,icf;
12. 重新生成(Rebuild)项目;

在 Visual C++ .NET 2003 中的方法:

   1. 打开 Visual C++ .NET 的项目文件(*.vcproj);
   2. 进入 Project 菜单,选择 Settings 菜单项 (Project->Settings),打开项目设置对话框;
   3. 在 “配置”下拉列表中选择项目的 “(活动)Release” 配置;
   4. 选择“配置属性”树型节点中的“C/C++” ==〉“常规”;
   5. 设置右边的“调试信息格式”选项(具体选项参见图一);
   6. 选择“配置属性”树型节点中的“链接器”==〉“调试”;
   7. 设置右边的“生成程序数据库文件”(具体选项参见图一);
   8. 选择“配置属性”树型节点中的“链接器”==〉“命令行”;
   9. 在“附加选项(D)”编辑框中添加如下指令:/opt:ref,icf;
10. 按“确定”退出;
11. 重新生成(Rebuild)项目;


图一

五、关于 Free Build(也称 Retail Build)和 Checked Build(也称 Debug Build)

每个基于 NT 操作系统有两种不同的程序生成模式,即:

    * Free Build (或 Retail Build)
    * Checked Build (或 Debug Build)

Free Build 生成的是最终用户版本,针对生成的二进制文件进行了彻底的优化,禁用了调试断言,并剥离了调试信息。这样一来使可执行程序文件更小,加载更快,使用的内存也更小。
Checked Build 生成的是测试和调试版本。它包含额外的 Free Build 所没有的错误检查,参数验证和调试信息,Checked Build 有助于隔离和跟踪可能导致不可预见的行为的问题,比如内存溢出,不正确的设备配置。虽然 Checked Build 提供了额外的保护,但与 Free Build 比较,它需要更多的内存开销和磁盘空间。由于可执行程序包含符号调试信息;调试时要执行附加的代码、参数检查和输出调试诊断信息,从而导致性能下降。

六、系统符号文件的更新方法

系统符号文件指 Windows 操作系统依赖的那几个重要的 DLL/SYS 和可执行文件对应的符号文件,常见的比如:gdi32.dll、Kernel32.dll、Kerberos.dll、psapi.dll、 user32.dll等,使用 WinDbg 调试时,你就会发现系统符号文件(PDB)有多重要,这些文件都与本地的 OS 密切相关,比如,Windows 2000 打了SP补丁的话,那么必须更新系统符号文件才能进行相关调试,原来的符号文件与打补丁后的系统就会不匹配,怎么办呢? 可以通过网络来更新!象下面这样在 WinDbg 的 Symbols Path 里面输入路径:

SRV*D:/Symbols/websymbols*http://msdl.microsoft.com/download/symbols (斜体部分是你在本地保存符号文件的路径)

如果你不是通过代理上网,那么在你用 WinDbg 打开一个被调试程序后,输入 symchk 回车,WinDbg 就会自动的连到微软的网站根据你的机器的情况更新的 PDB 文件,并将它保存在上面斜体部分指定的本地路径里,这样你就可以确保你的符号文件版本和你机器上的文件版本一致。

如果你是通过代理上网那么你需要配置 IE 的连接设置。具体方法恕不赘言。

如何得到帮助

 

在命令(Command)窗口中输入.hh 命会调出帮助文件令。

 

.hh keyword

会显示关于keyword的详细命令。

 

启动Debugger

 

Windbg可以用于如下三种调试:

   1. 远程调试:你可以从机器A上调试在机器B上执行的程序。具体步骤如下:

? 在机器B上启动一个调试窗口(Debug Session)。你可以直接在Windbg下运行一个程序或者将Windbg附加(Attach)到一个进程。

?           在机器B的Windbg命令窗口上启动一个远程调试接口(remote):

.server npipe:pipe=PIPE_NAME

PIPE_NAME是该接口的名字。

? 在机器A上运行:

windbg –remote npipe:server=SERVER_NAME,pipe=PIPE_NAME

SERVER_NAME是机器B的名字。

   2. Dump文件调试:如果在你的客户的机器上出现问题,你可能不能使用远程调试来解决问题。你可以要求你的用户将Windbg附加到出现问题的进程上,然后在命令窗口中输入: .dump /ma File Name

创建一个Dump文件。在得到Dump文件后,使用如下的命令来打开它:

windbg –z DUMP_FILE_NAME

   3. 本地进程调试:你可以在Windbg下直接运行一个程序:

Windbg “path to executable” arguments     

    也可以将Windbg附加到一个正在运行的程序:

    Windbg –p “process id”  

Windbg –pn “process name”

    注意有一种非侵入(Noninvasive)模式可以用来检查一个进程的状态并不进程的执行。当然在这种模式下无法控制被调试程序的执行。这种模式也可以用于查看一个已经在Debugger控制下运行的进程。具体命令如下:

    Windbg –pv –p “process id”

Windbg –pv –pn “process name”

调试多个进程和线程

 

如果你想控制一个进程以及它的子进程的执行,在Windbg的命令行上加上-o选项。Windbg中还有一个新的命令.childdbg 可以用来控制子进程的调试。如果你同时调试几个进程,可以使用 | 命令来显示并切换到不同的进程。

在同一个进程中可能有多个线程。~命令可以用来显示和切换线程。

 

调试前的必备工作

在开始调试前首先要做的工作是设置好符号(Symbols)路径。没有符号,你看到的调用堆栈基本上毫无意义。Microsoft的操作系统符号文件(PDB)是对外公开的。另外请注意在编译你自己的程序选择生成PDB文件的选项。如果设置好符号路径后,调用堆栈看起来还是不对。可以使用lm, !sym noisy, !reload 等命令来验证符号路径是否正确。

 

Windbg也支持源码级的调试。在开始源码调试前,你需要用.srcpath设 置源代码路径。如果你是在生成所执行代码的机器上进行调试,符号文件中的源码路径会指向正确的位置,所以不需要设置源代码路径。如果所执行代码是在另一台 机器上生成的,你可以将所用的源码拷贝(保持原有的目录结构)的一个可以访问的文件夹(可以是网络路径)并将源代码路径设为该文件夹的路径。注意如果是远 程调试,你需要使用.lsrcpath来设置源码路径。

 

静态命令:

显示调用堆栈:在连接到一个调试窗口后,首先要知道的就是程序当前的执行情况k* 命令显示当前线程的堆栈。~*kb会显示所有线程的调用堆栈。如果堆栈太长,Windbg只会显示堆栈的一部分。.kframes可以用来设置缺省显示框架数。

 

显示局部变量:接下来要做通常是用dv显示局部变量的信息。CTRL+ALT+V可以切换到更详细的显示模式。关于dv要注意的是在优化过的代码中dv的输出极有可能是不准确的。这时后你能做的就是阅读汇编代码来发现你感兴趣的值是否存储在寄存器中或堆栈上。有时后当前的框架(Frame)上可能找不到你想知道的数据。如果该数据是作为参数传到当前的方法中的,可以读一读上一个或几个框架的汇编代码,有可能该数据还在堆栈的某个地址上。静态变量是储存在固定地址中的,所以找出静态变量的值较为容易。.Frame(或者在调用堆栈窗口中双击)可以用来切换当前的框架。注意dv命令显示的是当前框架的内容。你也可在watch窗口中观察局部变量的值。

 

显示类和链表dt可以显示数据结构。比如dt PEB 会显示操作系统进程结构。在后面跟上一个进程结构的地址会显示该结构的详细信息:dt PEB 7ffdf000

Dl命令可以显示一些特定的链表结构。

 

显示当前线程的错误值!gle会显示当前线程的上一个错误值和状态值。!error命令可以解码HRESULT。

 

搜索或修改内存:使用s 命令来搜索字节,字或双字,QWORD或字符串。使用e命令来修改内存。

 

计算表达式:?命令可以用来进行计算。关于表达式的格式请参照帮助文档。使用n命令来切换输入数字的进制。

 

显示当前线程,进程和模块信息!teb显示当前线程的环境信息。最常见的用途是查看当前线程堆栈的起始地址,然后在堆栈中搜索值。!peb显示当前进程的环境信息,比如执行文件的路径等等。lm显示进程中加载的模块信息。

 

 

显示寄存器的值r命令可以显示和修改寄存器的值。如果要在表达式中使用寄存器的值,在寄存器名前加@符号(比如@eax)。

 

 

显示最相近的符号ln Address。如果你有一个C++对象的指针,可以用来ln来查看该对象类型。

 

查找符号x命令可以用来查找全局变量的地址或过程的地址。x命令支持匹配符号。x kernel32!*显示Kernel32.dll中的所有可见变量,数据结构和过程。

 

 

查看lock:!locks显示各线程的锁资源使用情况。对调试死锁很有用。

 

查看handle:!handle显示句柄信息。如果一段代码导致句柄泄漏,你只需要在代码执行前后使用!handle命令并比较两次输出的区别。有一个命令!htrace对调试与句柄有关的Bug非常有用。在开始调试前输入:

!htrace –enable

然后在调试过程中使用!htrace handle_value 来显示所有与该句柄有关的调用堆栈。

 

显示汇编代码u。    

程序执行控制命令:

设置代码断点bp/bu/bm 可以用来设置代码断点。你可以指定断点被跳过的次数。假设一段代码KERNEL32!SetLastError在运行很多次后会出错,你可以设置如下断点:

    bp KERNEL32!SetLastError 0x100.

在出错后使用bl 来显示断点信息(注意粗体显示的值):

0 e 77e7a3b0     004f (0100)  0:*** KERNEL32!SetLastError

重新启动调试(.restart命令)并设置如下的断点:

bp Kernel32!SetLastError 0x100-0x4f

Debugger会停在出错前最后一次调用该过程的地方。

你可以指定断点被激活时Debugger应当执行的命令串。在该命令串中使用J命令可以用来设置条件断点:

bp `mysource.cpp:143` "j (poi(MyVar)”0n20) ''; 'g' "

上面的断点只在MyVar的值大于32时被激活(g命令

 

条件断点的用途极为广泛。你可以指定一个断点只在特殊的情况下被激活,比如传入的参数满足一定的条件,调用者是某个特殊的过程,某个全局变量被设为特殊的值等等。

 

设置内存断点:ba可以用来设置内存断点。调试过程中一个常见的问题是跟踪某些数据的变化。如下的断点:

ba w4 0x40000000 "kb; g"

可以打印出所有修改0x40000000的调用堆栈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值