本篇涉及的知识点有:
- gdb 实际案例调试
- gdb 中可以使用set命令自定义变量、可以实际用ptype命令查看变量、函数返回值、typedef后的类型的真实类型
- 2038年时间戳问题: 采用32位整型有符号类型存储时间戳,最多可以表示到 2038年1月19日03:14:07 UTC
- boost 1.67.0版本以前存在的2038年时间戳的问题、以及问题解决方案
一、以问题引入
授权证书一般有到期时间的说法,公司测试同事在测试更新后的证书时,将系统时间调到了2050年,重启服务后发现各个进程的cpu占用率特别高;结合日志分析,发现这些进程 都在不停的刷heartbeat()的日志,也就是在频繁的进行心跳。
信息1:查看代码可知心跳间隔在配置文件中设置的是30秒
信息2:公司使用的mysql服务也在频繁重启,查看日志发现 This MySQL server doesn’t support dates later then 2038
信息3: 将系统时间调到2037年,发现mysql无问题,进程cpu占用率也下来了。
二、确定问题点或问题方向
#include <boost/date_time.hpp> //boost的date_time模块的头文件
#include <boost/thread.hpp>
#include <iostream>
int main(){
while(1){
auto sec = boost::posix_time::seconds(30); //心跳间隔30秒
boost::this_thread::sleep(sec);
std::cout<<"=== "<<sec<<std::endl; //使用cout代替心跳通信逻辑
}
return 0;
}
简化heartbeat()函数逻辑,分别在2037 2038 2050年测试,发现代码内采用的boost库计时功能在2038 2050年不准确(终端频繁打印输出)。
加上其他同事提示int32位存储时间戳会存在溢出的情况,就逐步确定问题是2038年时间戳的方向了
其他辅助:
相同测试代码在 boost-1.58 boost-1.71环境下对比,高版本始终无问题,低版本在2038年有问题,所以细分方向是boost-1.58版本的date_time模块在时间戳方便存在缺陷
三、boost低版本date_time模块在时间戳方面缺陷的解决方法
修改boost/date_time/time_resolution_traits.hpp 文件中 类模板time_resolution_traits的默认模板参数类型
typename var_type = boost::int32_t 替换为 boost::int64_t即可
对比boost-1.71版本内的该文件,可知1.67版本开始,官方已经开始采用int64_t类型来存储时间戳了
四、gdb定位流程(可选择性阅读)
图1
图 2
图3
图4
图5
图6
图7
图8
图9
图10
图11
注:实际调试过程可能并没有这么精简,可能会有下面的或者更多的疑问
- boost::this_thread 这个命名空间在哪个头文件定义的?
- boost::this_thread::sleep()对应的头文件我找到了,但是函数重载了,会走到哪个函数呢?应该给哪个加断点呢?
- 在total_seconds()函数返回负数时,我们可能不会直接反应到要去看它的函数定义、看它的类定义、看模板参数使用情况
五、复测
尝试将上述图11定位到的代码修改为int64_t, 再次测试,可以发现在2038年 2050年不会再存在问题(终端不会频繁输出)
六、2038年时间戳的大致梳理
- int32_t 本质是 int 类型,即32位有符号整数,取值范围 − 2 31 -2^{31} −231 到 2 31 − 1 2^{31}-1 231−1,即 − 2147483648 -2147483648 −2147483648 到 2147483647 2147483647 2147483647
- 使用python脚本输出
2038年1月19日03:14:07 UTC
对应的时间戳,结果是2147483647
from datetime import datetime, timezone
import time
# 设置目标日期和时间(UTC)
target_date_time = datetime(2038, 1, 19, 3, 14, 7, tzinfo=timezone.utc)
# 将datetime对象转换为时间戳(秒)
timestamp = int(target_date_time.timestamp())
# 打印时间戳
print(timestamp)
- 使用gdb对时间戳进行尝试
(gdb) set $aa=2147483647 # 赋值32位有符号整型数的最大值给aa,对应的是 2038年1月19日03:14:07 UTC
(gdb) ptype $aa # 打印aa的类型,结果是int
type = int
(gdb) p $aa # 打印aa的值
$8 = 2147483647
(gdb) set $aa=$aa+1 # aa+1,对应的是 2038年1月19日03:14:08 UTC
(gdb) ptype $aa # 可以看到aa的类型不变
type = int
(gdb) p $aa # 超出了int类型的最大值,溢出了,直接变为int类型的最小值
$9 = -2147483648
(gdb)
- 如果时间戳采用64位整型数表示,情况会怎么样?
先明确64位有符号整型数的取值范围 − 2 63 -2^{63} −263 到 2 63 − 1 2^{63}-1 263−1,即 − 9223372036854775808 -9223372036854775808 −9223372036854775808 到 9223372036854775807 9223372036854775807 9223372036854775807
(gdb) set $bb=9223372036854775807 # 赋值64位有符号整型数的最大值给bb
(gdb) ptype $bb # 打印bb的类型,输出是long long
type = long long
(gdb) p $bb
$10 = 9223372036854775807
(gdb) p $bb/(12*30*24*60*60) # 使用64位可以表示2965亿年的时间戳
$11 = 296533308798
(gdb)