Hexagon_DSP_User_Guide(4)
5 调试
为什么它不起作用?
所有软件开发人员都时不时发现自己处于这种情况。 有很多方法可以解决这种情况,无论是否使用工具。
工具有时可能会指出错误的根源。 然而,这个过程通常并不快。 因此,最好先确保您没有犯最常见的错误之一,然后再花时间实际调试代码。 第 5.1 节讨论了导致程序崩溃或行为异常的常见错误。 第 5.2 节讨论了您可能希望用来帮助调试代码的各种工具。
5.1 常见问题
注意:由失败的 FastRPC 调用返回的所有错误都在 Hexagon SDK 在线文档的 FastRPC 调试部分进行了讨论。
-
对 FastRPC 使用不正确的数组大小
使用 FastRPC 将数组传递给 DSP 时,数组大小必须指定为数组中元素的数量,而不是其大小(以字节为单位)。 -
缺少 128 字节编译标志
如果编译函数以 128 字节 HVX 模式运行,则必须在编译代码时显式设置 -mhvx-double。 -
对标量访问使用不正确的内存对齐
从程序集进行内存访问时,您必须确保地址与您正在访问的大小对齐。 确保正确指针对齐的常用方法包括使用:
* 具有足够大的类型以保证对齐的静态分配。 例如,
HVX_Vector ptr[3];
* 具有对齐属性的静态分配。 例如,
int16 ptr[N] __attribute__((aligned(128)));
* 使用 memalign 进行动态内存分配。 例如,
int16* ptr = (int16 *)memalign(128,N);
* 动态内存分配比所需更多的内存,将最低位清零并在地址上添加 128 字节的偏移量。 例如,
int16* tmpPtr = (int16 *)malloc(128,N+127); int16* ptr = (int16*) (((int8*)tmpPtr+127) &~127); // ensure to free(tmpPtr) and not free(ptr) when done!
注意:HVX 加载和存储是未对齐的:内存访问指令只是忽略低位,产生对齐的地址,并允许代码运行而不会崩溃。
-
从 DSP 调用输入/输出库,例如 printf
对于 Hexagon SDK 3.2 或更早版本,printf 仅在 CPU 上运行时受支持。 在 DSP 上运行代码时,请改用 FARF 或使用 Hexagon SDK 3.3 或更高版本。 -
编译错误的目标
确保您的编译标志与您的目标架构兼容。 例如,不要在 MSM8996 设备上运行为 V62 编译的代码。 -
进行越界内存访问
这显然是任何目标架构上的常见错误,并且可能导致各种行为。 但是,此行为也适用于 L2 预取:如果起始地址无效,则 L2 预取会引发异常。但是,如果预取跨入无效页面,则简单地删除剩余部分而不会失败。 -
缺少功能实现
如果函数声明为 extern,则缺少的函数实现不会导致编译错误,但会导致运行时错误。 检查 DSP 日志中是否存在未定义的 PLT 符号 在 .so 消息中。 另外,请记住 C++ 会破坏函数名称,因此要从 C++ 文件访问用 C 或汇编语言编写的函数,该函数必须定义为 extern “C”。 -
使用不正确的函数声明
如果您修改了实现的参数,请记住修改其声明的参数。 否则,您的代码可能仍会编译,但您最终会向函数传递不正确的参数并获得意外行为。 -
修改被调用者保存的寄存器
调用约定指定保存哪些寄存器是调用函数的职责,哪些寄存器是被调用函数保存的职责。 编写汇编代码时,如果您在实现中修改它们,保存所有被调用者保存的寄存器很重要。 这些寄存器是 R16-R27 和 R29-R31。 以下示例显示了如何使用寄存器 R16-R19 执行此操作,例如:{ ALLOCFRAME(#2*8) // enough space for 4 32-bit registers // Note: needs to be a multiple of 8 } { memd(r29 + #0) = r17:16 memd(r29 + #8) = r19:18 } { <your code here> } { r17:16 = memd(r29 + #0) r19:18 = memd(r29 + #8) } { DEALLOC_RETURN }
-
在执行 HVX 代码之前不锁定 HVX 上下文。
在 V65 之前,您必须在运行任何 HVX 代码之前显式锁定 HVX 上下文。 -
未能使汇编代码与位置无关
测试模拟器时可以正常编译代码,编译的是静态库,但是编译动态库时编译失败。 这可能是由将寄存器设置为立即地址引起的:
test_array: .byte 0,1,2,3 ... r1 = #test_array
而不是使用 PC 相关方法:
r1 = ADD(PC,##test_array@PCREL)
-
堆栈内存不足
目前,线程堆栈为 16 KB。 不要使用过多的局部变量,并且要特别小心 HVX 内在函数,这可能会溢出到堆栈上并迅速加起来。
5.2 使用工具进行调试
5.2.1 qprintf 库
Hexagon SDK 带有 qprintf 库。 这个库提供了不同的 API 来显示来自 C/C++ 或汇编的各种格式的标量和向量寄存器。
例如,在您的汇编代码中包含 qprintf_asm.h 后,插入以下行以在日志中将 V0 的内容显示为连续的 32 位整数:
qprintf("v0 = %d",v0);
qprintf_example_asm.S[174]: v0 =
-1,-1,-1,-1,-1,-1,-1,-1
-1,-1,-1,-1,-1,-1,-1,-1
-1,-1,-1,-1,-1,-1,-1,-1
-1,-1,-1,-1,-1,-1,-1,-1
该库还支持更高级的选项,用于控制输出格式、要隐藏或显示的元素,或用于显示向量寄存器元素的列数。 例如:
qprintf("Displaying scalar registers in various
formats: %u, %x. %23d. %+.6d or %5.2f. Etc.",r20,r20,r20,r20,r21);
qprintf_example_asm.S[162]: Displaying scalar registers in various
formats: 20, 14, 20, +000020, 4.00. Etc.
另一个例子:
qprintf("Masked vector register contents as 16-bit unsigned integers in
rows of 5 elements: %m(5)uu",v0);
qprintf_example_asm.S[183]: Masked vector register contents as 16-bit
unsigned integers in rows of 5 elements:
[7e]= 65535,[7c]= 65535,[76]= 65535,[74]= 65535,[6e]= 65535
[6c]= 65535,[66]= 65535,[64]= 65535,[5e]= 65535,[5c]= 65535
[56]= 65535,[54]= 65535,[4e]= 65535,[4c]= 65535,[46]= 65535
[44]= 65535,[3a]= 65535,[38]= 65535,[32]= 65535,[30]= 65535
[2a]= 65535,[28]= 65535,[22]= 65535,[20]= 65535,[1a]= 65535
[18]= 65535,[12]= 65535,[10]= 65535,[0a]= 65535,[08]= 65535
[02]= 65535,[00]= 65535,
有关此库的更多详细信息,请参阅 {HEXAGON_SDK_ROOT}/libs/common/qprintf 中的库包及其文档(Qualcomm qprintf 库,80-VB419-109)
有关如何使用该库的示例,请参见 {HEXAGON_SDK_ROOT}/examples/compute/qprintf_example 中的代码示例。
5.2.2 构建目标
在完全调试应用程序之前,最好构建 DSP 库的调试风格。 这种风格使用较少的编译优化并启用诊断消息,从而更容易跟踪反汇编代码。
5.2.3 反汇编代码
当应用程序未按预期运行时,可能需要了解 DSP 上正在执行的确切指令。 检查编译器生成的汇编代码有时可以帮助您了解错误的来源,即您希望 C 代码执行的操作与实际解释的内容之间的不匹配。
如果 foo.c 使用 hexagon_ReleaseG_dynamic_toolv81_v60 目标编译,则以下命令将 C/C++ 源代码与任何给定目标文件的相应程序集交错,并将输出写入 temp.txt:
hexagon-llvm-objdump -disassemble -source -demangle
hexagon_ReleaseG_dynamic_toolv81_v60\foo.o > temp.txt
注意:hexagon-llvm-objdump.exe 位于作为 Hexagon SDK 的一部分提供的 Hexagon 工具的 tools\bin 目录中。 -source 标志仅从 8.1.x Hexagon 工具开始可用,这些工具用于 SDK 3.3 及更高版本。
在某些情况下,编译器优化使得很难准确地遵循每个 C 表达式是如何被翻译成汇编的,并且在禁用所有优化的情况下重新编译代码以促进调试过程可能很有用。
禁用所有编译器优化是通过构建 Debug 风格或在 hexagon.min 中添加以下行来完成的:
_OPT = -O0
当模拟器报告崩溃时,它会返回以下信息:
- 错误代码 (SSR) 指示发生的异常类型
- 发生异常时执行的指令的 PC 值 (ELR)
- 适用时,表示异常发生时访问的数据的虚拟地址(BADVA)
如果使用 -r 选项(用于重定位)反汇编代码,则使用崩溃报告中返回的 ELR 值在反汇编代码中查找发生异常的对应位置。 此外,Hexagon V66 程序员参考手册 (80-N2040-42) 中的表 7-15 解释了各种 SSR 代码的含义。
5.2.4 使用调试器
从 Hexagon SDK 3.3 开始,可以在目标或模拟器模式下将 LLDB 调试器与 MSM8998 和更新的设备一起使用。 但是,没有针对早期处理器变体的目标调试器支持。
访问调试器的最简单方法是从 Eclipse 中使用它。 Hexagon SDK 在线文档的调试 > 远程调试器部分描述了从基于 make 的 Hexagon 项目创建 Eclipse 项目并设置调试支持目标的过程。
完成这些步骤后,您可以执行常见的调试操作,例如单步执行代码、使用断点以及检查寄存器和内存内容。
5.2.5 获取和分析崩溃报告
当 DSP 发生崩溃时,FastRPC 处理程序会向 logcat 发送崩溃报告。 如 Hexagon SDK 在线文档的异常部分所述,此崩溃报告包括以下信息:
- 用户进程名
- 线程名称
- 共享对象的名称及其加载地址
- 异常类型及其详细信息
- 最后已知的 PC 值
- 回调跟踪
如果此信息未生成或不足以调试您的应用程序,则强制 DSP 崩溃,收集崩溃转储,并使用调试器分析转储以获取其他信息,例如设备上运行的所有线程的回溯. 这些步骤记录在 Hexagon SDK 在线文档的 PD 转储收集部分。