背景
客户拿我们做的机器对接外部供应商的算法,在对接算法过程中,外部供应商反馈我们的机器读到的IMU数据中的时间戳有时会变成负数。
分析过程
从负责此IMU模块驱动模块的同事处了解到,这个时间并不是IMU的时钟,而是使用的平台自带的时钟,暂不确定是哪出了问题。随机让同事加调试信息,安排煲机,经过漫长的煲机,发现确实会出现负数的情况。
此时让同事用%llx的方式继续抓,从抓到的log中看,发现在数据增加到0x7FFFFFFF后,只要数据再增加,后面的数据就会变成负数,但是呢,底层上报额数据是uint64_t格式的,有点无厘头。
由于同事加的log不全,并没有体现是哪一层出的问题,遂让同事加全log(主要是加各层的接收和向下一层上报处的log)煲机。煲机后发现在获取时间戳时就已经出异常了。
此时同事针对性的查看代码,发现代码并无异常,代码的伪代码为:
a.c
uint64_t get_time(void) { return xxxx; // 此处为芯片寄存器值 }
b.c
uint64_t get_info(struct sensor_data *dat) { dat->xxxx = xxxx; .... dat->time = get_time()); // 此处的time也为uint64_t的结构 }
从代码上来看,这个逻辑没毛病,从一个返回值为uint64_t的函数中读到数据直接赋值给uing64_t的变量,没有任何问题。
但是考虑到此时发生了翻转,遂怀疑是不是寄存器本身给的信息错误,此时让同事将寄存器值改为变量值,每次读取时,变量都增加,查看是否异常,此时发现异常仍然存在,可以排除芯片本身设计问题。
由于数据发生突变,此时怀疑是不是参数值传递时出现异常,因此让同事确认包含get_time函数的头文件是否被b,c申明,此时一查,还真发现了问题,头文件并未被引用,遂让同事加上头文件后再次测试。经过测试,问题消失。
后续处理
1. 让同事排查所有用到此函数的c文件是否都有引用该头文件,确认完毕后出软件提交测试。测试通过后将修改项合并至代码主干。
2. 顺便让同事修改好上层接口,将上层读取数据结构体中的时间信息的格式与底层统一,确保不会出现数据交互时的信息突变。
背后原理
在头文件未包裹的情况下,编译器会根据设定的规则去确认是否按报错处理,刚好我们拿到的供应商代码使用的是相对宽松的规则,此时编译时仅仅会报函数不存在,默认使用 int get_time(void)的虚拟接口编译成中间文件,而在链接时,由于函数uint64_t get_time(void)在另一个中间文件中存在,因此链接是也没报错。
恰恰就是这种宽松的逻辑带来了问题,虽然函数返回值是uint64_t格式的,但当b.c那边调用时,会将uint64_t格式的数据转成int格式给到dat->time,此时由于uint64_t 到int时,不是强制类型转换,而是仅仅类似于共享内存的方式(两边各自按照各自的格式去读写,读操作并不改变存储的值),也就造成了0x8000000在int格式时对应的也是0x80000000。
之后又执行了一遍赋值操作,此时0x80000000由于高位为1,到uint64_t时,高位都会被补1。但此时还是不会有异常,因为格式是uint64_t。偏偏我们拿到的代码又有另一个坑,内核给的time是uint64_t,framework读数据时用的是int64_t,而数据交互过程又是采用共享内存的方式实现的,这就导致了上层拿到的数据因为最高位为1直接变成了负数了。
我要赚赏金
