FreeRtos入门-8 调试

调试

0,打印状态(略)

1,断言

在编程中,断言是一种一阶逻辑,即一个结果为真或假的逻辑判断式。其目的是表示与验证软件开发者预期的结果。当程序执行到断言的位置时,对应的断言应该为真。如果断言不为真,程序会中止执行,并给出错误信息。断言在编程中是一个关键工具,用于检测程序中的各种预期条件是否满足,帮助开发者迅速定位问题。它常用于安全性检查、代码重构和性能优化等方面。

一般的C库里面,断言就是一个函数:

assert(0);
#define assert(expr) ((expr) ? (void)0 : assert_failed(__FILE__, __LINE__))#ifdef  DEBUG
/* Keep the linker happy. */
void assert_failed( unsigned char* pcFile, unsigned long ulLine )
{printf("Wrong parameters value: file %s on line %d\r\n", pcFile, ulLine) ; for( ;; ){}
}
#endif

测试效果

int main( void )
{TaskHandle_t xHandleTask1;#ifdef DEBUGdebug();
#endifprvSetupHardware();printf("Hello, world!\r\n");assert(0);
}

在FreeRTOS里,使用configASSERT(),比如:

configASSERT(0);
#define configASSERT(x)     if (x==0){printf("%s %s %d\r\n", __FILE__, __FUNCTION__, __LINE__);  while(1); }

测试效果

int main( void )
{TaskHandle_t xHandleTask1;#ifdef DEBUGdebug();
#endifprvSetupHardware();printf("Hello, world!\r\n");configASSERT(0);
}

2,Trace宏(略)

实际使用时,再翻阅

FreeRTOS中定义了很多trace开头的宏,这些宏被放在系统个关键位置。

它们一般都是空的宏,这不会影响代码:不影响编程处理的程序大小、不影响运行时间。

我们要调试某些功能时,可以修改宏:修改某些标记变量、打印信息等待。

trace宏

描述

traceTASK_INCREMENT_TICK(xTickCount)

当tick计数自增之前此宏函数被调用。参数xTickCount当前的Tick值,它还没有增加。

traceTASK_SWITCHED_OUT()

vTaskSwitchContext中,把当前任务切换出去之前调用此宏函数。

traceTASK_SWITCHED_IN()

vTaskSwitchContext中,新的任务已经被切换进来了,就调用此函数。

traceBLOCKING_ON_QUEUE_RECEIVE(pxQueue)

当正在执行的当前任务因为试图去读取一个空的队列、信号或者互斥量而进入阻塞状态时,此函数会被立即调用。参数pxQueue保存的是试图读取的目标队列、信号或者互斥量的句柄,传递给此宏函数。

traceBLOCKING_ON_QUEUE_SEND(pxQueue)

当正在执行的当前任务因为试图往一个已经写满的队列或者信号或者互斥量而进入了阻塞状态时,此函数会被立即调用。参数pxQueue保存的是试图写入的目标队列、信号或者互斥量的句柄,传递给此宏函数。

traceQUEUE_SEND(pxQueue)

当一个队列或者信号发送成功时,此宏函数会在内核函数xQueueSend(),xQueueSendToFront(),xQueueSendToBack(),以及所有的信号give函数中被调用,参数pxQueue是要发送的目标队列或信号的句柄,传递给此宏函数。

traceQUEUE_SEND_FAILED(pxQueue)

当一个队列或者信号发送失败时,此宏函数会在内核函数xQueueSend(),xQueueSendToFront(),xQueueSendToBack(),以及所有的信号give函数中被调用,参数pxQueue是要发送的目标队列或信号的句柄,传递给此宏函数。

traceQUEUE_RECEIVE(pxQueue)

当读取一个队列或者接收信号成功时,此宏函数会在内核函数xQueueReceive()以及所有的信号take函数中被调用,参数pxQueue是要接收的目标队列或信号的句柄,传递给此宏函数。

traceQUEUE_RECEIVE_FAILED(pxQueue)

当读取一个队列或者接收信号失败时,此宏函数会在内核函数xQueueReceive()以及所有的信号take函数中被调用,参数pxQueue是要接收的目标队列或信号的句柄,传递给此宏函数。

traceQUEUE_SEND_FROM_ISR(pxQueue)

当在中断中发送一个队列成功时,此函数会在xQueueSendFromISR()中被调用。参数pxQueue是要发送的目标队列的句柄。

traceQUEUE_SEND_FROM_ISR_FAILED(pxQueue)

当在中断中发送一个队列失败时,此函数会在xQueueSendFromISR()中被调用。参数pxQueue是要发送的目标队列的句柄。

traceQUEUE_RECEIVE_FROM_ISR(pxQueue)

当在中断中读取一个队列成功时,此函数会在xQueueReceiveFromISR()中被调用。参数pxQueue是要发送的目标队列的句柄。

traceQUEUE_RECEIVE_FROM_ISR_FAILED(pxQueue)

当在中断中读取一个队列失败时,此函数会在xQueueReceiveFromISR()中被调用。参数pxQueue是要发送的目标队列的句柄。

traceTASK_DELAY_UNTIL()

当一个任务因为调用了vTaskDelayUntil()进入了阻塞状态的前一刻此宏函数会在vTaskDelayUntil()中被立即调用。

traceTASK_DELAY()

当一个任务因为调用了vTaskDelay()进入了阻塞状态的前一刻此宏函数会在vTaskDelay中被立即调用。

3,堆溢出钩子函数vApplicationMallocFailedHook

编程时,一般的逻辑错误都容易解决。难以处理的是内存越界、栈溢出等。

内存越界经常发生在堆的使用过程总:堆,就是使用malloc得到的内存。

并没有很好的方法检测内存越界,但是可以提供一些回调函数:

vApplicationMallocFailedHook 是 FreeRTOS 中的一个钩子函数(hook function),当内存分配失败时,FreeRTOS 会调用这个函数。这个钩子函数是可选的,用户可以根据自己的需要来实现它。它的主要目的是提供一种机制,让应用程序在内存分配失败时能够执行特定的操作,例如记录错误信息、进入安全模式或者执行其他恢复操作。

下面是一个简单的 vApplicationMallocFailedHook 函数的实现示例,它仅打印一条错误消息并进入一个无限循环:

#include "FreeRTOS.h"  
#include "task.h"  
#define configUSE_MALLOC_FAILED_HOOK    1/* 钩子函数,当内存分配失败时被调用 */  
void vApplicationMallocFailedHook( void )  
{  /* 这是一个错误情况,通常意味着系统没有足够的内存来继续运行 */  /* 打印错误消息 */  configPRINT_STRING( "Malloc Failed Hook called\n" );  /* 进入一个无限循环,因为此时系统可能处于不稳定状态 */  for( ;; )  {  /* 什么都不做,或者可以尝试一些恢复操作 */  }  
}

在这个例子中,configPRINT_STRING 是一个宏,用于将字符串发送到某个输出设备(例如串口)。这个宏的具体实现取决于你的应用程序配置和所使用的硬件平台。

请注意,这个函数的具体实现应该根据你的应用需求来定制。例如,在某些应用中,你可能希望尝试释放一些不再需要的资源,或者进入某种安全模式来避免系统崩溃。此外,如果系统检测到内存分配失败,这通常意味着系统已经处于资源紧张的状态,因此在这个钩子函数中执行的操作应该尽可能简单和快速。

确保你已经在你的 FreeRTOS 配置文件中启用了内存分配失败钩子(configUSE_MALLOC_FAILED_HOOK),并将其设置为 1,这样 vApplicationMallocFailedHook 才会在内存分配失败时被调用。

4,栈溢出钩子函数vApplicationStackOverflowHook

在切换任务(vTaskSwitchContext)时调用taskCHECK_FOR_STACK_OVERFLOW来检测栈是否溢出,如果溢出会调用:

void vApplicationStackOverflowHook( TaskHandle_t xTask, char * pcTaskName );

怎么判断栈溢出?有两种方法:

方法1:

  • 当前任务被切换出去之前,它的整个运行现场都被保存在栈里,这时很可能就是它对栈的使用到达了峰值。
  • 这方法很高效,但是并不精确
  • 比如:任务在运行过程中调用了函数A大量地使用了栈,调用完函数A后才被调度。
#ifndef configCHECK_FOR_STACK_OVERFLOW#define configCHECK_FOR_STACK_OVERFLOW    1
#endif#if ( ( configCHECK_FOR_STACK_OVERFLOW == 1 ) && ( portSTACK_GROWTH < 0 ) )/* Only the current stack state is to be checked. */#define taskCHECK_FOR_STACK_OVERFLOW()                                                            \{                                                                                                 \/* Is the currently saved stack pointer within the stack limit? */                            \if( pxCurrentTCB->pxTopOfStack <= pxCurrentTCB->pxStack )                                     \{                                                                                             \vApplicationStackOverflowHook( ( TaskHandle_t ) pxCurrentTCB, pxCurrentTCB->pcTaskName ); \}                                                                                             \}#endif /* configCHECK_FOR_STACK_OVERFLOW == 1 */

1,定义configCHECK_FOR_STACK_OVERFLOW 为1

2,

pxTopOfStackpxCurrentTCB中的一个成员,表示当前任务的栈顶指针。栈顶指针指向栈中最后一个有效位置之后的地址。当任务执行函数调用或局部变量压栈时,栈顶指针会向低地址方向移动。

pxStack 也是pxCurrentTCB中的一个成员,表示当前任务的栈底指针栈底指针指向栈的起始位置

if( pxCurrentTCB->pxTopOfStack <= pxCurrentTCB->pxStack ) 这个条件判断是在检查栈顶指针是否低于或等于栈底指针。如果是这样,那么意味着栈已经溢出,因为栈顶指针不应该回到或超过栈的起始位置。

在TCB结构体中,

pxStack,表示当前任务的栈底指针,分配栈空间时就固定了。

pxTopOfStack,表示当前任务的栈顶指针,调用函数时候,会向下压栈

如果pxTopOfStack<=pxStack 表示栈溢出。就去调用栈溢出的钩子函数

3,上诉提到使用这种方法不够准确的原因是:

在TASKA() while 循环A()函数过程中,如果需要分配100个字节(定义的栈空间也是100),相当于栈溢出了,pxTopOfStack 并没有实时更新,而是在退出任务A保存现场的时候才更新TCB。

但如果在此处发生了切换,

又被释放掉栈空间,pxTopOfStack又回到起始位置,并没有真实的记录原先栈溢出。

方法2:

  • 创建任务时,它的栈被填入固定的值,比如:0xa5
  • 检测栈里最后16字节的数据,如果不是0xa5的话表示栈即将、或者已经被用完了
  • 没有方法1快速,但是也足够快
  • 能捕获几乎所有的栈溢出
  • 为什么是几乎所有?可能有些函数使用栈时,非常凑巧地把栈设置为0xa5:几乎不可能
#ifndef configCHECK_FOR_STACK_OVERFLOW#define configCHECK_FOR_STACK_OVERFLOW    2
#endif#if ( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) && ( portSTACK_GROWTH < 0 ) )#define taskCHECK_FOR_STACK_OVERFLOW()                                                            \{                                                                                                 \const uint32_t * const pulStack = ( uint32_t * ) pxCurrentTCB->pxStack;                       \const uint32_t ulCheckValue = ( uint32_t ) 0xa5a5a5a5;                                        \\if( ( pulStack[ 0 ] != ulCheckValue ) ||                                                      \( pulStack[ 1 ] != ulCheckValue ) ||                                                      \( pulStack[ 2 ] != ulCheckValue ) ||                                                      \( pulStack[ 3 ] != ulCheckValue ) )                                                       \{                                                                                             \vApplicationStackOverflowHook( ( TaskHandle_t ) pxCurrentTCB, pxCurrentTCB->pcTaskName ); \}                                                                                             \}#endif /* #if( configCHECK_FOR_STACK_OVERFLOW > 1 ) */
/*-----------------------------------------------------------*/

1,定义个指针指向栈起始位置

2,判断最后4个连续空间的地址是否为a5

3,如果有一个不是a5,则溢出,调用栈溢出函数

问题0:在什么时候判断栈是否溢出

代码跟踪:vApplicationStackOverflowHook=》taskCHECK_FOR_STACK_OVERFLOW=》vTaskSwitchContext

即任务切换时,判断栈溢出。

所以任务1中,如果任务中间有多个函数,函数A,切换到函数B,会释放栈空间,如果溢出过,也无法发现。程序已经失控,无法切回函数B,也就无法被监控到栈溢出。

问题1:在哪里实现将栈空间都赋值成了a5

0xa5U=》tskSTACK_FILL_BYTE=》

( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );

=》prvInitialiseNewTask=》

xTaskCreate

问题2:vApplicationStackOverflowHook执行的具体函数是什么

vApplicationStackOverflowHook 是一个由 FreeRTOS 提供的钩子函数,它在检测到栈溢出时被调用。这个函数的默认实现通常什么都不做,或者只是简单地进入一个无限循环或执行一些基本的错误处理。然而,开发者可以提供一个自定义的 vApplicationStackOverflowHook 实现,以便在栈溢出发生时执行特定的操作。

自定义的 vApplicationStackOverflowHook 函数的具体实现取决于你的应用程序的需求和错误处理策略。以下是一个简单的示例,展示了如何在 vApplicationStackOverflowHook 中实现一些基本的错误处理:

void vApplicationStackOverflowHook( TaskHandle_t xTask, char * pcTaskName )  
{  /* 停止所有任务调度,以便安全地处理错误 */  vTaskSuspendAll();  /* 记录栈溢出错误到某个存储区域或打印到控制台 */  configPRINT_STRING( "Stack overflow detected in task: " );  configPRINT_STRING( pcTaskName );  configPRINT_STRING( "\n" );  /* 你可以在这里添加其他的错误处理代码,比如重启系统、进入安全模式等 */  /* 停止系统,因为栈溢出通常是严重的错误,需要干预 */  for( ;; )  {  /* 循环等待,或者执行一些简单的错误处理任务 */  }  
}

在这个例子中,vApplicationStackOverflowHook 首先暂停了所有任务的调度,以确保在错误处理期间系统不会继续执行其他任务。然后,它打印出发生栈溢出的任务名称。开发者可以在这里添加更多的错误处理逻辑,比如发送错误报告到外部设备、记录详细的调试信息,或者尝试恢复系统到一个安全状态。最后,函数进入一个无限循环,因为栈溢出通常是一个严重的错误,需要人工干预。

请注意,这个函数的实现必须非常小心,因为它在栈溢出的情况下被调用。此时,系统的状态可能已经不稳定,因此应避免执行任何可能进一步破坏系统状态的操作。特别是,你应该避免在这个函数中分配内存或调用可能使用大量栈空间的函数。

如果你没有提供一个自定义的 vApplicationStackOverflowHook 实现,并且你的 FreeRTOS 配置启用了栈溢出检测,那么当栈溢出发生时,FreeRTOS 将使用默认的空实现,这通常意味着没有任何动作发生,或者只是简单地进入一个无限循环。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/600316.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

海量智库 | ANY权限原理介绍

ANY权限是Vastbase中的一种特殊的管理权限&#xff0c;用户能够通过ANY权限执行更广泛的操作&#xff0c;更加便利的管理数据库。 本文将为您介绍ANY权限管理的相关原理。 ANY权限管理相关解释 ANY权限管理&#xff0c;是对数据库内的某一类对象的所有实体进行特定的权限管理…

国内ChatGPT大数据模型

在中国&#xff0c;随着人工智能技术的迅猛发展&#xff0c;多个科技公司和研究机构已经开发出了与OpenAI的ChatGPT类似的大型语言模型。这些模型通常基于深度学习技术&#xff0c;尤其是Transformer架构&#xff0c;它们在大量的文本数据上进行训练&#xff0c;以理解和生成自…

酱油行业市场需求及广阔前景分析

随着居民消费升级&#xff0c;对高品质生活的向往使得酱油市场需求持续增长。消费者对酱油的功能性需求日益细分&#xff0c;追求健康、天然与个性化的产品特性&#xff0c;从而推动了市场的多元化进步。 同时&#xff0c;餐饮业的蓬勃发展以及外卖市场的扩大&#xff0c;均为酱…

解决nginx代理后,前端拿不到后端自定义的header

先说结论&#xff0c;因为前端和nginx对接&#xff0c;所以需要在nginx添加如下配置向前端暴露header add_header Access-Control-Expose-Headers Authorization 排查过程 1.后端设置了Authorization 的响应头作为token的返回&#xff0c;前后端本地联调没有问题 response.s…

隔离放大器 用途 组成 应用领域

隔离放大器用于防止数据采集器件遭受远程传感器出现的潜在破坏性电压的影响。这些放大器还用于在多通道应用中放大低电平信号。它们也可以消除由接地环路引起的测量误差。由于不需要附加的隔离电源&#xff0c;带有内部变压器的隔离放大器可以降低电路成本 它由仪器放大器&…

LeetCode-33. 搜索旋转排序数组【数组 二分查找】

LeetCode-33. 搜索旋转排序数组【数组 二分查找】 题目描述&#xff1a;解题思路一&#xff1a;二分查找。1.找哨兵节点&#xff08;nums[0]或nums[-1]&#xff09;可以确定nums[mid]位于前一段或后一段有序数组中。2. 就是边界left和right的变换&#xff0c;具体看代码。解题思…

[LeetCode][LCR178]训练计划 VI——使用位运算寻找数组中不同的数字

题目 LCR 178. 训练计划 VI 教学过程中&#xff0c;教练示范一次&#xff0c;学员跟做三次。该过程被混乱剪辑后&#xff0c;记录于数组 actions&#xff0c;其中 actions[i] 表示做出该动作的人员编号。请返回教练的编号。 示例 1&#xff1a; 输入&#xff1a;actions [5, …

网络安全之命令注入

漏洞原理&#xff1a; 应用系统设计需要给用户提供指定的远程命令操作的接口&#xff0c;比如&#xff1a;路由器&#xff0c;防火墙&#xff0c;入侵检测等设备的web管理界面。一般会给用户提供一个ping操作的web界面 用户从web界面输入目标IP&#xff0c;提交后台会对改IP地…

【Java面试题】JVM(26道)

文章目录 JVM面试题基础1.什么是JVM&#xff1f;2.JVM的组织架构&#xff1f; 内存管理3.JVM的内存区域是什么&#xff1f;3.1堆3.2方法区3.3程序计数器3.4Java虚拟机栈3.5本地方法栈 4.堆和栈的区别是什么&#xff1f;5.JDK1.6、1.7、1.8内存区域的变化&#xff1f;6.内存泄露…

spring加载类初始化顺序

今天看spring官网的时候&#xff0c;提到了Ordered执行顺序。我当时记得PostConstruct注解会在bean加载后执行&#xff0c;现在又来了一个执行顺序&#xff0c;直接给我整蒙了。 于是我写了一个简单的dom来看看&#xff0c;它是什么&#xff1a; Service("t2ServerImpl&q…

【Redis】Redis的使用

登录redis [roottest2 ~]# redis-cli 127.0.0.1:6379> 或[roottest2 ~]# redis-cli -h 192.168.67.12 -p 6379 192.168.67.12:6379> redis-benchmark 测试工具 redis-benchmark 是官方自带的Redis性能测试工具&#xff0c;可以有效的测试Redis服务的性能 基本的测试语…

js正则给数值每三位加逗号

<template><div>{{ num.toFixed(2).replace(/\B(?(\d{3})(?!\d))/g, ",") }}</div> </template> <script> </script> <style> </style> 如上所示&#xff1a;保留两位小数后&#xff0c;用正则进行替换 效果&am…