2023年1月

我知道你现在在想什么。断点?真的吗?关于断点有什么需要掌握的?你按F9然后停在代码行。如果你是这么想的,这篇文章是给你的:继续读

 

Visual Studio提供了一组相当丰富的断点类型和操作,可以使您的调试体验更加舒适和高效。我们将在这篇文章中讨论四种断点。

条件断点

定义断点时,可以将其与条件关联(右键单击断点并选择何时命中)。在这种情况下,您几乎可以使用任何可以在即时窗口中计算的C++/C#语句,包括复杂表达式。调试器将在每次到达断点时计算表达式的值,并且仅当其计算结果为true时才会中断。

 

请注意,尽管断点可能只偶尔中断,但它仍然会减慢应用程序的速度,因为调试器必须在应用程序暂停时计算表达式,然后才能恢复它

追踪点---Tracepoints

断点不必中断应用程序的执行。在许多情况下,您只需要检查某些变量或表达式的当前值,然后立即继续执行。这不需要更改代码以包含跟踪语句:您只需将断点与跟踪操作相关联,跟踪操作将向Visual Studio输出窗口显示您想要的任何消息。同样,您可以显示复杂的表达式。

 

函数断点

即使没有函数的源代码,也可以在函数中设置断点。只需转到Debug>New Breakpoint>Break at函数并指定函数名。这也可以从“调用堆栈”窗口中获得-只需单击一个帧并按F9在该函数上放置一个断点。

 

数据断点

修改特定内存位置时,可以使用数据断点停止调试器。这个超级强大的特性对于多线程应用程序和各种内存损坏场景非常有用,在这些场景中,内存位置会以意外的方式修改。与源断点不同,您不会将断点放在代码行上。相反,您可以使用Debug>New Breakpoint>newdatabreakpoint菜单项指定内存位置。

 

不幸的是,这个特性只适用于C/C++应用程序。当托管对象在几代之间升级或发生堆压缩时,它们在堆中移动,使它们无法跟踪。

总结

如您所见,通过组合条件断点和跟踪点,您可以获得应用程序执行的跟踪,而无需向代码中添加任何跟踪语句或重新启动调试会话。通过添加函数断点和数据断点,可以进一步提高调试工作的灵活性。享受您的Visual Studio断点!

DML是一种非常简单的标记语言,它帮助基于现有命令的输出发现和执行新命令。许多WinDbg命令(以及扩展命令)都支持DML。例如,下面是lm D命令,它显示DML输出:

 

在上面的命令输出中,当我单击“SillyThreadPool”链接时,调试器为我执行了另一个命令lmDvmSillyThreadPool,它显示模块信息。同样,还有一个链接缺陷可以帮助我探索该模块中的符号和函数。

第一件事。您不必记住一堆奇怪的命令后缀就可以得到DML输出。有一个全局开关可以打开,.prefer_dml 1,这会导致许多内置命令和扩展命令显示dml输出。例如,下面是打开该开关后kn命令的输出:

 

单击时,此处的链接将切换到该帧并显示源代码和本地变量(执行的命令是.frame 0nN;dv/t/v)。
调试器脚本和扩展也可以生成DML输出。在调试器脚本中,只需使用.printf/D命令,并在其中嵌入DML链接。在调试器扩展中,可以使用IDebugClient::ControlledOutput函数输出DML。例如,下面显示单击时执行另一个命令的链接:

.printf /D "<exec cmd=\"lm m ntdll\">lm ntdll</exec>\n\n"

 

我敢打赌你不知道。应用程序本身可以向调试器输出DML命令!只需要使用outputDebugStringAPI,并在其中嵌入神奇的字符串<?dml?>. 该魔法令牌之后的所有内容都将被解释为DML字符串,并相应地显示在调试器中。例如,假设我们的应用程序中有以下代码:

OutputDebugString(L"Entered thread pool demo app.\n<?dml?><exec cmd=\"!uniqstack\">Dump unique stacks</exec>\n");

然后,调试器遇到此调试输出时将显示命令链接:

 

下一个命令是.dml_flow。此命令旨在通过将反汇编函数拆分为代码块并帮助使用DML链接在块之间导航,使其更易于读取。你自己试验这个命令比我用语言解释要容易得多,但总的来说,你提供了两个地址——一个开始地址和一个目标地址——这个命令帮助你理解从开始地址可以到达目标的代码路径。

 

前一个屏幕截图中的链接指向进入和退出屏幕上显示的基本代码块的跳转路径。

最近在调试几个问题时,发现跟abort函数有关,以前只是简单使用,现在却发现不简单,就多留意了下。

简介

abort中止当前进程并返回错误代码。异常终止一个进程。中止当前进程,返回一个错误代码。错误代码的缺省值是3。

代码

/***
*abort.c - abort a program by raising SIGABRT
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
* defines abort() - print a message and raise SIGABRT.
*
******************************************************************************
*/#include<cruntime.h>#include<stdlib.h>#include<internal.h>#include<awint.h>#include<rterr.h>#include<signal.h>#include<oscalls.h>#include<mtdll.h>#include<dbgint.h>#ifdef _DEBUG#define _INIT_ABORT_BEHAVIOR _WRITE_ABORT_MSG #else /* _DEBUG */ #define _INIT_ABORT_BEHAVIOR _CALL_REPORTFAULT #endif /* _DEBUG */unsignedint __abort_behavior =_INIT_ABORT_BEHAVIOR;/***
*void abort() - abort the current program by raising SIGABRT
*
*Purpose:
* print out an abort message and raise the SIGABRT signal. If the user
* hasn't defined an abort handler routine, terminate the program
* with exit status of 3 without cleaning up.
*
* Multi-thread version does not raise SIGABRT -- this isn't supported
* under multi-thread.
*
*Entry:
* None.
*
*Exit:
* Does not return.
*
*Uses:
*
*Exceptions:
*
******************************************************************************
*/ void__cdecl abort (void)
{
_PHNDLR sigabrt_act
=SIG_DFL;

#ifdef _DEBUG
if (__abort_behavior &_WRITE_ABORT_MSG)
{
/*write the abort message*/_NMSG_WRITE(_RT_ABORT);
}
#endif /* _DEBUG */ /*Check if the user installed a handler for SIGABRT.
* We need to read the user handler atomically in the case
* another thread is aborting while we change the signal
* handler.
*/sigabrt_act=__get_sigabrt();if (sigabrt_act !=SIG_DFL)
{
raise(SIGABRT);
}
/*If there is no user handler for SIGABRT or if the user
* handler returns, then exit from the program anyway
*/ if (__abort_behavior &_CALL_REPORTFAULT)
{
#if defined (_M_ARM) || defined (_CRT_APP)__fastfail(FAST_FAIL_FATAL_APP_EXIT);#else /* defined (_M_ARM) || defined (_CRT_APP) */ if(IsProcessorFeaturePresent(PF_FASTFAIL_AVAILABLE))
__fastfail(FAST_FAIL_FATAL_APP_EXIT);

_call_reportfault(_CRT_DEBUGGER_ABORT, STATUS_FATAL_APP_EXIT, EXCEPTION_NONCONTINUABLE);
#endif /* defined (_M_ARM) || defined (_CRT_APP) */}/*If we don't want to call ReportFault, then we call _exit(3), which is the
* same as invoking the default handler for SIGABRT
*/_exit(3);
}
/***
*unsigned int _set_abort_behavior(unsigned int, unsigned int) - set the behavior on abort
*
*Purpose:
*
*Entry:
* unsigned int flags - the flags we want to set
* unsigned int mask - mask the flag values
*
*Exit:
* Return the old behavior flags
*
*Exceptions:
* None
*
******************************************************************************
*/unsignedint __cdecl _set_abort_behavior(unsigned int flags, unsigned intmask)
{
unsigned
int oldflags =__abort_behavior;
__abort_behavior
= oldflags & (~mask) | flags &mask;returnoldflags;
}

简介

DBG_PRINTEXCEPTION_C,代码0x40010006;DBG_PRINTEXCEPTION_WIDE_C,代码0x4001000A;在调试器的控制台窗口打印异常信息/调试信息。它定义在 ntstatus.h头文件里,如下:

//
// MessageId: DBG_PRINTEXCEPTION_C
//
// MessageText:
//
// Debugger printed exception on control C.
//
#define DBG_PRINTEXCEPTION_C             ((NTSTATUS)0x40010006L)    // winnt

 

//
// MessageId: DBG_PRINTEXCEPTION_WIDE_C
//
// MessageText:
//
// Debugger printed exception on control C.
//
#define DBG_PRINTEXCEPTION_WIDE_C        ((NTSTATUS)0x4001000AL)    // winnt

它们唯一的差别是,DBG_PRINTEXCEPTION_C是多字节版本的,DBG_PRINTEXCEPTION_WIDE_C是Unicode版本的。

触发条件

OutputDebugString和其他类似的函数能有效地导致引发Windows异常(DBG_PRINTEXCEPTION_C或DBG_PRINTEXCEPTION_WIDE_C)。附加调试器后,Windows将接收异常并通知调试器。如果没有附加调试器,则将异常传递给程序(作为可继续的错误)。如果程序没有处理异常的处理程序,则继续生活。如果程序执行了,则调用该异常处理程序。,因为通常没有理由当OutputDebugString没有要打印到的调试器时,程序处理该事件。

异常结构填充

ExceptionAddress: 0xXXXXXXXX
ExceptionCode: 40010006/4001000A//错误代码
ExceptionFlags: 00000001
NumberParameters: 2//附加参数个数,一般为2个
Parameter[0]: //信息字符串长度
Parameter[1]://字符串指针

我最近花了一些时间分析OutputDebugString方法。在我的另一个实验中,我需要一个仅依赖于本机API的OutputDebugString版本。在实现它的过程中,我发现了一些关于OutputDebugString的有趣的事实,也许您也会感兴趣。

OutputDebugString的工作原理

简而言之,OutputDebugString尝试将消息发送到附加到给定进程的调试器,如果没有调试器侦听,则尝试将全局节映射到进程内存中并将调试消息保存在其中。我使用本机API实现OutputDebugStringA(ANSI版本)的示例如下:

voidNTAPI RtlOutputDebugStringA(_In_opt_ LPCSTR OutputString) {if(OutputString) {
EXCEPTION_RECORD exceptionRecord{
0};

exceptionRecord.ExceptionCode
=DBG_PRINTEXCEPTION_C;
exceptionRecord.NumberParameters
= 2;
exceptionRecord.ExceptionInformation[
0] = strlen(OutputString) + 1;
exceptionRecord.ExceptionInformation[
1] = reinterpret_cast<ULONG_PTR>(OutputString);

__try {
RtlRaiseException(
&exceptionRecord);
} __except (EXCEPTION_EXECUTE_HANDLER) {
NotifyGlobalDebugOutputMonitor(OutputString);
}
}
}