# 仿真调试 # ## 外部资源 ## 在[training.ti.com](https://training.ti.com/debugging-common-application-issues-ti-rtos)上可以观看调试TI-RTOS常见应用程序问题的视频。 ## 调试接口 ## CC2640R2F平台支持cJTAG(2线)和JTAG(4线)接口。任何支持cJTAG的调试器,如TI XDS100v3和XDS200,都可以原生工作。其他接口,如IAR I-Jet和Segger J-Link,只能在JTAG模式下使用,但是驱动程序会注入一个cJTAG序列,保证在连接时可以进行JTAG模式。用于调试的设备上包含的硬件资源如下。调试器和IDE的所有组合不是所有的调试功能都可用。 * 断点单元(FBP) - 6个指令比较器,2个文字比较器 * 数据观察单元(DWT) - 4个内存访问点 * 仪表跟踪模块(ITM) - 32×32位激励寄存器 * 跟踪端口接口单元(TPIU) - DWT和ITM事件的序列化和时间戳 SmartRF06板包含XDS100v3调试探针,CC2650 LaunchPad包含XDS110调试探针。默认情况下,这些调试器在相应的示例项目中使用。 ### 连接到XDS调试器 ### 如果仅连接一个调试器,则当您单击CCS中的![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/icon_debug_ccs.png)或IAR中的![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/icon_debug_iar.png)中的按钮时,IDE会自动使用它。 如果连接了多个调试器,则必须选择要使用的各自调试器。以下步骤详细介绍如何在CCS和IAR中选择调试器。 ## 在CCS中配置调试器 ## 要在CCS中选择特定的调试器,请执行以下操作: ### 查找序列号 ### 要查找XDS100v3调试器的序列号,请执行以下操作: 1. 打开DOS命令窗口。 2. 运行`C:\ti\ccsv6\ccs_base\common\uscif\xds100serial.exe`以获取连接的调试器的序列号列表。 对于XDS110调试器(LaunchPads),请运行以下命令: `c:\ti\ccsv6\ccs_base\common\uscif\xds110\xdsdfu.exe -e` ### 配置序列号 ### 1. 打开目标配置文件。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/ccs_targetconfig_file.png) 2. 打开Advanced pane(高级窗格)。 3. 选择最顶上的调试器条目。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/ccs_targetconfig_probe.png) 4. 输入序列号。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/ccs_targetconfig_serial.png) ## 在IAR中配置调试器 ## 要使用IAR进行调试,请执行以下操作: 1. 打开项目选项(Project->Options) 2. 转到Debugger选项。 3. 转到Extra options。 4. 添加以下命令行选项: `--drv_communication=USB:#select` 添加此命令行选项使IAR提示哪个调试器用于每个连接。 ## 断点 ## IAR和CCS都保留了一个步进等效指令比较器。五个硬件断点可用于调试。本节介绍在IAR和CCS中设置断点。 ### CCS中的断点 ### 要切换断点,请执行以下任何操作。 * 双击行号左侧的区域。 * 按Ctrl + Shift + B。 * 右键单击该行。 在第207行设置的断点如下所示。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/ccs_breakpoint_ex.png) 有关活动和非活动断点的概述,请单击“View” - >“Breakpoints”。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/ccs_breakpoint_list.png) 要设置条件中断,请执行以下操作。 1. 右键单击overview中的断点。 2. 选择属性。 调试时,跳过计数和条件可以帮助跳过多个断点,或者只有在变量为特定值时才会中断。 >**注意**:条件中断需要调试器响应,并且可能会使处理器长时间停止,以断开有源RF连接,否则会中断调试目标上的时序。 ## IAR中的断点 ## 要切换断点,请执行以下任何操作: * 单击行号左侧的区域。 * 跳转到行并按F9切换断点 * 右键单击该行并选择切换断点(代码)。 断点如下所示: ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/iar_breakpoint_ex.png) 图101. 断点`PIN_init()`。调试器在`main()`的开头停止。 有关活动和非活动断点的概述,请单击View->Breakpoints。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/iar_breakpoint_list.png) 图102. 断点列表。右键单击以编辑选项,或取消选择以停用。 要设置条件中断,请执行以下操作。 1. 右键单击概述中的断点。 2. 选择编辑.... 调试时,跳过计数和条件可以帮助跳过多个断点,或者只有在变量为特定值时才会中断。 >**注意**:条件中断需要调试器响应,并且可能会使处理器长时间停止,以断开有源RF连接,否则会中断调试目标上的时序。 ## 关于断点的注意事项 ## ### 断点和时机 ### 同步RF协议(如蓝牙低功耗协议)是时序敏感的,断点可以很容易地停止执行足够长的时间,从而失去网络时序并破坏链路。 为了仍然能够进行调试,将断点尽可能靠近可以读取相关调试信息的位置,或者通过相关代码段进行调试。 在您打破断点并读出必要的调试信息后,建议您重置设备并重新建立连接。 ### 断点和优化 ### 当启用编译器优化时,在C代码行上切换断点可能不会导致预期的行为。一些例子包括以下内容。 **代码被删除或未被编译** 在IDE中切换断点会导致断点不在选定行上。某些IDE在非现有代码上禁用断点。 **代码块是公共子表达式的一部分** 例如,断点可能会从另一个函数调用的函数内部切换,但也可能由于来自另一个无意的函数的调用而中断。 **if子句由汇编中的条件分支表示** if子句中的断点在条件语句中总是中断,即使不执行。 TI建议在调试时选择尽可能低的优化级别。有关修改优化级别的信息,请参阅`Optimizations`。 ## 观看变量和寄存器 ## IAR和CCS提供了几种查看暂停程序状态的方法。全局变量在链接时静态放置,并且可以在项目中可用的RAM中的任何位置,或者如果它们被声明为恒定值,则可能在Flash中。可以随时通过Watch和Expression窗口访问这些变量。 除非由于优化而被移除,否则这些视图中总是可以使用全局变量。仅在有限范围内有效的局部变量或变量将放置在活跃任务的堆栈中。也可以使用Watch或Expression视图来查看这些变量,但也可以在打破或逐步执行代码时自动显示。要通过IAR和CCS查看变量,请执行以下操作。 ### CCS中的变量 ### 您可以通过执行以下任一操作来查看全局变量。 * 选择View->Expressions * 在代码中选择一个变量名。 * 右键单击并选择添加监视表达式。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/ccs_watch.png) 图103. 可变观察窗口。请注意,您可以转换值,获取地址和大小等。 * 选择“View” - >“Variables”到`auto-variables`以便在逐步执行代码时出现在当前位置。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/ccs_locals.png) 图104. 局部变量。该截图是在执行简单外设初始化功能时执行的。 ### IAR中的变量 ### 要查看全局变量,请执行以下任一操作。 * 右键单击该变量。 * 选择添加观察:varName。 * 选择View –> Watch n * 输入变量的名称。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/iar_watch.png) 图105. 可变观察窗口。请注意,您可以转换值,获取地址和大小等。 View –> Locals 显示IAR中的局部变量。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/iar_locals.png) 图106. 局部变量。该截图是在执行简单外设初始化功能时执行的。 ### 查看变量时的注意事项 ### 局部变量通常放在CPU寄存器中,而不是堆栈。这些变量也具有有限的使用寿命,即使在有效的范围内,这取决于所执行的优化。由于CCS和IAR的寿命有限,CCS和IAR都可能难以显示特定的变量。调试时的解决方法如下。 * 将变量移动到全局范围,因此它可以在RAM中访问。 * 使变量volatile,因此编译器不会将值放在寄存器中。 * 将易变的全局变量备份。 IAR可以在优化过程中删除该变量,并内联该值的使用。如果是这样,请在前面添加`__root`指令。 ## 内存观察点 ## 如`Debug Interfaces`中所述,DWT模块包含四个内存观察点,允许内存访问断点。硬件匹配功能仅在地址上。如果要用于变量,变量必须是全局变量。在IAR和CCS中使用观察点如下所述。 >**注意**:如果使用值匹配的数据观察点,则使用四个观察点中的两个。 ### CCS中的观察点 ### 1. 右键单击全局变量。 2. 选择断点 - >硬件观察点 3. 转到断点列表(查看 - >断点) 4. 右键单击并编辑断点属性以配置观察点。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/ccs_watchpoint_add.png) 图107. 在变量上添加观察点。 此示例配置确保如果将0x42写入蓝牙低功耗简单外设示例项目中的特征1的存储位置,则设备将停止执行。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/ccs_watchpoint_configure.png) 图108. 配置硬件观察点以打破值为0x42的8位写入。 ### IAR中的观察点 ### 1. 右键单击变量(全局)。 2. 选择`Set Data Breakpoint for 'myVar'`将其添加到活动断点。 3. 转到断点列表(View –> Breakpoints) 4. 选择`Edit...`以设置观察点是否应该在读取,写入或任何访问上匹配。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/iar_watchpoint_configure.png) 图109. 配置硬件观察点以打破值为0x42的8位写入。 ## TI-RTOS对象查看器 ## IAR和CCS都包括RTOS对象查看器(ROV)插件,可以深入了解TI-RTOS的当前状态,包括任务状态,堆栈等。因为CCS和IAR都有类似的接口,所以这些例子只讨论CCS。 访问IAR中的ROV: * 使用菜单栏上的TI-RTOS菜单。 * 选择subview。 访问CCS中的ROV: * 单击Tools menu。 * 单击 RTOS Object View (ROV)。 本节讨论一些对调试和分析有用的ROV视图。更多详细信息,请参见“TI-RTOS用户指南”,包括有关如何将日志事件添加到应用程序代码的文档。 ### 扫描BIOS的错误 ### BIOS`Scan for errors`通过可用的ROV模块浏览并报告错误。如果出现任何问题,这个功能可以是一个很好的开始。此扫描仅显示与TI-RTOS模块相关的错误,并仅显示可捕获的错误。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/rov_bios_scan.png) 图110. 扫描错误。这里有一个任务堆栈被超载。 ### 查看每个任务的状态 ### 任务 `Detailed`视图对于查看每个任务的状态及其运行时相关的堆栈使用情况非常有用。此示例显示第一次调用用户线程时的状态。图111示出了由其ICall代理,空闲任务,simple_peripheral任务和GAPRole任务展示的蓝牙低功耗堆栈任务。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/rov_task_detailed.png) 图111. 任务的详细视图。请注意,溢出任务的地址与扫描的错误匹配实例ID。 以下列表说明了图111中的条列。 **address** 此列显示每个任务的Task_Struct实例的内存位置。 **priority** 此列显示任务的TI-RTOS优先级。 **mode** 此列显示任务的当前状态。 **fxn** 此列显示任务的输入功能的名称。 **arg0,arg1** 这两列显示了可以赋予任务的入口函数的清晰值。在图像中,ICall_taskEntry为0xb001,它是RF堆栈图像的入口函数的Flash位置和0x20003a30(在main()中定义的bleUserCfg_t user0Cfg的位置)。 **stackPeak** 该列显示了基于RAM中起始位置运行时使用的最大堆栈内存,其中堆栈以0xBE预填充,并且在运行时堆栈的末尾有一个哨兵字。 >**注意**:函数调用可能会将堆栈指针推出运行时堆栈,但实际上并不写入整个区域。stackSize附近的堆栈峰值不超过它可能表示堆栈溢出。 **stackSize** 此列显示在实例化任务时配置的运行时堆栈的大小。 **stackBase** 此列显示任务的运行时堆栈的逻辑顶部(用法从stackBase+stackSize开始,并且下降到该地址)。 ### 查看系统堆栈 ### Hwi `Module`视图允许在boot或main(),Hwi执行和Swi执行期间用来对系统堆栈进行分析。有关系统堆栈的更多信息,请参阅`System Stack`。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/rov_hwi_module.png) 图112.在Hwi中查看系统堆栈 hwiStackPeak,hwiStackSize和hwiStackBase可用于检查系统堆栈是否溢出。 ## 使用内存浏览器 ## IAR和CCS都能够显示设备上的内存表示。在CCS中,您可以按地址或符号名称进行索引。例如,考虑图111中堆栈的溢出: `Simple Peripheral` Task的堆栈。注意`BE`水印 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/mem_browser_sbp_stack.png) `GAPRole` Task的堆栈。注意它已经完全填满了。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/mem_browser_gaprole_stack.png) 在这种情况下,解决方案是增加失败任务的堆栈大小,并查看堆栈的顶点是什么。该`stackPeak`报告是依靠许多水印字节覆盖,所以不能知道超限达多少。 因为堆栈从较高的地址到较低的寻址(在图像中向上)使用,所以超过的堆栈将倾向于在堆栈之前的位置覆盖数据。 ## 分析ICall堆管理器(heapmgr.h) ## 如`Dynamic Memory Allocation`(动态内存分配)中所述,ICall堆管理器及其堆用于在蓝牙低功耗堆栈任务和应用程序任务之间分配消息,并作为任务中的动态内存分配。 为ICall堆提供性能分析功能,但默认情况下不启用。因此,必须通过添加`HEAPMGR_METRICS`到定义的预处理器符号来编译它 。此功能可用于查找不明原因行为的潜在来源,并优化堆的大小。当`HEAPMGR_METRICS`被定义,如下变量和函数变得可用。全局变量: **heapmgrBlkMax** 同时分配块的最大数量 **heapmgrBlkCnt** 当前分配块的数量 **heapmgrBlkFree** 当前的空闲块数量 **heapmgrMemAlo** 当前总内存分配以字节为单位 **heapmgrMemMax** 块中同时分配的内存的最大数量(此值不能超过堆的大小) **heapmgrMemUb** 被分配的块的最远的存储位置,被测量为从堆的开始的偏移量 **heapmgrMemFail** 内存分配失败的数量(`ICall_malloc()`已返回NULL的实例) ### 函数 ### `void ICall_heapGetMetrics(u16 *pBlkMax, u16 *pBlkCnt, u16 *pBlkFree, u16 *pMemAlo, u16 *pMemMax, u16 *pMemUb)` 返回前面描述的作为参数传递的指针中的变量 `int heapmgrSanityCheck(void)` 如果堆是正常的则返回0; 否则返回非零(即数组访问已经溢出) ### 确定自动堆大小 ### 当启用自动堆大小功能(`HEAPMGR_SIZE=0`)时,以下过程可用于查看ICall堆的大小。 在运行状态中,当main()中的`ICall_init()`执行后,查看全局内存符号`HEAPMGR_SIZE`的值。`HEAPMGR_SIZE`的值是ICALL堆的总大小。请参阅heapmgr.h中的`HEAPMGR_INIT()`来查看源代码实现。 * 在IAR中:View -> Watch -> Watch 1,add `HEAPMGR_SIZE` * 在CCS调试会话中:View -> Expressions,添加 `HEAPMGR_SIZE` >**注意**:自动堆大小功能不能确定应用程序所需的堆数量。系统设计人员必须确保堆具有满足应用程序运行时内存需求所需的空间。 >由于内存放置,实际的堆大小可能最多减少4个字节。 通过检查IAR中的应用程序的map文件来计算ICall堆的大小: ICall堆的大小是.bss部分中最后一个项目的地址与系统堆栈(CSTACK)的起始地址之间的区别。例如,`simple_peripheral\cc2650lp\app.map`文件中在PLACEMENT SUMMARY结尾处的部分: 清单94.bss结束和CSTACK开始之间的空间用于ICall堆。 ```C .bss zero 0x20001cf8 0x2 app_ble_prm3.orm3 [2] .bss zero 0x20001cfa 0x1 TRNGCC26XX.o [1] .bss zero 0x20001cfb 0x1 driverlib_release.o [8] - 0x20001cfc 0x1af4 “A3”: 0x400 CSTACK 0x20003f50 0x400 CSTACK uninit 0x20003f50 0x400 - 0x20004350 0x400 ``` 该示例中的ICall堆的大小是CSTACK开始的地址减去.bss中的最后一个项目的地址或堆的地址。`0x20003f50 - 0x20001cfc = 0x2254`即`8788bytes` 通过检查CCS中的应用程序map文件来计算ICall堆的大小: 堆的大小由链接器发出的heapStart和heapEnd全局符号地址决定。例如,`simple_peripheral_cc2650lp_app.map`文件: 清单95. 在CCS中的ICall堆是使用了heapStart end和heapEnd start之间的空间。 ```C 20003f48 heapEnd 20001cc1 heapStart ``` 此示例中的ICall堆的大小定义为: `0x20003f48 - 0x20001cc1 = 0x2287`即`8839 bytes` ## 优化 ## 调试时,关闭或降低优化,以缓解单步执行代码。可以在项目,文件和功能级别配置优化级别。 ### 项目范围优化 ### 对于调试,理想情况下,优化的项目范围设置应尽可能的低。目标可能没有足够的可用空间来执行此操作,但降低几个级别可能会有所帮助。 **在IAR中** Project Options –> C/C++ Compiler –> Optimizations ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/iar_optim_level_project.png) 图113.IAR中的项目级优化设置 **在CCS中** Project Properties –> CCS Build –> ARM Compiler –> Optimization ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/ccs_optim_level_project.png) 图114. CCS中的项目级优化设置 ### 单文件优化 ### >注意: >进行单文件优化时要小心,因为这也会覆盖整个项目范围的预处理符号。 **在IAR** 1. 右键单击“工作区”窗格中的文件。 2. 选择Options。 3. 检查覆盖继承的设置。 4. 选择优化级别。 **在CCS中** 1. 右键单击“工作区”窗格中的文件。 2. 选择Properties。 3. 使用CCS项目范围优化菜单中相同的菜单更改文件的优化级别。 ### 单功能优化 ### 使用编译器指令,您可以控制单个函数的优化级别。 **在IAR** 在函数定义之前使用`#pragma optimize = none`来优化整个函数,也就是如下。 清单96. IAR中的函数级优化设置 ```C #pragma optimize = none static void myFunction(int number ) { // ... return yourFunction(other_number ); } ``` **在CCS中** 清单97. CCS中的函数级优化设置 ```C #pragma FUNCTION_OPTIONS(myFunction,“--opt_level = 0”) static void myFunction (int number ) { // ... return yourFunction (other_number ); } ``` ### 在ROM符号中加载TI-RTOS ### 一些TI-RTOS内核模块包含在ROM中,并从ROM执行,以节省应用程序的Flash空间。当反汇编视图和调用堆栈视图中只显示地址时,可能会导致一些混淆。 ROM中所有的TI-RTOS内核代码都以地址`0x1001xxxx`开头。为了了解ROM的代码,您需要在调试会话中包含符号文件。 **在IAR** ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/rtos_symbols_add_iar.png) 图115. 在ROM中添加BIOS内核的符号信息 * 在project options,在`Debugger`的`Images`选项中,添加图像 * `\kernel\tirtos\packages\ti\sysbios\rom\cortexm\cc26xx\r2\golden\CC26xx\rtos_rom.xem3` * 勾选`Debug info only`框,并使`Offset = 0`。 **在CCS中** ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/rtos_symbols_add_ccs.png) 图116. 通过添加符号在ROM中添加BIOS内核的符号信息。 * 在调试模式下,单击加载程序图标旁边的下拉按钮 * 选择Add Symbols.. * 选择Browse.. 并找到 * `\kernel\tirtos\packages\ti\sysbios\rom\cortexm\cc26xx\r2\golden\CC26xx\rtos_rom.xem3` ## 辨认CPU异常 ## 存在几种可能的异常原因。如果捕获到异常,则可以调用异常处理函数。根据项目设置,这个处理程序可能是ROM中的一个默认处理程序,它只是一个无限循环,或者是从这个默认处理程序调用的自定义函数而不是一个循环。 发生异常时,根据调试器的不同,在调试模式下该异常可能会被立即捕获并暂停。如果稍后通过断点调试器手动停止执行,则它将在异常处理程序循环中停止。 ### 异常原因 ### 使用TI-RTOS的默认设置,可以在寄存器`CFSR`(Configurable Fault Status Register)中的`System Control Space`寄存器组(CPU_SCS)中找到异常原因。`ARM Cortex-M3用户指南`介绍了该寄存器。大多数异常原因分为以下三类。 * 堆栈溢出或损坏导致任意代码执行。 * 几乎任何例外都是可能的。 * NULL指针已被取消引用并写入。 * 通常(IM)PRECISERR异常 * 外部模块(如UART,Timer等)被访问但没有供电。 * 通常(IM)PRECISERR异常 该`CFSR`寄存器可以在IAR和CCS的View->Registers下查看。 当发生访问冲突时,异常类型为`IMPRECISERR`,因为对Flash和外围存储器区域的写入大多是缓冲写入。 如果`CFSR:BFARVALID`标志在异常发生时被设置(典型的是`PRECISER`R),则可以读取`CPU_SCS`中的`BFAR`寄存器,以找出引起异常的内存地址。 如果异常是`IMPRECISERR`,`PRECISERR`可以通过手动禁用缓冲写入来强制执行。将`[CPU_SCS:ACTRL:DISDEFWBUF]`设置为1,可以通过手动设置能在IAR/CCS中查看的寄存器的的位,或者包含`Driverlib`中``并调用以下命令。 ```C #include // .. int main () { // Disable write-buffering. Note that this negatively affect performance. HWREG (CPU_SCS_BASE + CPU_SCS_O_ACTLR )= CPU_SCS_ACTLR_DISDEFWBUF ; // .. } ``` ### 使用TI-RTOS和ROV解析异常 ### 要在`RTOS Object View`(ROV)中启用异常解码而不使用太多内存,请使用TI-RTOS中的最小异常处理程序。BLE5-Stack工程中的默认选择是不使用异常处理程序。 要进行设置,请更改与M3Hwi相关的TI-RTOS配置文件的部分,使其类似于以下代码: ```C //m3Hwi.enableException = true; m3Hwi.enableException = false ; //m3Hwi.excHandlerFunc = null; m3Hwi.excHookFunc = “&execHandlerHook” ; ``` 然后,在某些地方做一个功能签名`void (*Hwi_ExceptionHookFuncPtr)(Hwi_ExcContext*)`像下面的这段代码一样: ```C #include // ... volatile uintptr_t * excPC = 0 ; volatile uintptr_t * excCaller = 0 ; // ... void execHandlerHook(Hwi_ExcContext * ctx ) { excPC=ctx-> pc ; // Program counter where exception occurred excCaller=ctx-> lr ; //Link Register when exception occurred while(2 ); } ``` 设置`m3Hwi.enableException`为`false`可以使填满ROV查看的全局Hwi_ExcContext结构的最小处理程序来显示已解码的异常。通过设置`excHookFunc`,最小异常处理程序将调用此函数并传递一个指向异常上下文的指针,供用户使用。此结构在``中定义。 当发生异常时,设备应该以无限循环结束。检查`ROV->Hwi->Exception information(异常信息)`。 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/rov_hwi_exception.png) 图117. 解码异常,故意写入地址0x0013是非法的。请注意,禁止写缓冲区以获取精确的错误位置,并且`m3Hwi.enableException`已设置为`false`以获得解码。 在这种情况下,在函数writeToAddress中通过取消引用地址0x0013并尝试写入它来使总线故障被强制执行: 清单98. 写入FLASH存储器区域中的一个地址。 ```C void writeToAddress (uintptr_t *addr,int val){ *(int*)addr = val; } // .. void taskFxn (...){ // .. writeToAddress((void* )19,4);//Randomly chosen values } ``` 写指令放在`application.c`的第79行,如图所示。要想获得精确的位置,如前所述写入缓冲区是禁止的。 查看PC(程序计数器)和LR(链接寄存器)指定的位置的反汇编视图可能是有启发性的。PC是假定的异常位置,LR通常是故障功能应该返回的位置。例如,在这个异常中的PC: ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/exception_pc_of_write.png) 图118. 这里`pc`从反汇编视图中查找了解码的异常。 这里需要一些取证。我们从ROV中的Hwi解码(和异常钩子中的异常上下文)得到当异常发生时程序计数器是`0x708e`。 在该位置有一个存储指令`str r0, [r1]`,意思是将R0中的值存储到R1中的存储器地址。上图中`SP`的事务与优化被关闭有关,因此所有局部变量都存储在堆栈中,即使在这种情况下,R0和R1也可以直接从调用者使用。 现在我们知道异常发生,因为有人用了无效地址来调用`writeToAddress`。 由于有了异常解码器,我们可以通过查看堆栈调用来轻松找到调用站点,但是如果堆栈调用没有帮助,我们可以看看`lr`,在异常解码器中看到的是`0x198f` ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/exception_lr_of_write.png) 图119 调用站点中指定`lr`。请注意,`lr`是调用`writeToAddress`后的指令,因为执行将在此恢复。 我们可以看到,R0和R1用常量初始化。这意味着某些程序员有意地用一个地址来调用`write function`导致总线故障的发生。 通常,总线故障的原因是指针没有被初始化,并且类似`writeToAddress`这样的函数获得指针,假设它是有效的,然后引用指针就会写入无效的址。 ## 调试内存问题 ## 本节介绍如何调试程序运行内存不足的情况,无论是在堆上还是在单个线程上下文的运行时堆栈上。数组越界或为结构体动态分配太少内存会破坏内存,并可能导致异常,如INVPC,INVSTATE,IBUSERR出现在CFSR寄存器中。 ### 任务和系统堆栈溢出 ### 如果任务的运行时堆栈或系统堆栈发生溢出(使用ROV插件发现),请执行以下步骤。 1. 请注意每个任务的运行时堆栈的当前大小。 2. 增加几个100字节,如 `Initializing a Task`(初始化任务)和`System Stack`(系统栈)中所述 3. 减少运行时堆栈的大小但要大于它们各自的stackPeaks,以便节省一些内存。 ### 动态分配错误 ### `Profiling the ICall Heap Manager (heapmgr.h)`(分析ICall堆管理器(heapmgr.h))介绍如何使用ICall堆分析功能。要检查是否发生动态分配错误,请执行以下操作: 1. 检查`heapmgrMemAlo`或`heapmgrMemAlo`时候接近`HEAPMGR_SIZE` 2. 检查`memFail`以查看是否发生了分配故障。 3. 调用合理的检查功能。 如果堆是合理的但存在分配错误,请增加`HEAPMGR_SIZE`并查看问题是否仍然存在。 您可以在`heapmgr.h`中的`HEAPMGR_MALLOC()`函数的`hdr = NULL`行设置一个断点,以找到失败的分配。 ### 选项 ### 表22.列出了`simple_peripheral`项目中应用程序使用的预处理程序符号。必须把未修改的符号在`Modify`列中标记为`N`,修改的符号在`Modify`列中标记为`Y`。 表22. 应用程序预处理器符号 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/ApplicationPreprocessorSymbols.png) 表23.列出了可能被修改的唯一堆栈预处理器选项: 表23. 堆栈预处理器符号 ![](http://www.leconiot.com/md_res/cc2640r2f/debugging/images/StackPreprocessorSymbols.png) ## 利用Map文件检查系统Flash和RAM的使用情况 ## 应用程序和堆栈项目都会生成一个map文件,可以用于计算Flash和RAM组合的内存使用情况。两个项目都有自己的内存空间,我们必须分析两个map文件来确定系统内存的总体使用情况。map文件位于IAR中相应项目的输出文件夹中。要计算总内存使用量,请执行以下操作。 1. 打开应用程序map文件(即simple_peripheral_cc2650r2lp_app.map)。 >注意: >在文件末尾,三行包含`read-only code`,`read-only data`和`read/write data`的内存使用情况。 2. 把`read-only code`,`read-only data`内存的值加起来。 >注意: >这个总和是应用程序项目总Flash使用量。`read/write data`存储器是应用程序项目的总RAM使用量。 3. 注意这些值。 4. 打开堆栈map文件。 5. 为堆栈项目计算相同的Flash和RAM值。 6. 把应用程序的总Flash值和堆栈的总Flash加起来确定系统总Flash使用情况。 7. 把应用程序和堆栈的RAM使用情况加起来以获得系统RAM的总体使用情况。 对于CCS,各自项目的map文件提供了Flash和RAM使用情况的总和。要确定每个项目的剩余可用内存,请参阅`Flash`和`RAM`。由于部分放置和对齐要求,某些剩余的内存可能不可用。只有工程构建和链接成功后,map文件中的内存使用情况才有效。 ## 调试一个程序退出 ## 一个程序永远不会退出`main()`函数,通常导致它退出的原因是某些软件模块调用了`abort()`。 当这种情况发生时,IAR和CCS都会停止执行,反汇编和调用堆栈将显示某些类型的`__exit`符号。 当以下情况发生时,BLE协议栈会调用`ICall_abort()`: * 从堆栈回调中调用ICall函数 * 错误配置额外的ICall任务或实体 * ICall任务注册不正确 如果调用堆栈没有提供足够的信息来推断出中止的原因,则可以在`ICall_abort`函数中设置断点以跟踪此错误的发生。 ## HAL断言处理 ## 断言在调试时很有用,可以在代码中捕获不良状态。BLE堆栈项目默认设置将`EXT_HAL_ASSERT`全局预处理器符号启用,这将尝试调用用户应用程序可以定义的断言处理程序。 ### 在应用程序中捕获堆栈断言 ### 应用程序有一个断言回调来捕获堆栈项目中的断言。断言回调在每个工程的main()函数中注册: ```C /*Register Application callback to trap asserts raised in the Stack*/ RegisterAssertCback(AssertHandler); ``` `main.c`文件也包含一个示例`AssertHandler`函数。 可在回调返回一些通用的断言原因包括`HAL_ASSERT_CAUSE_TRUE`, `HAL_ASSERT_CAUSE_OUT_OF_MEMORY`,和`HAL_ASSERT_CAUSE_ICALL_ABORT`。 当使用分割映像构建配置时,可能会获得`HAL_ASSERT_CAUSE_INTERNAL_ERROR`断言。这通常表示ICall `bleAPITable`调度表缺少一些函数,因此它调用通用`icall_liteErrorFunction`错误处理程序。通常,修复此错误是使缺少的预定义编译器选项能够获取正确的`API bleAPITable`。 用户可以决定如何在回调中处理这些断言。默认情况下,它将进入大多数断言的自旋锁。 断言还可以定义一个给定断言的更具体原因的子句。子句的一个例子是`HAL_ASSERT_OUT_OF_HEAP`描述导致断言的内存的类型`HAL_ASSERT_CAUSE_OUT_OF_MEMORY`。 如果没有注册应用程序回调,则调用默认的assert回调函数,并返回,而不需要进一步的操作,除非`HAL_ASSERT_SPIN`在应用程序项目中定义了这个回调 ,这个应用程序在无限循环中陷阱。另外,如果堆栈项目没有被捕获到应用程序回调中,也可以定义以下之一: * `HAL_ASSERT_RESET`:重置设备 * `HAL_ASSERT_LIGHTS`:打开危险指示灯(由用户配置) * `HAL_ASSERT_SPIN`:无限期地旋转一个循环 通过确保在预处理器符号中定义上述相应符号之一来启用这些。 有关实现细节,请参阅堆栈工程中的`hal_assert.h`和`hal_assert.c`。 ## 加入我们 ## 文章所有代码、工具、文档开源。加入我们[**QQ群 591679055**](http://shang.qq.com/wpa/qunwpa?idkey=d94f12d37c3b37892af4b757c6dc34bea140f3f3128a8d68e556a3d728148e85)获取更多支持,共同研究CC2640R2F&BLE5.0。

CC2640R2F&BLE5.0-乐控畅联 © Copyright 2017, 成都乐控畅联科技有限公司.