使用“仅我的代码”仅调试用户代码

仅我的代码是一项 Visual Studio 调试功能,可自动单步跳过系统、框架和其他非用户代码调用。 在调用堆栈窗口中,“仅我的代码”将这些调用折叠到 [External Code] 帧中。

在 .NET 和 C++ 项目中,“仅我的代码”的工作方式有所不同。

启用或禁用“仅我的代码”

对于大多数编程语言,默认都启用“仅我的代码”。

  • 要在 Visual Studio 中启用或禁用“仅我的代码”,请在“工具”>“选项”(或“调试”>“选项”)>“调试”>“常规”下,选择和取消选择“启用‘仅我的代码’”。

“选项”对话框中的“启用‘仅我的代码’”的屏幕截图。

“选项”对话框中的“启用‘仅我的代码’”的屏幕截图。

注意

启用‘仅我的代码’是全局设置,会应用于所有语言的全部 Visual Studio 项目。

“仅我的代码”调试

在调试会话期间,模块窗口会显示调试器将哪些代码模块视为“我的代码”(用户代码),以及其符号加载状态。 有关详细信息,请参阅熟悉调试器如何附加到应用

“模块”窗口中的“用户代码”的屏幕截图。

“模块”窗口中的“用户代码”的屏幕截图。

在“调用堆栈”或“任务”窗口中,“仅我的代码”将非用户代码折叠到标签为 [External Code] 的灰色带批注代码帧 。

“调用堆栈”窗口中的“外部代码”的屏幕截图。

“调用堆栈”窗口中的“外部代码”的屏幕截图。

提示

必须处于调试会话才能打开模块调用堆栈任务或大多数其他调试窗口。 调试时,在调试>窗口下,选择要打开的窗口。

要查看折叠的 [External Code] 帧中的代码,请在调用堆栈任务窗口中右键单击,然后从上下文菜单中选择显示外部代码。 展开的外部代码行将替换 [External Code] 帧。

“调用堆栈”窗口中的“显示外部代码”的屏幕截图。

“调用堆栈”窗口中的“显示外部代码”的屏幕截图。

注意

显示外部代码是当前用户探查器设置,会应用于用户打开的所有语言的全部项目。

双击调用堆栈窗口中展开的外部代码行,将在源代码中以绿色突出显示调用代码行。 对于 DLL 或其他未找到或未加载的模块,可能会打开“找不到符号或源代码”页面。

从 Visual Studio 2022 版本 17.7 开始,可通过在“调用堆栈”窗口中双击外部代码来自动编译 .NET 代码。 有关详细信息,请参阅在调试时从 .NET 程序集生成源代码

.NET“仅我的代码”

在 .NET 项目中,“仅我的代码”使用符号 (.pdb) 文件和程序优化来区分用户和非用户代码。 .NET 调试器将优化的二进制文件和非加载的 .pdb 文件视为非用户代码。

还有三个编译器特性会影响 .NET 调试器视为用户代码的内容:

.NET 调试器将所有其他代码视为用户代码。

在 .NET 调试期间:

  • 对非用户代码执行调试>单步调试(或 F11)会单步跳过该代码,转到下一行用户代码。
  • 对非用户代码执行调试>单步跳出(或 Shift+F11)会运行到下一行用户代码。

如果没有更多的用户代码,调试将继续进行直到结束、命中另一个断点或者引发错误。

如果调试器在非用户代码中中断(例如,使用调试>全部中断在非用户代码中暂停),则将显示没有源代码窗口。 然后可以使用调试>单步执行命令来执行下一行用户代码。

如果在非用户代码中出现未经处理的异常,则调试器会在生成异常的用户代码行处中断。

如果针对异常启用了第一机会异常,则在源代码中以绿色突出显示调用用户代码行。 调用堆栈窗口会显示标签为 [External Code] 的带批注帧。

C++“仅我的代码”

从 Visual Studio 2017 版本 15.8 开始,还支持使用“仅我的代码”来单步执行代码。 此功能还要求使用 /JMC(仅我的代码调试)编译器开关。 C++ 项目默认启用此开关。 对于“仅我的代码”中的调用堆栈窗口和调用堆栈支持,无需使用 /JMC 开关。

若要归类为用户代码,调试器必须加载包含用户代码的二进制文件的 PDB(使用模块窗口进行检查)。

对于调用堆栈行为,例如在调用堆栈窗口中,C++ 中的“仅我的代码”仅将以下函数视为非用户代码

  • 在其符号文件中去除了源信息的函数。
  • 符号文件指示没有对应于堆栈帧的源文件的函数。
  • %VsInstallDirectory%\Common7\Packages\Debugger\Visualizers 文件夹下 *.natjmc 文件中指定的函数。

对于代码单步执行行为,C++ 中的“仅我的代码”仅将以下函数视为非用户代码

  • 调试器尚未加载相应 PDB 文件的函数。
  • %VsInstallDirectory%\Common7\Packages\Debugger\Visualizers 文件夹下 *.natjmc 文件中指定的函数。

注意

为了在“仅我的代码”中支持代码单步执行,必须在 Visual Studio 15.8 预览版 3 或更高版本中使用 MSVC 编译器来编译 C++ 代码,并且必须启用 /JMC 编译器开关(默认启用)。 有关其他详细信息,请参阅自定义 C++ 调用堆栈和代码单步执行行为和此博客文章。 对于使用较旧编译器编译的代码,.natstepfilter 文件是独立于“仅我的代码”设置自定义代码单步执行的唯一方法。 请参阅自定义 C++ 单步执行行为

在 C++ 调试期间,默认情况下会跳过非用户代码。 在 C++ 调试期间:

  • 如果从非用户代码调用单步执行,则对非用户代码执行调试>单步执行(或 F11)会逐过程执行该代码,或运行到下一行用户代码。
  • 对非用户代码执行调试>单步跳出(或 Shift+F11)会运行到下一行用户代码(当前堆栈帧外部)。

如果没有更多的用户代码,调试将继续进行直到结束、命中另一个断点或者引发错误。

如果调试器在非用户代码中中断(例如,使用调试>全部中断在非用户代码中暂停),则会继续在非用户代码中单步执行。

如果调试器遇到异常,它会在异常处停止,无论是处于用户还是非用户代码中。 会忽略异常设置对话框中的用户未处理异常选项。

自定义 C++ 调用堆栈和代码单步执行行为

对于 C++ 项目,可以指定调用堆栈窗口视为非用户代码的模块、源文件和函数,方法是在 *.natjmc 文件中进行指定。 如果使用最新的编译器,则此自定义也适用于代码单步执行(请参阅 C++“仅我的代码”)。

  • 要为 Visual Studio 计算机的所有用户指定非用户代码,请将 .natjmc 文件添加到 %VsInstallDirectory%\Common7\Packages\Debugger\Visualizers 文件夹。
  • 要为单个用户指定非用户代码,请将 .natjmc 文件添加到 %USERPROFILE%\My Documents\<Visual Studio version>\Visualizers 文件夹。

.natjmc 文件是具有以下语法的 XML 文件:

<?xml version="1.0" encoding="utf-8"?>
<NonUserCode xmlns="http://schemas.microsoft.com/vstudio/debugger/jmc/2015">

  <!-- Modules -->
  <Module Name="ModuleSpec" />
  <Module Name="ModuleSpec" Company="CompanyName" />

  <!-- Files -->
  <File Name="FileSpec"/>

  <!-- Functions -->
  <Function Name="FunctionSpec" />
  <Function Name="FunctionSpec" Module ="ModuleSpec" />
  <Function Name="FunctionSpec" Module ="ModuleSpec" ExceptionImplementation="true" />

</NonUserCode>

模块元素属性

特性 说明
Name 必需。 模块的完整路径。 可以使用 Windows 通配符 ?(零个或一个字符)和 *(零个或多个字符)。 例如,应用于对象的

<Module Name="?:\3rdParty\UtilLibs\*" />

告知调试器中的所有模块都视为 \3rdParty\UtilLibs 外部代码的任何驱动器上。
Company 可选。 发布在可执行文件中嵌入的模块的公司的名称。 可以使用此特性消除模块歧义。

文件元素属性

特性 说明
Name 必需。 要视为外部代码的源文件的完整路径。 可以在指定路径时使用 Windows 通配符 ?*

函数元素属性

特性 说明
Name 必需。 要视为外部代码的函数的完全限定的名称。 可以在指定路径时使用 Windows 通配符 ?*
Module 可选。 包含函数的模块的名称或完整路径。 可以使用此特性区分具有相同名称的函数。
ExceptionImplementation 设置为 true 时,调用堆栈显示的是引发异常的函数,而不是此函数。

独立于“仅我的代码”设置自定义 C++ 单步执行行为

在 C++ 项目中,可以通过在 *.natstepfilter 文件中将函数列为 NoStepInto 来指定要逐过程执行的函数。 *.natstepfilter 文件中列出的函数独立于“仅我的代码”设置。 NoStepInto 函数会告知调试器逐过程执行函数,即使它调用了一些 StepInto 函数或其他用户代码也是如此。 与 .natjmc 中列出的函数不同,调试器将单步执行 NoStepInto 函数中的第一行用户代码。

  • 要为所有 Visual Studio 本地用户指定非用户代码,请将 .natstepfilter 文件添加到 %VsInstallDirectory%\Common7\Packages\Debugger\Visualizers 文件夹。
  • 要为单个用户指定非用户代码,请将 .natstepfilter 文件添加到 %USERPROFILE%\My Documents\<Visual Studio version>\Visualizers 文件夹。

注意

某些第三方扩展可能会禁用 .natstepfilter 功能。

.natstepfilter 文件是具有以下语法的 XML 文件:

<?xml version="1.0" encoding="utf-8"?>
<StepFilter xmlns="http://schemas.microsoft.com/vstudio/debugger/natstepfilter/2010">
    <Function>
        <Name>FunctionSpec</Name>
        <Action>StepAction</Action>
    </Function>
    <Function>
        <Name>FunctionSpec</Name>
        <Module>ModuleSpec</Module>
        <Action>StepAction</Action>
    </Function>
</StepFilter>

元素 说明
Function 必需。 将一个或多个函数指定为非用户函数。
Name 必需。 ECMA-262 格式的正则表达式,指定要匹配的完整函数名。 例如:

<Name>MyNS::MyClass::.*</Name>

告知调试器将 MyNS::MyClass 中的所有方法都视为非用户代码。 匹配区分大小写。
Module 可选。 ECMA-262 格式的正则表达式,指定包含函数的模块的完整路径。 匹配不区分大小写。
Action 必需。 以下区分大小写的值之一:

NoStepInto - 告知调试器单步跳过函数。
StepInto - 告知调试器单步调试函数,为匹配的函数重写任何其他 NoStepInto

有关 .natstepfilter.natjmc 文件的其他信息

  • 从 Visual Studio 2022 版本 17.6 开始,可以将 .natjmc.natstepfilter 文件直接添加到解决方案或项目中。

  • .natstepfilter.natjmc 文件中的语法错误不会在调试器的“输出”窗口中报告。

  • .natvis 文件不同,.natstepfilter.natjmc 文件不会热重载。 相反,这些文件将在调试会话开始时重新加载。

  • 对于模板函数,在名称中使用 &lt;.*&gt;&lt;.* 可能有所帮助。

JavaScript“仅我的代码”

JavaScript“仅我的代码”控件通过采用以下分类之一对代码进行分类,来控制单步执行和调用堆栈显示:

分类 描述
MyCode 你拥有或控制的用户代码。
LibraryCode 你定期使用的库中以及应用必需具备才能正常运行(例如 jQuery)的非用户代码。
UnrelatedCode 非自有应用中或应用无需具备也能正常运行的非用户代码。 例如,显示广告的广告 SDK 可能是 UnrelatedCode。

JavaScript 调试器按照以下顺序归类为用户代码或非用户代码:

  1. 默认分类。

    • 通过将字符串传递给主机提供的 eval 函数来执行的脚本为 MyCode
    • 通过将字符串传递给 Function 构造函数来执行的脚本为 LibraryCode
    • 框架引用(如 WinJS 或 Azure SDK)中的脚本为 LibraryCode
    • 通过将字符串传递给 setTimeoutsetImmediatesetInterval 函数来执行的脚本为 UnrelatedCode
  2. 当前项目的 mycode.json 文件中的分类。

每个分类步骤都会重写前面的步骤。

其他所有代码都分类为 MyCode

可以通过将一个名为 mycode.json.json 文件添加到 JavaScript 项目的根文件夹,来修改默认分类并将特定文件和 URL 分类为用户或非用户代码。 请参阅自定义 JavaScript“仅我的代码”

在 JavaScript 调试期间:

  • 如果某个函数为非用户代码,则调试>单步调试(或 F11)的行为与调试>单步跳过(或 F10)相同。
  • 如果某个步骤在非用户(LibraryCodeUnrelatedCode)代码中开始,则单步执行的行为方式会暂时如同未启用“仅我的代码”。 单步返回用户代码中时,会重启“仅我的代码”单步执行。
  • 当用户代码单步执行导致退出当前的执行上下文时,调试器将在下一个已执行的用户代码行处停止。 例如,如果某个回调在 LibraryCode 代码中执行,则调试器会继续,直到执行下一行用户代码。
  • 单步跳出(或 Shift+F11)会在下一行用户代码上停止。

如果没有更多的用户代码,调试将继续进行直到结束、命中另一个断点或者引发错误。

始终命中代码中设置的断点,但会对代码进行分类。

  • 如果 LibraryCode 中出现 debugger 关键字,调试器将始终中断。
  • 如果 UnrelatedCode 中出现 debugger 关键字,调试器不会停止。

如果 MyCode 中发生未经处理的异常或 LibraryCode 代码,调试器将始终中断。

如果 UnrelatedCode 中发生未经处理的异常,并且 MyCodeLibraryCode 位于调用堆栈上,调试器将中断。

如果针对异常启用了第一机会异常,并且在 LibraryCodeUnrelatedCode中发生异常:

  • 如果异常已处理,则调试器不会中断。
  • 如果不处理此异常,调试器则会中断。

自定义 JavaScript“仅我的代码”

要针对单个 JavaScript 项目对用户和非用户代码进行分类,可以将一个名为 mycode.json.json 文件添加到该项目的根文件夹。

mycode.json 文件无需列出所有键值对。 MyCodeLibrariesUnrelated 值可为空数组。

Mycode.json 文件使用以下语法:

{
    "Eval" : "Classification",
    "Function" : "Classification",
    "ScriptBlock" : "Classification",
    "MyCode" : [
        "UrlOrFileSpec",
        . . .
        "UrlOrFileSpec"
    ],
    "Libraries" : [
        "UrlOrFileSpec",
        . .
        "UrlOrFileSpec"
    ],
    "Unrelated" : [
        "UrlOrFileSpec",
        . . .
        "UrlOrFileSpec"
    ]
}

Eval、Function 和 ScriptBlock

EvalFunctionScriptBlock 键值对确定如何对动态生成的代码进行分类:

“属性” 描述
Eval 通过将字符串传递给主机提供的 eval 函数来执行的脚本。 默认情况下,Eval 脚本分类为 MyCode
Function 通过将字符串传递给 Function 构造函数来执行的脚本。 默认情况下,Function 脚本分类为 LibraryCode
ScriptBlock 通过将字符串传递给 setTimeoutsetImmediatesetInterval 函数来执行的脚本。 默认情况下,ScriptBlock 脚本分类为 UnrelatedCode

可以将值更改为以下关键字之一:

  • MyCode 将脚本分类为 MyCode
  • Library 将脚本分类为 LibraryCode
  • Unrelated 将脚本分类为 UnrelatedCode

MyCode、Libraries 和 Unrelated

MyCodeLibrariesUnrelated 键值对指定要包含在分类中的 URL 或文件:

“属性” 描述
MyCode 分类为 MyCode 的 URL 数组或文件数组。
Libraries 分类为 LibraryCode 的 URL 数组或文件数组。
Unrelated 分类为 UnrelatedCode 的 URL 数组或文件数组。

URL 或文件字符串可以包含一个或多个 * 字符,这些字符匹配零个或多个字符。 * 与正则表达式 .* 相同。