调试器

使用 Android 游戏开发扩展时,可以使用 Visual Studio 调试器 (LLDB) 调试项目。

运行调试器

在运行调试器之前,您必须能够在 Android 上构建、部署和运行游戏。有关详细信息,请参阅运行示例部分。

确保您可以在没有调试器的情况下运行游戏后,可以通过按F5或选择调试菜单中的开始调试项来使用调试器。在调试器附加到游戏时,您应该会看到一个对话框。

启动调试器需要 10 秒到 1 分钟或更长时间,具体取决于应用程序的大小和启动时需要加载的符号数量。在首次附加到新设备时,它需要更多时间,因为调试器必须将某些 Android 库从设备下载到主机。如果在使用新设备的前几次尝试中花费的时间超过 1 分钟,请考虑取消调试会话,然后重新启动它。

以这种方式运行调试器时,游戏将以等待调试器模式启动,并且在调试器连接之前不会执行任何游戏代码。这使您还可以调试游戏的初始化部分。

您可以通过阅读Visual Studio 文档,找到有关特定 Visual Studio 调试器功能的更多信息。

附加到进程

如果要调试已经在物理或虚拟设备上运行的游戏,可以从 Visual Studio 将调试器附加到该进程。

在 Visual Studio 中,确保已打开 Android 解决方案,然后

  1. 转到调试菜单,然后选择附加到进程...
  2. 传输下拉列表中,选择Android 游戏开发扩展
  3. 限定符下拉列表中,选择您的 Android 设备。
  4. 从可用进程列表中选择游戏进程,然后单击附加

Attach to process

执行 LLDB.Shell 命令

在调试会话处于活动状态时,使用 Visual Studio 的命令窗口运行 LLDB.Shell 命令。

命令格式

LLDB.Shell [command]

示例

>LLDB.Shell expr myIntVariable = 9
Status:  Success
Output Message:
(int) $2 = 9

数据可视化

格式说明符

您可以使用格式说明符更改在自动变量局部变量监视和变量数据提示窗口中显示的值的格式。

格式说明符位于表达式的末尾。它们以逗号后跟一个短字符串开头。例如,_myInt,x表达式中的,x说明符将myInt格式化为小写十六进制。

可以通过在监视窗口中或在自动变量局部变量数据提示窗口中添加它们到 Natvis 表达式中来直接使用格式说明符。有关更多信息,请参阅Natvis

支持的说明符列表

格式名称 说明符 描述
布尔值 B 将其显示为真/假布尔值,使用惯例规则,其中 0 为假,其他所有值为真
二进制 b 将其显示为一系列位
二进制,无前导 0b bb 将其显示为一系列位,没有 0b 前缀
字节 y 显示字节,但同时尝试将其显示为 ASCII 字符。
例如 (int *) c.sp.x = 50 f8 bf 5f ff 7f 00 00 P.._....
带 ASCII 的字节 Y 显示字节,但同时尝试将其显示为 ASCII 字符。
例如 (int *) c.sp.x = 50 f8 bf 5f ff 7f 00 00 P.._....
字符 c 将字节显示为 ASCII 字符
例如 (int *) c.sp.x = P\xf8\xbf_\xff\x7f\0\0
可打印字符 C 将字节显示为可打印的 ASCII 字符
例如 (int *) c.sp.x = P.._....
复数浮点数 F 将此值解释为复数浮点数的实部和虚部
例如 (int *) c.sp.x = 2.76658e+19 + 4.59163e-41i
十进制 d, i 将其显示为带符号整数(这不会执行强制转换,它只是将字节显示为带符号的整数)
枚举 E,en 将其显示为枚举,如果可用则打印值的名称,否则打印整数值
例如 (enum enumType) val_type = eValue2
十六进制 - 小写 x, h 以小写十六进制表示法显示此值(这不会执行强制转换,它只是将字节显示为十六进制)
十六进制 - 大写 X, H 以大写十六进制表示法显示此值(这不会执行强制转换,它只是将字节显示为十六进制)
十六进制 - 小写,无前导 0x xb, hb 以小写十六进制表示法显示此值,没有 0x 前缀(这不会执行强制转换,它只是将字节显示为十六进制)
十六进制 - 大写,无前导 0x Xb, Hb 以大写十六进制表示法显示此值,没有 0x 前缀(这不会执行强制转换,它只是将字节显示为十六进制)
浮点数 f 将其显示为浮点数(这不会执行强制转换,它只是将字节解释为 IEEE754 浮点数)
八进制 o 以八进制表示法显示此值
操作系统类型 O 将此显示为 MacOS OSType
例如 (float) x = '\n\x1f\xd7\n'
字符串 - C 字符串 s 将此显示为以 0 结尾的 C 字符串
例如 "hello world"
字符串 - C 字符串,无引号 sb 将此显示为无引号的以 0 结尾的 C 字符串,
例如 hello world
字符串 - UTF-8 s8 将此显示为以 0 结尾的 UTF-8 字符串
例如 u8"hello world ☕"
字符串 - UTF-8,无引号 s8b 将此显示为无引号的以 0 结尾的 UTF-8 字符串
例如 hello world ☕
字符串 - UTF-16 su 将此显示为以 0 结尾的 UTF-16 字符串
例如 u"hello world ☕"
字符串 - UTF-16,无引号 sub 将此显示为无引号的以 0 结尾的 UTF-16 字符串
例如 hello world ☕
字符串 - UTF-32 s32 将此显示为以 0 结尾的 UTF-32 字符串
例如 U"hello world ☕"
字符串 - UTF-32,无引号 s32b 将此显示为无引号的以 0 结尾的 UTF-32 字符串
例如 hello world ☕
unicode16 U 将此显示为 UTF-16 字符
例如 (float) x = 0xd70a 0x411f
unicode32 U32 将此显示为 UTF-32 字符
例如 (float) x = 0x411fd70a
无符号十进制 u 将此显示为无符号整数(这不会执行强制转换,它只是将字节显示为无符号整数)
指针 p 将此显示为本地指针(除非这确实是指针,否则结果地址可能无效)
复数整数 I 将此值解释为复数整数的实部和虚部
例如 (int *) pointer = 1048960 + 1i
字符数组 a 将此显示为字符数组
例如 (char) *c.sp.z = {X}
原始 ! 原始格式,忽略任何数据类型视图自定义

Natvis

Natvis 框架允许您自定义 Visual Studio 在调试器变量窗口中显示本机类型的方式。例如,使用 Natvis 自定义 **监视**、**局部变量** 和 **数据提示** 窗口的显示。

默认情况下启用 Natvis 功能,但可以通过将 Visual Studio 中的 **工具 > 选项 > Android 游戏开发扩展 > Natvis** 标志设置为 **禁用** 来禁用它。

加载 Natvis 文件

Visual Studio 从下面列出的三个位置加载 Natvis 文件,并在每次启动调试会话时重新加载它们。文件必须遵循 Visual Studio 2017 Natvis 架构。

  • .natvis 文件,它是已加载项目或顶级解决方案项目的一部分。
  • 用户特定目录 (%USERPROFILE%\Documents\Visual Studio 2017\Visualizers)
  • 系统范围的目录 (%VSINSTALLDIR%\Common7\Packages\Debugger\Visualizers)
重新加载 Natvis 文件

在调试会话期间,通过在命令窗口或监视窗口中评估 .natvisreload 来重新加载 Natvis 文件。

Natvis 文件示例

此 Natvis 文件示例包含当前支持的所有标签和属性。

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">

  <Type Name="demo::Vector&lt;*&gt;">
    <AlternativeType Name="MySimilarVectorType&lt;*&gt;"/>

    <!-- Included to show the <SmartPointer> feature is supported. -->
    <SmartPointer Optional="true" Usage="Minimal">ptr</SmartPointer>

    <!-- Included to show the <DisplayString> feature is supported. -->
    <DisplayString Condition="_size == 0" Optional="true">()</DisplayString>
    <DisplayString Condition="_size == 1">(x={_items[0]})</DisplayString>
    <DisplayString Condition="_size == 2">(x={_items[0]}, y={_items[1]})</DisplayString>
    <DisplayString Condition="_size == 3">(x={_items[0]}, y={_items[1]}, z={_items[2]})</DisplayString>
    <DisplayString>[Size={_size,x}] (x={_items[0]}, y={_items[1]}, z={_items[2]}, ...)</DisplayString>

    <!-- Included to show the <StringView> feature is supported. -->
    <StringView Condition="true" Optional="true">_stringViewText</StringView>

    <Expand HideRawView="false">
      <!-- Included to show the <Item> feature is supported. -->
      <Item Name="X" Condition="_size &lt; 4 &amp;&amp; _size &gt;= 1" Optional="true">_items[0]</Item>
      <Item Name="Y" Condition="_size &lt; 4 &amp;&amp; _size &gt;= 2" Optional="true">_items[1]</Item>
      <Item Name="Z" Condition="_size &lt; 4 &amp;&amp; _size &gt;= 3" Optional="true">_items[2]</Item>

      <!-- Included to show the <ArrayItems> feature is supported. -->
      <ArrayItems Condition="_size >= 4" Optional="true">
        <Size Condition="true" Optional="true">_size</Size>
        <ValuePointer Condition="true">_items</ValuePointer>
      </ArrayItems>

      <!-- Included to show the <IndexListItems> feature is supported. -->
      <IndexListItems Condition="true" Optional="true">
        <Size Condition="true" Optional="true">_listSize</Size>
        <ValueNode Condition="true">_list[%i]</ValueNode>
      </IndexListItems>

      <!-- Included to show the <LinkedListItems> feature is supported. -->
      <LinkedListItems Condition="true" Optional="true">
        <Size Optional="true">_listSize</Size>
        <HeadPointer>_head</HeadPointer>
        <NextPointer>_next</NextPointer>
        <ValueNode>_value</ValueNode>
      </LinkedListItems>

      <!-- Included to show the <ExpandedItem> feature is supported. -->
      <ExpandedItem Condition="true" Optional="true">_childVar</ExpandedItem>

      <!-- Included to show the <Synthetic> feature is supported. -->
      <Synthetic Name="[Size]" Condition="true" Optional="true">
        <DisplayString>_size</DisplayString>
        <Expand HideRawView="true">
          <!-- Any supported <Expand> sub-tags. -->
        </Expand>
      </Synthetic>

      <!-- Included to show the <TreeItems> feature is supported. -->
      <TreeItems Condition="true" Optional="true">
        <Size>_treeSize</Size>
        <HeadPointer>_head</HeadPointer>
        <LeftPointer>_left</LeftPointer>
        <RightPointer>_right</RightPointer>
        <ValueNode>_value</ValueNode>
      </TreeItems>

      <!-- Included to show format specifiers are supported. -->
      <Item Name="[Hex Dump at {_index,x}]">myInt[_index],x</Item>
    </Expand>
  </Type>
</AutoVisualizer>

编写 Natvis 文件

Visual Studio 支持编写您自己的 Natvis 文件。有关自定义调试器变量窗口的更多信息,请参阅 MSDN

调试 Natvis 文件

在某些情况下,错误将作为变量的 **值** 显示(例如,在 **自动**、**监视** 等窗口中)。例如:<error: use of undeclared identifier 'missingVar'>

您可以通过从 Android 游戏开发扩展工具栏打开 GoogleAndroid.log 文件来访问有关错误的更多详细信息。

已知限制

  • 如果您的标签或属性未在上面的示例文件中列出,则当前不支持它。Visual Studio 会忽略不受支持的标签和属性,因此您可以将它们保留在现有 Natvis 文件中,并且该文件将正常工作,只要它使用我们的架构即可。

  • 尽管架构需要 Usage 属性,但它不受 <SmartPointer> 支持。但是,LLDB 并不限制对 C++ 中定义的运算符的访问,因此可以在 C++ 中定义任何所需的运算符。