cc682/NetRoc
http://netroc682.spaces.live.com/
通过Remote.exe的远程调试
通过remote.exe进行的远程调试包括在远程计算机上运行调试器和在本地计算机上运行的remote.exe工具。
远程计算机必须运行在Windows 2000和之后的系统中调试器才能正常工作。本地计算机可以运行任何Windows操作系统。
注意 由于remote.exe只能和控制台应用程序一起工作,所以不能用于控制WinDbg。
本节包含:
Remote.exe 实用程序
开始Remote.exe会话
Remote.exe批处理文件
Remote.exe实用程序
remote.exe三实用程序是一个用于在远程计算机上运行命令行程序的服务器/客户端通用工具。
Remote.exe 依靠命名管道,为以STDIN和STDOUT作为输入输出的程序提供远程网络访问。用户在网络中的另一台计算机上,或者通过直接拨号调制解调器连接,可以查看远程会话或输入命令。
该使用程序有很多使用方法。例如,在开发软件时,可以使用远程计算机的处理器和资源来编译代码,而在本机上进行其他工作。也可以通过remote.exe来为一个需要多台计算机的特殊任务分发处理需求。
注意remote.exe没有安全认证,允许任何运行remote.exe客户端的人连接到remote.exe服务器上。这使得运行Remote.exe服务器的机器帐号对任何连接过来的人都是开放的。
开始Remote.exe会话
有两种方法可以用来和KD、CDB一起启动remote.exe会话。只有第二种方法可以被NTSD使用。
自定义命令提示符窗口
Remote.exe客户端和Remote.exe服务器运行在命令提示符窗口中。
要准备好使用远程会话,需要自定义该窗口来增强可用性。打开一个命令提示符窗口。在标题栏右键点击并选择属性(Properties)。选择布局(Layout)选项卡。在"屏幕缓冲区大小"("Screen Buffer Size")中的宽度(Width)中填入90,在高度(Height)中填入4000到9000的值。这样会启用内核调试器的远程会话的滚动条。
如果要改变命令提示符窗口的大小,可以改变"窗口大小" ("Windows Size")中的高度和宽度。选择选项(Options)选项卡,启用编辑选项(Edit Options)中的快速编辑模式和插入模式。这使得可以在命令提示会话中剪切和粘贴信息。点击确定(OK)来应用这些修改。选择这些选项以在以后的所有提示符会话中使用。
启动Remote.exe服务器:第一种方法
启动Remote.exe服务器的常规语法如下:
remote /s "Command_Line" Unique_Id [/f Foreground_Color] [/b Background_Color]
使用和下面例子同样的方式来在远程计算机上启动KD或CDB:
remote /s "KD [options]" MyBrokenBox
remote /s "CDB [options]" MyBrokenApp
这样会在命令提示符窗口中启动Remote.exe服务器,并启动调试器。
可以用该方法来直接启动NTSD,因为NTSD的进程运行在另一个不同的窗口中。
启动Remote.exe服务器:第二种方法
还有另外一种用于启动Remote.exe服务器的方法。该方法先启动调试器,然后再使用.remote (Create Remote.exe Server) 命令启动服务器。
由于.remote 命令在调试器启动之后才输入,所以该方法在KD、CDB和NTSD上的使用是相同的。
这里是一个例子。首先用常规方式启动调试器:
KD [options]
调试器运行起来之后,使用.remote命令:
.remote MyBrokenBox
这样也会创建ID为"MyBrokenBox"的Remote.exe服务器,和使用第一种方法的结果一样。
这种方法的一个好处是不用预先决定是否使用远程调试。当使用控制台调试器进行调试时决定让位于其他地方的人来接管调试,可以用.remote命令来让他们连接到会话上。
启动Remote.exe客户端
启动Remote.exe客户端的常规语法如下:
remote /c ServerNetBIOSName Unique_ID [/l Lines_to_Get] [/f Foreground_Color] [/b Background_Color]
例如,如果前面描述过的"MyBrokenBox" 会话在网络名为"Server2"的本地主机上已经启动起来,就可以使用该命令连接到它:
remote /c server2 MyBrokenBox
网络上任何具有适当权限的人都可以连接到该调试会话上,只要他们知道机器名和会话ID。
输入命令
通过Remote.exe客户端输入的命令会传输到Remote.exe服务器。可以在客户端输入任何命令,如同直接在调试器中输入一样。
在Remote.exe客户端上退出remote.exe会话,输入@Q 命令。这会让Remote.exe服务器和调试器继续运行。
在Remote.exe服务器上输入@K 命令结束服务器会话。
Remote.exe 批处理文件
作为更详细一些的通过remote.exe进行远程调试的例子,下面假设一次三机内核调试中的一台本地主机的情形:
- 调试需要通过COM2上的mull-modem电缆进行。
- 符号文件在c:\winnt\symbols文件夹下。
- c:\temp 下面创建了一个名为debug.log的日志文件。
日志文件中包含了调试会话中能在调试器屏幕中看到的所有内容。所有调试人员的输入和目标系统的内核调试器的输出都会记入日志文件。
在本地主机上用于运行一个调试会话的简单批处理文件如下:
set _NT_DEBUG_PORT=com2
set _NT_DEBUG_BAUD_RATE=19200
set _NT_SYMBOL_PATH=c:\winnt\symbols
set _NT_LOG_FILE_OPEN=c:\temp\debug.log
remote /s "KD -v" debug
注意 如果该批处理文件和Remote.exe不在同一个目录下,并且Remote.exe不在系统路径列出的目录中,批处理中需要指定Remote.exe的全路径。
该批处理文件运行后,任何连接到该本地主机的网络的Windows计算机都可以使用下面的命令连接到调试会话:
remote /c computername debug
computername 是本地主机的NetBIOS名。
cc682/NetRoc
http://netroc682.spaces.live.com/
远程调试
本节包含:
选择最好的远程调试方法
通过调试器的远程调试
通过Remote.exe的远程调试
其他远程调试方法
用户模式远程调试需要两台计算机:客户端和服务器。服务器是用于运行被调试的应用程序和用户模式调试器的机器。客户端是用于远程控制调试会话的机器。
远程内核调试需要三台计算机:客户端、服务器和目标机。目标机是被调试的机器。服务器是运行调试器的机器,它和目标机位于同一个物理位置。客户端是用于远程控制调试会话的机器。查看设置内核模式调试的硬件获得关于服务器和目标机之间的连接的更多信息。
选择最好的远程调试方法
有两种主要的远程调试方法,以及一些额外方法和很多组合方法。
这里有一些主题用于帮助选择最合适的调试技术。
-
通过调试器的远程调试常常是最好的方法。如果有一台服务器和一台客户端,并且他们可以自由的连接到对方、在两台机器都安装了同样的调试器二进制文件,并且调试的技术人员可以和服务器房间里的另一个人直接联系,那么这是最推荐的一种方法。
客户端和服务器可以运行于Microsoft Windows NT、Windows 2000或之后版本的Windows。它们不需要运行相同版本。如果连接使用TCP协议,客户端和服务器还可以运行Windows 9x/Me。
如果客户端不能连接到服务器,但是服务期可以连接到客户端,可以通过使用clicon 参数来使用反向连接来进行远程调试。
-
如果客户端没有调试器二进制文件的拷贝,就必须使用remote.exe方法。如果运行Windows 9x/Me并不能使用TCP协议时也需要使用该方法。
-
一个进程服务器(process server)或一个KD连接服务器(KD connection server)在调试技术人员不能和服务器所在房间的人交流时使用。所有实际的调试工作都是由客户端(称为灵巧客户端(smart client))进行的;这样就不需要有一个人在服务器旁。
进程服务器用于用户模式调试;KD连接服务器用于内核模式调试。除此之外,它们的行为有很多相似的地方。
该方法在服务器不能处理很重的进程负荷,或运行客户端的技术人员需要访问一些不能由服务器访问的机密的符号或源文件时非常有用。但是,该方法没有通过调试器进行的远程调试那么快和有效。该方法不能用于调试dump文件。
查看进程服务器(用户模式)和KD连接服务器(内核模式)获得详细信息。
-
转发器是用于在两台计算机间传送数据的轻量级的代理服务器。在使用通过调试器的远程调试或进程服务器时,可以在客户端和服务器间加入一个转发器。
当客户端和服务器不能直接通信,但是都可以访问另外一台计算机时,转发器可能是必须的。同样可以对转发器使用反向连接。也可以一次使用两个转发器,但是需要这样的情况极少。
查看转发器获得详细信息。
- 也可以从内核调试器控制CDB(或NTSD)。这又是另一种形式的远程调试。查看通过内核调试器控制用户模式调试器获取详细信息。
所有这些方法都可以有所变化。
可以将几台计算机一起使用以获得多种传输方法的优点。可以创建复杂的传输序列以顾及到技术人员的位置、符号所在的位置、以及是否有防火墙阻止某些方向上的连接。查看高级远程调试获得一些例子。
甚至可以在单台计算机上进行远程调试。例如,启动一个低权限的进程服务器,然后使用一个高权限的灵巧客户端连接上去可能会很有用。另外,在Windows 2000 终端服务器计算机上,可以从一个会话调试另外一个会话。
通过调试器的远程调试
直接通过调试器进行的远程调试通常是用于远程调试的最好最容易的方法。
该方法使用在不同位置运行的两个调试器。实际进行调试的一个称为调试服务器。在另外一边用于控制调试会话的调试器称为调试客户端。
两台计算机不需要运行同样版本的Windows;它们可以运行Windows NT、Windows 2000或之后版本的Windows。实际的调试器不需要相同,一个WinDbg调试客户端可以连接到一个CDB调试服务器,等等。
但是,两台计算机上的调试器二进制文件最好是同一个Windows调试工具包版本中的,或者至少都是最新版本的。
要设置远程会话,需要首先设置调试服务器,然后再激活调试客户端。任意数量的调试客户端可以连接到调试服务器。一个单独的调试器可以同时将自己分成几个调试服务器,以适应不同的连接类型。
但是,单个调试器不能同时作为调试客户端和调试服务器。如果要连接三个调试器,查看其他远程调试方法。
本节包含:
激活调试服务器
搜索调试服务器
激活调试客户端
客户端和服务器示例
控制远程调试会话
激活调试服务器
有两种方法用于激活调试服务器。可以通过使用-server 命令行选项激活。也可以通过在启动调试器之后使用.server 命令激活。
调试器支持几种传输协议:命名管道(NPIPE)、TCP、COM端口、安全pipe(SPIPE)和安全套接字层(SSL)。SPIPE和SSL协议仅在Windows 2000和之后的系统中支持。如果使用Windows 9x/Me,必须使用TCP协议,并且调试服务器必须使用-noio 命令行选项启动。
启动调试器的常规语法决定于使用的协议。有些面一些选择:
Debugger -server npipe:pipe=PipeName[,hidden][,password=Password] [-noio] [Options]
Debugger -server tcp:port=Socket[,hidden][,password=Password][,ipversion=6] [-noio] [Options]
Debugger -server tcp:port=Socket,clicon=Client[,password=Password][,ipversion=6] [-noio] [Options]
Debugger -server com:port=COMPort,baud=BaudRate,channel=COMChannel[,hidden][,password=Password] [-noio] [Options]
Debugger -server spipe:proto=Protocol,{certuser=Cert|machuser=Cert},pipe=PipeName[,hidden][,password=Password] [-noio] [Options]
Debugger -server ssl:proto=Protocol,{certuser=Cert|machuser=Cert},port=Socket[,hidden][,password=Password] [-noio] [Options]
Debugger -server ssl:proto=Protocol,{certuser=Cert|machuser=Cert},port=Socket,clicon=Client[,password=Password] [-noio] [Options]
另一种开始调试服务器的方法是在启动调试启之后使用.server (Create Debugging Server) 命令。该命令的语法和命令行相似:
.server npipe:pipe=PipeName[,hidden][,password=Password]
.server tcp:port=Socket[,hidden][,password=Password][,ipversion=6]
.server tcp:port=Socket,clicon=Client[,password=Password][,ipversion=6]
.server com:port=COMPort,baud=BaudRate,channel=COMChannel[,hidden][,password=Password]
.server spipe:proto=Protocol,{certuser=Cert|machuser=Cert},pipe=PipeName[,hidden][,password=Password]
.server ssl:proto=Protocol,{certuser=Cert|machuser=Cert},port=Socket[,hidden][,password=Password]
.server ssl:proto=Protocol,{certuser=Cert|machuser=Cert},port=Socket,clicon=Client[,password=Password]
上面命令中的参数有下面一些可能的值:
Debugger
可以是KD、 CDB、NTSD或WinDbg。
PipeName
使用NPIPE或SPIPE协议时,PipeName 是用作管道名的字符串。每个管道名都必须标识一个唯一的调试服务器。如果试图重复使用管道名,会收到一条错误信息。PipeName 不能包含空格或引号。PipeName 可以包含数字的printf 形式的格式化代码,如%x 或%d。调试器使用自己的进程ID来替换它,第二个这样的代码以调试器的线程ID替换。
Socket
当使用TCP或SSL协议时,Socket 指定套接字端口号。也可以用一个冒号来指定端口范围(例如port=2:7)。调试器会检查该范围内的端口是否可用。如果找到了一个空闲端口并且没有出现错误,调试服务器会被创建起来。调试客户端需要指定用于连接到服务器的实际端口号。(如果使用了clicon参数来进行反向连接,调试客户端也可以按这种方式指定端口范围,调试服务器必须指定实际使用的端口。)
clicon=Client
当使用TCP或SSL协议并指定clicon参数时,一个反向连接会被打开。这意味着调试服务器会试图连接调试客户端,而不是由客户端来发起连接。这在当防火墙阻止通常方向上的连接时会比较有效。客户端指定调试客户端所在的计算机的网络名或IP地址。两个反斜杠(\\)是可选的。
如果使用服务器来寻找特定客户端的方式,就不能将多个客户端连接到一个服务器。如果连接被拒绝或丢失,必须重起服务器的连接。当另一个调试器显示所有活动服务器时,反向连接服务器不会出现。
注意 如果使用了clicon,最好在调试服务器创建之前就启动调试客户端,虽然常规顺序(服务器在客户端之前启动)也可以使用。
COMPort
当使用COM协议时,COMPort 指定所使用的COM端口。前缀"COM" 是可选的—如, "com2" 和"2" 都可以。
BaudRate
使用COM协议时,BaudRate 指定连接所使用的波特率。硬件支持的所有波特率都可以使用。
COMChannel
如果使用COM协议,COMChannel 指定和调试客户端通信所使用的COM频道。它可以是0到254之间的任意值(包含0和254)。单个COM端口可以通过使用不同的频道号来支持多个连接。 (这和调试电缆使用COM的情况不同—那种情况下COM端口不能使用频道。)
Protocol
(Windows 2000 和之后的系统) 如果使用SSL或SPIPE,Protocol 指定安全频道(S-Channel)协议。它可以是tls1、pct1、ssl2或ssl3。
Cert
(Windows 2000和之后的系统) 如果使用SSL或SPIPE协议,Cert 用于指定证书。它可以是证书名或证书的指纹(由证书管理机构提供的16进制数字字符串)。如果使用certuser=Cert 形式的语法,调试器会在系统中查找证书(默认存储位置)。如果使用machuser=Cert 的语法,调试器在机器存储中查找证书。指定的证书必须支持服务器验证。
hidden
避免服务器在另一个调试器显示所有活动服务器时被显示出来。
password=Password
要求客户端在连接到调试会话时提供指定的密码。Password 可以是任何字母和数字。
警告 在TCP、NPIPE或COM协议下使用密码仅提供低程度的保护,因为密码不会被加密。当密码和SSL或SPIPE协议一起使用时,会被加密。如果需要安全的远程会话,必须使用SSL或SPIPE协议!
ipversion=6
(仅Debugging Tools for Windows 6.6.07 和更老的版本) 用TCP连接到Internet时强制调试器使用IPV6而不是V4。在Windows Vista和之后版本中,调试器会尝试默认使用IP v6
,所以没有必要使用该选项。
-noio
如果调试器使用-noio 选项一起启动,通过服务器就本身不能进行输入输出了。调试器仅接收来自调试客户端的输入(加上任何初始命令或通过-c命令行选项指定的命令脚本)。所有输出都被定向到调试客户端。-noio 选项仅在KD、CDB和NTSD可用。如果用NTSD 作为服务器,则不会创建控制台窗口。
Options
任何更多的命令行参数都可以放在这里。完整列表查看命令行选项。
可以使用.server 命令来使用不同的协议选项启动多个服务器。这使得不同类型的调试客户端可以加入到会话中。
搜索调试服务器
可以使用KD或CDB和-QR 命令行选项来获得一个网络服务器上的所有可用的调试服务器列表。
这个列表中可能包含已经不存在但是没有被完全关闭的服务器—连接到这样的服务器会产生一条错误信息。该列表也包含进程服务期和KD连接服务器。每种情况下的服务器类型都会被指明。
使用语法如下:
Debugger -QR \\Server
Debugger 可以是KD或CDB—两种情况下输出都一样。反斜杠(\\)开头的Server是可选的。
在WinDbg中,可以使用Connect to Remote Debugger Session 对话框来查看可用服务器列表。查看File | Connect to Remote Session 获得详细信息。
对于每个调试服务器都会按照用于启动调试客户端的完整的连接字符串形式列出(除了密码会用星号替代之外) 。
激活调试客户端
一旦调试服务器被激活,就可以在另外一台计算机上启动一个调试客户端连接到该调试会话上。
有两种方法可以启动调试客户端:使用 -remote 命令行选项,或者使用WinDbg图形界面。
客户端使用的协议必须和服务器匹配。开始调试客户端的一般语法由使用的协议决定。有下面一些选择:
Debugger -remote npipe:server=Server,pipe=PipeName[,password=Password]
Debugger -remote tcp:server=Server,port=Socket[,password=Password][,ipversion=6]
Debugger -remote tcp:clicon=Server,port=Socket[,password=Password][,ipversion=6]
Debugger -remote com:port=COMPort,baud=BaudRate,channel=COMChannel[,password=Password]
Debugger -remote spipe:proto=Protocol,{certuser=Cert|machuser=Cert},server=Server,pipe=PipeName[,password=Password]
Debugger -remote ssl:proto=Protocol,{certuser=Cert|machuser=Cert},server=Server,port=Socket[,password=Password]
Debugger -remote ssl:proto=Protocol,{certuser=Cert|machuser=Cert},clicon=Server,port=Socket[,password=Password]
要使用图形界面连接到远程调试会话,WinDbg必须在静止模式下—它必须以没有命令行参数的形式启动,或必须中止已有的调试会话。选择File | Connect to Remote Session 菜单命令或按下 快捷键。当Connect to Remote Debugger Session对话框出现后,在 Connection string 文本框中输入下面的字符串之一:
npipe:server=Server,pipe=PipeName[,password=Password]
tcp:server=Server,port=Socket[,password=Password][,ipversion=6]
tcp:clicon=Server,port=Socket[,password=Password][,ipversion=6]
com:port=COMPort,baud=BaudRate,channel=COMChannel[,password=Password]
spipe:proto=Protocol,{certuser=Cert|machuser=Cert},server=Server,pipe=PipeName[,password=Password]
ssl:proto=Protocol,{certuser=Cert|machuser=Cert},server=Server,port=Socket[,password=Password]
ssl:proto=Protocol,{certuser=Cert|machuser=Cert},clicon=Server,port=Socket[,password=Password]
另外,也可以使用Browse 来定位激活的调试服务器。查看File | Connect to Remote Session 获取详细信息。
上面命令中的参数有下面这些可能的值:
Debugger
该参数不一定要和调试客户端(疑为调试服务器)使用的一样—WinDbg、KD和CDB都可以用于通过调试器进行的远程调试。
Server
这是创建调试服务器的计算机的网络名字或IP地址。两个反斜杠(\\) 在命令行中是可选的,但是在WinDBg中不能使用。
PipeName
如果使用NPIPE或SPIPE,PipeName 是服务器创建时设定的管道名。
Socket
如果使用TCP或SSL协议,Socket 和服务器创建时指定的套接字段口号一样。
clicon
指定调试服务器将试图通过反向连接来连接到客户端。客户端当且仅当服务器也使用clicon时才能使用clicon。大多数情况下,使用反向连接时,调试客户端应比服务器先启动。
COMPort
如果使用COM协议,COMPort 指定使用的COM端口。前缀"COM"是可选的—例如, "com2" 和"2"都可以使用。
BaudRate
如果使用COM协议,BaudRate 必须和服务器创建时选择的波特率匹配。
COMChannel
如果使用COM协议,COMChannel 必须和服务器创建时选择的频道号匹配。
Protocol
(Windows 2000 和之后的系统) 如果使用SSL或SPIPE协议,Protocol 必须和服务器创建时选择的安全协议匹配。
Cert
(Windows 2000 和之后的系统) 如果使用SSL或SPIPE协议,必须和服务器创建时指定一样的certuser=Cert 或machuser=Cert 参数。
Password
如果服务器创建时使用了密码,创建调试客户端时必须使用Password 。它必须和原始密码匹配。密码是大小心敏感的。如果密码错误,会出现"Error 0x80004005." 错误。
ipversion=6
(仅Debugging Tools for Windows 6.6.07 和之前的版本) 强制调试器在使用TCP连接到Internet时使用IP v6而不是v4。在Windows Vista和之后的版本中,调试器尝试默认使用IP v6,所以不再需要该选项。
用于开始新调试会话的命令行选项(如-p)不能用在调试客户端上,而只能由服务器使用。配制选项(如-n)在客户端或服务器上都可以使用。
客户端和服务器示例
假设一个人在一台名为\\BOX17的计算机上运行一个应用程序。该程序有一些问题,但是调试技术人员在其他地方。
第一个人使用CDB在\\BOX17上设置调试服务器。目标应用程序的进程ID为122。使用TCP协议,套接字段口号为1025。用如下命令启动服务器:
E:\Debugging Tools for Windows> cdb -server tcp:port=1025 -p 122
在另一台计算机上,技术人员决定使用WinDbg作为调试客户端。它可以用如下命令启动:
G:\Debugging Tools> windbg -remote tcp:server=BOX17,port=1025
这里有另一个例子。该例子中使用NPIPE,调试器使用CDB而不是WinDbg。第一个用户选择一个管道名。它可以是任何字母或数字—这个例子中为"MainPipe"。使用如下命令启动调试服务器:
E:\Debugging Tools for Windows> cdb -server npipe:pipe=MainPipe -v winmine.exe
技术人员不确定使用了什么名字,所以他/她在网络上查询管道名:
G:\Debugging Tools> cdb -QR \\BOX17
Servers on \\BOX17:
Debugger Server - npipe:Pipe=MainPipe
Remote Process Server - npipe:Pipe=AnotherPipe
显示了两个管道。但是,只有一个是调试服务器—另一个是进程服务器,所以不是需要的那个。所以MainPipe肯定是正确的名字。然后可以使用下面的命令来开始调试客户端:
G:\Debugging Tools> cdb -remote npipe:server=BOX17,pipe=MyPipe
使用安全服务器
这里有一个使用安全服务器的例子。该服务器使用安全套接字层,并使用TLS1作为S-Channel 协议。调试器会在机器存储中查找证书。证书是用它的16进制特征码指定的。
D:\> cdb -server "ssl:proto=tls1,machuser=ab 38 f7 ae 13 20 ac da 05 14 65 60 30 83 7b 83 09 2c d2 34,port=1234" notepad.exe
控制远程调试会话
当远程会话开始之后,调试服务器和调试客户端都可以输入命令。如果有多个客户端,他们也都可以输入命令。当按下ENTER时,命令会传输到调试服务器并执行。
当用户输入命令后,所有用户都会看到命令输出。如果是从调试客户端输入的命令,所有其他用户都会看到一个标识表明是谁使用了该命令。在调试服务器上输入的命令不会有这个前缀。
某个用户执行了一条命令之后,其他通过KD或CDB连接的用户看不到新的命令提示符。另一方面,WinDbg用户会一直看到调试器命令窗口底部面板的提示符,即使调试器引擎正在运行。遇到这样的问题都不用在意,任何用户在任何时候都可以输入命令,引擎会按照接收道命令的顺序来执行。
通过WinDbg界面进行的操作也会在调试服务器中被执行。
用户之间的通信
每当有新的调试客户端连接到会话中,所有其他用户都会看到该客户端连接的信息。一个客户端断开连接不会显示信息。
.clients (List Debugging Clients)命令会列出当前连接到调试会话的所有客户端。
.echo (Echo Comment) 命令在一个用户发送信息到另一个用户时很有用。
WinDbg 工作空间
当WinDbg用作调试客户端时,它的工作空间只保存通过图形界面设置的值。通过调试器命令窗口进行的改变不会保存。 (这保证了只有本地客户端进行的改变会被体现出来,因为调试器命令窗口和调试服务器一样接收来自所有客户端的输入。)
文件路径
符号路径、可执行映像路径和扩展DLL路径都被解释为调试服务器上的Windows调试工具包安装目录的相对路径。
当WinDbg作为调试客户端时,它也拥有自己的本地源码路径(local source)。所有源码相关的命令都访问本地计算机上的源码文件。因此,任何需要使用源码命令的客户端和服务器上都必须设置适合的路径。
查看设置路径和加载文件获取关于这些路径的详细信息。
这种多路径系统使得调试客户端可以使用源码相关的命令而不需要和其他客户端或服务器实际共享源文件。这在某个用户需要访问私有或机密代码时很有用。
删除调试服务器
.endsrv (End Debugging Server)命令可以用来终止一个调试服务器。如果有多个调试服务器,可以停止一些并保留另外一些。
终止一个服务器可以避免之后的任何客户端附加到它上面。这样不会中断当前已连接到该服务器的客户端。
退出调试器和终止会话
要退出一个调试客户端而不终止服务器,必须在该客户端上执行一条命令。如果客户端是KD或CDB,使用CTRL+B 键退出。如果用脚本来运行KD或CDB,使用.remote_exit (Exit Debugging Client)。如果客户端是WinDbg,选择File 菜单的Exit 来退出。
要终止整个会话并退出调试服务器,使用q (Quit) 命令。该命令可在任何服务器或客户端输入,并且它会终止整个会话的所有用户。
cc682/NetRoc
http://netroc682.spaces.live.com/
调试器扩展
CDB、KD和WinDbg允许使用调试器扩展命令。这些扩展使得三个Microsoft调试器拥有强大的功能和适应性。
调试器扩展命令的使用很类似标准命令。但是,内置的调试器命令是由调试器的二进制文件本身实现的,而扩展命令是由调试器之外的DLL实现的。
这样就允许为特殊需要编写新的调试器命令。另外,调试工具本身带了很多调试器扩展DLL。
本节包含:
加载调试器扩展DLL
使用调试器扩展命令
编写新的调试器扩展
关于特定扩展命令的更多信息,查看调试器扩展命令参考节。
加载调试器扩展DLL
有很多办法用于加载调试器扩展DLL、控制默认调试器扩展DLL和默认的调试器扩展路径:
也可以通过完整的!module.extension语法在第一次运行某个扩展DLL中的命令时加载它。查看使用调试器扩展命令获得详细信息。
所使用的扩展DLL必须和目标机的操作系统相匹配。Windows调试工具包自带的扩展DLL都放在安装路径的不同子目录中:
- nt4fre 目录包含用在Microsoft Windows NT 4.0发行版中的扩展。
- nt4chk 目录包含使用在Windows NT 4.0调试版中的扩展。
- w2kfre 目录包含使用在Microsoft Windows 2000发行版中的扩展。
- w2kchk目录包含使用在Windows 2000调试版中的扩展。
- winxp 目录包含在Windows XP和之后版本Windows中的扩展。
- winext 目录包含能在所有Windows版本中使用的扩展。Windows调试工具包根目录下的dbghelp.dll 也包含这样的扩展。
如果编写了自己的调试器扩展,可以将他们放在任何目录下。但是建议将他们放到一个新目录中并将该目录加入调试器扩展路径中。
可以加载多达32个扩展DLL。
使用调试器扩展命令
使用调试器扩展命令和使用调试器命令非常类似。在调试器命令窗口中输入命令,会在该窗口中产生输出或改变目标程序或目标机。
一个实际的调试器扩展命令是由调试器调用的一个DLL入口点。
以如下语法调用调试器扩展:
![module.]extension [arguments]
module 名字不能包含.dll扩展名。如果module 包含完整路径,默认的字符串尺寸限制为255。
如果模块还没有被加载,调试器会使用LoadLibrary(module)调用来加载它。调试器加载了扩展库之后,会调用GetProcAddress 函数来定位扩展模块中的命令名。扩展命令名是大小写敏感的,并且必须和出现在扩展模块的.def 中的名字完全相同。如果找到了命令地址,则会调用该命令。
搜索顺序
如果没有指定模块名,调试器会在已加载的扩展模块中搜索。
默认的搜索顺序如下:
- 能在所有操作系统和两种调试模式中使用的扩展模块:dbghelp.dll 、winext\ext.dll。
- 在所有模式下可以使用,但是和特定操作系统相关的扩展模块。在Windows XP和之后版本的Windows中为winxp\exts.dll。Windows NT 4.0和Windows 2000没有对应的模块。
- 可以在所有操作系统中使用,但是和调试模式相关的扩展模块。内核模式下为winext\kext.dll。用户模式下为winext\uext.dll。
- 既和操作系统相关又和调试模式相关的扩展模块。下表列出了这些模块。
Windows 版本
|
用户模式
|
内核模式
|
Windows NT 4.0 (free build)
|
nt4fre \ ntsdexts.dll
|
nt4fre \ kdextx86.dll
|
Windows NT 4.0 (checked build)
|
nt4chk \ ntsdexts.dll
|
nt4chk \ kdextx86.dll
|
Windows 2000 (free build)
|
w2kfre \ ntsdexts.dll
|
w2kfre \ kdextx86.dll
|
Windows 2000 (checked build)
|
w2kchk \ ntsdexts.dll
|
w2kchk \ kdextx86.dll
|
Windows XP and later
|
winxp \ ntsdexts.dll
|
winxp \ kdexts.dll
|
如果某个扩展模块被卸载了,他会被从搜索链中移除。当一个扩展模块被加载,会被添加到搜索顺序的开始位置。.setdll (Set Default Extension DLL)命令可以用来将任何模块提升到搜索链的顶端。可以通过重复使用该命令来完全控制搜索链。
用.chain (List Debugger Extensions)命令来以当前搜索顺序显示所有加载的扩展模块列表。
如果执行不在任何已加载扩展模块中的命令,会得到一条Export Not Found 错误信息。
更多信息
关于特定的扩展命令的信息,查看调试器扩展命令参考节。
编写新的调试器扩展
可以通过编写扩展DLL来创建自己的调试命令。例如,可能想编写一条命令来显示复杂数据结构,或者根据特定变量或内存位置的值来停止或启动调试目标。
有两种不同的调试器扩展:
- DbgEng 扩展。它们基于 dbgeng.h 和wdbgexts.h头文件中的原型。
- WdbgExts 扩展。它们仅基于wdbgexts.h头文件中的原型。
关于如何编写调试器扩展的信息,查看编写DbgEng 扩展和编写WdbgExts扩展。
cc682/NetRoc
http://netroc682.spaces.live.com/
调试器操作(内核模式)
本节包含以下主题:
崩溃和重起目标机
和目标机同步
改变上下文
映射驱动文件
本地内核调试
崩溃和重起目标机
进行内核调试时,使用.crash (Force System Crash) 命令可以造成目标机停止响应(即崩溃或bug check)。该命令会使目标机立即停止响应。如果已启用崩溃转储,调试器会创建一个内核模式dump文件。(关于这些文件的更多信息,查看创建内核模式dump文件。)
使用.reboot (Reboot Target Computer) 命令重起目标机。
如果想目标机创建一个dump文件之后重起,可以先使用.crash 命令,然后再使用.reboot命令。如果只是想重起,则不需要.crash命令。
在引导进程的初期,主控机和目标机之间的连接会丢失。调试器不能接收到目标机的信息。
连接断开之后,调试器会关闭所有符号文件,并卸载所有调试器扩展。这时如果运行的是KD或CDB,所有断点都会丢失。在WinDbg中,可以保存当前工作空间,这样可以保存所有断点。
如果这时想结束调试会话,使用CTRL+B 命令( KD中)或点击File菜单中的Exit(在WinDbg中)。
如果不退出调试器,当引导进程到达一定程度时会重新建立连接。这时符号和扩展会重新加载起来。如果运行WinDbg,内核模式工作空间也会重新加载。
可以让调试器在引导过程中的两个可能的时间点自动中断目标机:
要在第一个内核模块加载时自动中断,使用-d
命令行选项。
要设置内核初始化时的自动断点,使用-b
命令行选项。
也可以在调试器运行时改变中断情况:
和目标机同步
有时候在内核模式调试时,目标机会停止响应调试器。
在KD中,可以按下CTRL+R (Re-synchronize) 再按下ENTER来和目标机同步。在WinDbg中,使用CTRL+ALT+R 或Debug | Kernel Connection | Resynchronize。
这些命令可以重建主控机和目标机的连接。但是,重新同步可能并不总是成功,特别在使用1394内核连接时。
.restart (Restart Kernel Connection) 命令提供了更加强大的重新同步方法。该命令等同于退出调试器并重新附加一个新的调试器到存在的会话上。该命令仅在KD中支持,而不在WinDbg中支持。
.restart 命令在进行通过remote.exe的远程调试时很有用。 (在这种调试中,要结束和重起调试器可能很困难。)但是,如果使用通过调试器的远程调试,则可以在调试客户端使用.restart命令。
改变上下文
在内核模式调试时,同时有很多进程、线程有时也有很多用户会话在运行。所以,例如"虚拟地址 0x80002000"或" eax 寄存器"这样的短语都是不明确的。必须指定这些短语所在的上下文,才可以被理解。
调试器在调试时有5种不同的上下文可以设置:
- 会话上下文(session context)指定默认的用户会话。 (该上下文仅在Microsoft Windows XP和之后的Windows中具有。这些系统允许同时有多个登陆会话共存。)
- 进程上下文(process context) 用于决定调试器如何解释虚拟地址。
- 用户模式地址上下文(user-mode address context)几乎从来不会直接设置。该上下文在改变进程上下文时自动设置。
- 寄存器上下文(register context)决定调试器如何 解释寄存器,也用于控制堆栈跟踪的结果。该上下文也称为线程上下文,尽管这个术语不是完全准确。显式上下文(
explicit context)也是一种寄存器上下文。如果指定了显式上下文,则它会取代当前的寄存器上下文。
- 局部上下文(local context)决定调试器如何解释局部变量。该上下文也称为作用域。
会话上下文
在Windows XP和之后版本的Windows中,同时可以运行多个登陆会话。每个登陆会话都有它自己的进程。
!session 扩展命令显示所有登陆会话或改变当前会话上下文。
当!sprocess 和!spoolused 扩展命令输入的会话号为"-2"时,会用到会话上下文。
会话上下文改变时,进程上下文会自动切换到该会话的活动进程。
进程上下文
每个进程都有自己的页目录,用于记录虚拟地址如何映射到物理地址的信息。当一个进程中的任何线程执行时,Windows操作系统使用页目录来解释虚拟地址。
用户模式调试时,当前进程决定了进程上下文。调试器命令、扩展命令和调试信息窗口都会使用当前进程的页目录来解释虚拟地址。
在内核模式调试时,使用.process (Set Process Context) 命令来设置进程上下文。该命令用来选择解释虚拟地址时使用哪个进程的页目录。设置了进程上下文滞后,任何用到地址的命令中都使用该上下文。也可以在该地址中设置断点。通过使用.process 和/i 选项来指定入侵式调试,在内核调试器里面也可以设置用户空间的断点。
也可以在内核空间函数上使用指定进程的断点来在内核调试器中设置用户模式断点。设置这种断点并等待切换到适合的上下文。
用户模式地址上下文是进程上下文的一部分。一般来说不用直接设置用户模式地址上下文。如果设置了进程上下文,用户模式地址上下文会自动改变为使用该进程关联的页表。但是,在基于Itanium的处理器上,一个进程可能拥有多于一个的页目录。这种情况下,可以使用.context (Set User-Mode Address Context) 命令来改变用户模式地址上下文。
在内核模式调试时设置了进程上下文之后,它会一直保持到使用另一个.process命令改变为止。用户模式地址上下文也会保持到使用.process 或.context 命令改变为止。这些上下文在目标机运行时都不会改变,也不会因为改变寄存器上下文和局部上下文而受影响。
寄存器上下文
每个线程都有它自己的寄存器值。当线程执行时这些值保存在CPU寄存器中;当其他线程运行时,则保存在内存中。
用户模式调试时,当前线程一般决定了寄存器上下文。所有调试器命令、扩展命令和调试信息窗口中对寄存器的引用都根据当前线程的寄存器来解释。
使用下面的方法之一,在用户模式调试时可以将寄存器上下文改变为当前线程之外的值:
.cxr (Display Context Record)
.ecxr (Display Exception Context Record)
内核模式调试时,可以使用包括下面一些命令在内的多种命令来控制寄存器上下文:
.thread (Set Register Context)
.cxr (Display Context Record)
.trap (Display Trap Frame)
这些命令不改变CPU寄存器的值,调试器是从内存位置中获得指定的寄存器上下文。实际上,调试器只能找到已保存的寄存器值。(其他值是动态设置的并且不会保存下来。重建堆栈跟踪使用已保存的值就足够了。)
寄存器上下文设置之后,任何使用寄存器值的命令都会使用新的寄存器上下文,例如k (Display Stack Backtrace) 和r (Registers)。
但是,当调试多处理器的计算机时,一些命令可以指定处理器。 (关于这些命令的更多信息,查看多处理器语法。)如果为一条命令指定了处理器,该命令使用指定的处理器上的激活线程的寄存器上下文,而不是当前寄存器上下文,即使指定的处理器就是活动处理器。
同样,如果寄存器上下文和当前处理器模式设置不匹配,这些命令会产生错误或无意义的输出。要避免这种输出错误,在将处理器模式设置为和寄存器上下文匹配之前,这些依赖寄存器状态的命令都会失败。使用.effmach (Effective Machine) 命令改变处理器模式。
改变寄存器上下文也会改变局部上下文。所以,寄存器上下文会影响局部变量的显示。
如果任何程序发生执行、单步或跟踪的事件,寄存器上下文会立即重设为和程序计数器的位置匹配。在用户模式下,当前进程或线程改变时,寄存器上下文也会重设。
寄存器上下文也作用于堆栈跟踪,因为堆栈跟踪是从堆栈寄存器(基于x86处理器上的esp或基于Itanium处理器上的sp)的指向的位置开始的。如果寄存器上下文设置为非法或不可访问的值,就不能进行堆栈跟踪。
可以使用.apply_dbp (Apply Data Breakpoint to Context) 命令在指定的寄存器上下文中设置数据断点。
局部上下文
程序运行时,局部变量的意义由程序计数器来决定,因为这些变量的作用域仅在定义它们的函数内部。
进行用户模式或内核模式调试时,调试器使用当前函数的作用域(堆栈上的当前帧)作为局部上下文。使用.frame (Set Local Context) 命令或者在Calls window中双击需要的帧来改变局部上下文。
在用户模式调试时,局部上下文总是当前线程的堆栈回溯中的一帧。在内核模式调试时,局部上下文总是当前寄存器上下文的线程的堆栈回溯中的一帧。
同一时刻只能使用一个堆栈帧作为局部上下文。位于其他帧中的局部变量不能被访问。
下面这些事件发生时,局部上下文会被重置:
- 任何程序执行、单步或跟踪的动作
- 任何命令中使用线程限定符(~)
- 对寄存器上下文的任何改变
!for_each_frame 扩展命令可以对堆栈中的每一个帧使用一条单独的命令。该命令为每一个帧改变局部上下文,执行命令,然后将局部上下文设置为原始值。
映射驱动文件
更换驱动文件可能是困难的。经常需要启动到Microsoft Windows安全版(safe build),替换驱动的二进制文件,然后再重新启动。
但是,Windows XP和之后版本的Windows支持一种更加简单的替换驱动文件的方法。可以使用它来替换任何内核模式驱动(包括显示驱动)、任何Windows子系统驱动或其他任何内核模式模块。本主题为了简单,将这些文件统称为驱动,但是可以对任何内核模块使用该方法。
只要WinDbg或KD作为内核调试器附加上来,任何时候都可以使用该方法。也可以对引导驱动使用,但是要困难一些。关于如何在引导驱动上使用的更多信息,查看替换引导驱动。
以下面的方法使用驱动替换映射来替换驱动文件:
创建一个驱动替换映射文件(driver replacement map file)。该文件是列出了目标机上的驱动和主控机上的替代驱动的文本文件。可以替换任意数量的驱动。例如,可以在主控机上d:\Map_Files目录创建一个包含以下内容的名为Mymap.ini 的文件。
map
\Systemroot\system32\drivers\videoprt.sys
\\myserver\myshare\new_drivers\videoprt.sys
该文件的语法的更多信息,查看驱动替换映射文件格式。
- 设置到目标机的内核调试连接,在主控机上启动内核调试器(KD或WinDbg)。 (不需要真的中断到目标机。)
以下面的方法加载驱动替换映射文件:
启动内核调试器之前,设置_NT_KD_FILES
环境变量。
D:\Debugging Tools for Windows> set _NT_KD_FILES=d:\Map_Files\mymap.ini
D:\Debugging Tools for Windows> kd
D:\Debugging Tools for Windows> kd
kd> .kdfiles d:\Map_Files\mymap.ini
KD file associations loaded from 'd:\Map_Files\mymap.ini'
可以使用.kdfiles 命令来显示当前的驱动替换映射文件或删除驱动替换映射。如果不使用该命令,映射会一直保持到退出调试器。
完成上面的步骤后,驱动替换映射就起作用了。
当目标机加载驱动时,会询问主控机是否需要映射该驱动。如果需要,替换文件会通过内核连接传送过去并覆盖掉旧驱动。然后加载新驱动。
驱动替换映射文件格式
每个驱动文件替换由驱动替换映射文件中的三行指定。
- 第一行由单词"map"组成
- 第二行指定目标机上的旧驱动的路径和文件名。
- 第三行指定新驱动的完整路径。该驱动可以放在主控机上或其他服务器上。
可以这样重复指定任何多次。
路径和文件名是不区分大小写的,实际的驱动文件名可以不同。当目标机加载驱动之前,第二行的文件会被第三行指定的文件覆盖。
警告 旧驱动的路径和文件名必须是和保存在服务管理器(Service Control Manager (SCM))数据库中的路径和文件名不区分大小写的精确匹配。该路径经常以\SystemRoot\system32\drivers开头。但是,也可能有一些变形(例如,路径以\??\c:\windows\system32\drivers开头)。SCM数据库中的名字和传递给MmLoadSystemImage的名字一样。
文件中可以包含空白行,也可以包含以井号(#)开始的注释行。但是,在"map"出现之后,紧跟的两行必须是旧驱动和新驱动。 三行组成的块不能被空白行和注释行分割开
下面是一个驱动替换映射文件的示例。
map
\Systemroot\system32\drivers\videoprt.sys
e:\MyNewDriver\binaries\videoprt.sys
map
\Systemroot\system32\mydriver.sys
\\myserver\myshare\new_drivers\mydriver0031.sys
# Here is a comment
map
\??\c:\windows\system32\beep.sys
\\myserver\myshare\new_drivers\new_beep.sys
驱动替换映射文件必须是一个文本文件,但是可以使用任何文件名和扩展名 (.ini,.txt, .map等等)。
其他注意事项
驱动替换发生时,调试器中会出现一条信息。
如果使用CTRL+D (KD中) 或CTRL+ALT+D (WinDBg中),可以看到替换请求的详细信息。当不知道自己列出的名字和SCM数据库中的是否匹配时,该信息非常有用。
如果内核调试器退出,则不会再继续进行驱动替换。但是,任何已经被替换掉的驱动不会再恢复成旧的二进制文件,因为驱动文件被实际覆盖掉了。
这种驱动替换功能自动绕过Windows文件保护(Windows File Protection (WFP))。
不需要重起目标机。目标机任何时候加载驱动时都会发生驱动替换,不管它是否经过了重新起动。当然,在大部分驱动在引导过程中就已经加载了,所以实际上还是需要在加载了映射文件之后重起目标机。
如果定义了_NT_KD_FILES 环境变量,指定的驱动替换映射文件在内核调试器启动时就会加载。如果使用了.kdfiles 命令,会立即读取指定文件。这时,调试器会验证文件是否具有map/行/行的格式。但是,实际的路径和文件名直到替换发生时才会进行验证。
映射文件读取之后,调试器会保存它的内容。如果之后修改了该文件,这些修改不会生效。(除非再使用.kdfiles 命令)。
对于大的驱动文件,建议使用1394内核连接。使用COM端口的调试连接可能花很长时间用来复制文件。
替换引导驱动
如果要使用驱动替换方法来替换一个引导驱动,必须将内核调试器连接到Windows boot loader(Ntldr),而不是Windows内核。进行这种连接时,必须安装一个特殊的启用调试的Ntldr版本。可以在Windows Driver Kit(WDK)的%DDKROOT%\debug目录中找到该版本的Ntldr。
由于目标机会忽略Boot.ini文件,所以这种情况下不能设置内核连接协议。必须通过COM1端口连接到目标机。波特率为115200。因此,主控机上的内核调试器需要设置为使用115200速度的COM连接。
这种特殊方法仅对引导驱动有效(即Acpi.sys、Classpnp.sys、 Disk.sys和任何在Windows初始断点时使用!drivers 命令能显示出来的驱动)。如果要替换引导完成之后使用 MmLoadSystemImage 加载的标准驱动,需要用前面描述的标准方法。
不能在使用EFI固件替代Boot.ini文件的计算机上替换引导驱动。(例如,基于Itanium的计算机。)。
本地内核调试
KD和WinDbg可以进行本地内核调试。这种调试是在单台计算机上进行的内核调试。换句话说,调试器调试自己正在运行的计算机。
本地内核调试仅在Microsoft Windows XP和之后版本的Windows中支持。只有拥有调试权限的用户可以开始本地内核调试。
开始本地内核调试
使用/debug 引导开关来启用本地内核调试。关于该开关的更多信息,查看配置目标机的软件。
要开始一个本地内核调试会话,使用-kl命令行选项启动调试器、点击Kernel Debugging 对话框(File | Kernel Debug)的Local 选项卡、或使用.attach -k 命令。关于开始这种会话的更多信息,查看附加到目标机(内核模式)。
不支持的命令
本地内核调试会话中并不支持所有命令。一般来说,不能使用任何使得目标机停止的命令,因为不能恢复操作。
特别是,不能使用下面一些命令:
- 执行命令,如g (Go)、 p (Step)、 t (Trace)、 wt (Trace and Watch Data)、 tb (Trace to Next Branch)、 gh (Go with Exception Handled)和 gn (Go with Exception Not Handled)
- 关闭和dump文件命令,如.crash、 .dump和 .reboot
- 断点命令,例如bp、 bu、 ba、bc、 bd、 be和 bl
- 寄存器显示命令,如r和它的变种
- 堆栈跟踪命令,如k和它的变种
如果使用WinDbg进行本地内核调试,所有同样效果的菜单命令和按钮也不可用。
可以使用的命令
所有的内存输入和输出命令可用。可以自由的读取用户内存和内核内存,也可以写入内存。要确认不会写到错误的内核内存中,因为这会破坏数据结构并经常造成计算机停止响应(即崩溃)。
本地内核调试的困难
本地内核调试是非常敏感的操作。注意不要破坏系统或使得系统崩溃。
本地内核调试一个最困难的地方是机器状态一直在改变。内存在换入和换出、活动进程在不断改变、虚拟地址上下文也不会保持固定。但是,在这些情况下,还是可以慢慢对一些东西进行有效的分析,例如特定的设备状态。
内核模式驱动和Windows系统经常会通过DbgPrint 和相关函数发送信息给内核调试器。在本地内核调试时,这些消息不会自动显示出来。可以通过!dbgprint扩展命令来显示。
LiveKD
LiveKD 工具可以模拟本地内核调试。该工具会创建内核内存的"快照" 转储文件,而在生成快照时不会实际停止系统。 (因此,该快照可能不能实际反映计算机某一时刻的状态。)
LiveKD 不是Windows调试工具包的一部分。可以在SysInternals 网站上下载
LiveKd 。
cc682/NetRoc
http://netroc682.spaces.live.com/
调试器操作(用户模式)
本节包含以下主题:
被创建的进程行为
控制进程和线程
重新附加到目标进程
调试托管代码
被创建进程的行为
由调试器创建的进程(也称为产生的进程)的行为和不是调试器创建的进程有轻微不同。
调试器创建的进程使用特殊的调试堆,而不是使用标准的堆API。在Microsoft Windows XP和之后版本Windows中,可以通过_NO_DEBUG_HEAP 环境变量或 -hd
命令行选项强制子进程使用标准堆。
同样,由于子目标程序是调试器的子进程,所以继承了调试器的权限。这种权限可能使得目标进程能做一些通常情况下不能进行的操作。例如,目标进程可能可以影响其它被保护的进程。
控制进程和线程
当进行用户模式调试时,可以激活、显示、冻结、解冻、挂起、恢复进程和线程。
当前正被调试的进程称为当前活动进程。类似的,当前调试器正在控制的线程称为当前线程或活动线程。很多调试器命令的行为由当前进程和当前线程决定。
调试开始时,当前进程是调试器附加到的进程或因为异常中断到调试器的进程。同样,当前线程是当调试器附加到进程时的线程或产生异常的线程。但是,可以利用调试器改变当前线程和进程,也可以分别冻结或解冻线程。
在内核模式调试下,不使用本节描述的方法来控制线程和进程。关于在内核模式下操作进程和线程的更多信息,查看改变上下文。
显示进程和线程
使用如下方法显示进程和线程信息:
设置当前进程和当前线程
使用如下方法改变当前进程和线程:
冻结和挂起线程
调试器可以通过挂起或冻结线程来改变它的执行。这两种操作有一些不同的地方。
每个线程都有一个关联的挂起计数(suspend count)。如果这个数字是大于等于1,则系统不会运行该线程。如果计数小于等于0,系统会在适当的时机运行该线程。
一般来说,每个线程的挂起计数都是0。当调试器附加到进程时,会将它的所有线程的挂起计数加1。如果调试器停止对进程的附加,会将所有挂起计数减1。当调试器执行进程时,会临时将所有的挂起计数减少1。
使用下面一些方法可以用调试器控制任何一个线程的挂起计数:
一般用这些命令来将指定线程的挂起计数从1加到2。当调试器执行或停止附加进程时,该线程由于挂起计数为1,即使进程中其他线程都开始执行,该线程仍然保持挂起。
在进行非侵入式调试时也可以挂起线程。
调试器也可以冻结线程。该行为和以某些方式挂起线程类似。但是,"冻结"仅仅是一种调试器设置。Windows系统不会知道该线程有任何不同点。
默认情况下,所有线程都是非冻结的。当调试器运行进程时,被冻结的线程不会运行。但是,当调试器停止对该进程的附加时,所有线程都会变为非冻结状态。
使用下面一些方法来冻结和解冻各个线程:
在任何情况下,当调试器中断目标时,该进程中的所有线程永远不会被执行。线程的挂起计数仅在调试器运行进程或者停止进程附加时有效。冻结状态仅在调试器运行进程时有效。
其他命令中的线程和进程
在很多其他命令中也可以指定进程或线程。更多信息,查看各个命令的主题。
在很多命令和扩展命令前都可以加上~e (Thread-Specific Command) 限定词。该限定词使得命令对指定线程起作用。在想对一个以上线程使用某个命令时它非常有用。例如,下面的命令对被调使得所有线程使用!gle 扩展命令。
~*e !gle
多系统
调试器可以同一时刻附加到多个目标。当这些处理包含不止一台计算机上的dump文件或活动目标时,调试器的每个行为都以一个系统、进程和线程为基准。关于这类调试的更多信息,查看调试多个目标。
重新附加到目标程序
如果调试器在用户模式调试时冻结了,或因为其他原因停止响应(即崩溃),可以将一个新调试器附加到已存在的进程上。
注意 该方法仅在Microsoft Windows XP和之后版本Windows中支持。该方法不管进程是调试器创建的还是附加上去的,也不管是否使用了-pd选项。
使用以下步骤来重新将调试器附加到目标程序:
- 对目标程序确认进程ID。
打开一个新的CDB或WinDbg。使用-pe命令行选项。
Debugger -pe -p PID
也可以使用其他命令行选项。
可以在一个静止的调试器中使用.attach (Attach to Process) 命令和-e选项。
- 附加完成后,结束原来的调试器进程。
- 如果进程没有正常响应,可能是因为挂起计数太高。可以使用~m (Resume Thread) 命令来减少挂起计数。关于挂起计数的更多信息,查看控制进程和线程。
如果原来的调试器还在正常工作,该方法可能无效。两个调试器会竞争调试事件,并且Windows操作系统没有必要将所有调试事件都通知新调试器。
如果原来的调试器在附加新调试器之前就已经结束,目标程序也会被结束掉。 (但是,如果调试器以-pd选项附加上去并正常结束,目标程序会继续运行。这种情况下,第二个调试器可以不使用-pe选项附加到目标程序。)
如果已经在调试一个进程并想停止附加,但保持进程在调试状态冻结,可以使用.abandon (Abandon Process) 命令。该命令之后,任何Windows调试器都可以用本主题描述的方法重新附加到进程上。
调试托管代码
WinDbg、CDB和NTSD也可以对包含托管代码的目标程序进行有限制的调试。
介绍托管代码
托管代码是和Microsoft .NET 公用语言运行时(CLR)一同执行的代码。.NET CLR 管理程序的原始代码和数据,并且提供类似垃圾回收和平台无关代码这样的高级支持。
需要该运行时的编译后代码称为托管代码。不需要该运行时的代码成为非托管代码。只包含托管代码的应用程序称为托管应用程序。
托管的.NET应用程序可以在支持.NET CLR 的任何平台上运行,因为编译器生成的二进制代码是平台无关的。托管程序中的二进制代码是Microsoft中间语言(Microsoft intermediate language (MSIL))。这种二进制代码还包含对象信息和其他引用(称为元数据)。
托管应用程序和传统的应用程序有所不同,因为很多程序执行的细节是在运行时决定的,例如数据结构如何分布和本地代码如何生成和使用。当这种程序执行的时候,运行时决定程序运行的数据使用和代码使用,生成和平台相关的本地代码。从MSIL产生本地代码的过程称为托管或just-in-time (JIT) 编译(有时也称JITting)。运行时用来进行这种翻译的组件成为JIT编译器。
当JIT编译器为某个方法编译了MSIL后,该方法的存根被编译后代码的地址取代。不管之后这个方法什么时候被调用,执行的都是本地代码,而JIT编译器不需要再重复这个步骤。
构建托管代码
可以使用各个软件厂商提供的各个编译器来构建托管代码。特别是,Microsoft Visual Studio .NET可以使用4种不同语言来生成托管代码:
- 带托管扩展的C++
- C#
- Visual Basic
- JScript
默认的,Microsoft Visual C++ .NET不会构建托管应用程序。必须通过图形界面或命令行开关来指定这样的构建。
调试托管代码
可以使用Sos.dll 扩展来调试托管代码。该扩展在Windows调试工具包安装目录的\clr10 子目录下。
要使用该扩展,可以加载它之后输入!clr10\sos.help。
该命令会列出所有可用的扩展命令和它们的参数。
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返回到静止模式。这时,可以使用任何开始的方法。即可以开始调试一个运行中的进程、创建新进程、附加到目标机或连接到远程调试会话。关于这些选择的更多信息,查看启动调试器。
cc682/NetRoc
http://netroc682.spaces.live.com/
读写寄存器和标志
寄存器是CPU中的一些小的挥发性存储空间。一些寄存器被定义作为特定用途,另一些寄存器可以被用户模式程序使用。
基于x86和Itanium的处理器有不同的寄存器集合。关于每种处理器寄存器的更多信息,查看处理器体系结构。
使用如下方法之一来操作CPU寄存器:
寄存器也在每次目标停止下来时自动显示。如果使用p (Step) 或t (Trace) 命令来单步执行程序,每一步执行之后都可以看到寄存器的值。要停止这样的显示,可以在使用这些命令时加上r选项。
在基于x86的处理器上,r选项也用于控制一些例如flags这样的位寄存器。要修改这些标志,和修改其他规则寄存器有少许不同。关于这些标志和语法的更多信息,查看x86 标志寄存器。
查看调用堆栈
调用堆栈(call stack)是到达当前程序计数器位置的函数调用序列。调用堆栈顶部的函数是当前函数,下一个函数是调用当前函数的函数,等等。
当某个函数调用另外一个函数时,调用堆栈也保存了一些信息。这些信息不同的处理器上不同,但是一般它们包含下面这些部分:
- 被调用函数的名字或位置。
- 返回地址。(一般是紧跟着调用本函数的那条指令的下一条指令。)
- 传递给函数的参数。
- 堆栈帧的基指针。
使用下面一些方法显示调用堆栈:
如果在函数刚开始时显示调用堆栈(在函数的代码开始执行之前),可能会看到错误的结果。调试器使用帧指针寄存器来进行回溯,但是当函数代码开始执行前,该寄存器并没有被设置。
如果没有修改寄存器上下文,被显示的调用堆栈基于当前程序计数器。关于如何修改寄存器上下文的更多信息,查看修改上下文。
有很多原因都会造成调用堆栈回溯上的困难。关于这些原因的更多信息,查看堆栈回溯。
汇编模式的调试
如果拥有程序的C或C++ 源码文件,使用源码模式的调试可以利用调试器更强大的功能。
但是,有时候并不能进行源码调试。可能没有程序的源代码、可能在调试其他人的代码也可能没有使用完全的.pdb 符号来构建可执行文件,另外在有源码调试的情况下也可能需要跟踪到应用程序调用到的或者用于加载应用程序的Microsoft Windows函数。
在这些情况下,都必须以汇编模式调试。此外,汇编模式拥有在源码调试下不具备的一些有用特性。调试器会在内存位置或寄存其被访问到时自动显示他们的内容,并且显示程序计数器中的地址。这些显示使得汇编模式调试成为和源码调试一起使用的很有价值的工具。
反汇编代码
调试器首先分析二进制可执行代码。它以反汇编的形式,而不是原始格式显示代码。即调试器将机器码转换为汇编语言。
可以使用几种不同办法显示汇编代码:
- u (Unassemble) 命令反汇编并显示一段机器码。
- uf (Unassemble Function) 命令反汇编并显示一个函数。
- (仅WinDbg)
反汇编窗口反汇编并显示指定节的机器码。如果在Window 菜单中选择了Automatically Open Disassembly ,该窗口会自动激活。也可以通过点击View 菜单的Disassembly 、按下ALT+7或点击工具栏上的Disassembly (Alt+7) 按钮(
)。
反汇编的显示内容有4列:地址偏移、二进制代码、汇编语言助记符和汇编语言细节。下面是一个显示的例子。
0040116b 45 inc ebp
0040116c fc cld
0040116d 8945b0 mov eax,[ebp-0x1c]
在一行的右边,根据当前程序计数器的值,可能会显示将被访问的内存位置或寄存器的值。如果某行包含分支指令,会出现[br=1] 或[br=0]的提示。该提示表明了分支是否会实现。
使用.asm (Change Disassembly Options)命令来修改反汇编指令如何被显示。
在WinDbg反汇编窗口中,当前程序计数器所在的行会被用绿色高亮。设置了断点的行以红色 (启用的断点)、黄色(禁用的断点)或紫色(当前程序计数器位置是一个断点)。
也可以用下面的命令来操纵汇编代码:
汇编模式和源码模式
调试器有两个不同的操作模式:汇编模式和源码模式。
当对程序进行单步执行时,根据不同的模式,每一步执行的大小可以是一行汇编代码或一行源代码。
不同模式下,一些命令的显示输出也不相同。
WinDbg中, 在汇编模式下单步执行或运行程序时反汇编窗口自动切换到前台。在源码模式下, 源码窗口会切换到前台。
使用下面的方法之一来设置模式:
WinDbg中在汇编模式下,状态栏中会显示ASM。
WinDbg反汇编窗口的快捷菜单中有一项Highlight instructions from the current source line 命令。 该命令可以将当前源码行对应的所有汇编指令高亮。一般来说,单个源码行对应多个汇编指令。如果代码经过优化,这些汇编指令可能并不连贯。Highlight instructions from the current source line 命令用于找到当前代码行所生成的所有汇编指令。
汇编语言源文件
如果程序是用汇编语言编写的,调试器生成的反汇编可能和原始代码并不完全相同。特别是不存在NO-OPs(what?)和注释。
如果想用原始的.asm文件来调试,必须使用源码模式。可以像C或C++源码文件一样加载汇编文件。这类调试的更多信息,查看源码模式调试。
cc682/NetRoc
http://netroc682.spaces.live.com/
读写内存
三个调试器都可以直接读写内存。这些内存可以用地址或变量名来引用。
使用虚拟地址访问内存
可以使用各种命令访问内存或内存区域。
下面的这些命令可以以各种格式读写内存。这些格式包括16进制字节、字(字、双字和4字)、整数(short, long, quad integers 和unsigned integers)、实数(10字节、16字节、32字节和64字节实数)、以及ASCII字符。
使用下面一些命令来处理更特殊的数据类型:
使用如下一些命令来操作内存块:
大多数情况下,这些命令都按照当前的基数来解释参数。因此,当前基数不是16时,要在16进制数前加上0x 。但是,这些命令的显示输出不管当前基数是什么,一般是16进制格式。(关于输出的更多信息,查看各个命令的主题。) 内存窗口(Memory window)使用10进制显示整数和实数,用16进制显示其他格式的数据。
使用n (Set Number Base) 命令改变当前基数。用? (Evaluate Expression) 或 .formats (Show Number Formats) 命令来快速的将数字从一种基数转变为另一种。
进行用户模式调试时,虚拟地址的意义由当前进程决定。进行内核模式调试时,虚拟地址的意义可以由调试器控制。更多信息,查看进程上下文。
使用物理地址访问内存
用!db、!dc、!dd、!dp、!du和!dw 扩展命令读取物理内存的内容。
使用!eb 和!ed 扩展命令写入物理内存。
fp (Fill Physical Memory) 命令在物理内存范围内写入一个模板,并重复直到内存块被填充满。
内核模式下使用WinDbg时,也可以直接使用Memory窗口读写物理内存。
在物理内存中搜索一块数据或一个范围内的数据,使用!search 扩展命令。
同样,关于物理地址的更多信息,查看将虚拟地址转换为物理地址。
访问全局变量
全局变量的名字保存在应用程序编译时创建的符号文件中。调试器将全局变量名转换成虚拟地址。因此,任何接受地址作为参数的命令也可以适用变量名作为参数。
因此,可以使用本主题前面提到的所有命令来读写全局变量。
另外,可以使用? (Evaluate Expression) 命令来显示和任何符号关联的地址。
下面例子中,假设需要查看一个32位整数MyCounter 全局变量的值。假设当前的基数为10。
可以用下面的方法得到它的地址并显示它。
0:000> ? MyCounter
Evaluate expression: 1244892 = 0012fedc
0:000> dd 0x0012fedc L1
0012fedc 00000052
第一个命令的输出表明MyCounter 的地址为0x0012FEDC。然后可以使用 D* 命令来显示该地址的一个双字。 (也可以使用1244892,这个地址的10进制值。但是,大多数C程序员会选择使用0x0012FEDC。) 第二条命令表明MyCounter 的值为0x52 (10进制82)。
可以用下面一条命令实现上面这些步骤。
0:000> dd MyCounter L1
0012fedc 00000052
要将MyCounter 修改为10进制的83,使用下面的命令。
0:000> ed MyCounter 83
这个例子使用10进制输入,因为这样对于整数来说要自然一些。但是,D* 命令的输出仍然是16进制格式。
0:000> dd MyCounter L1 0012fedc 00000053
访问局部变量
局部变量和全局变量类似,也在符号文件中保存了信息。调试器同样会将它们的名字转换为地址。它们可以使用和全局变量一样的方式来读写。
也可以使用下面这些方法来显示、修改和使用局部变量:
但是,局部变量和全局变量有一个主要的不同。当程序运行时,局部变量的意义由程序计数器的位置决定,因为这些变量的作用范围仅在定义它们的函数内部。
调试器根据局部上下文来解释局部变量。默认情况下这个上下文和程序计数器位置匹配。但是调试器可以改变上下文。关于局部上下文的更多信息,查看局部上下文。
当局部上下文被改变时,局部窗口会立即更新以显示新的局部变量集合。DV 命令也显示新的变量。所有这些新的变量都能够被上述的内存命令正确解释,之后就可以读写这些变量了。
调试优化后的代码时,有些局部变量可能被合并(collapsed?)、使用寄存器替代或可能只是临时存放到堆栈中。如果要在调试中使用源码文件或局部变量,最好不要优化代码。
通过监视窗口控制变量
在WinDbg中,也可以使用监视窗口(Watch window)来显示、修改全局和局部变量。
监视窗口可以显示任何需要的变量列表。可以包含全局变量和任何函数的局部变量。任何时候,监视窗口都显示这些变量中和当前函数作用范围匹配的那部分变量。同样可以通过监视窗口修改变量的值。
和局部窗口不同,监视窗口不会受到局部上下文的影响。只有当前的程序计数器作用范围内定义的变量能够被显示和修改。
关于该窗口的更多信息,查看Watch 窗口。
cc682/NetRoc
http://netroc682.spaces.live.com/
使用断点
断点位于可执行代码中,它使得操作系统停止程序执行并中断到调试器。 然后,就可以分析目标和执行调试器命令。
可以通过指定虚拟地址、模块和函数偏移或源码文件和行号来设置断点(当在源码模式中时)。如果在某个例程上设置断点并且没有使用偏移,则当运行到这个例程上时就会触发断点。
还有下面一些类型的断点:
- 和特定线程关联的断点。
- 在被触发前可以被挑过指定次数的断点。
- 触发时自动执行特定命令的断点。
- 设置在非执行内存上的断点,并等待该内存被读写时中断。
如果在用户模式下调试多于一个进程,每个进程都有它自己的断点集合。要查看或修改某个进程的断点,必须将该进程设置为当前进程。关于当前进程的更多信息,查看控制进程和线程。
控制断点的方法
使用下面一些方法来控制或显示断点:
每个断点都有一个关联的10进制数字称为断点ID 。该数字在各种命令中用于指定断点。
如果一个断点是设置在某个还未加载的函数名上,则称为延迟、虚拟或未定断点。 (这些术语可交替使用。) 未定断点没有被关联到任何具体被加载的模块上。每当一个新的模块被加载时,会检查该函数名。如果这个函数出现,调试器计算虚拟断点的实际位置并启用它。
使用bu设置的断点自动被认为是未定断点。如果断点在一个已加载模块中,则会启用并正常生效。但是,如果模块之后被卸载并重新加载,这个断点不会消失。而使用bp设置的断点会立即绑定到某个地址。
bp和bu断点有以下三个主要的不同点:
- bp 断点的位置总是被转换成地址。如果某个模块改变了,并且bp设置的地址位置改变,断点还是在原来的位置。而bu断点仍然和使用的符号值关联(一般是符号加上偏移),它会一直跟踪符号的地址,即使这个地址已经改变。
- 如果bp的断点地址在某个已加载模块中找到,并且该模块之后被卸载,则该断点会从断点列表中移除。而bu断点经过反复的卸载和加载仍然存在。
- 用bp设置的断点不会保存到WinDbg 工作空间(workspaces)中,而使用bu设置的断点会保存。
当在WinDbg 反汇编窗口或源码窗口中使用鼠标设置断点时,调试器创建的是bu断点。
当调试器启动一个新的目标程序时,初始断点在主映像和所有静态加载的DLL被加载、DLL初始化例程被调用之前自动触发。
调试器附加到一个已存在的用户模式程序时,初始断点立即触发。
-g 命令行选项使得WinDbg或CDB跳过初始断点。在这时可以自动执行命令。更多信息,查看控制异常和事件。
如果想启动新调试目标并在实际的程序即将开始执行的时候中断下来,就不要使用-g选项。应该让初始断点被触发。当调试器激活之后,在main或winmai函数上设置断点并使用g (Go) 命令。之后所有初始化过程都会运行并且程序在main函数即将执行时停止。
关于内核模式的自动断点的更多信息,查看崩溃和重起目标机。
断点中的地址
断点支持几种地址语法,包括虚拟地址、函数偏移和源码行号。例如,可以使用下面的方法之一来设置断点:
0:000> bp 0040108c
0:000> bp main+5c
0:000> bp `source.c:31`
关于这些语法的更多信息,查看数值表达式语法, 源码行语法,以及各个命令的主题。
断点的数量
在内核模式下,最多可以使用32个断点。在用户模式下,可以使用任意数量的断点。
数据断点的数量由目标处理器架构决定。
方法的断点
如果要在MyClass类的MyMethod方法上设置断点,可以使用两种不同语法:
-
用MASM表达式语法,可以用双冒号或者双下划线来指定一个方法。
0:000> bp MyClass::MyMethod
0:000> bp MyClass__MyMethod
-
用C++表达式语法,必须用双冒号指定方法。
0:000> bp @@( MyClass::MyMethod )
如果要使用更复杂一些的断点命令,应该使用MASM表达式语法。表达式语法的更多信息,查看表达式求值。
用户空间和系统空间
每个用户模式应用程序在虚拟内存0x00000000 到0x7FFFFFFF 的地址称为用户空间。
当WinDbg或CDB在小于0x80000000的地址上下断时,断点是在单个进程的指定的用户空间的地址设置。用户模式调试时,当前进程决定了虚拟地址的意义。更多信息,查看控制进程和线程。
在内核模式,可以使用bp、 bu、 ba 命令和Breakpoints 对话框在用户空间设置断点。必须首先使用.process /i (或在一些内核空间的函数上的指定进程的断点)来将目标切换成当前进程上下文,并使用该进程上下文来指定拥有目标地址空间的用户模式进程。
用户模式的断点总是和设置该断点时进程上下文为激活状态的进程关联起来。如果有用户模式调试器在调试该进程,而还有一个内核模式调试器在调试进程运行的机器,即使断点由内核调试器设置,它中断时也是进入用户模式调试器。这时可以从内核模式调试器中断系统,或使用.breakin (Break to the Kernel Debugger) 命令来将控制权交给内核调试器。
注意 如果目标机运行在Microsoft Windows NT 4.0上,则不能使用内核调试器在用户空间中设置断点。
断点伪寄存器(Pseudo-Registers)
如果在某个表达式中想引用某个断点的地址,可以使用一个$bpNumber 语法的伪寄存器,Number是断点ID。关于该语法的更多信息,查看伪寄存器语法。
设置断点时的风险
当使用内存地址或符号加偏移的方式设置断点时,一定不能将断点设置到一条指令的中间。
例如,有下面一段汇编代码。
770000f1 5e pop esi
770000f2 5b pop ebx
770000f3 c9 leave
770000f4 c21000 ret 0x10
770000f7 837ddc00 cmp dword ptr [ebp-0x24],0x0
前三条指令只有1个字节长。但是第四条指令有3字节长。(包含在0x770000F4,0x770000F5, 和0x770000F6三个地址的字节)如果要在该指令上使用bp、bu或 ba设置断点,则必须将地址指定为0x770000F4 。
如果使用ba命令在0x770000F5 地址设置了断点,处理器将在该位置设置断点。但是 该断点永远不会被触发,因为处理器认为0x770000F4 才是这条指令的实际地址。
如果使用bp 或bu命令在 0x770000F5 设置断点,调试器在这个位置会写入断点。但是由于调试器使用如下方法设置断点,它可能造成目标运行错误:
- 调试器保存0x770000F5 地址的内容,并用一条断点指令写入该地址。
- 如果用调试器显示这段内存的内容,并不会显示被写入的断点指令,而是显示那里原来"应该是"的内容。即调试器显示原来的内存,或者断点设置之后该内存被修改的内容。
- 如果使用BC命令删除断点,调试器将断点位置的内存恢复成原始值。
当在0x770000F5设置断点时,调试器保存它的值并写入断点指令。但是当程序运行时到达0x770000F4 时,会将它视为一条多字节指令的第一个字节。处理器将0x770000F4、0x770000F5可能还有后面的一些字节当作一条指令。这会产生各种非正常的行为。
因此,当使用bp、bu或ba 命令设置断点时,要确定断点在合适的地址上。如果使用WinDbg图形界面来添加断点就不用在意这样的情况,因为它会自动选择正确的地址。
断点命令
可以在断点中包含一条命令用于在断点触发时自动执行。
也可以包含一条用于执行的命令字符串。但是,其中任何恢复程序执行的命令(例如g和t)都会终止命令列表的执行。
例如下面的命令在MyFunction+0x47中断,写入一个dump文件并恢复执行。
0:000> bu MyFunction+0x47 ".dump c:\mydump.dmp; g"
注意 如果正在从内核调试器控制用户模式调试器,不要在命令字符串中使用g (Go) 。串口的速度可能跟不上该命令,并且不能再中断到CDB中。关于这种情况的更多信息,查看从内核调试器控制用户模式调试器。
@!"<chars>"
@!"<chars>" 语法用于在MASM求值器中进行转义,使得符号解析支持任意文本。必须以@!"开始并以引号(")结束。如果不使用该语法,则在MASM表达式的符号名中不能使用空格、大于小于号(<, >)和其他特殊字符。模板和重载是符号中需要这种引号的主要原因。也可以使用如下的@!"<chars>" 语法来设置bu 命令。
0:000> bu @!"ExecutableName!std::pair<unsigned int,std::basic_string<unsigned short,std::char_traits<unsigned short>,std::allocator<unsigned short> > >::operator="
这个例子中,ExecutableName 是一个可执行文件的名字。
这种转义语法在C++中比C中更加有用(例如重载的操作符),因为C函数名中不会存在空格(或特殊字符)。但是,该语法在托管代码中也同样重要,因为.NET Framwork中非常多的使用重载。
条件断点
可以设置仅在特定条件下被触发的断点。关于这类断点的更多信息,查看设置条件断点。