pydisass:Python中的6502个拆卸器

2021-03-22 05:58:35

在本文中的Imo Hinterding在Sun Mar 21 2021上,我分享了一些关于拆卸6502机器代码的一些学习。事实证明,在80%左右的工作很容易,改善到90%是棘手的,使它工作100%几乎不可能。

Commodore 64台计算机毫无疑问是一个永恒的经典。因此,如果您在今日在未来的某个时间阅读本文,这会让我感到惊讶。也许飞行车终于成为一件事。也许DOGE COINS成为Prime行星货币。甚至更大胆,它是桌面上Linux的年份。荒野。但是,它可能更有可能更容易成为人类成功地成功地成为了第二个致命的火星。

对我来说,它'我们的2021年。我们刚刚幸存了邪恶皇帝特朗普和我们'重新庆祝一年,追逐在他妈的全球大流行中,这是一个相当致命的病毒,但是后来突变成一个更危险的大脑slug,令人震惊的危险案件在人口中令人困惑的愚蠢,导致永无止境的土地。好家伙。

我的第一台电脑是C16,我信誉这一事实,即我对编程中的激情和专业。从80年代中期拥有家用电脑的学校的那些孩子,他们中的大多数都有一个C64。我看着他们一直在分享最酷的游戏,而我只是......冬季游戏和鬼城。但我的电脑特色比游戏要好得多,它具有惊人的基本3.5,远远超过64'基本2.0。所以我创造了自己非常糟糕的游戏。

这种情况与今天的复古工具支持类似。对于Windows机器来说,最好的程序是为Windows机器制作的,并让它们在Linux或Mac上工作,需要葡萄酒或运行资源要求的虚拟机。事实上,我最后一次想为C64创建一个新的游戏,我可以找到一个很好的Native Sprite编辑,而且因为那天散发出来。拆卸也是如此。 Windows有一些惊人的,如Regenerator,甚至是Masswerk的基于Web的解决方案,如Masswerk' S 6502 Disssembler。但我的金鱼大脑思想"嗯...写下我自己的拆解器有多难?"

由于撕裂的小牛肌纤维而被困住,我决定训练我生锈的装配知识并在一点项目上工作。我想做一个完全拆卸的一个最具标志性的C64裂缝组成,着名的Fairlight介绍:

介绍非常简单,具有自定义字符集,一个漂亮的SID调整,柔和的滚动仪和一些颜色洗涤。即使是Rasterbars也很简单,因为它们实际上没有莱斯特乐队,而是简单的精灵。

组装(也称为编译)正在将程序代码转换为机器语言程序。让'做一个简单的例子:

LDA#$ 02;将值02加载到累加器STA $ D020中;将值存储在内存地址$ D020 RTS;返回(到基本)

在C64上执行时,该代码将边框颜色(D020)转到红色($ 02)。这些命令(LOA D累加器的LDA和存储累加器的STA)称为助记符。该机器实际上不会理解这些本地,但它们由00到FF的八个长十六进制数表示,称为操作码。让'■将这些操作码添加到列表中:

A9 02 LDA#$ 02;将值02装入累加器8d 20 d0 sta $ d020;将值存储在内存地址$ D020 60 RTS;返回(到基本)

A9表示LDA,8D用于STA,60是RTS助记符的等同物。如果你想知道D020如何成为20 d0,那个' s是因为所谓的小endian表示法,这在前面的最小八字。每当我尝试调试代码时,在肠道中都是一个很好的拳。

整个程序将驻留在计算机中,作为这种短序列的十六进制号码:

您是否曾因通过电路路由电流(1)或无电流(0),计算机对计算机如何进行多大魔法?嗯,将上面的数字转换为二进制表示法,您可以从字面上看矩阵:

要总结一下,编写汇编程序并不难。你将助记符转换为Opcodes,照顾小endian符号和你'几乎准备好了。不言而喻,我在这里过度简化了这个过程,但你得到了这个想法。

如果我们使用伪代码接近任务,可以像这个一样简单吗?

我们的数据将被解释为代码,导致Jibberish不会接受作为有效计划。这将我们带来了撰写拆卸时的最大挑战。

重要的是要理解,6502指令集具有不同的寻址模式。这些描述了实际行为,并告诉我们如何解释遵循助记符的数据。对于LDA助记符有八种不同的寻址模式:

A1 LDA($ hh,x); X索引,间接A5 LDA $ HH; Zeropage A9 LDA#$ HH;立即广告LDA $ HHLL;绝对b1 lda($ hh),y;间接,Y索引B5 LDA $ HH,X; Zeropage,X索引B9 LDA $ HHLL,Y;绝对,y-indexed bd lda $ hhll,x;绝对,x索引

我赢了' t解释了那些详细的,有很大的网站,已经比我能做得好,比如Skilldrick的Easy 6502,只要注意到总共有13种不同的寻址模式:

累积器ABS绝对ABS,X绝对,X索引ABS,Y Absolute,Y索引#立即iclich隐含X,Ind X索引,Indirectind,Y间接,Y索引Rel相对ZPG Zeropage ZPG,X Zeropage, X索引ZPG,Y Zeropage,Y索引

在这里,您可以查看6502的所有法律说明(表格图表提供优秀的网站Masswerk)。还有非法操作码,那些填写图表中的空白点。其中一些可以用于特殊技巧,其他人只会崩溃程序。

现在我们拥有我们所需的所有信息来识别遵循指令是数据的数字节。

A9; A9 = LDA立即,期望一个更大的字节02;因此,02必须是数据字节8d; 8d = sta绝对,期待两个字节20; 20必须是数据D0; D0必须是数据(并且由于小endian表示法而与前一个字节有位置)60; 60 =默示的RTS,预计没有进一步的字节

对于6502处理器,有256(或十六进制符号中的FF)OPCODES可能是可能的,但是它们中只有151个产生预期行为的指令。另一个105称为非法操作码。如果您想了解更多关于这些相关信息,我强烈推荐了优秀的文章MOS 6502如何由Michael Steil真正工作的MOS 6502。

我的反汇编程序的第一个(和繁琐的)步骤是使这些操作码可用于解析代码。我决定一个简单的JSON文件,其中包括所有256个操作码。这是第一行(所有代码可以从我的github存储库下载)。

{" 00" :{" ins" :" brk" }," 01" :{" ins" :" ora($ hh,x)" }," 02" :{" ins" :"果酱" ,"生病" :1}," 03" :{" ins" :" slo($ hh,x)" ,"生病" :1}," 04" :{" ins" :" SLO($ HH),Y" ,"生病" :1}," 05" :{" ins" :" ora $ hh" }," 06" :{" ins" :" ASL $ HH" }," 07" :{" ins" :" SLO $ HH" }," 08" :{" ins" :" php" }," 09" :{" ins" :" ora#$ hh" }," 0a" :{" ins" :" ASL" }," 0b" :{" ins" :" ANC#$ HH" ,"生病" :1}," 0C" :{" ins" :" NOP $ HHLL" ,"生病" :1}," 0d" :{" ins" :" ora $ hhll" }," 0e" :{" ins" :" ASL $ HHLL" }," 0f" :{" ins" :" SLO $ HHLL" ,"生病" :1}}

并使用指令获取一个对象。通过检查HH和LL,我可以轻松识别以下几个字节是与此指令相关的数据。我也可以在JSON数据中包含该信息,但考虑过它冗余。除了指令外,我还使用ILL来标记非法操作码,并标记相对分支指令(稍后更新)。

这里' s在字节中读取的Python代码的简化版本,检查匹配操作码,请注意以下数据字节,并构建一个合适的程序集。

def bytes_to_asm(startaddr,bytes,操作码):"""检查字节的字节并将它们转换为基于操作码JSON的指令,返回每个代码行和#34的对象的数组;"" ASM = [] PC = 0 END = LEN(字节)而PC<结束:字节=字节[PC]操作码=操作码[Number_to_hex_byte(byte)]指令=操作码[" INS" ] memory_location_hex = number_to_hex_word(startaddr + pc)byte_sequence = [] byte_sequence .append(byte)指令_length = 0 if" hh"在说明:指令_length + = 1 if" ll"在指令中:指令_length + = 1如果指令_length == 1:pc + = 1 high_byte = bytes [pc]指令=指令.replace(" hh",number_to_hex_byte(hign_byte)字节_sequence .append(high_byte)指令_Length == 2:PC + = 1 Low_Byte = Bytes [PC] PC + = 1 High_Byte = Bytes [PC]#替换为新的单词指令=指令.Replace(" hh",number_to_hex_byte(high_byte))指令=指令.replace(" ll",number_to_hex_byte(low_byte))#存储字节 - 我们稍后可能需要它们byte_sequence .append(low_byte)byte_sequence .append(high_byte)行= {" a& #34; :Memory_Location_HEX," B" :byte_sequence,"我" :指令} ASM .Append(行)PC = PC + 1返回ASM

再次我们的小示例程序,包括起始地址,它定义了C64' S内存中的内存将存储:

我们的程序现在将使用ACME汇编程序编译。我使用我的ACME汇编程序VSCode模板进行开发,支持Windows,Linux和Mac。将PRG加载到副仿真器中,并使用SYS 2064开始程序(在十六进制中为0810)我们看到了漂亮的红色边框。

[{' a' :' 0810' ,' b' :[169,2],'我' :' LDA#$ 02' },{' a' :' 0812' ,' B' :[141,22,208],'我' :' sta $ d020' },{' a' :' 0815' ,' B' :[96],' i' :' rts' }]

并在格式化数据后核心将结果读取,如下所示:

;由Mayday的AWSM转换使用PyDisass6502! * = $ 0810 LDA#$ 02 sta $ d020 rts

让我们的基本计划拆卸是一个很棒的第一步,但是有更多的待完成方法来将其转化为有用的东西。让' s通过在代码中的不同位置引入跳转来修改我们的程序一点

我们的反汇编程序仍然可以正常工作,但是通过用标签替换我们自己的代码范围内的内存跳转地址(例如,上面的示例中的价格为0816美元),我们可以使其更加多功能。标签是对内存位置的引用。在此示例中可能看起来不那么重要,但如果我们不执行此操作,我们的拆卸程序可能会破坏将代码移动到不同地址的任何更改。我们将非常有限。 Let'添加标签。

地址是绝对的(如HHLL)吗?如果是,是否在我们的代码中?如果是,请用标签替换它

$ 0810 INC $ D020; $ d020不是我们的程序中的地址,不要取代它0813€0810; $ 0810是我们程序中的地址,用标签替换它' x0810'

但是我们'还没有完成,我们还必须添加跳转目的地的标签。

阅读当前地址是我们在我们之前定义的所有标签创建的数组中的当前地址?如果是,请在指令之前添加标签

这是惊人的,因为我们现在可以改变代码甚至将它移动到内存中的不同位置,它仍然会完全良好! \ o /

由于我们的拆解器变得更聪明,我们可以对其进行新的挑战,这是相对分支的新挑战。使用JMP $ 0810指令之前我们有一个绝对地址,我们可以通过标签替换。但这是第6502的分支指示的工作,这是BPL,BMI,BVC,BVS,BCC,BCS,BNE和BEQ。这里的一个小程序和分支:

* = $ 0810 LDY#$ 07;将07加载到Y寄存器环TYA中;将y复制到累加器sta $ 0400,Y;将累加器储存在$ 0400 +无论如何是Dey;通过一个燃气回路减少y;如果y不是零,则跳到'循环' RTS; y为零,退出基本

程序存储字节$ 00 - $ 07在内存位置$ 0400 - $ 0407,即屏幕,导致字母ABCDEFG(加空字符)在左上角打印。

.c:0810 A0 07 LDY#$ 07 .c:0812 98 TYA .c:0813 99 00 04 STA $ 0400,Y .c:0816 88 Dey .c:0817 D0 F9 BNE $ F9 .c:0819 60 RTS

人们会期待BNE命令阅读BNE $ 0812,但它' S BNE $ F9!那个' s是因为它相对于它的分支'自己的位置。分支指令只能跳128字节或向后128字节。在我们的示例中,我们将跳回6个字节:从地址$ 0818达到0812美元。我们通过从$ FF(255)开始并减去6,所以它' s $ f9(249)。如果我们跳起来,我们将增加6到00美元,导致06美元。

通过该知识,我们可以在代码中查看相对分支的代码。这里''是包括以前的标签检查的完整python函数,添加有关非法操作码的信息和检查文件末尾的信息。

def bytes_to_asm(startaddr,bytes,操作码):"""检查字节的字节并将它们转换为基于操作码JSON的指令,返回每个代码行和#34的对象的数组;"" asm = [] pc = 0 end = len(字节)label_prefix =" x"而PC<结束:字节=字节[PC]操作码=操作码[Number_to_hex_byte(byte)]指令=操作码[" INS" ]#检查钥匙" rel"在Opcode Json#代表"相对地址" #是需要的。为BNE,BCS等分支。如果" rel"在操作码:is_relative = true else:is_relative = false memory_location_hex = number_to_hex_word(startaddr + pc)标签= label_prefix + memory_location_hex byte_sequence = [] byte_sequence .append(byte)指令_length = 0 if" hh"在说明:指令_length + = 1 if" ll"在说明:指令_length + = 1#操作码比文件结束更长?然后将长度设置为0如果PC +指令_Length>结束:指令_length = - 1如果指令_length == 1:pc + = 1 high_byte = bytes [pc]#如果如果is_relative发生如bcc或bne这样的相对指令:如果high_byte> 127:#sexstract(255 - 高音)从当前地址地址= number_to_hex_word(startaddr + pc - (255 - high_byte))else:##向当前地址地址= number_to_hex_word(startaddr + pc + high_byte + 1)指令=指令。替换(" $ hh",label_prefix +地址)else:指令=指令.replace(" hh",number_to_hex_byte(hig_byte)byte_sequence .append(high_byte),如果pression_length == 2: PC + = 1 low_byte = bytes [pc] pc + = 1 high_byte = bytes [pc]#替换为新的单词指令=指令.replace(" hh",number_to_hex_byte(high_byte))指令=指令.replace (" ll" number_to_hex_byte(low_byte))#是我们自己代码中的内存地址? #然后我们应该用标签替换为该地址Absolute_address =(high_byte<< 8)+ low_byte if(absolute_address> = startaddr)& (absolute_address< startaddr +结束):指令=指令.replace(" $",label_prefix)is_relative = true地址= number_to_hex_word((high_byte&lt 2.)+ low_byte)#存储字节 - 我们稍后可能需要它们byte_sequence .append(low_byte)byte_sequence .append(high_byte),如果pression_length == - 1:指令="!字节$" + number_to_hex_byte(byte)行= {" a" :Memory_Location_HEX," L" :标签," B" :byte_sequence,"我" :指令}#所有相对/标签数据应该得到一个新密钥,所以我们可以识别它们,当我们清理不需要的标签时,我们需要这个,如果是_relative:line [" rel" ] = label_prefix +地址if"生病"在操作码:线["生病" ] = 1 ASM .append(行)PC = PC + 1 ASM = Remove_Unused_Labels(ASM)返回ASM

好的,鼓槌,让' s在我们最新的程序上使用这个杰作,并拆除了sh#t of它!

;由Mayday的AWSM转换使用PyDisass6502! * = $ 0810 LDY#$ 07 x0812 tya sta $ 0400,y dey bne x0812 RTS

好吧,到目前为止,我一直在做快乐的道路编程。显然,我的胜利之路' t.在这里展示(而且它永远不会是 - Don' t' t' t' t' t' t需要大量的审判和错误所有桶的WTFS)。我们的拆解器确实在许多情况下工作,但在最重要的地区失败。

到目前为止,拆卸器缺少任何智能性确定字节是否是助记符或数据。它只是遵循我们的程序始终以指令(实际上是一个好的假设)开始的规则,并且是更多指令(这是一个糟糕的假设)。对于仅限代码的程序(就像到目前为止)的程序,我们确实将与我们的工作完成并获得相当可靠的转换。但是,这是一个罕见的案例,因为大多数时候,数据是任何程序的重要组成部分。让'再次升起困难,并同时尝试更常见和复杂的东西。

* = $ 0810 LDA#$ 00;颜色值STA $ D020;更改背景颜色sta $ d021;更改边框颜色彩色#$ 0b;字符串"你好世界!"有12个字符循环lda hello,y; LOAD字符串的字符号y为STA $ 0400,Y;将其保存在屏幕RAM Dey的位置;减少1 bpl环;是阳性吗?然后重复rts;退出程序你好!SCR"你好世界!" ;我们的字符串显示

啊,经典"你好世界!"不要和我一样讨厌它?我会喜欢使用不同的文本,但是我很确定它'法律要求在任何教程中始终包括这一点。很高兴我们现在脱离了这一点。我们可信赖的面包丁可以将边框和背景颜色设置为黑色,然后在屏幕左上角写下文本。

;由Mayday的AWSM转换使用PyDisass6502! * = $ 0810 LDA#$ 00 sta $ d020 sta $ d021 ldda#$ 0b x081a lda x0824,y sta $ 0400,y dey bpl x081a rts x0824 php ora $ 0c $ 0c nop $ 200f; $ 0c,$ 0f,$ 20 slo $ 0f,x; $ 17,$ 0f果酱; $ 12 NOP $ 2104; $ 0C,04美元,21美元

一切顺利,直到我们与&#34到达数据部分;你好世界!" 细绳。 由于我们假设一切都是代码,因此所有字节都被翻译成助记符。 其中一些是非法的操作码,我知道 ......