bp
、bu
和bm
命令设置一个或多个软件断点。您可以组合位置、条件和选项来设置不同类型的软件断点。
本文中的调试代码示例如下:
#include <iostream>using namespace std;struct JKGirl{std::string name;int age;friend ostream& operator <<(ostream& o,const JKGirl& that){return o << that.name << " is " << that.age << " years old.";}
};int factorical(unsigned int n){static int result = 1;if(n <= 0 ) return result; // 递归结束条件result *= n; // 算子return factorical(--n) ; // 递归
}int main()
{int num = 20;double f = 30.25;int result = factorical(3);cout << result << endl;JKGirl girl = {"Anna",18};cout << girl << endl;return 0;
}
一、语法
用户模式
[~Thread] bp[ID] [Options] [Address [Passes]] ["CommandString"]
[~Thread] bu[ID] [Options] [Address [Passes]] ["CommandString"]
[~Thread] bm [Options] SymbolPattern [Passes] ["CommandString"]
内核模式
bp[ID] [Options] [Address [Passes]] ["CommandString"]
bu[ID] [Options] [Address [Passes]] ["CommandString"]
bm [Options] SymbolPattern [Passes] ["CommandString"]
二、参数
Thread
指定要应用该断点的线程。该语法的更多信息,查看线程语法。只能在用户模式下指定线程。如果没有指定线程,则断点应用到所有线程。
ID
指定用于标识该断点的十进制数字。
调试器在创建断点时指派ID,但是之后可以通过 br
(Breakpoint Renumber)命令来改变它。在其他调试器命令中使用ID来引用断点。要显示断点的ID,可以使用bl
(Breakpoint List)命令。
在命令中使用ID时,不能在命令(bp
或bu
)和ID
号之间加入空格。
ID 参数总是可选的。如果不指定ID,调试器使用第一个可用的断点号。内核模式下只能设置32个断点。用户模式下可以设置任意多个断点。但是哪种情况下对ID号的值都没有限制。如果使用中括号([])将ID括起来,ID可以包含任意表达式。
示例2.1: 通过bp
添加设置自动生成 id 的断点;通过bl
指令查看设置的断点列表
0:000> bp WD01!main
0:000> bl0 e Disable Clear 00007ff7`247718a0 0001 (0001) 0:**** WD01!main
示例2.2: 通过bp
添加设置指定 id号 的断点;通过bl
指令查看设置的断点列表
0:000> bp12 `WD01!main.cpp:42`
0:000> bl0 e Disable Clear 00007ff7`247718a0 0001 (0001) 0:**** WD01!main12 e Disable Clear 00007ff7`24771932 0001 (0001) 0:**** WD01!main+0x92
bp12 `WD01!main.cpp:42` 指定在 main.cpp 的第42行设置断点
Options
指定断点选项。除非特别指出,可以设置任意数量的下面的选项:
选项 | 描述 |
---|---|
/1 | 创建一个一次性(“one-shot”)断点。该断点触发之后就会被从断点列表中永远去除。 |
/f PredNum | (仅Itanium、仅用户模式) 指定一个断言号。该断点使用相应的断言寄存器(predicate register)进行判定(例如,bp /f 4 address设置一个使用p4断言寄存器进行判定的断点)。 |
/p EProcess | (仅内核模式) 指定一个和该断点关联的进程。EProcess 必须是EPROCESS结构的实际地址,而不是PID。这种断点仅在指定的进程上下文内遇到的时候才会触发。 |
/t EThread | (仅内核模式) 指定一个和断点关联的线程。EThread必须是ETHREAD结构的实际地址而不是线程ID。这种断点仅在指定的线程上下文内遇到的时候才会触发。如果同时使用/p EProcess 和/t EThread ,它们可以按任意顺序排列。 |
/c MaxCallStackDepth | 使得断点仅当调用堆栈小于MaxCallStackDepth 深度时才激活。不能将此选项和/C 组合使用。 |
/C MinCallStackDepth | 使得断点仅当调用堆栈大于MinCallStackDepth深度时才激活。不能将此选项和/c 组合使用。 |
/a | (仅bm 使用) 在所有指定位置设置断点,不管他们在数据空间还是代码空间。由于数据上的断点可能造成程序错误,所以只能在确认安全的位置使用该选项。 |
/d | (仅bm 使用) 将断点位置转换为地址。因此,如果代码位置改变了,这个断点还是保持在原来的位置,而不是像使用SymbolPattern 来设置的一样。使用/d 来避免模块加载或重加载时重新求值对断点进行的改变。 |
/( | (仅bm 使用) 在SymbolString 定义的符号字符串中包含参数列表信息。 这个功能使得可以对具有相同名字但是不同参数列表的重载函数设置断点。例如, bm /( myFunc 同时在myFunc(int a)和myFunc(char a)上设置断点。如果没有"/(",对myFunc 设置的断点会失败,因为这样不能确定断点设置到哪一个myFunc 上。 |
address
指定要设置断点处的指令的第一个字节位置。如果省略Address ,则使用当前指令指针。
Passes
指定断点激活之前要忽略的次数。调试器跳过该断点指定次数。该数字可以是任何16位或32位值。
默认情况下,断点在第一次执行断点位置的代码时被激活。这种默认情况和把Passes 设置为1是一样的。要使得断点在程序至少执行该代码一次之后才激活,可以将这个值设置为2或更大。例如,值为2时,使得断点在第二次执行到该代码时被激活。
该参数创建一个在每次执行断点处的代码时被减少1的计数器。要查看Passes 计数器的初始值和当前值,使用bl (Breakpoint List)。
Passes 仅当程序响应g (Go)命令并执行通过断点时才减少。单步或跟踪(tracing)通过它是不会减少的。当Passes 到达1时,可以通过清除并重设断点来重置它。
CommandString
指定每次遭遇断点指定次数后需要执行的命令列表。必须将CommandString 放到引号中。使用分号来分隔多条命令。
CommandString 中的调试器命令可以包含参数。可以使用标准C控制字符(如\n 和")。二级引号(")中的分号被当作引号中的字符串的一部分。
CommandString命令仅当程序响应g (Go)命令并执行通过断点时才会执行。单步或跟踪(tracing)执行断点处的命令时是不会触发的。
任何在中断后恢复程序运行的命令(如g 或t)都会结束命令列表的执行。
SymbolPattern
指定符号模板。调试器尝试使用已存在的符号来匹配该模板,并在所有匹配项上设置断点。SymbolPattern可以包含各种通配符和修饰符。该语法的更多信息,查看字符串通配符语法。因为这些字符时用来匹配符号的,这种匹配不区分大小写,并且 头部的单个下划线(_)表示任意数量的起始下划线。
三、注释
bp
、bu
和bm
命令设置新断点,但是它们有不同的特点:
指令 | 描述 |
---|---|
bp (Set Breakpoint) | 命令在指定地址的断点位置上设置断点。如果设置断点时调试器还不能将地址表达式计算为断点位置,bp 断点被自动转换为bu 断点。使用bp 命令来设置在模块被卸载之后就不会再被激活的断点。 |
bu (Set Unresolved Breakpoint) | 命令设置延迟的或未定断点。bu 设置在命令中指定的符号引用的断点位置上(不是一个地址上),并且当所引用的模块能够确定时激活。 |
bm (Set Symbol Breakpoint) | 命令在能够匹配指定模板的符号上设置断点。该命令可以设置多于一个的断点。默认情况下,当模板被匹配之后,bm 断点和bu 断点相同。即bm 断点是针对符号引用设置的延迟断点。但是bm /d 命令会创建一个或多个bp断点。每个断点都被设置在能够匹配的位置的地址上,并且不会跟随模块状态改变。 |
-
使用bp命令时,断点位置始终被转换成地址。如果bp 断点设置的代码被移动了,该断点仍然保持在相同位置并且可能指向不同的代码或者非法位置。
-
相反的,bu 断点始终和命令指定的符号化的断点位置关联(一般是符号加上一个可选的偏移)。这种关联在符号的值改变或者包含该位置的模块加载或卸载之后仍然保持。
-
使用bp 设置的断点会保持到使用bc (Breakpoint Clear)命令或WinDbg的Breakpoints 对话框移除为止。但是,因为这些断点指向的是一个地址,bp 断点在包含所引用的位置的模块卸载之后就不再有效了。
-
bu 设置的断点会保存在WinDbg工作空间中,但是bp 设置的断点不会保存。
-
当使用鼠标在WinDbg的反汇编窗口或源码窗口中设置断点时,调试器创建bu断点。
-
bm 在想使用包含通配符的符号模板来设置断点时很有用。
bm SymbolPattern
语法和使用x SymbolPattern
然后对搜索结果使用bu
是一样的。例如,要在WD01模块中所有以字符串"factor"开头的符号上设置断点,可以使用如下命令。
0:000> bm WD01!factor*3: 00007ff7`24771850 @!"WD01!factorical"0:000> bl0 e Disable Clear 00007ff7`247718a0 0001 (0001) 0:**** WD01!main1 e Disable Clear 00007ff7`24771932 0001 (0001) 0:**** WD01!main+0x923 e Disable Clear 00007ff7`24771850 0001 (0001) 0:**** WD01!factorical
由于bm
命令设置软断点(不是处理器断点),它会自动避开数据位置,以避免破坏数据。
但是,当使用bp
和bm /a
时要小心。这些命令可以在数据段中设置软断点。调试器在代码上设置软断点时,会将处理器指令替换为中断指令。但是当调试器在数据段设置软断点时,会把数据替换为中断指令。这种中断指令会破坏数据。只有在只当作代码进行执行的数据上设置软件断点才是安全的。要在数据段设置断点,使用ba
(Break on Access)命令。该命令可以设置数据断点而不是软件断点。
要在例如C++公有类这样的任意文本上设置断点,或者在operator new
函数上设置断点,需要将表达式括在圆括号中。例如,使用bp (??MyPublic)
或bp (operator new)
。
要在MASM表达式类型的任意文本上设置断点,使用bu @!"text"
。要使用C++语法文本设置断点,对C++兼容的符号使用bu @@c++(text)
。
bp
、bu
和bm
命令通过将处理器命令替换为中断指令来设置软断点。要调试只读代码或不能改变的代码,使用ba e
命令,e
用于设置执行访问。
如果单个逻辑代码行跨越了多个物理行,断点设置在语句或调用的最后一个物理行上。如果调试器在要求的位置不能设置断点,则会将断点放到下一个可用的位置上。
如果指定了Thread
,断点设置在指定线程上。例如,~*bp
在所有线程上设置断点, ~#bp
在产生当前异常的线程上设置,而 ~123bp
在线程123上设置。~bp
和~.bp
命令都在当前线程设置断点。
在内核模是下调试多处理器系统时,使用bp
或ba
(Break on Access)设置的断点会应用到所有处理器。例如,如果当前处理器是3,并且输入bp MemoryAddress
来在 MemoryAddress
上设置了断点。任何执行到该地址的处理器都会产生断点陷阱。
例如:在示例代码中,factorical
函数后4个字节的位置设置断点,忽略前两次,在第三次停下来。
0:000> bp WD01!factorical+0x04 2
0:000> bl0 e Disable Clear 00007ff7`247718a0 0001 (0001) 0:**** WD01!main1 e Disable Clear 00007ff7`24771932 0001 (0001) 0:**** WD01!main+0x922 e Disable Clear 00007ff7`24771854 0002 (0002) 0:**** WD01!factorical+0x4