文章目录
1.Breakpoint
1.1 Breakpoints Window
断点是程序地址或表达式,当条件为真时,会暂停程序执行或执行指定的命令。可以通过以下几种方式定义和修改断点:
- 使用"Insert/Remove
Breakpoint"工具栏按钮插入断点。在编辑器或反汇编窗口中选择代码行,然后单击工具栏按钮或按下F9键。 - 在编辑器或反汇编窗口的左边栏点击。
- 使用编辑器或反汇编窗口的上下文菜单。
- 使用调试命令BreakSet、BreakAccess、BreakKill、BreakList、BreakEnable和BreakDisable。
- 参考SBC命令(仅适用于Cortex-M)。
以上是定义和修改断点的几种常见方法。
- Expression type
-
Access Break(A)在设置Read、Write或两者都设置标志时定义。当发生指定的内存访问时,触发断点。以字节或表达式的对象大小指定内存访问窗口的大小。对于此断点类型,表达式必须解析为内存地址和内存类型。允许使用的运算符(&、&&、<、<=、>、>=、==和!=)在程序执行被暂停或执行命令之前比较变量值。
-
Execution Break(E)在Expression解析为代码地址时定义。当到达指定的代码地址时触发断点。代码地址必须引用CPU指令的第一个字节。
-
Conditional Break(C)在Expression不能简化为地址时定义。当条件表达式成为TRUE时,触发断点。条件表达式在每个CPU指令后重新计算,可能会显著减慢程序的执行速度。
-
- Count
计数值指定了在断点触发之前断点表达式必须评估为TRUE的次数。在第一次触发后,计数将被忽略。换句话说,在初始触发后,无论计数值如何,断点将继续触发。 - Command
当指定了一个命令时,µVision会执行该语句,然后恢复程序执行。这里指定的命令可以是µVision调试或信号函数。要从这些函数内部停止程序执行,请设置系统变量_break_。
1.2 Breakpoint Examples
- 勾选Access方式Read或Write,设置Count值,点击Define。这里选择Write,Count值为4,表示该变量第四次被写入时程序会停止;
- 勾选Access方式Read或Write,删除Expression下原来的内容,填写表达式“AD== 10”。点击Define。这样当AD==10时程序会停止。
- 在Command下填写printf(),则在运行到断点处时,程序不会停止,而是在Command窗口打印设置的信息;
1.3 BreakSet命令
- BreakSet命令可以为指定的表达式(exp)设置断点。断点是程序地址或表达式,当为True时,会暂停目标程序的执行或执行指定的命令(“cmd”)。
- exp可以是一个地址规范或在执行期间进行评估的表达式。请参考“表达式”以获取详细信息。
- cnt是一个表达式,用于确定在目标程序停止执行或执行指定的命令之前,断点条件满足的次数。默认的计数值为1。在第一个断点触发后,计数将被忽略。
- cmd是一个µVision命令字符串,在断点触发时执行。程序执行不会被停止。用户定义的和信号函数可以在命令表达式中使用。在函数中,您可以将系统变量_break_设置为1来停止程序执行。当没有指定命令时,程序执行被暂停。
- 有效的断点示例:
-
设置一个在time.sec写入时触发的断点:BS WRITE time.sec
-
设置一个在主函数地址处执行时触发的断点:BS main
-
设置一个在timer0函数地址处执行并在第10次调用之后触发的断点,并执行命令"MyRegs()":BS timer0,10,“MyRegs()”
-
设置一个当sindex等于8时触发的条件断点:BS sindex == 8
-
设置一个当save_record[5].time.sec大于5时触发的条件断点,并在第三次满足条件时停止程序执行:BS save_record[5].time.sec > 5, 3
-
设置一个在读取interval.min访问时,当interval结构或联合的min元素为3时触发的内存访问断点:BS READ interval.min == 3
-
设置一个在写入savefirst访问时,并且savefirst为5且累加器(acc)为0x12时触发的内存访问断点:BS WRITE savefirst == 5 && acc == 0x12
-
对于Cortex-M处理器,使用常量作为地址表达式可能会导致数据大小比较不明确。可以使用指针类型转换来消除歧义。例如:
设置一个在地址0x20000018处写入0x00000003时触发的内存访问断点:BS WRITE * ((unsigned int*)0x20000018) == 0x00000003 -
对于Cortex-M处理器,可以使用相对路径指定文件并设置执行断点。例如:
设置一个在cpp_template应用程序中文件STM32F10x.s的第136行代码执行之前触发的断点:BS \cpp_template…/…/source/RVCT/STM32F10x.s\136, 1
2.Debug Function
2.1 简介
Debug函数是µVision的一个强大功能,可以帮助调试和测试应用程序。Debug函数可以实现以下功能:
- 扩展µVision调试器的功能。
- 生成外部中断。
- 将内存内容记录到文件中。
- 定期更新模拟输入值。
- 向片上串行端口输入串行数据。
Debug函数使用C编程语言的子集。其基本的能力和限制如下:
- 流程控制语句可以在debug函数中使用,并按照ANSI
C的操作方式(if、else、while、do、switch、case、break、continue和goto)。 - 局部标量变量的声明方式与ANSI C相同。
- 在debug函数中不允许使用数组。
本章包含以下几节内容:
- 创建函数介绍了内部的Debug函数编辑器。
- 调用函数介绍了调用debug函数的选项。
- 预定义函数列出了µVision提供的内置debug函数。
- 用户函数是开发人员创建的用于测试或调试应用程序的函数。
- 信号函数在程序运行时执行重复操作。
- µVision和C之间的区别解释了µVision Debug函数与ANSI C之间的语法和语义差异。
注意:
µVision Debug函数和目标程序的函数是不同的。µVision Debug函数用于调试应用程序,可以在命令窗口或函数编辑器中创建。它们不是目标应用程序的一部分。
2.2 Creating Functions
-
使用内置的Debug函数编辑器创建、修改和编译debug函数。在调试模式下,通过菜单Debug - Function Editor打开编辑器。系统会要求您输入一个文件名,或者编辑器会打开在“Options for Target - Debug - Initialization File”字段中指定的初始化文件。该文件的内容在每次启动调试会话时进行执行编译处理并且执行脚本函数,类似于python 脚本编译器。
-
在debug 环境下,在命令行窗口,在调试会话中使用INCLUDE命令来读取和处理文件。在调试过程中,定义在该文件中的Debug函数会立即生效。调试命令会在解析文件时执行。例如:
>INCLUDE MYFUNCS.INI
2.3 触发函数调用
从命令窗口调用和运行debug函数。在命令行中输入函数名和参数。例如,要运行内置的printf函数,请输入以下文本:
>> printf ("Hello World\n")
µVision调试器将打印文本"Hello World"到输出。
作为另一种方法,可以定义一个工具栏按钮来调用该函数:
DEFINE BUTTON "button_label", "command"
DEFINE BUTTON "Print HelloWorld", "printf ("Hello World\n")"
用户鼠标点击工具栏按钮即可触发;
2.4 预定义函数
预定义函数可以帮助开发人员调试应用程序并创建自己的函数。
下表列出了所有预定义的调试函数:
2.5 自定义函数
用户函数是由开发人员创建的,可用于µVision调试器中进行调试。可以直接在命令窗口或函数编辑器中创建用户函数。有关详细信息,请参阅创建函数。用户函数可以使用系统变量。
用户函数以关键字FUNC开头,定义如下:
FUNC 返回类型 函数名 (参数列表) {
语句
}
其中:
- 返回类型是函数返回值的类型,可以是bit、char、float、int、long、uchar、uint、ulong、void。如果没有指定返回类型,则默认为int类型。
- 函数名是函数的名称。
- 参数列表是传递给函数的参数列表。每个参数都必须有类型和名称。如果没有参数传递给函数,则在参数列表中使用void。多个参数用逗号分隔。
- 语句是函数执行的指令。
- 大括号{}是开放和闭合的括号。只有当开放的大括号与闭合的大括号数量平衡时,函数定义才算完整。
示例:
以下用户函数显示了几个CPU寄存器的内容。
FUNC void MyRegs (void) {
printf ("---------- MyRegs() ----------\n");
printf (" R4 R8 R9 R10 R11 R12\n");
printf (" %04X %04X %04X %04X %04X %04X\n",
R4, R8, R9, R10, R11, R12);
printf ("------------------------------\n");
}
通过在命令窗口中输入函数名来调用函数。
>> MyRegs()
函数输出可能如下所示:
2.6 Signal Functions
Signal Functions可以在µVision运行目标程序的同时在后台重复执行操作。它们有助于模拟和测试串行和模拟I/O、端口通信和其他重复的外部事件,如信号输入和脉冲。
开发人员可以直接在命令窗口或函数编辑器中创建Signal Functions。有关详细信息,请参阅Creating Functions。
必须至少调用内置函数twatch一次以延迟执行并让µVision运行目标程序。对于从未调用twatch的Signal Functions会触发错误。系统变量可以在Signal Functions中使用。
Signal Functions以关键字SIGNAL开头,定义如下:
SIGNAL void fname (parameter_list) {
statements
}
其中,
- fname是函数的名称。
- void是Signal Functions的返回类型必须为void。
- parameter_list是传递给函数的参数列表。每个参数都必须有类型和名称。如果没有参数传递给函数,则在参数列表中使用void。多个参数用逗号分隔。一个Signal
Function最多可以有八个参数。 - statements是函数执行的指令。
- {}是开放和闭合的括号。只有当开放的大括号与闭合的大括号数量平衡时,函数定义才算完整。
示例:
以下Signal Function将字符’A’放入串行输入缓冲区中,延迟1,000,000个CPU状态,然后重复执行。
SIGNAL void StuffS0in (void) {
while (1) {
S0IN = 'A';
twatch (1000000);
}
}
在命令窗口中输入函数名称来调用该函数。
>> StuffS0in()
µVision维护着所有活动Signal Functions的队列。一个Signal Function可以处于空闲(idle)或运行(running)状态。当一个Signal Function调用twatch函数时,它会进入空闲状态,持续的CPU状态数由twatch指定。在用户程序执行了指定数量的CPU状态后,Signal Function会变为运行状态。执行将继续在twatch之后的语句上进行。
当调用Signal Function时,它会被添加到队列并标记为运行状态。Signal Function只能被调用一次。如果函数被调用两次,则会显示警告信息。
如果一个Signal Function因为return语句而退出,则它会自动从活动Signal Functions的队列中移除。
命令SIGNAL STATE可以显示所有活动Signal Functions的状态。
命令SIGNAL KILL fname可以从队列中移除一个Signal Function
2.7 Differences Between µVision and C
ANSI C和µVision中用于支持用户函数和Signal Functions特性的语言子集之间存在一些区别。
-
µVision不区分大小写。对象和控制语句的名称可以使用大写或小写字母。
-
µVision没有预处理器。不支持预处理指令,如#define、#include和#ifdef。
-
µVision不支持全局声明。标量变量必须在函数定义内部声明。您可以使用DEFINE命令定义符号,并像使用全局变量一样使用它们。
-
在µVision中,变量在声明时不能进行初始化。必须使用显式的赋值语句来初始化变量。
-
µVision函数仅支持标量变量类型。不允许使用结构体、数组和指针。这适用于函数的返回类型和参数类型。
-
µVision函数只能返回标量变量类型,不能返回指针和结构体。
-
µVision函数不能递归调用。在函数执行过程中,µVision会识别递归调用,并在检测到递归时中止函数执行。
-
µVision函数只能通过函数名直接调用,不支持通过指针进行间接函数调用。
-
µVision仅支持具有参数列表的ANSI风格函数声明。不支持旧的K&R(Kernighan和Ritchie)格式。
例如,以下是可接受的ANSI风格函数声明:
func test (int pa1, int pa2) { /* ... */ }
以下是不被接受的K&R风格函数声明:
func test (pa1, pa2) int pa1, pa2; { /* ... */ }
3 debug function 应用(独创)
3.1 非侵入式调试实现
-
keil debug script 提供debug function 的扩展功能,能够使用类C语言风格的函数与变量,不能使用结构体定义,宏定义等,且在debug script中无法调用工程已经定义的C函数,会产生***error 34:undefined identifier,其函数库仅限于debug function,但是可以引用C环境下的全局变量(包括函数变量);
-
设计如下流程可以在命令行中触发C语言环境下的函数执行:
1:用户在命令行输入如下命令:>> test_function_2(uart_send_cmd,0x20002008,0x10)
在debug模式下,输入命令可以不打断C语言环境运行,只是在命令输入时刻插入运行部分代码;
2:触发函数执行,部分函数执行于PC端,通过func_addr = addr,直接给C语言环境下全局变量赋值,通过set_bit 函数调用_RDWORD _WDWORD等预定义函数操作CPU寄存器; set_bit(0xE000ED04UL,28,1,1); 用于设置SVC 标志位触发 SVC_Handler函数执行;FUNC void test_function_2(int addr,int param1,int param2) { func_addr = addr; func_param_num =2; cmd_debug_flag =1; func_param_1 = param1; func_param_2 = param2; debug_dhcsr = _RDWORD(0xE000EDF0); set_bit(0xE000ED04UL,28,1,1); exec("G"); }
3:test_function_2 函数退出,芯片硬件环境继续执行;
4:芯片硬件探测SVC 被置起,跳转到SVC_Handler 函数执行,最终跳转到PendSV_Handler函数中执行;
5:PendSV_Handler 判断 cmd_debug_flag 触发是否由debug function触发,若是则通过强制转换实现目标函数执行;
6:uart_send_cmd(0x20002008,0x10)函数执行将相关内存中的内容发送出去;函数入参可以为全局变量,或者具体的地址数值,需要用户清楚理解函数调用的入参;
7:清除相关寄存器的触发设置,使得系统全速运行;
3.2 效果:
1: 快速实现类python/ 类shell 执行效果,相对而言,不用绑定函数和命令,格式类似于C语言的调用;
比如:可以直接调用UART等接口,也可以调用更上层的函数API,比如HCI_SEND() HCI底层实现调用UART接口发送数据;
2:可以快速调试系统,在C环境下添加一下获取系统信息的函数,可以在函数运行的时候,定时获取一定的执行信息,或者输入一定的信息;
3:复用一定的C环境下的C函数,容易在一定的条件下重复触发函数执行;
4:与breakpoint 结合,可以在断点时候触发额外执行复杂函数,而不仅仅是 user defined function;
3.3 代码
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "platform.h"
uint32_t cpu_reg_r0 =0;
uint32_t cpu_reg_r1 =0;
uint32_t cpu_reg_r2 =0;
uint32_t cpu_reg_r3 =0;
uint32_t cpu_reg_r4 =0;
uint32_t cpu_reg_r5 =0;
uint32_t cpu_reg_r6 =0;
uint32_t cpu_reg_r7 =0;
uint32_t cpu_reg_r8 =0;
uint32_t cpu_reg_r9 =0;
uint32_t cpu_reg_r10 =0;
uint32_t cpu_reg_r11 =0;
uint32_t cpu_reg_r12 =0;
uint32_t cpu_reg_sp =0;
uint32_t cpu_reg_lr =0;
uint32_t cpu_reg_pc =0;
uint32_t cpu_reg_xpsr =0;
uint32_t func_addr =0;
uint32_t func_param_num =0;
uint32_t func_param_1 =0;
uint32_t func_param_2 =0;
uint32_t func_param_3 =0;
uint32_t func_param_4 =0;
uint32_t cmd_debug_flag =0;
uint32_t debug_dhcsr =0;
void svc_function_end( void )
{
cmd_debug_flag =0;
}
#include "FreeRTOSConfig.h"
typedef uint32_t (* func_1_t)(void *);
typedef uint32_t (* func_2_t)(void *,void *);
typedef uint32_t (* func_3_t)(void *,void *,void *);
typedef uint32_t (* func_4_t)(void *,void *,void *,void *);
__weak void PendSV_Handler_c(void)
{
}
void PendSV_Handler(void)
{
__nop();
__nop();
if(cmd_debug_flag ==1)
{
if(func_param_num ==1)
{
func_1_t fun = (func_1_t)(func_addr+1);
fun((void *)func_param_1);
}
if(func_param_num ==2)
{
func_2_t fun = (func_2_t)(func_addr+1);
fun((void *)func_param_1,(void *)func_param_2);
}
if(func_param_num ==3)
{
func_3_t fun = (func_3_t)(func_addr+1);
fun((void *)func_param_1,(void *)func_param_2,(void *)func_param_3);
}
if(func_param_num ==4)
{
func_4_t fun = (func_4_t)(func_addr+1);
fun((void *)func_param_1,(void *)func_param_2,(void *)func_param_3,(void *)func_param_4);
}
cmd_debug_flag =0;
if(debug_dhcsr&CoreDebug_DHCSR_S_HALT_Msk)
{
CoreDebug->DHCSR = 0XA05F0000 |(CoreDebug->DHCSR&0XFFFF)|CoreDebug_DHCSR_C_HALT_Msk;
CoreDebug->DHCSR = 0XA05F0000 |(CoreDebug->DHCSR&0XFFFF)|CoreDebug_DHCSR_C_STEP_Msk;
}
}else
{
PendSV_Handler_c();
}
}
__asm void svc_function_start( void )
{
PRESERVE8
IMPORT cpu_reg_r0
IMPORT cpu_reg_r1
IMPORT cpu_reg_r2
IMPORT cpu_reg_r3
IMPORT cpu_reg_r4
IMPORT cpu_reg_r5
IMPORT cpu_reg_r6
IMPORT cpu_reg_r7
IMPORT cpu_reg_r8
IMPORT cpu_reg_r9
IMPORT cpu_reg_r10
IMPORT cpu_reg_r11
IMPORT cpu_reg_r12
IMPORT cpu_reg_sp
IMPORT cpu_reg_lr
IMPORT cpu_reg_pc
IMPORT cpu_reg_xpsr
IMPORT func_addr
IMPORT func_param_num
IMPORT func_param_1
IMPORT func_param_2
IMPORT func_param_3
IMPORT func_param_4
// PUSH {R0-R12}
// POP {R0-R11}
// ldr R12,=cpu_reg_r0
// ldr R0,[R12]
// ldr R12,=cpu_reg_r1
// ldr R1,[R12]
// ldr R12,=cpu_reg_r2
// ldr R2,[R12]
// ldr R12,=cpu_reg_r3
// ldr R3,[R12]
// ldr R12,=cpu_reg_r4
// ldr R4,[R12]
// ldr R12,=cpu_reg_r5
// ldr R5,[R12]
// ldr R12,=cpu_reg_r6
// ldr R6,[R12]
// ldr R12,=cpu_reg_r7
// ldr R7,[R12]
// ldr R12,=cpu_reg_r8
// ldr R8,[R12]
// ldr R12,=cpu_reg_r9
// ldr R9,[R12]
// ldr R12,=cpu_reg_r10
// ldr R10,[R12]
// ldr R12,=cpu_reg_r11
// ldr R11,[R12]
// ldr R12,=cpu_reg_sp
// ldr R12,[R12]
//
// ldr R12,=cpu_reg_pc
// ldr LR,[R12]
// BX LR
SVC #0
//SVC_Dead
// B SVC_Dead ; None Existing SVC
ALIGN
}
void cpu_regs_reset(void)
{
cpu_reg_r0 =0;
cpu_reg_r1 =0;
cpu_reg_r2 =0;
cpu_reg_r3 =0;
cpu_reg_r4 =0;
cpu_reg_r5 =0;
cpu_reg_r6 =0;
cpu_reg_r7 =0;
cpu_reg_r8 =0;
cpu_reg_r9 =0;
cpu_reg_r10 =0;
cpu_reg_r11 =0;
cpu_reg_r12 =0;
cpu_reg_sp =0;
cpu_reg_lr =0;
cpu_reg_pc =0;
cpu_reg_xpsr =0;
}
FUNC void get_cpu_regs(void)
{
printf ("---------- cpu_regs ----------\n");
printf ("R0 = 0x%08X\r\n",R0 );
printf ("R1 = 0x%08X\r\n",R1 );
printf ("R2 = 0x%08X\r\n",R2 );
printf ("R3 = 0x%08X\r\n",R3 );
printf ("R4 = 0x%08X\r\n",R4 );
printf ("R5 = 0x%08X\r\n",R5 );
printf ("R6 = 0x%08X\r\n",R6 );
printf ("R7 = 0x%08X\r\n",R7 );
printf ("R8 = 0x%08X\r\n",R8 );
printf ("R9 = 0x%08X\r\n",R9 );
printf ("R10 = 0x%08X\r\n",R10 );
printf ("R11 = 0x%08X\r\n",R11 );
printf ("R12 = 0x%08X\r\n",R12 );
printf ("SP = 0x%08X\r\n",SP );
printf ("LR = 0x%08X\r\n",LR );
printf ("PC = 0x%08X\r\n",PC );
printf ("XPSR = 0x%08X\r\n",XPSR);
printf ("------------------------------\n");
}
FUNC void save_cpu_regs(void)
{
cpu_reg_r0 = R0 ;
cpu_reg_r1 = R1 ;
cpu_reg_r2 = R2 ;
cpu_reg_r3 = R3 ;
cpu_reg_r4 = R4 ;
cpu_reg_r5 = R5 ;
cpu_reg_r6 = R6 ;
cpu_reg_r7 = R7 ;
cpu_reg_r8 = R8 ;
cpu_reg_r9 = R9 ;
cpu_reg_r10 = R10 ;
cpu_reg_r11 = R11 ;
cpu_reg_r12 = R12 ;
cpu_reg_sp = SP ;
cpu_reg_lr = LR ;
cpu_reg_pc = PC ;
cpu_reg_xpsr = XPSR;
}
FUNC void resume_cpu_regs(void)
{
R0 = cpu_reg_r0 ;
R1 = cpu_reg_r1 ;
R2 = cpu_reg_r2 ;
R3 = cpu_reg_r3 ;
R4 = cpu_reg_r4 ;
R5 = cpu_reg_r5 ;
R6 = cpu_reg_r6 ;
R7 = cpu_reg_r7 ;
R8 = cpu_reg_r8 ;
R9 = cpu_reg_r9 ;
R10 = cpu_reg_r10 ;
R11 = cpu_reg_r11 ;
R12 = cpu_reg_r12 ;
SP = cpu_reg_sp ;
LR = cpu_reg_lr ;
PC = cpu_reg_pc ;
XPSR = cpu_reg_xpsr;
}
FUNC void test_function_0(int addr)
{
}
FUNC unsigned long set_bit(unsigned long addr,int start_bit, int bits,int value)
{
unsigned long temp;
unsigned long mask;
//WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))
temp =0;
temp = _RDWORD(addr);
// printf("get the register addr: %08x value : %08x \r\n",addr,temp);
if(bits!= 32)
{
mask = (((1<<bits) -1)<<start_bit);
//printf("get the register mask: %08x bits : %08x \r\n",mask,bits);
}else
{
mask = 0XFFFFFFFF;
}
temp = temp&(~mask);
//printf("get the register addr: %08x value : %08x \r\n",addr,temp);
temp = temp|(value<<start_bit);
// printf("set the register addr: %08x value : %08x \r\n",addr,temp);
_WDWORD(addr,temp);
return 0;
}
FUNC void test_function_1(int addr,int param)
{
func_addr = addr;
func_param_num =1;
cmd_debug_flag =1;
func_param_1 = param;
set_bit(0xE000ED04UL,28,1,1);
exec("G");
}
FUNC void test_function_2(int addr,int param1,int param2)
{
func_addr = addr;
func_param_num =2;
cmd_debug_flag =1;
func_param_1 = param1;
func_param_2 = param2;
debug_dhcsr = _RDWORD(0xE000EDF0);
set_bit(0xE000ED04UL,28,1,1);
exec("G");
}
FUNC void test_function_3(int addr,int param1,int param2,int param3)
{
func_addr = addr;
func_param_num =3;
cmd_debug_flag =1;
func_param_1 = param1;
func_param_2 = param2;
func_param_3 = param3;
debug_dhcsr = _RDWORD(0xE000EDF0);
set_bit(0xE000ED04UL,28,1,1);
exec("G");
}
FUNC void test_function_4(int addr,int param1,int param2,int param3,int param4)
{
func_addr = addr;
func_param_num =2;
cmd_debug_flag =1;
func_param_1 = param1;
func_param_2 = param2;
func_param_3 = param3;
func_param_4 = param4;
debug_dhcsr = _RDWORD(0xE000EDF0);
set_bit(0xE000ED04UL,28,1,1);
exec("G");
}