Christian Gingras:那个直接在机器码中改Bug的家伙

背景知识:《机器人大战:2084》(英文:Robotron: 2084,也常简称为Robotron)是由Vid Kidz开发、Williams Electronics于1982年发行的一款街机游戏。它是一款2D射击游戏。游戏设定在2084年的一个虚构世界,在那儿机器人起来反抗人类的统治。玩家的任务是击退一波又一波的机器人,拯救幸存的人类,并赢取尽可能多的分数。(摘自维基百科

介绍:

  • 『拐角射击失灵』,所有Robotron的ROM原始程序里头有个bug,当玩家对在战场边缘行进的暴徒射击的时候,程序会读取代码里一个错误的位置。导致代码不知道接下来做什么,程序监视器就会重置游戏到地毯模式。
  • Eugene Jarvis 提到:1987年前后,一个叫Christian Gingras的加拿大小孩完整地分析了ROM里头的机器代码,综合得到了源程序列表。他发给我们他通过读代码发现的7个bug,然后我们修复了这些bug。这其中就有一个很著名的拐角射击崩溃bug。
  • Sean Riddl提到:Williams Arcade Classics(一款游戏)出现于1995年,我假设Eugene(Robotron游戏的设计者)已经修复了ROM里头的bug,我试用了一下程序然后回复了作者,他做了一些ROM的改动,这些改动让仿真容易一点。(他把Stargate重做成了Defender II, 这肯定是非常多的工作),Sean Riddle写道:我用google搜索了一下,好像人们认为拐角射击的bug修复了。之后补丁出来了,在补丁里头可以看到“1987修改”。我想我是从Williams Arcade Classics Widows95版里头找到的补丁。

备注:虽然 Robotron 让 Christian 获得了声望,但实际上他酷爱另一款游戏 Joust。

Robotron 2084

 

Christian 的自述(2012年11)

1982年的时候我在电子方面是很超前的,我可以设计模拟和数字系统。这些年出现的电子游戏表明,Joust/Robotron主板上100个芯片就可以让系统执行比标准晶体管逻辑门上更复杂的功能,比如说Williams的游戏。

详细地研究原理图,有一个很明显的选择:在Joust里头,摩托罗拉6809微处理器相当于大脑,而用来创建电脑控制有智能行为的鸟的数据,保存在12个电可编程序只读存储器(EPROM)里头。

我意识到,如果我要自己设计复杂的电子系统,我必须了解摩托罗拉6809是如何工作的。如果我是个幸运的孩子可以访问大型主机,我会拿6809作为参考来学习微处理器。

但是我能拥有的最强大的计算机是街机游戏机。这些游戏比当时的家庭计算机比如TRS-80或者TI99a快几个数量级。

用不了多长时间我就找到了最快的游戏, Robotron 2084有10波全速的120个对象,很显然是最快的。在Joust里头,只有10只鸟3个翼龙和两个玩家。相对于Joust而言,Robotron 2084性能更具有挑战性。如果我们用Z80处理器,比如Miss Pac-man,来处理4个鬼魂,一个水果和一个玩家,6809微处理器明显要快很多

我花了几年的时间才找到一个专家,他把Robotron主板上的EPROM去掉,并且让我读EPROM里头的程序然后还可以写回去。这是1984年或者1985年的事。

那个时候,我在Radio Shack买了一台彩色电脑COCO2, 里头有一个微软写的6809的调试器edtasm。我反向工程破解了edtasm,于是我明白了6809的汇编和反汇编程序是怎么创建的。但是我觉得Robotron:2084是如此复杂,所以我没有敢研究它。1985年还是1986年,我反向工程研究了COCO2里8K大的ROM,这个ROM创建了COCO2的基本语言。然后我也研究了很多其他的应用比如磁带软盘拷贝应用,为了节约我的工作,我也通过研究一些游戏来了解如何访问COCO2的图形模式。

最后在1986年8月,我准备好了要开始研究Robotron:2084。我做了一个定制的edtasm,它可以从EPROM里头每次读4K的二进制数据,并且反编译代码同时把实际硬件地址映射成虚拟的地址。不是在屏幕上显示, 反编译器把结果传到打印端。总共用了3天才打印完512页程序。我时不时往墨盒里头加点红色或者黑色或者蓝色的墨水。为了节约纸的空间还有钱,我打印了双面的。这些包含了反编译的6个EPROM的所有程序。

我前些年研究过一些6809的应用,我很高兴我能用相似的方式读懂这些代码。不过有些明显的区别:

Robotron: 2084用了一些变址指令比如:“LDA A,Y++, STB B,X+$0A”(从地址为寄存器X值的内存里读一个字节,增加那个寄存器,把那个字节写道寄存器Y指向的内存地址的第10个字节)

它证明Robotron:2084虽然是汇编写的,却是很干净的面向对象的代码。

Robotron依赖一些共享方法来做一些操作,比如分配内存,休眠一段时间,在这中间处理器可以做一些其他的操作,休眠结束之后会重新执行之前的代码。

这说明Robotron是一个完全的多线程应用,每个移动对象都是由不同的线程来控制的。

如果我没记错,IRQ中断每秒执行300次,是一个高优先级的线程负责执行高优先级的任务,比如说当阴极射线管(CRT)扫描的距离某个地方足够远的时候,绘制屏幕的一部分来避免闪烁。一个状态机读取CRT垂直扫描的位置,然后选择当前对象列表里头的可以安全刷新的对象。比如说,如果我们用手柄控制一个玩家到接近屏幕的顶部,

重绘操作只会当CRT扫描在屏幕中间和底部的之间执行。

我用了8个月来学习这512页代码,在我当保安的地方,每天晚上在物业检查的间歇里,我会打开那卷纸,用彩色的笔画箭头来标记那些执行了特殊功能或者读写了特殊端口的地方。

几个月之后,我得到了 “为什么Robotron:2084会这么快”的答案。位块传送器(blitter,一个垂直或者水平的直接内存控制芯片)是高性能的主要因素。但是Defender(一款游戏)屏幕绘制的速度和Robotron一样,但它没有blitter(blitter是Robotron的设计,Stargate(一款游戏)额外宣扬了blitter芯片)

我决定接着研究Robotron,同时也看看Joust 和Stargate,来比较它们的相同和不同特性。有些代码处理一些很难的算法,给了我很深的印象,比如说:它如何用6809的MUL指令来做除法,用“固定点数(相对于浮点数)”来做二进制计算。

当研究代码如何处理游戏事务的时候,我就能猜到这些代码过去引起过一些问题。 有些测试可以核实可能的问题,比如用4堵墙来比较子弹的位置,从而确认子弹被限制在可视的屏幕范围之内。总共48K的内存有40K用来显示每个象素,剩下的8K用来作为代码的栈指针,“对象”(和C++的对象概念相似,一小段内存用来记录enforcer,坦克,子弹的位置还有其他对象的参数),局部变量和全局变量。如果一个对象被错误地被绘制里屏幕的右边太远,这样就有可能污染这8K的程序内存。

在后来的4个月里,我研究到了管理enforcers的代码。我很好奇,没有正弦余弦对数值数的代码是怎么样让子弹是怎么做螺旋运动的。我也想明白,当一个enforcer在屏幕左边慢慢接近玩家的时候,被惊吓的小狗飞到屏幕的右边,然后慢下来,划了一条优美的曲线回到玩家身边。后来这个被证明是个错误,代码里头留下了一个8个字节的二进制溢出。屏幕320像素宽,但是8位寄存器只能保存从左边开始的256个像素,所以Enforcer可能会认为玩家在屏幕的右边其实玩家就在旁边很近的位置上,于是enforcer会计算新的速度去追踪它想象中的玩家。然后,再这个疯狂赛跑的中间,enforcer会感知到玩家的正确位置在左边,所以他会减慢速度,修改自己的方向,划出优雅的曲线。

通过对这个bug的研究,我也弄清楚了这个多线程多任务高性能软件的另一个特性:每个enforcer都有自己私有的数据块,这个私有的数据块让每个enforcer保持独立。Enforcer的数据结构里有一个8位的变量,来记录enforcer应该保持当前的状态多久(多少个刷新循环),然后在改变主意前做点其它的事情。当enforcer错误地朝右边飞行的时候,它会追踪这个实际上不在那里的玩家一段时间,当上面提到的那个变量减少到零,enforcer就会重新评估玩家在哪里,然后采取新的动作。走过一条螺旋曲线到达玩家的位置。

六个月的研究让我看到了代码的每个细节,我创建了一个函数调用的列表(谁调用了谁,调用了多少次的一个交叉参考),相似地,我也列出了所有的全局变量,这些全局变量可以被DP寄存器用8位指针的方式访问。我记录下了所有可以读写这些变量的代码位置。我可以看到有些变量指挥被射击单位(坦克,enforcer)使用。有些仅仅被分配内存的神秘代码和其他的操作(操作系统,实时多任务使用者)使用

我也找到所有的加密文本和所有的代码保护措施,这些秘密指令防止别人盗版Robotron这个游戏。Joust这个游戏里,如果有人把Williams修改成另外一个名字,这游戏就不能工作。但是如果我想修复那个随机重置的bug,我就必须禁用这些保护措施。

我知道“fancy模式”是防止随即重置bug的一个办法,如果关闭,射击的时候就不会绘制爆炸,越来越清楚地表明这爆炸的绘制引起了随机重置的问题。

1986年秋天,我决定写信给Eugene P. Jarvis 和Larry E. DeMar,告诉他们我已详细研究了Robotron:2084,而且我可能修改了那个bug。我从一个游戏机杂志上面知道了他们的名字。当我解密一部分代码里头的加密文本的时候,我又看到这两个名字,所以我确信他们就是这个游戏的设计者。

当我等待回信的时候,我得了肺炎。我意识到我有可能不能战胜疾病(我以前没有吃过抗生素,不知道医生能帮什么,我访问了所有的以前有联系方式的医生,他们不在乎而且什么忙都帮不了)。我感到了总结我一生中最重要工作的紧迫性,如果我不在了,这些工作都会浪费掉,我身边没有人知道我做的工作。

我用了我所有的精力来修改代码,而且写了一封详细的信给Eugene 和Larry。我记录了所有我发现的错误,解释了错误行为和原因,还有解决办法。我记录了6个字节来执行我的测试,只有Eugene 和 Larry会知道什么意思。我不想让他们以为我是某些非法赞助人雇佣的黑客来攻破这些保护。我谨慎地说明我绝对理解代码的每一部分,当我修复随机重置bug的时候,没有愚蠢地触发保护措施。6页信里有关于bug的浓缩后的精确技术描述。

在信的结尾我写到:我所有这些研究都是因为我热爱硬件(6809基于图形显示),软件(难以置信高效的多任务代码)和它的设计者。这48K代码就像一部小说或者一个很精彩的故事,为向作者的天资致敬,所以我写了信的最后一部分。事实上,这些工作是有自己生命的,无数人付出汗水和精力推动手柄来挑战机器。也许比一部优秀小说的一些角色更加让人动情。

如果使用一个555电子振荡器来产生IRQ中断(把6809上面的300赫兹的IRQ卸载,然后接上555振荡器),然后调整振荡器频率到足够高,这样CPU会被占用而只剩下很少的时间也就是几千赫兹周期来执行代码, 这样我就可以让player用很多分钟初始化爆炸,而且游戏的速度非常低。我用了几小时来玩这个慢游戏,享受着更加容易毫无压力的游戏。即使粗鲁地违反计时,代码居然没有任何问题,这让我挺惊讶。我知道我只要把Player挪动到左上角,游戏就会崩溃。我发现如果我关掉fancy模式,不用初始化爆炸,机器就不会有任何崩溃的情况。简而言之,这个bug跟绘制爆炸相关的线索又多了一条。优秀的选手,比如双胞胎兄弟Ian 和Yvan Girouard发誓他们发现每次机器崩溃的时候都有一个enforcer在左上角。这些聪明的玩家学会了避免斜对角线设计,这样他们用一个游戏币就可以玩好几个小时。

几个星期后,一个星期六早上电话响了,有人说英语。我(加拿大人估计说法语的)告诉我的同伴Michel-Guy起来跟那个人说话,来电话的是Robotron设计者,Michel告诉我说他们想见我,他们会给我提供费用包括火车旅馆等等。

去芝加哥的那天,我打印了绘制对角线爆炸的程序,然后开始耐心地给每个分支划箭头来表明代码流程。当差不多到芝加哥的时候,我又一次发现6809执行了从指针列表里头跳转的指令。当对象离墙太近的时候,这段代码允许CPU跳过绘制太多的线,因为这个时候已经无法完全显示爆炸的形状了。所以,如果enforcer靠近墙的时候,只需要绘制比较少的线条。一种极端情况,如果enforcer靠墙太近,甚至容不下一条线会怎么样?重新看了一下跳转表,如果没有线条可以绘制,BEQ(结果为零跳转指令)就会避开跳转表,然后CPU会跳转到6809的OP-Code决定的地址,导致处理器失败,监视器发现了这个“死亡代码”之后硬件重置了CPU。

我异常高兴,我终于可以告诉Eugene 和Larry导致机器崩溃的真正bug。

当我们讨论那个bug的时候,他们两个就像被吓倒的小动物,小心地走过来,然后抛开,然后又犹豫着回来。我问他们:是不是一个溢出?是一个用320水平像素来计算但实际上只有256像素。你们实际上早就发现了这个问题却由他去,作为enforcers的一个复杂行为。

Larry笑了并且确认了我的直觉。然后Larry停下来跟我说:“最近5年,我从来没有跟任何人讨论过Robotron的设计。我的朋友们不能理解这样的技术问题。但是今天,一个不会说我们语言的陌生人,我们一起讨论就像我们曾经在一起工作一样,就像你也是Robotron最初的设计者一样。”

后来我问他们怎么评价我对Robotron的理解,Larry说很好,不过当然没有超过他和Eugene。Eugene说我对代码的每一部分都清晰地理解,从启动测试到操作系统的其他部分,还有游戏本身。他觉得从系统整体上我的理解超过了他和Larry。

那天晚些时候,Larry需要离开我们。他推荐我做Joust的设计者,Eugene有些懊恼,因为比起Robotron我更喜欢Joust。我告诉设计者我找不到任何问题。我不喜欢IRQ程序里头的测试,这个测试可能会引起自动重置,但是我明白了除非硬件问题这些代码永远不会触发的。Mister Newcommer解释给我说他发现了一个鸟和平台的冲突。他用RAM里头地一个数组发现了一个有些粗略但是有效的冲突,这个数组的修改用来反映平台的增加或者移动。

Larry坚持回来并且开车送我到机场。当他过分地朝一个公交车司机按喇叭打算从他那里切过去的时候,我感觉这一切就是我一生的课堂。

1 收藏 6 评论

关于作者:姚Justin

新浪微博:@姚Justin 个人主页 · 我的文章 · 1

相关文章

可能感兴趣的话题



直接登录
最新评论
  • 不明觉厉。

  • wjch   2013/08/09

    这个,太厉害了吧。

  • 八个月,512页代码,能这么专注的研究问题的人太少了!

  • sunjun   2013/08/09

    资本主义国家的人都很闲的样子

  • Robotron2084Guidebook   2013/08/26

    Christian Gingras wrote this for you, with help from Google Translator.

    谢谢你们的翻译我的故事
    学习软件 Robotron:2084 是非常困难的,但如此奖励。
    顺便说一下,我的中文名字是:



    Wang2 Jin1 Bao3
    The number on each PinYin represent the tones : 2:raisng, 1:flat, 3:down-up

跳到底部
返回顶部