运行时错误检查(/RTC)编译选项及实现原理

本文详细介绍了Visual Studio 2005中四种重要的调试编译选项:/GS(缓冲区安全检查)、/RTCu(未初始化变量使用检查)、/RTCs(堆栈检查)和/RTCc(数据转换检查),并提供了具体的实现原理和应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自:http://blog.youkuaiyun.com/magictong/article/details/6306820

环境:VS2005

前因后果:debug居然编不过!!!这里准备说4个例子,都是为了说明debug版本对于调试是很重要的,很多问题在调试版本下都会提前暴露出来。

 

注意:随意调整优化,可调试选项可能会遇到下面的编译错误:

Command line error D8016 : '/O2' and '/RTC1' command-line options are incompatible

 

(http://msdn.microsoft.com/zh-cn/library/8wtf2dfz(v=vs.80).aspx)

1、/GS

名称:缓冲区安全检查(http://msdn.microsoft.com/zh-cn/library/8dbf701c(v=vs.80).aspx)。

编译选项位置:C/C++ à Code Generation à Buffer Security Check

说明:缓冲区溢出安全检测,要强调的是,该编译选项并不是对每一个函数都设置安全cookies。编译器首先会判断一个函数是否是属于“潜在危险”的函数,例如是否在函数的堆栈上分配了字符串数组等等,就可以作为一个特征(编译器怎么判断的偶没研究过o(_)o )。只有被编译器判断是存在“潜在危险”的函数之后,编译器才会在这个函数里面使用安全cookie检测。但是要注意,它不能预防所有的安全漏洞。

原理:

#include "stdafx.h"

#include <string.h>

void Function(const char*);

 

int _tmain(int argc, _TCHAR* argv[])

{

         char pBuf[] = "aaaaaaaaaaaa";

         Function(pBuf);

         return 0;

}

 

void Function(const char* pBuf)

{

         char szBuf[10];

         strcpy(szBuf, pBuf);

}

void Function(const char* pBuf)

{

004017E0  push        ebp 

004017E1  mov         ebp,esp

004017E3  sub         esp,10h

004017E6  mov         eax,dword ptr [___security_cookie (403000h)]

004017EB  xor         eax,ebp

004017ED  mov         dword ptr [ebp-4],eax

      char szBuf[10];

      strcpy(szBuf, pBuf);

004017F0  mov         eax,dword ptr [ebp+8]

004017F3  push        eax 

004017F4  lea         ecx,[ebp-10h]

004017F7  push        ecx 

004017F8  call        strcpy (401010h)

004017FD  add         esp,8

}

00401800  mov         ecx,dword ptr [ebp-4]

00401803  xor         ecx,ebp

00401805  call        __security_check_cookie (401000h)

0040180A  mov         esp,ebp

0040180C  pop         ebp 

0040180D  ret

 

__security_check_cookie声明:

void __declspec(naked) __fastcall __security_check_cookie(UINT_PTR cookie)

 

00401000  cmp        ecx,dword ptr [___security_cookie (403000h)]

00401006  jne         failure (40100Ah)

00401008  rep ret         

0040100A  jmp        __report_gsfailure(4012BFh)

备注:

在以下情况中,编译器不会对易受攻击的参数提供安全保护:

a.函数不包含缓冲区。

b.如果未启用优化 ( /O 选项(优化代码))。

c.函数具有可变参数列表 (...)。

d.函数标记为 naked (C++)

e.函数的第一行语句包含内联程序集代码。

f.如果仅通过在缓冲区溢出事件中不太可能利用的方式使用参数。

 

2、/ RTCu

名称:未初始化变量使用检查

编译选项位置:C/C++ à Code Generation à Basic Runtime Checks

说明:这是一个动态的警告,编译的时候也会有警告,不过这个是动态的警告,更直观。

原理:

void Function(const char* pBuf)

{

         int a;

         int b;

         b = a;

}

         通过额外使用一个变量来跟踪某个变量是否初始化,下面的代码里面那个额外的跟踪变量放在[ebp-49h]里面。

0042D759  mov         byte ptr [ebp-49h],0

0042D75D  cmp         byte ptr [ebp-49h],0

0042D761  jne         Function+20h (42D770h)

0042D763  push        offset  (42D77Dh)

0042D768  call        @ILT+1935(__RTC_UninitUse) (42B794h)

0042D76D  add         esp,4

 

 

把代码稍微改一改:

void Function(const char* pBuf)

{

         int a;

         int b;

 

         if (pBuf)

         {

                   a = 100;

         }

 

         b = a;

}

很明显那个分支里面,给a赋值之前更改了那个跟踪变量的值(修改为1),这样那个警告框就不会再弹出来了。

0042D72D  cmp         dword ptr [pBuf],0

0042D731  je          Function+1Eh (42D73Eh)

0042D733  mov         byte ptr [ebp-49h],1

0042D737  mov         dword ptr [a],64h

0042D73E  cmp         byte ptr [ebp-49h],0

0042D742  jne         Function+31h (42D751h)

0042D744  push        offset  (42D75Eh)

0042D749  call        @ILT+1935(__RTC_UninitUse) (42B794h)

0042D74E  add         esp,4

 

3、/ RTCs

名称:堆栈检查

编译选项位置:C/C++ à Code Generation à Basic Runtime Checks

说明:该选项主要做三件事情:

(1)、Debug模式下把stack上的变量全部初始化为0xcc(使用这个值是因为0xcc对应汇编代码int 3,而且这个值很大容易引起程序员的注意),检查未初始化的问题;

(2)、检查数组越界;

(3)、检查ESP是否被破坏。

原理:

0x0D4 = 0x35 * 4

void Function(const char* pBuf)

{

         char szBuf[10];

         strcpy(szBuf, pBuf);

}

00411470  push        ebp 

00411471  mov         ebp,esp

00411473  sub         esp,0D4h

00411479  push        ebx 

0041147A  push        esi 

0041147B  push        edi 

0041147C  lea         edi,[ebp-0D4h]

00411482  mov         ecx,35h

00411487  mov         eax,0CCCCCCCCh

0041148C  rep stos    dword ptr es:[edi]

 

同样是上面的那一小段代码,在strcpy执行完之后,插入了如下一段代码:

0042D6B8  push        edx 

0042D6B9  mov         ecx,ebp

0042D6BB  push        eax 

0042D6BC  lea         edx,[ (42D6E8h)]

0042D6C2  call        @ILT+1480(@_RTC_CheckStackVars@8) (42B5CDh)

 

_RTC_CheckStackVars函数里面对数组的前后两端进行了对比看是否和0CCCCCCCCh相等,如果不相等会报错,用这种方法判断数组是否越界。

0042E140  push        ebp 

0042E141  mov         ebp,esp

0042E143  push        ecx 

0042E144  push        ebx 

0042E145  push        esi 

0042E146  push        edi 

0042E147  xor         edi,edi

0042E149  mov         esi,edx

0042E14B  cmp         dword ptr [esi],edi

0042E14D  mov         ebx,ecx

0042E14F  mov         dword ptr [i],edi

0042E152  jle         _RTC_CheckStackVars+58h (42E198h)

0042E154  mov         eax,dword ptr [esi+4]

0042E157  mov         ecx,dword ptr [eax+edi]

0042E15A  add         eax,edi

0042E15C  cmp         dword ptr [ecx+ebx-4],0CCCCCCCCh

0042E164  jne         _RTC_CheckStackVars+34h (42E174h)

0042E166  mov         edx,dword ptr [eax+4]

0042E169  add         edx,ecx

0042E16B  cmp         dword ptr [edx+ebx],0CCCCCCCCh

0042E172  je          _RTC_CheckStackVars+48h (42E188h)

0042E174  mov         eax,dword ptr [esi+4]

0042E177  mov         ecx,dword ptr [eax+edi+8]

0042E17B  mov         edx,dword ptr [ebp+4]

0042E17E  push        ecx 

0042E17F  push        edx 

0042E180  call        _RTC_StackFailure (42B82Ah)

0042E185  add         esp,8

0042E188  mov         eax,dword ptr [i]

0042E18B  add         eax,1

0042E18E  add         edi,0Ch

0042E191  cmp         eax,dword ptr [esi]

0042E193  mov         dword ptr [i],eax

0042E196  jl          _RTC_CheckStackVars+14h (42E154h)

0042E198  pop         edi 

0042E199  pop         esi 

0042E19A  pop         ebx 

0042E19B  mov         esp,ebp

0042E19D  pop         ebp 

0042E19E  ret             

 

         再看最后一小段代码,通过检测esp的值来判断堆栈是否平衡,首先把esp加上初始化时减出的值,然后和ebp对比,正常情况下应该是相等的(看看初始化的代码这很好理解):

0042D6D6  add         esp,0D8h

0042D6DC  cmp         ebp,esp

0042D6DE  call        @ILT+3445(__RTC_CheckEsp) (42BD7Ah)

0042D6E3  mov         esp,ebp

0042D6E5  pop         ebp 

0042D6E6  ret

 

_RTC_CheckEsp极其简单就下面两句:

0042D9E0  jne         esperror (42D9E3h)

0042D9E2  ret

 

构造这样一个函数测试一下:

void Function(const char* pBuf)

{

         char szBuf[10];

         strcpy(szBuf, pBuf);

         __asm push ebx

}

 

 

4、/RTCc

名称:数据转换检查

编译选项位置:C/C++ à Code Generation à Smaller Type Check

说明:在数据赋值时,如果把一个较长的数据类型赋值给一个较小的数据类型,很有可能发生数据截断的问题,这个选项就是用来在发生数据截断时提示程序员的。(注意的是这里是动态检测的,只有确实会丢失数据时才报错)

原理:

void Func2()

{

         int nSrc = 100;

         char chDes = 'a';

         chDes = nSrc;

}

0042D679  mov         dword ptr [nSrc],64h

0042D680  mov         byte ptr [chDes],61h

0042D684  mov         ecx,dword ptr [nSrc]

0042D687  call        @ILT+4935(@_RTC_Check_4_to_1@4) (42C34Ch)

0042D68C  mov         byte ptr [chDes],al

 

         一堆这种函数,哈哈。

0042C338  jmp         _RTC_Check_2_to_1 (42D760h)

0042C33D  jmp         _RTC_Check_8_to_1 (42E520h)

0042C342  jmp         _RTC_Check_8_to_2 (42E560h)

0042C347  jmp         _RTC_Check_8_to_4 (42E790h)

0042C34C  jmp         _RTC_Check_4_to_1 (42DE60h)

0042C351  jmp         _RTC_Check_4_to_2 (42DF50h)

 

         我们还是来看看_RTC_Check_4_to_1:

0042DE60  push        ebp 

0042DE61  mov         ebp,esp

0042DE63  push        ebx 

0042DE64  mov         ebx,ecx

0042DE66  mov         eax,ebx

0042DE68  and         eax,0FFFFFF00h

0042DE6D  je          _RTC_Check_4_to_1+24h (42DE84h)

0042DE6F  cmp         eax,0FFFFFF00h

0042DE74  je          _RTC_Check_4_to_1+24h (42DE84h)

0042DE76  mov         eax,dword ptr [ebp+4]

0042DE79  push        1   

0042DE7B  push        eax 

0042DE7C  call        _RTC_Failure (42C1EEh)

0042DE81  add         esp,8

0042DE84  mov         al,bl

0042DE86  pop         ebx 

0042DE87  pop         ebp 

0042DE88  ret 

         逻辑还是很简单的,先判断int的高三个字节是不是都是0,如果都是0说明没超出范围,直接跳到末尾;再检查高三个字节是不是全是1,如果是说明是个一个负数也没超出范围,直接跳转到末尾,其他情况直接报错。

 

备注:

         如果你确信要这么做,确实要进行数据截断(譬如取数据的高位低位的),请按上面错误MSGBOX里面说明的做,写这个玩意的哥么想得很周到啊o(∩_∩)o 哈哈。

<think>好的,我需要帮用户分析`ls /dev/rtc*`的输出结果`/dev/rtc /dev/rtc0`,并给出后续步骤建议。首先,用户提到参考了之前的回答和引用,但之前的引用可能不直接相关,我需要专注于当前问题。 首先,了解`/dev/rtc*`设备文件的含义。在Linux中,RTC(实时时钟)设备通常对应硬件时钟。`/dev/rtc`和`dev/rtc0`的存在可能意味着系统中有多个RTC设备或者新旧驱动并存。常见的情况是`/dev/rtc`是旧版接口,而`/dev/rtc0`是新版驱动创建的设备。 接下来,检查是否存在多个RTC硬件。用户可能需要确认系统中有多个RTC芯片,比如主板内置的和外接模块。使用命令`dmesg | grep rtc`查看内核启动日志,确认检测到的RTC设备。如果有多个,系统可能会为每个分配不同的设备文件,如`rtc0`, `rtc1`等。 然后,确定当前使用的RTC设备。通过`hwclock`命令查看当前使用的设备。例如,`hwclock -r -f /dev/rtc0`可以指定设备读取时间,对比不同设备的时间是否一致。 还要验证符号链接关系。通常`/dev/rtc`可能是指向`/dev/rtc0`的符号链接。使用`ls -l /dev/rtc`检查链接情况。如果不是链接,可能需要进一步确认系统配置是否正确。 用户可能遇到的问题是时间同步错误或硬件冲突。建议检查系统时间和硬件时间是否一致,使用`hwclock --compare`查看差异。如果存在多个RTC设备,需要明确系统默认使用哪一个,可能需要配置相关服务如`chronyd`或`systemd-timesyncd`指向正确的设备。 最后,根据分析结果给出操作建议,如检查硬件、配置驱动、选择正确的RTC设备等。同时提醒用户注意权限问题,操作RTC设备可能需要root权限。</think>### 分析 `ls /dev/rtc*` 输出结果与后续步骤 #### **1. 输出结果解析** 当执行 `ls /dev/rtc*` 显示 `/dev/rtc` 和 `/dev/rtc0` 时,表明系统存在 **多个 RTC(实时时钟)设备接口**。具体含义如下: - **`/dev/rtc`**:传统的 RTC 设备接口,通常是主板上 CMOS 芯片的 RTC 驱动生成的符号链接,指向当前激活的 RTC 设备(如 `/dev/rtc0`)[^2]。 - **`/dev/rtc0`**:内核 RTC 驱动框架生成的第一个 RTC 设备节点,通常对应主板内置的硬件时钟。 #### **2. 可能原因** - **多 RTC 硬件共存**:系统检测到多个 RTC 设备(如主板 RTC + 外接 RTC 模块)。 - **驱动兼容性**:旧版驱动与新版 `rtc-cmos` 驱动并存。 - **虚拟化环境**:虚拟机中可能虚拟化出多个 RTC 设备。 #### **3. 关键验证步骤** ##### **步骤1:确认 RTC 硬件数量** ```bash dmesg | grep -i rtc # 查看内核启动日志中的 RTC 设备注册信息 ``` - **预期输出示例**: ``` rtc_cmos 00:01: RTC can wake from S4 rtc_cmos 00:01: registered as rtc0 ``` 若存在类似 `rtc1` 的条目,则表明存在多个硬件 RTC。 ##### **步骤2:检查符号链接关系** ```bash ls -l /dev/rtc # 验证是否为符号链接 ``` - **正常情况**:`/dev/rtc -> rtc0`(指向当前活动的 RTC 设备)[^2]。 - **异常情况**:若为独立设备文件,需排查驱动冲突。 ##### **步骤3:验证时间同步状态** ```bash hwclock -r -f /dev/rtc0 # 读取 rtc0 的时间 hwclock -r -f /dev/rtc # 读取 rtc 的时间 ``` - **预期行为**:两者时间应一致。若不一致,表明存在 RTC 设备冲突或硬件故障。 #### **4. 后续操作建议** ##### **场景1:需要指定特定 RTC 设备** - **修改服务配置**(如 Chrony/NTPd): ```bash # 编辑 Chrony 配置文件 sudo vi /etc/chrony.conf ``` 添加以下行以指定 RTC 设备: ``` rtcdevice /dev/rtc0 ``` 重启服务: ```bash sudo systemctl restart chronyd ``` - **手动同步时间**: ```bash sudo hwclock -w -f /dev/rtc0 # 将系统时间写入 rtc0 ``` ##### **场景2:驱动冲突导致多设备** - **禁用冗余驱动**: ```bash # 查看已加载的 RTC 驱动模块 lsmod | grep rtc # 尝试卸载冲突模块(例如 rtc-ds1307) sudo modprobe -r rtc-ds1307 ``` - **更新内核启动参数**: 在 `/etc/default/grub` 中添加: ``` GRUB_CMDLINE_LINUX="rtc_cmos.use_acpi_alarm=1" ``` 更新配置并重启: ```bash sudo update-grub && reboot ``` ##### **场景3:排查硬件故障** - **检查 RTC 电池电压**: ```bash sudo cat /sys/class/rtc/rtc0/device/power_supply/cmos_battery/voltage_now ``` - **正常范围**:3V 左右(若低于 2.5V,需更换主板电池)[^1]。 - **强制重置 RTC**: ```bash echo 0 | sudo tee /sys/class/rtc/rtc0/wakealarm # 清除唤醒闹钟 sudo hwclock --systohc -f /dev/rtc0 # 从系统时间重新初始化 ``` #### **5. 流程图:多 RTC 设备处理逻辑** ```mermaid graph TD A[发现 /dev/rtc 和 /dev/rtc0] --> B{时间是否一致?} B -->|是| C[检查服务是否绑定正确设备] B -->|否| D[排查硬件冲突/电池故障] C --> E[修改 Chrony/systemd-timesyncd 配置] D --> F[更换 RTC 电池或禁用冗余驱动] ``` --- ### 相关问题 1. 如何通过 `sysfs` 接口修改 RTC 的唤醒闹钟? 2. 虚拟机环境中为何会出现多个 RTC 设备? 3. `hwclock` 命令写入时间时如何避免时区混淆? 4. 系统日志中 `rtc0: alarms up to one day, 242 bytes nvram` 的含义是什么? [^1]: 主板 CMOS 电池电压检测方法参考 Linux 内核文档。 [^2]: RTC 设备符号链接行为由 `udev` 规则定义,详见 `/lib/udev/rules.d/50-udev-default.rules`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值