打开网易新闻 查看精彩图片

整理 | 屠敏

出品 | CSDN(ID:CSDNnews)

如果说游戏史上有哪款作品最经得起时间考验,《DOOM》一定榜上有名。

这款 1993 年 12 月发布的 FPS 开山之作,不仅掀起了玩家对“第一人称射击”的狂热,还影响了此后几十年的游戏文化。自那时起近 32 年时间里,数不清的玩家投入了数百万、甚至数十亿小时沉浸其中,更别说社区也贡献了大量模组与自制关卡。可即便如此,它的底层代码里依旧埋着一些奇怪的遗留问题。

为了验证其中一个鲜有人注意的32 位整数溢出 Bug 是否真的会在现实中触发,一名极客决定做个实验:把《DOOM》在一款老旧的硬件上开着跑上几年,看看会发生什么。结果是——在两年半之后,游戏真的崩溃了。

打开网易新闻 查看精彩图片

极客实验:掌机 + UPS + “忘掉它”,让 DOOM 跑到崩溃

这名极客叫 Minki,谈及此次实验的初衷,其表示是源于他当时偶然间看到了一篇讲 DOOM 引擎的文章,发现有个用来记录 demo 播放的变量,每次都会一直往上加,甚至在下一个 demo 开始之后也还在加。程序里会把这个变量和另一个保存“前一个值”的变量做比较。问题在于,这个变量每次加一,迟早会接近溢出。虽然在正常情况下几乎不可能跑到那一步,但他就特别好奇:要是一直运行下去,多久会因为这个溢出而把游戏搞崩?

为了搞明白这个问题,他在自己的网站 LenOwO 上分享了这次“实验”过程——将移植版的 WinDOOM 装进一台 2003 年的华硕 MyPal A620 掌机里。这台掌机搭载 Windows Mobile 系统和 Intel XScale ARMv5 处理器,性能早已落伍。为了确保设备长时间不断电,Minki 还动手 DIY 改装了一块 18650 锂电池 UPS,并通过路由器的 USB 接口稳定供电,保证能一直有 5V 电压。

然后,这台掌机就被丢在角落里,绝大多数时间无人问津,直到两年半后,Minki 才发现屏幕上弹出了熟悉的“应用程序崩溃”提示。

打开网易新闻 查看精彩图片
打开网易新闻 查看精彩图片

崩溃幕后:gametic 的“死亡倒计时”

问题的根源其实埋在 DOOM 的代码里。和很多老牌商业软件一样,即便到了 1.9 最终版本,游戏依旧遗留了不少 bug。这里最关键的就是一个名叫gametic的计时器变量。

这个数值用于追踪游戏内的时间流逝,并且以35Hz(每秒 35 次)的固定频率递增,而不依赖游戏的渲染循环。即使不需要多复杂的数学知识,也能看出如果 gametic 永远不清零,时间一长它就会变成一个天文数字。

作为 DOOM 引擎的主要开发者,John Carmack 当年其实肯定知道这里的设计有点小问题。但他大概觉得没什么关系。为什么?因为这个数是存在有符号的 32 位整数里的。

这种整数能表示的范围大概是:从-2,147,483,647+2,147,483,647。所以只要你从 0 一直往上加,加到2,147,483,647这个天花板,下一次再加,就会溢出。

在 C 语言的标准里,“整数溢出”属于“未定义行为”。但在 x86 PC 这种常见的硬件上,几乎总是会出现一个很直观的结果:数值会绕一圈回到负数的最小值。也就是说,它会从最大正数跳到-2,147,483,647

换句话说,Carmack 知道这个变量理论上会溢出,但考虑到要数二十多亿次才会触发,在正常游戏流程里压根没人能跑到这一步,所以他就没当回事。

打开网易新闻 查看精彩图片

经过 2.5 年后,游戏崩了

正如上文所述,按照每秒 35 次计数来算,大约1.95 年就会让 gametic 数值溢出。当时 Minki 自己算过一遍,具体怎么算的已经记不清了,但他记得结果大概是——差不多能跑两年半才会溢出。

如今的实验证明,这款游戏真的经过了 2.5 年后崩了。所以,别把 DOOM 持续开着两年——或者说,任何游戏都别这么做,除非它是专用的服务器而不是客户端。

打开网易新闻 查看精彩图片

一场实验引发的思考

这场实验虽然看起来毫无实用意义,但却透露出几个耐人寻味的地方。首先,很多开发者在写代码时会假设用户不会让程序运行那么久,于是一些看似微不足道的边界条件就被忽略了。其次,C 语言中的未定义行为总是让人摸不透——有时表现一致,有时却完全不可预测。

随着 Minki 的帖子登上 Hacker News 热榜,也引发了不少讨论。许多人分享了类似经历:

  • 一位叫jbreckmckye的用户分享说,他在研究《古惑狼 3》的计时系统时发现,它同样有一个不断递增的 int32 计数器,只有在角色死亡时才会归零。如果一直运行下去,2.26 年后也会溢出。

  • jsheard则提到,《最终幻想 9》有一把隐藏武器,必须在游戏时间不到 12 小时的情况下赶到后期地图才能拿到。PAL 版本因为疏忽更狠,只给 10 小时。听上去几乎不可能,但还有另一条路:把游戏放着别管它,两年多之后计时器自己溢出归零,你就能拿到这武器了。慢慢来,照样能赢。

其实游戏中看似不可能触发的边界条件,不仅真的会发生,还能在不同游戏里演变成一种另类的“隐藏彩蛋”。

来源:https://lenowo.org/viewtopic.php?t=31