在调试器引擎的版本 10.0.25310.1001 及更高版本中,现在支持不明确的断点解析。
模糊断点允许调试器在某些情况下设置断点,这些情况下断点表达式会解析到多个位置。 例如,在以下情况下,可能会发生这种情况:
- 函数的多个重载。
- 有多个符号与断点表达式匹配。
- 同一个符号名称用于多个位置。
- 符号已被内嵌。
- 在源窗口中具有多个实例化的模板函数中设置断点。
启用后,调试器将在给定断点表达式的每个符号匹配上设置断点。 如果满足某些条件,调试器还将筛选符号匹配项。
有关使用断点的一般信息,请参阅 “使用断点”。
启用模糊断点解析
默认情况下,禁用不明确的断点。 若要在调试器会话中启用此功能,请在 WinDbg 控制台中运行以下命令:
dx @$debuggerRootNamespace.Debugger.Settings.EngineInitialization.ResolveAmbiguousBreakpoints = true;
若要确认模糊断点设置处于活动状态,请按照以下步骤操作:
0:010> dx @$debuggerRootNamespace.Debugger.Settings.EngineInitialization.ResolveAmbiguousBreakpoints
@$debuggerRootNamespace.Debugger.Settings.EngineInitialization.ResolveAmbiguousBreakpoints : true
有关使用 dx 命令的详细信息,请参阅 dx (显示调试器对象模型表达式)。
若要禁用该功能,请将上述值设置为 false
. 若要确保设置在会话之间保留,请确保单击 File -> Settings -> Debugger Settings
,然后选中标记为 Persist engine settings across debugger sessions
的框。
用法适用于单个断点
解析不明确的断点表达式仅适用于运行断点命令以在调试器中设置单个断点。 换句话说,使用 bm
命令设置多个断点将继续像往常一样工作。 执行启用此功能的命令会导致单个断点的行为模式发生变化。
有关断点命令的常规信息,请参阅 bp、bu、bm(设置断点)。
分层断点
分层断点表示将不明确的断点表达式解析为多个断点的结果。 如果表达式生成两个或多个用来设置断点的匹配项,则会创建另一个控制这些断点的主断点。 此重写断点(分层断点)可以像正常断点一样启用、禁用、清除和列出,同时具有增强功能,可以对子断点执行相同的操作。
例如,如果运行命令 bp foo!bar
,导致两个匹配项与符号 bar
匹配,则将创建一个分层断点来控制这两个匹配项。 如果启用/禁用/清除分层,匹配的断点也会相应被启用/禁用/清除。
.bpcmds(显示断点命令)将列出可以运行以设置每个断点的断点命令。 分层断点拥有的断点仍将列出一个有效的 bp 命令,该命令将在其地址上设置断点。 分层断点也将列在输出中,并显示可用于重新创建整个断点集的命令,而不仅仅是单个断点。
不明确符号
如果在符号名称上设置断点,当符号为以下之一时,应该导致下列行为:
重载:与符号匹配的每个重载都应具有断点。
模板函数:
如果表达式指定了所有模板参数(例如
bp foo!bar<int>
),则将在模板函数的特定实现上设置断点。如果表达式未指定类型实现(例如
bp foo!bar
),则不会设置断点。 在这种情况下,bm
应用于在模板函数上设置断点。调试器不支持部分模板规范,在这种情况下不会设置断点。
内联函数:每个内联位置都有一个断点
请注意,当符号表达式包含需要调试器进行更多计算的运算符或偏移量时,将不会设置多个断点。 例如,如果符号 foo
解析为多个位置,但表达式 foo+5
被计算,调试器将不会尝试解析所有可设置断点的位置。
断点代码示例
给定以下代码片段:
class BikeCatalog
{
public:
void GetNumberOfBikes()
{
std::cout << "There are 42 bikes." << std::endl;
}
int GetNumberOfBikes(int num)
{
std::cout << "There are " << num << " bikes." << std::endl;
return num;
}
};
调用命令 bu BikeCatalog::GetNumberOfBikes
将导致创建两个断点,每个重载都有一个断点。 列出断点将导致以下输出:
0:000> bl
2 e Disable Clear <hierarchical breakpoint> 0001 (0001) 0:**** {BikeCatalog!BikeCatalog::GetNumberOfBikes}
0 e Disable Clear 00007ff6`c6f52200 [C:\BikeCatalog\BikeCatalog.cpp @ 13] 0001 (0001) 0:**** BikeCatalog!BikeCatalog::GetNumberOfBikes
1 e Disable Clear 00007ff6`c6f522a0 [C:\BikeCatalog\BikeCatalog.cpp @ 9] 0001 (0001) 0:**** BikeCatalog!BikeCatalog::GetNumberOfBikes
不明确的源行
如果源行是以下情况之一,在该行上设置断点应会导致以下行为:
- 编译器优化函数:如果行由于编译器优化而拆分在多个位置,则将在函数中与指定行对应的最低位置设置断点。
- 内联函数:为每个调用点设置断点,除非指定的行已在内联过程中进行了优化。
- 解析到多个位置:如果未满足上述条件,则会根据以下条件为每个地址设置断点:
- 如果表达式中有一组与源行匹配的 N 地址,并且这些 N地址的子集 M 在表达式中的源行中没有源行位移,则只有 M 地址将具有断点。
- 如果在表达式中,N 地址集中没有地址的源行偏移量为零,则所有 N 地址都将设置断点。
基于符号索引进行筛选
每个符号都应具有唯一的符号索引。 有关符号结构的详细信息,请参阅 SYMBOL_INFO结构。
调试器将使用符号索引来确保在具有零源代码行偏移的多个地址情况下过滤掉重复的匹配项。
模板和重载函数的示例
模板函数
在模板函数定义的源行上设置断点,将为该模板函数的每个实现设置断点。 在BikeCatalog.cpp
中的第 19 行给出了以下模板函数:
template <class T>
void RegisterBike(T id)
{
std::cout << "Registered bike " << id << std::endl;
}
及其用法:
catalog.RegisterBike("gravel bike");
catalog.RegisterBike(1234);
调用命令 bp `BikeCatalog.cpp:19`
将设置两个断点,这些断点解析为稍后在文件中使用的模板函数的实现。 如果用户想要在函数上设置单个断点,则必须在模板函数实现的特定源行上设置断点,或者在具有适当类型信息(例如 bp BikeCatalog::RegisterBike<int>
)的模板函数符号上设置断点。
列出断点会导致以下输出:
0:000> bl
2 e Disable Clear <hierarchical breakpoint> 0001 (0001) 0:**** {BikeCatalog!BikeCatalog::RegisterBike<int>}
0 e Disable Clear 00007ff7`6b691dd0 [C:\BikeCatalog\BikeCatalog.cpp @ 20] 0001 (0001) 0:**** BikeCatalog!BikeCatalog::RegisterBike<int>
1 e Disable Clear 00007ff7`6b691e60 [C:\BikeCatalog\BikeCatalog.cpp @ 20] 0001 (0001) 0:**** BikeCatalog!BikeCatalog::RegisterBike<char const *>
重载函数
为重载函数的定义在源行上设置断点将导致重载函数的该定义仅产生一个断点。 重用上述代码片段,第一行从第 5 行开始:
class BikeCatalog
{
public:
void GetNumberOfBikes()
{
std::cout << "There are 42 bikes." << std::endl;
}
int GetNumberOfBikes(int num)
{
std::cout << "There are " << num << " bikes." << std::endl;
return num;
}
};
调用命令 bp `BikeCatalog.cpp:9`
将在行上设置一个断点,以便 void
实现 GetNumberOfBikes
。 列出断点会导致以下输出:
0:000> bl
0 e Disable Clear 00007ff7`6b691ec0 [C:\BikeCatalog\BikeCatalog.cpp @ 9] 0001 (0001) 0:**** BikeCatalog!BikeCatalog::GetNumberOfBikes
内联函数
在内联函数的调用站点的源行上设置断点只会在该特定调用站点上生成一个断点,即使同一函数中存在另一个调用站点也是如此。
多个分层断点
分层断点将拥有其集中的每个断点,除非:
清除其集合中的断点
- 已清除层级断点。
- 创建另一个分层断点,其中包括此分层断点集合中的一个断点。
另一种理解方法是,断点可能只有一个层次结构的断点所有者,并且最近的断点命令将决定断点列表的应有状态。
此外,分层断点不能拥有另一个分层断点。
将预先存在的断点进行合并
如果断点 A 本身存在,然后解析不明确的断点表达式以创建断点 A、 B,则 A 将包含在带有 B 的新断点集中。
包含分层断点集交集
如果分层断点 A 拥有断点 B 和 C,随后一个模糊的断点表达式被解析以创建断点:
B、 C、 D:断点 B、 C 将联接具有断点 D 的新分层断点组,并清除分层断点 A 。
C、 D 或 B、 D:其中一个断点将联接具有断点 D 的新分层断点组,而分层断点 A 将继续存在,其余一个未加入新组的断点。