利用 _RTC_CheckStackVars(...) 和 windbg 发现访问越界

本文介绍如何使用_RTC_CheckStackVars进行栈变量完整性的检查,包括如何定位访问越界的变量,以及windbg调试技巧。

背景:在项目 debug 过程中遇到 failwithmessage 函数,但是程序正常运行,判断这个函数应该是类似 exception 之类的功能。查看调用栈,发现是 _RTC_CheckStackVars(...) 引起 failwithmessage 的调用。网上查到这个函数是检查栈变量完整性,很可能程序当中已经发生了访问越界。项目稳定性的要求比较高,需要捉一下虫子。

通过本文可以了解:

1. RTC API (run time check) 的基本原理。

2. 实际分析调用栈的形式。

3. 初步探索windbg调试的一些方法。


代码:猜测是访问越界导致,所以单写了代码越界的代码调试用

  1. int main()  
  2. {  
  3.     int a[3] = { 0 };  
  4.     int b[3] = { 0 };  
  5.     a[3] = 3;  
  6.     return 0;  
  7. }  

编译,并利用windbg调试,果然出现同样的函数调用栈。第一行是我们可以通过windbg查看的关于调用栈的所有信息。这里选了Addrs和Source args。第一列是每一个函数调用栈栈顶地址。函数名紧接着传进函数的参数的值。我们的调试函数是wmain,单步跟踪可以发现,在wmain完全退出之前(位于renturn之后)调用了_RTC_CheckStackVars。这是一个编译器行为。


图中高亮的函数是我们需要仔细分析的函数。通过搜索visual studio安装文件夹,找到了对应的定义文件。

C:\Program Files\Microsoft Visual Studio 12.0\VC\include\rtcapi.h

  1. /* These unsupported functions are deprecated in native mode and not supported at all in /clr mode */  
  2. _RTCINTERNAL_DEPRECATED  void   __fastcall _RTC_CheckStackVars(void *_Esp, _RTC_framedesc *_Fd);  
  3.   
  4. /* Compiler generated calls (unlikely to be used, even by power users) */  
  5. /* Types */  
  6. typedef struct _RTC_vardesc {  
  7.     int addr;  
  8.     int size;  
  9.     char *name;  
  10. } _RTC_vardesc;  
  11.   
  12. typedef struct _RTC_framedesc {  
  13.     int varCount;  
  14.     _RTC_vardesc *variables;  
  15. } _RTC_framedesc  
不太明白,网络搜索知道_RTC_CheckStackVars第一个参数是函数要检查的栈顶地址,第二个参数指向结构体_RTC_framedesc。该结构体第一个参数是压栈的局部变量个数,第二个参数保存局部变量相关信息。分别是变量的栈偏移地址,变量大小和变量的名字。因此我们可以通过地址跳转最终找到出错的变量。

首先查看_RTC_framedesc。0x0002代表检查的栈中变量的个数。0x00281434代表变量信息存储的地址。


接下来查看地址0x00281434以获得变量的信息。

第一个变量的偏移地址是0xffffffec,大小是0x0c,名称存储在0x28144e,可以看到是a(0x61)。第二个变量偏移地址是0xffffffd8,大小是0x0c,名字存储在0x28144c,b(0x62)。最后要找到出错的内存地址:栈顶地址+偏移量,得到是0x16faf8

高地址对应是a的内存,低地址是b。可以看出,对于debug版本,VS不止会为变量分配空间,而且给变量周围包围4个字节的boudary,均初始化成0xcccccccc。但读写越界的时候,这四个字节中的数据被改写,_RTC_CheckStackVars就会发现异常,并调用failwithmessage函数。而且查看该函数的栈空间,会发现Stack arround the variable 'a' corrupted字样。对应图中0x16fb04位置的内存已经从0xcccccccc改成0x03。

最后可以利用上述信息定位错误。重头开始运行程序,在进入main函数入口添加断点。查看变量a的地址是0x163f24,所以a[3]的地址是0x163f30。添加断点

ba w4 0x163f30

表示从0x163f30开始四个字节内的内存被写入时,进入断点。运行程序发现断点停在a[3] = 3。由此我们定位到了访问越界的根源。


相关的知识:

1. windbg的几种断点:bu - defferred breakpoints 每次会根据符号去计算断点地址。因此即使相应的模块还没有被加载,也可以通过它来设置断点。

                                 bp - original breakpoints    通过地址设置断点。

                                 ba - data breakpoints        可以针对指定内存的读写操作(另外一种是对指定代码)设置断点。

                                 bm - set breakpoints using symbol pattern 在设置符号断点的时候可以利用通配符。比如在一个类的成员函数入口都设置断点,bm module!class::*。

2. 为了验证猜想,修该代码为a[3] = 0xcccccccc。这个时候_RTC_CheckStackVars不再报错。

3. 在VS中关闭RTC(runtime check)。

              /RTC1 /RTCu /RTCc /RTCs      -         编译器开关。

              #pragma runtime_checks( "[runtime_checks]", {restore | off} )         -          宏编译开关。
void RTE_RTC_Init(RTC_Information_st_t g_stRTERTCInformation) { RTC_InitTypeDef RTC_InitStructure = {0}; // GPIO_InitTypeDef GPIO_InitStruct = {0}; #ifdef USED_FSUB_RTC_FCLK RTC_InitStructure.RTC_Clk = RTC_FSUB; #else RTC_InitStructure.RTC_Clk = RTC_8MHZ; #endif // RTC_InitStructure.RTC_Clk = RTC_FSUB; //?????��?????��?? RTC_InitStructure.RTC_Time.RTC_Seconds = g_stRTERTCInformation.u8RTCSecond; RTC_InitStructure.RTC_Time.RTC_Minutes = g_stRTERTCInformation.u8RTCMinute; RTC_InitStructure.RTC_Time.RTC_Hours = g_stRTERTCInformation.u8RTCHour; //????8??30��?20?? RTC_InitStructure.RTC_Time.RTC_H12 = RTC_H12_AM; RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24;//RTC_HourFormat_24 RTC_InitStructure.RTC_Date.RTC_Day = g_stRTERTCInformation.u8RTCDayOfMonth;// 22 RTC_InitStructure.RTC_Date.RTC_WeekDay = g_stRTERTCInformation.u8RTCDayOfWeek;//???? RTC_InitStructure.RTC_Date.RTC_Month = g_stRTERTCInformation.u8RTCMonth; //5?? RTC_InitStructure.RTC_Date.RTC_Year = g_stRTERTCInformation.u8RTCYear; //2022?�� RTC_InitStructure.RTC_Period = Period_None; //RTC ????????1s?��?��???��???????? RTC_InitStructure.RTC_Alarm_Onoff = RTC_Alarm_Off; RTC_InitStructure.RTC_Alarm.Alarm_Minute = 0; RTC_InitStructure.RTC_Alarm.Alarm_Hour = 12; RTC_InitStructure.RTC_Alarm.Alarm_Week = ALARM_WEEK(SUNDAY) | ALARM_WEEK(SATURDAY); RTC_InitStructure.RTC_1HZ_Output = DISABLE; //RTC1HZ diable output RTC_Init(&RTC_InitStructure); RTC->SUBCUD = 0x15; // ISR_Register(RTC_IRQn, rtc_interrupt); //RTC????��????��??��??�� RTE_RTC_Start(); // ISR_DisRegister(RTC_IRQn, rtc_interrupt); } 代码注释
最新发布
08-27
<think>好的,我现在需要帮用户给他们的C代码添加注释,并且根据之前的示例格式生成相关问题。首先,我得仔细阅读用户提供的代码,理解每一部分的功能,然后添加合适的注释。 用户提供的代码是一个RTC初始化函数,名为RTE_RTC_Init,接收一个结构体参数g_stRTERTCInformation。代码中配置了RTC的时钟源、时间、日期、报警功能等,并调用了RTC_Init函数,最后启动RTC。 首先,我会逐行分析代码,确定每个部分的作用。比如,RTC_InitTypeDef结构体的初始化,时钟源的选择(根据宏定义使用FSUB或8MHz),时间、日期的设置,报警配置,以及最后的SUBCUD寄存器设置启动RTC。 接下来,添加注释时,需要明确每个参数的意义配置的原因。例如,时钟源的选择可能影响RTC的精度功耗,需要注明不同宏定义的作用。时间设置部分需要说明是从传入的结构体获取初始值,并且转换为BCD格式。报警配置中的位操作需要解释如何设置星期几的报警。 然后,关于相关问题部分,需要参考之前的例子,提出与代码逻辑、潜在问题、优化建议等相关的5个问题。例如,时钟源的选择是否影响低功耗模式,报警配置的位操作是否需要掩码保护,SUBCUD寄存器设置的必要性,结构体初始化的默认值是否安全,以及ISR注册的注释原因。 需要注意的问题:确保注释准确,不遗漏关键步骤;相关问题要具体,针对代码中的技术点,如参数校验、硬件寄存器操作、同步机制、异常处理等。同时,保持问题多样性,涵盖不同方面,如正确性、可靠性、优化、潜在风险等。 现在,检查是否有遗漏的部分,比如RTC_InitStructure的初始化为{0},这确保了未显式设置的字段为0,是否需要注释说明?可能用户已经了解,但为了清晰,可以提到结构体初始化为默认值。 另外,RTC->SUBCUD = 0x15; 这一行可能涉及校准寄存器的设置,但用户代码中没有注释,需要根据实际硬件手册解释,但如果没有信息,可能只能询问其作用。 最后,确保相关问题符合之前的格式,使用中文,编号正确,且末尾没有多余内容。例如,问题可能涉及时钟源选择的影响、报警配置的位操作安全性、SUBCUD寄存器的作用、结构体初始化的安全性、以及ISR注册被注释的原因。 </think>```c /** * @brief RTC模块初始化函数,配置时钟源、初始时间日期、报警参数等 * @param g_stRTERTCInformation: 包含RTC初始配置信息的结构体 * @retval 无 */ void RTE_RTC_Init(RTC_Information_st_t g_stRTERTCInformation) { // 初始化RTC配置结构体并清零 RTC_InitTypeDef RTC_InitStructure = {0}; // 根据宏定义选择RTC时钟源(FSUB低功耗晶振或8MHz内部时钟) #ifdef USED_FSUB_RTC_FCLK RTC_InitStructure.RTC_Clk = RTC_FSUB; #else RTC_InitStructure.RTC_Clk = RTC_8MHZ; #endif // 从输入参数提取并设置初始时间(需确保输入值符合BCD编码要求) RTC_InitStructure.RTC_Time.RTC_Seconds = g_stRTERTCInformation.u8RTCSecond; RTC_InitStructure.RTC_Time.RTC_Minutes = g_stRTERTCInformation.u8RTCMinute; RTC_InitStructure.RTC_Time.RTC_Hours = g_stRTERTCInformation.u8RTCHour; RTC_InitStructure.RTC_Time.RTC_H12 = RTC_H12_AM; // 默认设置AM时段 // 设置时间格式为24小时制 RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24; // 设置初始日期信息(需确认输入值范围符合RTC规范) RTC_InitStructure.RTC_Date.RTC_Day = g_stRTERTCInformation.u8RTCDayOfMonth; RTC_InitStructure.RTC_Date.RTC_WeekDay = g_stRTERTCInformation.u8RTCDayOfWeek; RTC_InitStructure.RTC_Date.RTC_Month = g_stRTERTCInformation.u8RTCMonth; RTC_InitStructure.RTC_Date.RTC_Year = g_stRTERTCInformation.u8RTCYear; // 禁用周期中断功能,关闭报警功能 RTC_InitStructure.RTC_Period = Period_None; RTC_InitStructure.RTC_Alarm_Onoff = RTC_Alarm_Off; // 设置报警时间为12:00并配置周报警掩码(仅周六日触发) RTC_InitStructure.RTC_Alarm.Alarm_Minute = 0; RTC_InitStructure.RTC_Alarm.Alarm_Hour = 12; RTC_InitStructure.RTC_Alarm.Alarm_Week = ALARM_WEEK(SUNDAY) | ALARM_WEEK(SATURDAY); // 禁用1Hz方波输出功能 RTC_InitStructure.RTC_1HZ_Output = DISABLE; // 执行RTC硬件初始化(底层寄存器配置) RTC_Init(&RTC_InitStructure); // 设置亚秒寄存器补偿值(0x15用于校准,需根据实际晶振特性调整) RTC->SUBCUD = 0x15; // 注册RTC中断服务函数(当前被注释,需根据应用需求启用) // ISR_Register(RTC_IRQn, rtc_interrupt); // 启动RTC计数器 RTE_RTC_Start(); // 中断反注册示例(调试用保留代码) // ISR_DisRegister(RTC_IRQn, rtc_interrupt); } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值