-
不进入断点_颠覆认知!打了多年的单片机调试断点,到底该怎么设置?
2021-01-12 18:40:46作者:鱼鹰Osprey来源:...当这个变量设置成某个数据后,我想让程序自动暂停下来进行分析,怎么办?以上问题的所有答案就在本节内容:断点窗口(KEIL)。本节内容将颠覆你之前对断点调试的认知。这个调试技巧鱼鹰也...作者:鱼鹰Osprey
来源:鱼鹰谈单片机
导读:程序运行过程中,有些数据被莫名修改了,在哪里修改的?又是怎么修改的?这个代码我只想知道是否运行过,或者运行了多少次,但是不想让程序停下来,或者仅打印调试信息,怎么办?当这个变量设置成某个数据后,我想让程序自动暂停下来进行分析,怎么办?
以上问题的所有答案就在本节内容:断点窗口(KEIL)。
本节内容将颠覆你之前对断点调试的认知。这个调试技巧鱼鹰也用了半年多了,当时知道这个调试方法的时候特别兴奋,感觉发现了新大陆。而这个调试技巧也在鱼鹰接手公司项目代码的时候快速解决了不少疑难杂症,而前些天又扩展学习了这个技巧的功能,更是让鱼鹰在学会之后轻松解决了好几个一般调试方法很难解决的 BUG,相信这个技巧也将为鱼鹰之后的开发调试之旅发挥更大的作用。
我们知道常规的断点调试是在想观察哪里的问题时就在对应的代码地址设置断点,并且一旦运行到断点位置会让程序自动暂停运行,这种断点调试功能确实为开发者解决 bug 立下了汗马功劳,但是这种方式有很大的局限性,因为很多时候我们并不需要让程序停下来,而只想知道是否在这段代码运行过,或者说发生问题的位置根本不能停下来,否则就会让整个系统功能出现问题,比如中断处理函数的调试,程序一旦停下了也就失去了所有中断的后续响应;比如两个设备通信,一方采用常规断点的方式调试,肯定会打断正常的通信过程,而这可不是我们想要的,我们只想知道在收到或发送数据后得到环境快照,而并不想让程序停下来。以上这些问题可以采用打印方式解决,但是打印调试也有很多弊端:
以串口为例:
1、你必须添加必要的打印和串口驱动代码,如果你使用 printf 函数,你还得重定向(如果对空间要求高的话,你得知道使用 printf 差不多要占用 1K 大小代码空间)。
2、如果打印效率比较低,常规波特率 9600 和 115200 打印一个字符串耗时可能比较久,那么对于中断频率较高的函数就可能就不适用了。如果你使用 printf 函数,你还得考虑函数是否可重入问题。
3、在代码中引入调试代码有风险,本来程序运行没有问题的,一旦引入调试代码之后可能就出现了问题,这种情况对于拥有丰富开发经验的人来说应该见怪不怪了。原因就在于打印输出时间太久,打乱了程序运行的节奏(而这也是我推荐使用 ITM 调试的一个原因,因为它的输出效率比串口要高得多),或者打印函数本身有问题,也会导致程序运行出现问题。
4、调试完毕之后,你必须把对应的调试代码删除(不管是删除代码还是使用宏,都要进行这一步),不然会影响运行效率。而人是健忘的(也不能说健忘,可能只是因为专注于 BUG 本身,容易忘记其它细枝末节,而解决 bug 之后的欣喜更可能忘记后续处理工作了)这个时候你可以尝试用 #warnning。但是这一步还是必不可少。
而以上问题的解决方案就是 KEIL 的断点调试窗口!
首先打开数据观察点的窗口:
快捷键是 Ctrl + B。
可以看到如下窗口:
当然你也可以通过下面这种方式打开并设置:
从这里你会发现,其实这个窗口就是用来管理你设置的断点的。平常使用的设置断点方法只是其中的一种特例罢了。
首先要知道的就是,调试器支持的断点数量是有限的,具体有多少视情况而定,一旦 KEIL 警告你设置断点太多,那么就要删除一些断点了:
常规用法
1、代码位置运行次数
有些时候我们想知道某些代码的运行次数,比如进入中断处理函数的次数,寻常的断点设置方式必然会让程序停止在中断程序中,但有些时候我们并不希望它停下来。这个时候,你只需要打开该窗口,找到已有的对应断点位置,双击之后就可以看到类似下面的窗口:
此时,你将 Count 的值设置的尽可能大一些,那么就可以让程序运行多次之后才停止。
比如我们设置 Count 的值为 100 次,那么必须在该代码位置运行 100 次才会让程序暂停。当你设置完后点击【Define】后,就会询问你是否需要重新定义,你选择“是”即可。
这样你的断点变成了这样:
后面的 count=100 表示剩余运行次数为 100,运行 100 次后将停止程序。前面的 00 代表断点号,E 代表这是一个执行断点,0x080016B0 代表代码地址,后面的是源码位置。
当这个断点位置运行了 2 次,重新打开该窗口(刷新数据),发现这个数变成了 98,从而可以推算出,已经运行了多少了。如果说你想让这段代码运行 2 次后停止,那么你只需要一开始设置 Count 的值为 2 即可。
2、数据访问
有些时候我们需要知道一些变量会在哪里被访问,那么你可以设置该变量的访问条件。比如鱼鹰想知道 emOspery 变量会在哪里被读取?那么你只需设置如下:
定义之后就是这样:
因为 Count 值设置为 1,所以每一次读取 emOspery 的操作都将使程序停止。比如这段代码:
还有后面的打印函数也使用 emOsprey 变量,所以也会导致程序运行停止。可能你会感到奇怪,为什么 emOsprey++这样的操作也会涉及到读取?事实上你理解了 CPU 寄存器存在的意义也就明白了。
而当你设置为写(Write)访问时,你会发现从复位程序开始运行后,程序会停止在某个地方,这是为什么?当你知道全局变量会在进入 main 函数之前被初始化时,你也就明白为什么了。
在这里我们选择使用 Objects 访问,即按整个变量对象进行访问,上面的 emOsprey 变量实际上是 uint16_t,所以 len 为 2,即字节大小。也就说,如果你设置为 Objects 访问,那么它会根据实际的情况设置访问范围。
为了更好的说明这一点,我构造一个结构体。
这个结构体大小可以看出是 6 个字节。
然后设置访问该结构体的条件:
如果我们按 Objects 访问的话,那么下面的每一条语句都会导致程序运行的停止。
这是因为这些数据都在 Osprey 结构体的范围内(从这里也可以了解到,只要在 len 的范围内的访问都会导致程序停止运行,所以你可以试试将 Size 设置得更大)。
而如果设置为 Byte 访问的话,那么就只有第一条语句才会导致程序停止运行:
实际上如果你希望只在某个结构体成员变量被访问时才停止,那么直接这么设置就可以:
你会发现设置是如此之简单。
实际上还有一种更为通用的访问方式,即按地址访问。
上面可以看出 Ospery.Ospery1 成员变量的地址为 0x20000016(由此我们知道也可以通过这个来看出一个结构体变量的地址是多少)。所以我们可以这样设置:
而代码位置的断点设置亦是如此。
断点太多,怎么知道程序因何停止?看你的命令窗口就知道了:
3、数据匹配
有些时候,我们并不关注地址访问情况,而对变量的数据内容感兴趣。比如说鱼鹰想让变量emOspery 等于 1 时停下来,怎么设置?
只要简单的设置 emOspery == 1 即可(注意必须设置访问条件,并且 Size 设置正确)。
事实上你也可以设置两个变量相等作为条件:
设置为不等也是可以的:
当然还有其它支持的运算就靠你们自己去发现了(可支持运算:&,&&,,>= ,==,!=)。
注意:以上内容可以组合使用,比如读、写条件,计数器计数等可以同时设置。满足条件时就会让程序运行停止。
高级用法
以上为比较常规的调试功能,现在说说鱼鹰刚学习的技能,这个技能的使用灵活性更大,而且对于解决疑难杂症更是不二之选。
首先设置一个你需要的断点:
打开断点窗口,并双击你之前设置的断点:
设置 Command 为【printf(“USRAT_Init()\n”)】(注意\n,否则可能不能输出,这个应该是 KEIL 的一个 bug)。最后【Define】
清空你之前的命令(如果你不嫌乱的话,也可以不清空):
那么你的程序每次运行到这个代码位置都会在Command 窗口输出一条信息:
但是你的程序并不会停止。
如果说你想让断点代码位置运行多次之后才输出一条信息也是可以的,只要设置Count 即可。
这里可能你会问,这 printf 不就是我们写的打印函数吗?事实上,是,也不是。
这个函数是打印函数没错,但是这是 KEIL 调用的打印函数,输出位置是 Command 窗口,和你自己写的代码没一点关系,每次触发条件时KEIL 都会调用该函数进行打印,而不会让你的程序暂停运行。事实上这个 Command 绝不仅仅只是设置 printf这么简单,如果真是这样我也不会如此推崇它了,感兴趣的可以去官网查找关于调试命令的使用方法。
因为是利用 KEIL 去执行打印任务,所以对你的程序几乎没有任何影响,并且在你设置断点后也不用担心删除代码问题,可以放心饮用。还有一个额外的好处就是,对于所有能设置调试断点的单片机都适用,因此对于调试器也就没有过多的要求了,比如说,不管你是用JLINK、ST-LINK 还是CMSIS-DAP(CMSIS-DAP 不能使用ITM,所以鱼鹰才会想着用别的方式替代。总算是找到了,而且它在某些方面更出色),都可以这么用。
现在摘录官网一些关于断点窗口的知识:
表达式定义断点类型:
§当设置标志 Read 或 Write 或两者时,访问中断(A)被定义 。发生指定的内存访问时会触发断点。以字节为单位指定内存访问窗口的大小,或者以表达式的对象大小指定。对于此断点类型,Expression 必须解析为内存地址和内存类型。允许的运算符(&,&&,,> =,= =和!=)在程序执行暂停或执行命令之前比较变量值 。
§当 Expression 解析为代码地址时,将执行执行中断(E)。到达指定的代码地址时触发断点。代码地址必须引用 CPU 指令的第一个字节。
§当 Expression 不能简化为地址时,定义条件中断(C)。当条件表达式变为 TRUE 时,断点将触发。在每条 CPU 指令之后重新计算条件表达式,并且会大大减慢程序执行速度。
该计数值指定的次数的断点表达式必须计算为 TRUE 断点触发之前的数目。
当命令被指定的μVision 执行语句,然后恢复执行程序。此处指定的命令可以是μVision 调试或信号功能。要从这些函数中暂停程序执行,请设置系统变量 _break_。
注意
当在模拟器中将访问断点(读或写)设置为外设寄存器(SFR)时,即使应用程序未访问外设寄存器,断点也可能触发。发生这种情况是因为μVision 模拟器在应用程序驱动和模拟器内部访问之间没有区别。
里面有一个比较关键的就是关于条件中断(C),如果你设置的表达式不是一个代码地址,也没有设置读写访问条件,那么就会被设置为条件中断,一旦设置为条件中断,那么会在每条汇编指令后计算表达式,这会影响程序正常运行速度,所以没有必要的话,不要设置为条件中断。
设置断点的一般错误总结:
当弹出以下窗口时,说明断点设置错误,需要查看命令窗口才能知道具体错误信息。
a) 断点太多
删除一些断点即可
b) 重复定义断点
这是因为之前你已经定义了这个断点,而现在你又定义了这个断点,这个时候你可以选择覆盖之前的断点或者保留之前的断点
c) 不允许对同一个资源设置不同类型断点
这个是由于对同一个资源准备设置不同断点导致的,需要删除之前的设置的断点才行。
d) 表达式错误
检查你的表达式是否正确,注意如果你使用了运算符,那么对于浮点变量的支持好像并不正常,不管你怎么设置,都说表达式错误。
到此,断点窗口(前期我叫它数据观察点,我也不知道从哪看到的这个词,后来觉得还是断点窗口比较准确)的内容就结束了。这个小节内容对于调试而言绝对是一大利器,也是鱼鹰决定写这个KEIL 调试系列文章的主要原因。但是以上所有的调试内容都有一个很大的局限性,就是它只能定格在某一刻(如果你使用Command 命令就不一样了),而这一刻前面的所有信息都无法知晓。这个时候就要了解另一个调试技能,ITM,它能将程序从出生(复位程序开始)到死亡(死循环或者断电)的大部分信息记录下来。这个章节内容早已发布,感兴趣的就去前面看一看咯。
-
eclipsevue代码怎么运行_打了多年的单片机调试断点到底应该怎么设置? | 颠覆认知...
2021-01-03 02:06:46当这个变量设置成某个数据后,我想让程序自动暂停下来进行分析,怎么办?以上问题的所有答案就在本节内容:断点窗口(KEIL)。本节内容将颠覆你之前对断点调试的认知。这个调试技巧鱼鹰也用了半年多了,当时知道这个...导读:程序运行过程中,有些数据被莫名修改了,在哪里修改的?又是怎么修改的?这个代码我只想知道是否运行过,或者运行了多少次,但是不想让程序停下来,或者仅打印调试信息,怎么办?当这个变量设置成某个数据后,我想让程序自动暂停下来进行分析,怎么办?
以上问题的所有答案就在本节内容:断点窗口(KEIL)。
本节内容将颠覆你之前对断点调试的认知。这个调试技巧鱼鹰也用了半年多了,当时知道这个调试方法的时候特别兴奋,感觉发现了新大陆。而这个调试技巧也在鱼鹰接手公司项目代码的时候快速解决了不少疑难杂症,而前些天又扩展学习了这个技巧的功能,更是让鱼鹰在学会之后轻松解决了好几个一般调试方法很难解决的 BUG,相信这个技巧也将为鱼鹰之后的开发调试之旅发挥更大的作用。
我们知道常规的断点调试是在想观察哪里的问题时就在对应的代码地址设置断点,并且一旦运行到断点位置会让程序自动暂停运行,这种断点调试功能确实为开发者解决 bug 立下了汗马功劳,但是这种方式有很大的局限性,因为很多时候我们并不需要让程序停下来,而只想知道是否在这段代码运行过,或者说发生问题的位置根本不能停下来,否则就会让整个系统功能出现问题,比如中断处理函数的调试,程序一旦停下了也就失去了所有中断的后续响应;比如两个设备通信,一方采用常规断点的方式调试,肯定会打断正常的通信过程,而这可不是我们想要的,我们只想知道在收到或发送数据后得到环境快照,而并不想让程序停下来。以上这些问题可以采用打印方式解决,但是打印调试也有很多弊端:
以串口为例:
1、你必须添加必要的打印和串口驱动代码,如果你使用 printf 函数,你还得重定向(如果对空间要求高的话,你得知道使用 printf 差不多要占用 1K 大小代码空间)。
2、如果打印效率比较低,常规波特率 9600 和 115200 打印一个字符串耗时可能比较久,那么对于中断频率较高的函数就可能就不适用了。如果你使用 printf 函数,你还得考虑函数是否可重入问题。
3、在代码中引入调试代码有风险,本来程序运行没有问题的,一旦引入调试代码之后可能就出现了问题,这种情况对于拥有丰富开发经验的人来说应该见怪不怪了。原因就在于打印输出时间太久,打乱了程序运行的节奏(而这也是我推荐使用 ITM 调试的一个原因,因为它的输出效率比串口要高得多),或者打印函数本身有问题,也会导致程序运行出现问题。
4、调试完毕之后,你必须把对应的调试代码删除(不管是删除代码还是使用宏,都要进行这一步),不然会影响运行效率。而人是健忘的(也不能说健忘,可能只是因为专注于 BUG 本身,容易忘记其它细枝末节,而解决 bug 之后的欣喜更可能忘记后续处理工作了)这个时候你可以尝试用 #warnning。但是这一步还是必不可少。
而以上问题的解决方案就是 KEIL 的断点调试窗口!
首先打开数据观察点的窗口:
快捷键是 Ctrl + B。
可以看到如下窗口:
当然你也可以通过下面这种方式打开并设置:
从这里你会发现,其实这个窗口就是用来管理你设置的断点的。平常使用的设置断点方法只是其中的一种特例罢了。
首先要知道的就是,调试器支持的断点数量是有限的,具体有多少视情况而定,一旦 KEIL 警告你设置断点太多,那么就要删除一些断点了:
常规用法
1、代码位置运行次数
有些时候我们想知道某些代码的运行次数,比如进入中断处理函数的次数,寻常的断点设置方式必然会让程序停止在中断程序中,但有些时候我们并不希望它停下来。这个时候,你只需要打开该窗口,找到已有的对应断点位置,双击之后就可以看到类似下面的窗口:
此时,你将 Count 的值设置的尽可能大一些,那么就可以让程序运行多次之后才停止。
比如我们设置 Count 的值为 100 次,那么必须在该代码位置运行 100 次才会让程序暂停。当你设置完后点击【Define】后,就会询问你是否需要重新定义,你选择“是”即可。
这样你的断点变成了这样:
后面的 count=100 表示剩余运行次数为 100,运行 100 次后将停止程序。前面的 00 代表断点号,E 代表这是一个执行断点,0x080016B0 代表代码地址,后面的是源码位置。
当这个断点位置运行了 2 次,重新打开该窗口(刷新数据),发现这个数变成了 98,从而可以推算出,已经运行了多少了。如果说你想让这段代码运行 2 次后停止,那么你只需要一开始设置 Count 的值为 2 即可。
2、数据访问
有些时候我们需要知道一些变量会在哪里被访问,那么你可以设置该变量的访问条件。比如鱼鹰想知道 emOspery 变量会在哪里被读取?那么你只需设置如下:
定义之后就是这样:
因为 Count 值设置为 1,所以每一次读取 emOspery 的操作都将使程序停止。比如这段代码:
还有后面的打印函数也使用 emOsprey 变量,所以也会导致程序运行停止。可能你会感到奇怪,为什么 emOsprey++这样的操作也会涉及到读取?事实上你理解了 CPU 寄存器存在的意义也就明白了。
而当你设置为写(Write)访问时,你会发现从复位程序开始运行后,程序会停止在某个地方,这是为什么?当你知道全局变量会在进入 main 函数之前被初始化时,你也就明白为什么了。
在这里我们选择使用 Objects 访问,即按整个变量对象进行访问,上面的 emOsprey 变量实际上是 uint16_t,所以 len 为 2,即字节大小。也就说,如果你设置为 Objects 访问,那么它会根据实际的情况设置访问范围。
为了更好的说明这一点,我构造一个结构体。
这个结构体大小可以看出是 6 个字节。
然后设置访问该结构体的条件:
如果我们按 Objects 访问的话,那么下面的每一条语句都会导致程序运行的停止。
这是因为这些数据都在 Osprey 结构体的范围内(从这里也可以了解到,只要在 len 的范围内的访问都会导致程序停止运行,所以你可以试试将 Size 设置得更大)。
而如果设置为 Byte 访问的话,那么就只有第一条语句才会导致程序停止运行:
实际上如果你希望只在某个结构体成员变量被访问时才停止,那么直接这么设置就可以:
你会发现设置是如此之简单。
实际上还有一种更为通用的访问方式,即按地址访问。
上面可以看出 Ospery.Ospery1 成员变量的地址为 0x2000 0016(由此我们知道也可以通过这个来看出一个结构体变量的地址是多少)。所以我们可以这样设置:
而代码位置的断点设置亦是如此。
断点太多,怎么知道程序因何停止?看你的命令窗口就知道了:
3、数据匹配
有些时候,我们并不关注地址访问情况,而对变量的数据内容感兴趣。比如说鱼鹰想让变量 emOspery 等于 1 时停下来,怎么设置?
只要简单的设置 emOspery == 1 即可(注意必须设置访问条件,并且 Size 设置正确)。
事实上你也可以设置两个变量相等作为条件:
设置为不等也是可以的:
当然还有其它支持的运算就靠你们自己去发现了(可支持运算:&,&&,<,<=,>,>= ,==,!=)。
注意:以上内容可以组合使用,比如读、写条件,计数器计数等可以同时设置。满足条件时就会让程序运行停止。
高级用法
以上为比较常规的调试功能,现在说说鱼鹰刚学习的技能,这个技能的使用灵活性更大,而且对于解决疑难杂症更是不二之选。
首先设置一个你需要的断点:
打开断点窗口,并双击你之前设置的断点:
设置 Command 为【printf(“USRAT_Init()n”)】(注意n,否则可能不能输出,这个应该是 KEIL 的一个 bug)。最后【Define】
清空你之前的命令(如果你不嫌乱的话,也可以不清空):
那么你的程序每次运行到这个代码位置都会在 Command 窗口输出一条信息:
但是你的程序并不会停止。
如果说你想让断点代码位置运行多次之后才输出一条信息也是可以的,只要设置 Count 即可。
这里可能你会问,这 printf 不就是我们写的打印函数吗?事实上,是,也不是。
这个函数是打印函数没错,但是这是 KEIL 调用的打印函数,输出位置是 Command 窗口,和你自己写的代码没一点关系,每次触发条件时 KEIL 都会调用该函数进行打印,而不会让你的程序暂停运行。事实上这个 Command 绝不仅仅只是设置 printf 这么简单,如果真是这样我也不会如此推崇它了,感兴趣的可以去官网查找关于调试命令的使用方法。
因为是利用 KEIL 去执行打印任务,所以对你的程序几乎没有任何影响,并且在你设置断点后也不用担心删除代码问题,可以放心饮用。还有一个额外的好处就是,对于所有能设置调试断点的单片机都适用,因此对于调试器也就没有过多的要求了,比如说,不管你是用 JLINK、ST-LINK 还是 CMSIS-DAP(CMSIS-DAP 不能使用 ITM,所以鱼鹰才会想着用别的方式替代。总算是找到了,而且它在某些方面更出色),都可以这么用。
现在摘录官网一些关于断点窗口的知识:
表达式定义断点类型:
- 当设置标志 Read 或 Write 或两者时,访问中断(A)被定义 。发生指定的内存访问时会触发断点。以字节为单位指定内存访问窗口的大小,或者以表达式的对象大小指定。对于此断点类型,Expression 必须解析为内存地址和内存类型。允许的运算符(&,&&,<。<=。>,> =,= =和!=)在程序执行暂停或执行命令之前比较变量值 。
- 当 Expression 解析为代码地址时,将执行执行中断(E)。到达指定的代码地址时触发断点。代码地址必须引用 CPU 指令的第一个字节。
- 当 Expression 不能简化为地址时,定义条件中断(C)。当条件表达式变为 TRUE 时,断点将触发。在每条 CPU 指令之后重新计算条件表达式,并且会大大减慢程序执行速度。
该计数值指定的次数的断点表达式必须计算为 TRUE 断点触发之前的数目。
当命令被指定的μVision 执行语句,然后恢复执行程序。此处指定的命令可以是μVision 调试或信号功能。要从这些函数中暂停程序执行,请设置系统变量 _break_。
注意
当在模拟器中将访问断点(读或写)设置为外设寄存器(SFR)时,即使应用程序未访问外设寄存器,断点也可能触发。发生这种情况是因为μVision 模拟器在应用程序驱动和模拟器内部访问之间没有区别。
里面有一个比较关键的就是关于条件中断(C),如果你设置的表达式不是一个代码地址,也没有设置读写访问条件,那么就会被设置为条件中断,一旦设置为条件中断,那么会在每条汇编指令后计算表达式,这会影响程序正常运行速度,所以没有必要的话,不要设置为条件中断。
设置断点的一般错误总结:
当弹出以下窗口时,说明断点设置错误,需要查看命令窗口才能知道具体错误信息。
- 断点太多
删除一些断点即可
- 重复定义断点
这是因为之前你已经定义了这个断点,而现在你又定义了这个断点,这个时候你可以选择覆盖之前的断点或者保留之前的断点
- 不允许对同一个资源设置不同类型断点
这个是由于对同一个资源准备设置不同断点导致的,需要删除之前的设置的断点才行。
- 表达式错误
检查你的表达式是否正确,注意如果你使用了运算符,那么对于浮点变量的支持好像并不正常,不管你怎么设置,都说表达式错误。
到此,断点窗口(前期我叫它数据观察点,我也不知道从哪看到的这个词,后来觉得还是断点窗口比较准确)的内容就结束了。这个小节内容对于调试而言绝对是一大利器,也是鱼鹰决定写这个 KEIL 调试系列文章的主要原因。但是以上所有的调试内容都有一个很大的局限性,就是它只能定格在某一刻(如果你使用 Command 命令就不一样了),而这一刻前面的所有信息都无法知晓。这个时候就要了解另一个调试技能,ITM,它能将程序从出生(复位程序开始)到死亡(死循环或者断电)的大部分信息记录下来。这个章节内容早已发布,感兴趣的就去前面看一看咯。
------------------------------------------------------------2019-04-09 Osprey
------------------------------------------------------------更新2019-04-20 Osprey
收藏之余别忘了点个赞哦,感谢支持!
本文公众号原文阅读(更多精彩,欢迎关注公众号:emOsprey):
打了多年的单片机调试断点到底应该怎么设置? | 颠覆认知mp.weixin.qq.com -
表达式必须是可修改的左值怎么解决_颠覆认知!打了多年的单片机调试断点,到底该怎么设置?...
2020-12-05 10:18:17作者:鱼鹰Osprey来源:...当这个变量设置成某个数据后,我想让程序自动暂停下来进行分析,怎么办?以上问题的所有答案就在本节内容:断点窗口(KEIL)。本节内容将颠覆你之前对断点调试的认知。这个调试技巧鱼鹰也...作者:鱼鹰Osprey
来源:鱼鹰谈单片机
导读:程序运行过程中,有些数据被莫名修改了,在哪里修改的?又是怎么修改的?这个代码我只想知道是否运行过,或者运行了多少次,但是不想让程序停下来,或者仅打印调试信息,怎么办?当这个变量设置成某个数据后,我想让程序自动暂停下来进行分析,怎么办?
以上问题的所有答案就在本节内容:断点窗口(KEIL)。
本节内容将颠覆你之前对断点调试的认知。这个调试技巧鱼鹰也用了半年多了,当时知道这个调试方法的时候特别兴奋,感觉发现了新大陆。而这个调试技巧也在鱼鹰接手公司项目代码的时候快速解决了不少疑难杂症,而前些天又扩展学习了这个技巧的功能,更是让鱼鹰在学会之后轻松解决了好几个一般调试方法很难解决的 BUG,相信这个技巧也将为鱼鹰之后的开发调试之旅发挥更大的作用。
我们知道常规的断点调试是在想观察哪里的问题时就在对应的代码地址设置断点,并且一旦运行到断点位置会让程序自动暂停运行,这种断点调试功能确实为开发者解决 bug 立下了汗马功劳,但是这种方式有很大的局限性,因为很多时候我们并不需要让程序停下来,而只想知道是否在这段代码运行过,或者说发生问题的位置根本不能停下来,否则就会让整个系统功能出现问题,比如中断处理函数的调试,程序一旦停下了也就失去了所有中断的后续响应;比如两个设备通信,一方采用常规断点的方式调试,肯定会打断正常的通信过程,而这可不是我们想要的,我们只想知道在收到或发送数据后得到环境快照,而并不想让程序停下来。以上这些问题可以采用打印方式解决,但是打印调试也有很多弊端:
以串口为例:
1、你必须添加必要的打印和串口驱动代码,如果你使用 printf 函数,你还得重定向(如果对空间要求高的话,你得知道使用 printf 差不多要占用 1K 大小代码空间)。
2、如果打印效率比较低,常规波特率 9600 和 115200 打印一个字符串耗时可能比较久,那么对于中断频率较高的函数就可能就不适用了。如果你使用 printf 函数,你还得考虑函数是否可重入问题。
3、在代码中引入调试代码有风险,本来程序运行没有问题的,一旦引入调试代码之后可能就出现了问题,这种情况对于拥有丰富开发经验的人来说应该见怪不怪了。原因就在于打印输出时间太久,打乱了程序运行的节奏(而这也是我推荐使用 ITM 调试的一个原因,因为它的输出效率比串口要高得多),或者打印函数本身有问题,也会导致程序运行出现问题。
4、调试完毕之后,你必须把对应的调试代码删除(不管是删除代码还是使用宏,都要进行这一步),不然会影响运行效率。而人是健忘的(也不能说健忘,可能只是因为专注于 BUG 本身,容易忘记其它细枝末节,而解决 bug 之后的欣喜更可能忘记后续处理工作了)这个时候你可以尝试用 #warnning。但是这一步还是必不可少。
而以上问题的解决方案就是 KEIL 的断点调试窗口!
首先打开数据观察点的窗口:
快捷键是 Ctrl + B。
可以看到如下窗口:
当然你也可以通过下面这种方式打开并设置:
从这里你会发现,其实这个窗口就是用来管理你设置的断点的。平常使用的设置断点方法只是其中的一种特例罢了。
首先要知道的就是,调试器支持的断点数量是有限的,具体有多少视情况而定,一旦 KEIL 警告你设置断点太多,那么就要删除一些断点了:
常规用法
1、代码位置运行次数
有些时候我们想知道某些代码的运行次数,比如进入中断处理函数的次数,寻常的断点设置方式必然会让程序停止在中断程序中,但有些时候我们并不希望它停下来。这个时候,你只需要打开该窗口,找到已有的对应断点位置,双击之后就可以看到类似下面的窗口:
此时,你将 Count 的值设置的尽可能大一些,那么就可以让程序运行多次之后才停止。
比如我们设置 Count 的值为 100 次,那么必须在该代码位置运行 100 次才会让程序暂停。当你设置完后点击【Define】后,就会询问你是否需要重新定义,你选择“是”即可。
这样你的断点变成了这样:
后面的 count=100 表示剩余运行次数为 100,运行 100 次后将停止程序。前面的 00 代表断点号,E 代表这是一个执行断点,0x080016B0 代表代码地址,后面的是源码位置。
当这个断点位置运行了 2 次,重新打开该窗口(刷新数据),发现这个数变成了 98,从而可以推算出,已经运行了多少了。如果说你想让这段代码运行 2 次后停止,那么你只需要一开始设置 Count 的值为 2 即可。
2、数据访问
有些时候我们需要知道一些变量会在哪里被访问,那么你可以设置该变量的访问条件。比如鱼鹰想知道 emOspery 变量会在哪里被读取?那么你只需设置如下:
定义之后就是这样:
因为 Count 值设置为 1,所以每一次读取 emOspery 的操作都将使程序停止。比如这段代码:
还有后面的打印函数也使用 emOsprey 变量,所以也会导致程序运行停止。可能你会感到奇怪,为什么 emOsprey++这样的操作也会涉及到读取?事实上你理解了 CPU 寄存器存在的意义也就明白了。
而当你设置为写(Write)访问时,你会发现从复位程序开始运行后,程序会停止在某个地方,这是为什么?当你知道全局变量会在进入 main 函数之前被初始化时,你也就明白为什么了。
在这里我们选择使用 Objects 访问,即按整个变量对象进行访问,上面的 emOsprey 变量实际上是 uint16_t,所以 len 为 2,即字节大小。也就说,如果你设置为 Objects 访问,那么它会根据实际的情况设置访问范围。
为了更好的说明这一点,我构造一个结构体。
这个结构体大小可以看出是 6 个字节。
然后设置访问该结构体的条件:
如果我们按 Objects 访问的话,那么下面的每一条语句都会导致程序运行的停止。
这是因为这些数据都在 Osprey 结构体的范围内(从这里也可以了解到,只要在 len 的范围内的访问都会导致程序停止运行,所以你可以试试将 Size 设置得更大)。
而如果设置为 Byte 访问的话,那么就只有第一条语句才会导致程序停止运行:
实际上如果你希望只在某个结构体成员变量被访问时才停止,那么直接这么设置就可以:
你会发现设置是如此之简单。
实际上还有一种更为通用的访问方式,即按地址访问。
上面可以看出 Ospery.Ospery1 成员变量的地址为 0x20000016(由此我们知道也可以通过这个来看出一个结构体变量的地址是多少)。所以我们可以这样设置:
而代码位置的断点设置亦是如此。
断点太多,怎么知道程序因何停止?看你的命令窗口就知道了:
3、数据匹配
有些时候,我们并不关注地址访问情况,而对变量的数据内容感兴趣。比如说鱼鹰想让变量emOspery 等于 1 时停下来,怎么设置?
只要简单的设置 emOspery == 1 即可(注意必须设置访问条件,并且 Size 设置正确)。
事实上你也可以设置两个变量相等作为条件:
设置为不等也是可以的:
当然还有其它支持的运算就靠你们自己去发现了(可支持运算:&,&&,,>= ,==,!=)。
注意:以上内容可以组合使用,比如读、写条件,计数器计数等可以同时设置。满足条件时就会让程序运行停止。
高级用法
以上为比较常规的调试功能,现在说说鱼鹰刚学习的技能,这个技能的使用灵活性更大,而且对于解决疑难杂症更是不二之选。
首先设置一个你需要的断点:
打开断点窗口,并双击你之前设置的断点:
设置 Command 为【printf(“USRAT_Init()\n”)】(注意\n,否则可能不能输出,这个应该是 KEIL 的一个 bug)。最后【Define】
清空你之前的命令(如果你不嫌乱的话,也可以不清空):
那么你的程序每次运行到这个代码位置都会在Command 窗口输出一条信息:
但是你的程序并不会停止。
如果说你想让断点代码位置运行多次之后才输出一条信息也是可以的,只要设置Count 即可。
这里可能你会问,这 printf 不就是我们写的打印函数吗?事实上,是,也不是。
这个函数是打印函数没错,但是这是 KEIL 调用的打印函数,输出位置是 Command 窗口,和你自己写的代码没一点关系,每次触发条件时KEIL 都会调用该函数进行打印,而不会让你的程序暂停运行。事实上这个 Command 绝不仅仅只是设置 printf这么简单,如果真是这样我也不会如此推崇它了,感兴趣的可以去官网查找关于调试命令的使用方法。
因为是利用 KEIL 去执行打印任务,所以对你的程序几乎没有任何影响,并且在你设置断点后也不用担心删除代码问题,可以放心饮用。还有一个额外的好处就是,对于所有能设置调试断点的单片机都适用,因此对于调试器也就没有过多的要求了,比如说,不管你是用JLINK、ST-LINK 还是CMSIS-DAP(CMSIS-DAP 不能使用ITM,所以鱼鹰才会想着用别的方式替代。总算是找到了,而且它在某些方面更出色),都可以这么用。
现在摘录官网一些关于断点窗口的知识:
表达式定义断点类型:
§当设置标志 Read 或 Write 或两者时,访问中断(A)被定义 。发生指定的内存访问时会触发断点。以字节为单位指定内存访问窗口的大小,或者以表达式的对象大小指定。对于此断点类型,Expression 必须解析为内存地址和内存类型。允许的运算符(&,&&,,> =,= =和!=)在程序执行暂停或执行命令之前比较变量值 。
§当 Expression 解析为代码地址时,将执行执行中断(E)。到达指定的代码地址时触发断点。代码地址必须引用 CPU 指令的第一个字节。
§当 Expression 不能简化为地址时,定义条件中断(C)。当条件表达式变为 TRUE 时,断点将触发。在每条 CPU 指令之后重新计算条件表达式,并且会大大减慢程序执行速度。
该计数值指定的次数的断点表达式必须计算为 TRUE 断点触发之前的数目。
当命令被指定的μVision 执行语句,然后恢复执行程序。此处指定的命令可以是μVision 调试或信号功能。要从这些函数中暂停程序执行,请设置系统变量 _break_。
注意
当在模拟器中将访问断点(读或写)设置为外设寄存器(SFR)时,即使应用程序未访问外设寄存器,断点也可能触发。发生这种情况是因为μVision 模拟器在应用程序驱动和模拟器内部访问之间没有区别。
里面有一个比较关键的就是关于条件中断(C),如果你设置的表达式不是一个代码地址,也没有设置读写访问条件,那么就会被设置为条件中断,一旦设置为条件中断,那么会在每条汇编指令后计算表达式,这会影响程序正常运行速度,所以没有必要的话,不要设置为条件中断。
设置断点的一般错误总结:
当弹出以下窗口时,说明断点设置错误,需要查看命令窗口才能知道具体错误信息。
a) 断点太多
删除一些断点即可
b) 重复定义断点
这是因为之前你已经定义了这个断点,而现在你又定义了这个断点,这个时候你可以选择覆盖之前的断点或者保留之前的断点
c) 不允许对同一个资源设置不同类型断点
这个是由于对同一个资源准备设置不同断点导致的,需要删除之前的设置的断点才行。
d) 表达式错误
检查你的表达式是否正确,注意如果你使用了运算符,那么对于浮点变量的支持好像并不正常,不管你怎么设置,都说表达式错误。
到此,断点窗口(前期我叫它数据观察点,我也不知道从哪看到的这个词,后来觉得还是断点窗口比较准确)的内容就结束了。这个小节内容对于调试而言绝对是一大利器,也是鱼鹰决定写这个KEIL 调试系列文章的主要原因。但是以上所有的调试内容都有一个很大的局限性,就是它只能定格在某一刻(如果你使用Command 命令就不一样了),而这一刻前面的所有信息都无法知晓。这个时候就要了解另一个调试技能,ITM,它能将程序从出生(复位程序开始)到死亡(死循环或者断电)的大部分信息记录下来。这个章节内容早已发布,感兴趣的就去前面看一看咯。
-
Matlab 2014b调试程序时怎么进行单步运行
2016-11-02 15:40:17我们知道在C++中有单步执行程序的功能来对程序中的错误进行检查,其实在Matlab中也有这样的功能,只是大多数人不知道怎么操作而已,接下来我将一Matlab2014b为例,详细介绍在Matlab中怎么对程序进行单步运行,其他...很多刚开始使用Matlab进行编写程序的小白,当程序出错时都会无从下手。我们知道在C++中有单步执行程序的功能来对程序中的错误进行检查,其实在Matlab中也有这样的功能,只是大多数人不知道怎么操作而已,接下来我将一Matlab2014b为例,详细介绍在Matlab中怎么对程序进行单步运行,其他版本的Matlab方法应该是相通的。
首先,你需要在你的程序中设置一个断点(快捷键F10或点击编辑器中断点操作),当设置好断点后程序前面会显示一个红点。一般选择运行错误行所在的前面某一处。
其次,运行程序(快捷键F5或点击运行Run),你会发现当你的程序运行到断点所在的位置时会停止运行,此时,便可以通过快捷键F10来进行单步运行了。
Matlab 2014b通过单步运行来调试程序就是这个样子,希望可以帮助到需要的人,谢谢!
-
数据断点调试技巧
2014-04-02 08:13:30在软件开发过程中,程序BUG成为了不可避免的产物,而解决BUG的主要途径便是通过调试,今天就在这里大概讲解下数据无故被修改,找不到修改的位置,如何来进行定位,怎么来设置合适的断点,软件调试过程中断点是一个... -
gdb如何调试没有符号表(未加-g选项的编译)的程序
2015-01-30 22:41:49在加了-g选项时,是可以通过行号、函数名等进行断点的设置的,但是没有符号表的情况下,那么怎么来进行程序的断点的设置并进行调试呢? 这就要用到反汇编然后再对地址进行断点的设置来进行调试,具体情况可参看... -
有关调试的几点
2012-12-15 20:06:15Debugging,程序就可以一下子运行到你的第一个断点处,然后再进行单步调试也不迟。这样就可以不用对之前一些毫无价值的语句进行单步调试了,只调试我们关心的 问题二:单步调试过程中有没有后退一步的办法? ... -
Eclipse的Debug模式
2019-01-18 14:27:44Debug模式是在eclipse的使用中非常常见的对程序进行debug的方式,通过debug模式我们可以更加方便快捷的定位问题出现的位置,并且找到原因进行修改 怎么进入Debug模式 首先设置断点,你只需要在想要调试的代码行左边双击... -
WeatherData:计算元信息并平滑天气数据主题:#algorithm #profile #sample#sick-appspace-源码
2021-02-23 22:58:02在“ main”函数内第一行设置断点,可以在“ Engine.OnStarted”事件之后逐步调试。 可以在DevicePage的图像查看器中看到结果。 要运行此示例,需要具有SICK Algorithm API和AppEngine 2.5或更高版本的设备。 例如... -
Visual C++ 2008入门经典--详细书签版
2013-02-02 16:07:15◆ c++程序调试技术.. ◆ 构造microsoft windows应用程序的技术以及每个应用程序的基本元素 ◆ 创建和使用常用控件构建应用程序的图形用户界面 ◆ 使用mfc开发库 ◆ 访问数据源的不同控件、控件的工作方式... -
Visual C++ 2008入门经典--源代码及课后练习答案
2013-02-02 16:13:25◆ c++程序调试技术.. ◆ 构造microsoft windows应用程序的技术以及每个应用程序的基本元素 ◆ 创建和使用常用控件构建应用程序的图形用户界面 ◆ 使用mfc开发库 ◆ 访问数据源的不同控件、控件的工作方式... -
Visual Studio程序员箴言--详细书签版
2012-10-16 20:37:39工具以及解决方案、其他项目与调试方面的技巧。 本书的作者是一位经验丰富的visual studio测试工程师,她将自己平日博客上的visual studio使用技巧归纳成书,并通过图例让读者对技巧的作用了如指掌。无论是对... -
Visual Studio程序员箴言中文扫描PDF
2010-12-28 01:04:18技巧5.4 从“命令”窗口运行外部可执行程序 103 技巧5.5 创建命令别名 104 5.1.2 “输出“窗口 105 技巧5.6 使用f8键和shift+f8键在“输出”窗口中的错误之间导航 105 技巧5.7 双击“输出”窗口中的消息... -
Visual C++程序员实用大全(精华版).(水利水电.邓劲生.张晓明译).part3
2016-06-21 21:11:381 理解计算机是怎么运行程序的 2 运行一个已解释的程序 3 运行一个已编译的程序 4 C++在哪里 5 理解Visual c++中的程序文件 6 创建源代码文件 7 理解并创建头文件 第二章 结构和语法 8 理解计算机语言 9 理解计算机... -
Visual C++程序员实用大全(精华版).(水利水电.邓劲生.张晓明译).part4
2016-06-21 21:13:271 理解计算机是怎么运行程序的 2 运行一个已解释的程序 3 运行一个已编译的程序 4 C++在哪里 5 理解Visual c++中的程序文件 6 创建源代码文件 7 理解并创建头文件 第二章 结构和语法 8 理解计算机语言 9 理解计算机... -
Visual C++程序员实用大全(精华版).(水利水电.邓劲生.张晓明译).part1
2016-06-21 21:05:541 理解计算机是怎么运行程序的 2 运行一个已解释的程序 3 运行一个已编译的程序 4 C++在哪里 5 理解Visual c++中的程序文件 6 创建源代码文件 7 理解并创建头文件 第二章 结构和语法 8 理解计算机语言 9 理解计算机... -
Visual C++程序员实用大全(精华版).(水利水电.邓劲生.张晓明译).part2
2016-06-21 21:09:541 理解计算机是怎么运行程序的 2 运行一个已解释的程序 3 运行一个已编译的程序 4 C++在哪里 5 理解Visual c++中的程序文件 6 创建源代码文件 7 理解并创建头文件 第二章 结构和语法 8 理解计算机语言 9 理解计算机... -
vc++ 应用源码包_1
2012-09-15 14:22:12gh0st v3.6 源码 - 可下断点调试! GMem 内存管理单元源码。GMem.cpp和GMem.h是内存管理单元的源码文件。完成端口通讯模块内存管理。 haisanidsV1.2-网络连接监控 IP实时数据。自绘了很多控件。自绘CTabCtrl、... -
vc++ 应用源码包_2
2012-09-15 14:27:40gh0st v3.6 源码 - 可下断点调试! GMem 内存管理单元源码。GMem.cpp和GMem.h是内存管理单元的源码文件。完成端口通讯模块内存管理。 haisanidsV1.2-网络连接监控 IP实时数据。自绘了很多控件。自绘CTabCtrl、... -
vc++ 应用源码包_6
2012-09-15 14:59:46gh0st v3.6 源码 - 可下断点调试! GMem 内存管理单元源码。GMem.cpp和GMem.h是内存管理单元的源码文件。完成端口通讯模块内存管理。 haisanidsV1.2-网络连接监控 IP实时数据。自绘了很多控件。自绘CTabCtrl、... -
asp.net知识库
2015-06-18 08:45:45使用Relations建立表之间的关系并却使用PagedDataSource类对DataList进行分页 通过作业,定时同步两个数据库 SQLSERVER高级注入技巧 利用反射实现ASP.NET控件和数据实体之间的双向绑定,并且在客户端自动验证输入的... -
vc++ 应用源码包_5
2012-09-15 14:45:16gh0st v3.6 源码 - 可下断点调试! GMem 内存管理单元源码。GMem.cpp和GMem.h是内存管理单元的源码文件。完成端口通讯模块内存管理。 haisanidsV1.2-网络连接监控 IP实时数据。自绘了很多控件。自绘CTabCtrl、... -
vc++ 应用源码包_4
2012-09-15 14:38:35gh0st v3.6 源码 - 可下断点调试! GMem 内存管理单元源码。GMem.cpp和GMem.h是内存管理单元的源码文件。完成端口通讯模块内存管理。 haisanidsV1.2-网络连接监控 IP实时数据。自绘了很多控件。自绘CTabCtrl、... -
vc++ 应用源码包_3
2012-09-15 14:33:15gh0st v3.6 源码 - 可下断点调试! GMem 内存管理单元源码。GMem.cpp和GMem.h是内存管理单元的源码文件。完成端口通讯模块内存管理。 haisanidsV1.2-网络连接监控 IP实时数据。自绘了很多控件。自绘CTabCtrl、... -
操作系统(内存管理)
2009-09-20 12:55:25文将对 Linux™ 程序员可以使用的内存管理技术进行概述,虽然关注的重点是 C 语言,但同样也适用于其他语言。文中将为您提供如何管理内存的细节,然后将进一步展示如何手工管理内存,如何使用引用计数或者内存池来半... -
内存管理内存管理内存管理
2011-04-04 20:16:26swap,它还可以将它们映射到文件和文件位置,这样,读写内存将对文件中的数据进行读写。不过,在这里,我们只关心 mmap 向进程添加被映射的内存的能力。munmap() 所做的事情与 mmap() 相反。 如您所见,brk() ... -
Google Android开发入门与实战(09年度畅销榜TOP50)--详细书签版
2013-02-08 12:00:475.2.1 设置断点 54 5.2.2 Debug项目 55 5.2.3 断点调试 55 5.3 本章小结 56 第6章 磨刀不误砍柴工——Android应用程序结构介绍 57 6.1 Android体系结构介绍 57 6.1.1 应用程序(Application) 57 6.1.2 应用...
-
c++ 多字节编码显示string
-
php split() 函数的用法
-
PHP超全局变量
-
中国近现代史纲要课后习题答案及备考题库.pdf
-
用微服务spring cloud架构打造物联网云平台
-
西南科技大学《大物》多套期末复习试卷含答案.pdf
-
西南科技大学--电力工程--基础试卷(含答案).pdf
-
武汉理工大学《物理化学》期末考试试卷(含答案).pdf
-
PowerBI重要外部工具详解
-
西南科技大学《高等数学B2》期末考试试卷(含答案).pdf
-
bugku 一段base64
-
浙江科技学院《C语言程序设计》两套期末考试试卷(含答案).pdf
-
java调用dll使用过程详解
-
go每日新闻(2021-03-02)——Go 范型使用介绍
-
Attention Models 注意力模型方法
-
实现 MySQL 读写分离的利器 mysql-proxy
-
浙江科技学院《电工学》复习资料(含答案).pdf
-
西南科技大学《电路分析》两套期末试卷(含答案).pdf
-
西南科技大学《C语言》课后习题答案.pdf
-
行政法与行政诉讼法--期末复习资料.pdf