cc682/NetRoc
http://netroc682.spaces.live.com/
源码模式调试
如果可以分析源代码而不是反汇编二进制代码,调试程序会更加容易一些。
当源代码是C、C++或汇编语言时,WinDbg、CDB和KD可以在调试中使用它们。
编译的要求
要进行源码调试,必须让编译器或链接器在构建二进制文件时生成符号文件(.pdb文件)。这些符号文件保存了二进制指令和源码行之间的对应关系。
另外,调试器必须能够访问源码文件,因为符号文件中并不包含实际的源代码文本。
如果这些都满足,编译器和链接器还不能对代码进行优化。如果代码经过优化,在源码调试时访问局部变量会变得很困难,有时候几乎是不可能的。如果使用Build 实用程序作为编译器和链接器,可以将MSC_OPTIMIZATION 宏设置为/Od /Oi 来避免优化。
定位符号文件和源码文件
在源码模式下调试,调试器必须能够找到源码文件和符号文件。更多信息,查看设置路径和加载文件。
开始源码调试
只要调试器拥有当前被调试线程的正确的符号和源码文件,就可以显示源码信息。
如果使用调试器启动一个新的用户模式程序,在Ntdll.dll加载程序时初始断点就会触发。由于调试器不能访问Ntdll.dll的源码文件,所以这时不能访问应用程序的源码信息。
要将程序计数器移动到程序的开始位置,可以在二进制代码入口点设置断点。在调试器命令窗口输入下面的命令。
bp main
g
之后,程序会被加载起来并在进入main函数时停止。(当然,可以使用任何入口点,而不仅仅是main。)
如果程序抛出一个异常,它会中断到调试器中。这时源码信息是可用的。但是,如果通过CTRL+C、 CTRL+BREAK或Debug | Break 命令来中断,调试器创建了一个新线程,所以不能看到源代码。
当到达具有源码文件的线程时,在调试器命令窗口中就可以执行源码调试命令了。如果使用WinDbg,Source 窗口会出现。如果已经通过点击File菜单的 Open Source File 打开了源码窗口,WinDbg一般会为该源码再创建一个新窗口。关闭先前的窗口不会对调试进程产生影响。
在WinDbg GUI中进行源码调试
如果使用WinDbg,当程序计数器运行到调试器拥有源码信息的代码时,一个源码窗口会出现。
WinDbg为用户或它自己打开的每个源文件显示一个源码窗口。关于该窗口的文本属性的更多信息,查看源码窗口。
之后可以单步执行程序、执行到断点或执行到光标。关于单步和跟踪命令的更多信息,查看控制目标。
源码模式调试时,如果单步执行程序,合适的源码窗口会移动到前台。因为应用程序执行中也会调用到一些Microsoft Windows函数,这时调试器可能会将反汇编窗口移到前台(因为调试器不能访问这些函数的源码)。当程序计数器又返回到已知的源码文件,相关的源码窗口又会被激活。
控制应用程序执行时,WinDbg将源码窗口和汇编窗口中所在的位置用绿色高亮。设置了断点的行为红色(启用的断点)、黄色(禁用的断点)或紫色(如果当前程序计数器是断点位置)。
在WinDbg中激活源码调试,可以使用L+t 命令、点击Debug 菜单的Source Mode 或在工具栏点击Source mode on 按钮()。
源码模式激活时,状态栏的ASM指示器会变为灰色。
源码模式下单步执行某个函数时,可以查看或修改它的任何局部变量的值。更多信息,查看读写内存。
调试器命令窗口中的源码调试
如果使用CDB,则没有单独的源码窗口。但是,在单步执行源码时还是可以查看运行的情况。
使用CDB源码调试之前,必须通过.lines (Toggle Source Line Support) 命令加载源码行符号,或者使用-lines 命令行选项启动调试器。
如果使用了l+t 命令,则每次单步执行一行源码。使用L-t 来一次执行一条汇编指令。如果使用WinDbg,该命令和选中或清除Debug 菜单上的Source Mode 或使用工具栏菜单的效果一样。
l+s命令在提示符显示当前的代码行和行号。如果只想显示行号,使用l+l 。
如果使用l+o 和l+s,在单步执行时只会显示源码行。程序计数器、汇编码和寄存器信息都不会显示。这种显示类型使得可以快速通过源码来单步调试而不会看到除了源码之外的东西。
用lsp (Set Number of Source Lines) 命令来指定单步或者执行程序时显示的源码行数。
下面的命令序列是一种单步执行源码文件的有效方式。
.lines 启用源码行信息
bp main 设置初始断点
l+t 按源码行进行单步
l+s 命令窗口中显示源码行
g 运行程序,直到"main"函数
pr 执行一行源码,并将寄存器切换为不显示
p 执行一行源码
因为ENTER 会重复最后一条命令,所以现在可以通过ENTER键来单步调试程序了。每一步都会有源码行、内存偏移和汇编代码显示出来。
关于反汇编显示的更多信息,查看汇编模式调试。
当显示汇编代码时,在每行右边末尾会显示出任何访问到的内存位置。用d* (Display Memory) 和e* (Enter Values) 命令来查看或修改这些位置的值。
如果需要查看每条汇编代码来确认偏移或内存信息,使用l-t 来以汇编指令单步而不是用源码。源码行信息仍然可以显示出来。每行源码和一条或多条汇编指令对应。
所有这些命令在WinDbg和CDB中都可用。可以使用这些命令来在WinDbg的调试器命令窗口查看源码,而不是在源码窗口中。
源码行和偏移
使用表达式求值器来确定和指定源码行关联的偏移位置也可以进行源码调试。
下面的命令显示一个内存偏移。
? `[[module!]filename][:linenumber]`
如果省略filename,调试器会搜索和当前程序计数器位置符合的代码。
不管当前使用的基数是什么,如果没有添加0x前缀,调试器认为linenumber 是10进制数。如果省略了linenumber ,表达式的值为和源码文件关联的可执行文件初始地址。
CDB中只有在使用了.lines 命令或-lines 命令行选项来加载源码行符号时才能识别该语法。
该技术非常有用,因为不管当前的程序计数器指向什么地方都可以使用。例如,可以使用下面这样的命令来预先设置断点。
bp `source.c:31`
更多信息,查看源码行语法和使用断点。
源码模式下的单步和跟踪
以源码模式调试时,单行代码种可能有多个函数调用。不能使用p和t来分开这些调用。
例如,在下面的命令中,t命令会单步进入GetTickCount 和printf ,而p命令会单步步过两个调用。
printf( "%x\n", GetTickCount() );
如果在跟踪进入其他调用时想步过某个调用,用.step_filter (Set Step Filter) 来指定步过哪些调用。
可以使用_step_filter 来跳过框架函数(例如,对微软基本类库(Microsoft Foundation Classes,MFC)或活动模板库(ATL)的调用)。
调试BIOS代码
BIOS代码不是从一般汇编代码构建出来的,所以它要求一些不同的调试技术。
在x86处理器上,BIOS使用16位代码。需要使用ux (Unassemble x86 BIOS) 命令来反汇编这些代码。要显示Intel多处理器规范(Intel Multiprocessor Specification (MPS))的信息,使用!mps 扩展命令。
在Itanium处理器上,BIOS使用EFI代码(32位)和实模式代码(16位)。要反汇编实模式代码,使用ur (Unassemble Real Mode BIOS)。调试器不能反汇编EFI代码。
如果调试ACPI BIOS代码,前面的命令不能使用,因为ACPI BIOS使用ACPI机器语言(ACPI Machine Language (AML))编写。要反汇编这些代码,需要使用!amli u。关于这类调试的更多信息,查看ACPI调试。
调试多个目标
可以同时调试多个dump文件或者活动的用户模式程序。每个目标可以包含一个或多个进程,每个进程可以包含一个或多个线程。
这些目标也被分组进系统中。将一组目标分组成系统来便于区分和操纵。系统的定义如下:
- 每个内核模式或用户模式dump文件是一个单独的系统。
- 调试在不同计算机上的活动的用户模式程序时(使用进程服务器,例如Dbgsrv),,每个应用程序是一个单独系统。
- 在本地计算机上调试活动的用户模式程序时,这些程序被结合成一个系统。
当前正在进行调试的系统称为当前系统或活动系统。
获得多个目标
第一个调试目标通过通常的方式获得。
要调试更多的活动用户模式程序,可以使用.attach (Attach to Process) 或.create (Create Process) 命令,然后是g (Go)命令。
使用.opendump (Open Dump File) 来调试更多的dump文件,后跟g (Go) 命令。当调试器启动之后也可以打开多个dump文件。在该命令后使用多个-z 开关,每个后接一个不同的文件名来调试多个dump文件。
也可以使用上面的命令来调试另一个系统中的进程。必须在每个系统上都打开一个进程服务器,然后对.attach 和.create 命令加上-premote 参数来指定需要的进程服务器。如果再使用.attach 或.create 命令并且不指定-premote 参数,调试器在当前系统附加或创建一个进程。
操纵系统和目标
当调试开始时,当前系统是调试器最近附加到的系统。如果发生异常,当前系统会切换到发生异常的那个系统。
使用.kill (Kill Process) 命令来关闭一个目标并继续调试其他目标。在Microsoft Windows XP和之后版本的Windows中,也可以使用 .detach (Detach from Process) 命令或WinDbg的Debug | Detach Debuggee 菜单命令。这些命令停止对目标的附加,但是会让目标继续运行。
使用下面一些方法来控制多个系统的调试:
使用这些命令来选择当前系统并使用标准命令来选择当前进程和线程,可以决定显示内存和寄存器的命令的上下文。
但是,不能将这些进程分开执行。g (Go) 命令总是使得所有目标一起开始执行。
注意 建议不要同时调试活动目标和dump目标,因为对于每种调试类型命令的行为不一样。例如,当当前系统是一个dump文件时使用g (Go)命令,调试器可以开始执行,但是不能中断到调试器,因为调试dump文件时不能识别中断命令。
结束调试会话
在调试会话中时可以退出任何Microsoft Windows调试器。三个调试器退出的方法有一些不同点。WinDbg可以在不结束自己的情况下结束调试会话。
如果想避免意外结束调试会话,可以使用.quit_lock (Prevent Accidental Quit) 命令。
退出 CDB
可以使用q (Quit) 命令退出CDB。该命令同时关闭被调试的程序。
在Windows XP和之后版本的Windows中, qd (Quit and Detach) 命令停止CDB对目标程序的附加,退出调试器,并让目标程序继续运行。如果启动调试器时使用了-pd命令行选项,因为任何原因产生的会话结束都会造成停止附加。(该技术使得-pd 在调试不希望被结束的敏感程序如CSRSS时特别有用。)
在Windows NT和Windows 2000中,qd 命令和-pd 选项会产生一条警告消息,并且不起作用。这些系统上不允许调试器停止对目标程序的附加。 (如果目标一定不能被结束,应该使用非侵入式的调试,并且使用Q命令退出。更多信息,查看附加到运行中的进程(用户模式)。)
如果调试器停止了响应,可以通过按下CTRL+B 再按下ENTER来退出。这种方法是退出的后备机制。它强制性的结束调试器,类似从任务管理器结束进程或关闭窗口。
退出 KD
有两种方法用于退出KD。
使用CTRL+B退出调试器不会清除内核模式断点。但是附加一个新的内核调试器上去会清除这些断点。
进行远程调试时,使用q结束调试会话。CTRL+B退出调试器,但是保持会话激活。这种情况使得另一个调试器可以连接到该会话。
如果q命令不起作用,在主控机上按下CTRL+R 再按下ENTER,再尝试使用q命令。如果还是无效,则只能使用CTRL+B来退出调试器。
退出WinDbg
可以通过在File菜单点击Exit 或按下ALT+F4退出WinDbg。
如果在进行用户模式调试,这些命令会关闭调试的程序,除非在启动调试器时使用了-pd命令行选项。
如果在进行内核模式调试,目标机会保持它的当前状态。所以这样可以让目标保持运行或冻结。(如果让目标冻结,之后的任何内核调试器的连接都可以恢复调试。)
不退出的情况下停止用户模式会话
要停止用户模式调试会话将调试器返回到静止状态,并关闭目标程序,可以使用下面一些方法:
要停止用户模式调试会话,将调试器返回到静止模式,并让目标程序重新运行起来,可以使用下面方法:
在Windows XP和之后的Windows中可以停止对目标的附加。在Windows NT和Windows 2000中,qd 命令和-pd选项产生一条警告信息,并且不起作用。这些操作系统不允许调试器停止对目标的附加。如果目标一定不能被结束,应该使用非侵入式调试。更多信息,查看非侵入式调试(用户模式)。
停止用户模式调试会话并将调试器返回到静止模式,但保持目标程序在调试状态,可以使用下面的方法:
关于重新附加到目标的更多信息,查看重新附加目标程序。
不退出的情况下停止内核模式会话
停止内核模式调试会话,将调试器返回到静止模式并保持目标机被冻结,可以使用如下方法:
当WinDbg会话结束时,会提示保存当前会话的工作空间,并且WinDbg返回到静止模式。这时,可以使用任何开始的方法。即可以开始调试一个运行中的进程、创建新进程、附加到目标机或连接到远程调试会话。关于这些选择的更多信息,查看启动调试器。