搅拌的程序,创建程序,第1部分:解除索赔

2021-04-12 03:10:20

人们似乎享受​​博客帖子关于我将不同的编译器移植到OpenBSD。我想为下一个帖子开头的帖子开始,是返回返回和解除这些程序。 D编译器和GNU Modula-2编译器都是高度复杂的软件。但在他们的核心,他们是完全相同的事情:一个可以创建程序的程序。我们不需要探索这么复杂的东西,以了解如何创建自己创建程序的程序。在这一系列博客文章中,我们将创建两个程序,帮助我们揭示创建程序的程序:首先,在此博客文章中,我们将创建一个读取程序的分解器,或读取程序并生成更高级别表示的程序(集会);其次,在几个后续博客文章中,我们将创建一个汇编程序,一个程序,了解更高级别的汇编语言并从中生成一个程序。

对我来说,我们选择真正的CPU,您今天可以真正购买全新。 8位CPU非常适合这一点。一些像MOS技术6502这样的人,它从20世纪80年代推出了很多电脑,包括苹果二,任天堂Famicom和Domecore 64,其中许多其他人。我喜欢Zilog Z80,它是在Sinclair ZX Spectrum,Sega Master System等机器中找到的CPU,(以略微修改的形式)Nintendo游戏男孩。就像我提到的那样,你仍然可以购买自己的全新Z80。 Z80还具有二进制与略早的英特尔8080 CPU兼容的益处。这种二进制兼容性将使我们的生活更加容易,因为我们将专门针对我们的汇编器和反汇编程序来定制英特尔8080。而在这样做,我们会知道我们的Z80将理解我们的意思。 Z80是英特尔8080的超集,但我们不会担心Z80扩展,因为它们没有必要使用我们的汇编程序创建复杂和有趣的程序。

当然,您不需要购买Z80以便参与。有许多用于8位CPU的模拟器。我喜欢Z80的TNYLPO,因为它也为您提供了完整的CP / M环境。您可能在您最喜欢的包管理器中找到一包TNYLPO。

英特尔8080指令集包含256个指令,但有些是重复的。我们将避免在我们的汇编程序中重复,因为Z80使用这些来实现其扩展。事实证明,英特尔8080指令集中有245个非重复说明。有一个方便的网站,提供整个指令集的颜色编码表。我们将在我们编写汇编程序和反汇编程序时将本网站保留。

指令可以是一个,两个或三个字节的大小。当指令大于一个字节时,它将始终如此,附加字节是以小端格式编写的数字。让'读取网站的指令小区,以了解我们如何识别每个指令的字节数。 Let'首先以第一条指说明:NOP。该指令被编码为0x00,Y轴为我们提供了" 10s&#34的数字;数字和X轴为我们提供了" 1s"数字。该电池为我们提供以下信息:

NOP是英特尔8080汇编语言用于表示0x00指令的助记线声。 1表示该指令的大小,并且4表示指令执行的时钟周期的数量。我们不需要担心时钟周期,但要知道你是否要编写模拟器是很重要的。底部的五个破折号代表了该命令改变了CPU标志寄存器的哪些位,但如果我们正在编写仿真器,那么再次对我们仅有用。

我们需要做的事情,然后写下我们的解索勒是创建一个具有所有助记符和指令大小的程序,并将它们映射到适当的字节。

随着与原始字节相比的汇编语言只是一种高级语言,我们绝对不想在装配中写一下我们的程序,但这是程序员的现实。让' S选择一个高级语言来实现我们的程序。我要选择D.我们可以选择C,但我认为D在C的顶部有一些额外的设施,这将使我们的生活更容易,特别是当我们编写汇编程序。我的目标是' t必然要从这个中创建一个教程,虽然我认为一些是不可避免的。那个'很好。任何高级语言都会在这里做,我必须选择一个,所以我选择D.

让' s为我们的反汇编程序设置基础知识。我们可以通过单个主要函数的简单程序和表示指令集表的结构来消除一个简单的程序。 Let'首先使用一些D Main功能样板:

导入std.stdio;导入std.file; void main(string [] args){if(args.length!= 2){stderr.writeln("使用:d80 file.com");返回; } ubyte [] b = cast(ubyte [])读取(args [1]);}

在D中,主要函数与C中的函数不像C,其中主函数是int。在实践中,这对我们来说并不重要。同样与c不同,d中的主要函数需要零或一个参数,如果需要一个参数,则该参数是一个字符串数组。我们希望通过确保用户提供完全一个程序来拆卸用户来开始我们的程序,换句话说,我们有一个具有两个项目的数组(第一个项目是程序名称本身)。如果我们没有,请让我们向用户提供一个暗示下次搞定它,然后退出,因为我们可能无法做任何有意义的工作。

如果用户确实正确调用了分解器,我们将直接移动到程序中的读数内存。 Z80的程序非常小,64 KB是最大尺寸。由于我们最有可能在64位系统(或者在最差,32位系统)上,我们在内存中有足够的空间,以便于轻松地保持最大的Z80程序。

我们使用D标准库中的读取功能来执行此操作。根据文档,读取功能返回Void []和GDC,我使用的D编译器,不知道如何将其转换为UByte []所以我添加了演员,告诉编译器要做什么。可能有更惯用的D方法,但我喜欢演员解决方案。也许我的C编码是展示的。

我为什么选择ubyte []来存储我们的程序?好吧,让'想想一个程序真的是什么。程序只是一系列字节,当CPU解释时执行我们要承担的操作。我们将根据我们的反汇编程序进行同样的工作,除了我们将停止执行请求的操作,而是输出这些字节的汇编语言表示。

Warning: Can only detect less than 5000 characters

请记住,我们只需要表中的指令助记符和大小。另一个信息,虽然有用,如果我们编写模拟器,对于反汇编程序没有用,所以我省略了它。

我们的指令表是一个简单结构的数组,其中包含助记符的字符串打印和指令尺寸,因此我们可以在Disssembler逻辑中使用该informaton。我决定将阵列标记为不可变,因为它在执行期间永远不会改变。如果它确实如此,我担心我们的手上可能有更大的问题!

通过我们读取的每个字节,我们的反汇编逻辑将拍摄当前字节并在表格对应于该字节并打印的表中查看。然后它检查该指令的大小,如果它大于1,则将打印下一个字节或接下来的两个字节(从小endian顺序翻译),这取决于指令大小是两到三个。

有了这个,我们的反汇编程序是完整的。它可以拆卸任何有效的英特尔8080程序,或任何仅使用基线英特尔8080指令的有效Z80程序。这是一个小程序,以便您可以看到Diassembler工作。

我们的反汇编程序无法做的一件事就是了解指令和数据之间的区别。有点有意义。我们的CP / M程序存在于磁盘上的方式将它们剥离了该上下文。更复杂的文件格式,例如ELF具有保留该上下文信息的方法。但不是我们的CP / M程序。你'在示例计划中注意到,有一个Hello World!在那里的字符串。您'如果将程序加载到十六进制编辑器中,请立即看到它。但是我们的拆解器打印出相应的指示是什么。我们必须忍受这种限制。但是,这可能导致不准确的分解器输出,这取决于如何排列数据字节。

我们可以采用此反汇编程序并将其扩展以了解所有Z80指令。事实上,如果你想看到一个潜在的解决问题,我已经完成了。如果您想尝试此功能,有更多的逻辑和更多表可以填写。但整体技术完全相同。还有更多。

实际上,这种直接的技术可以适于为(几乎)存在的任何其他CPU写入拆卸。

不幸的是,沿着相反的方向,从汇编语言到程序,并不像我们的解索伦一样简单而简单。 但这并不意味着我们的汇编程序需要复杂。 我们可以使用策略来使我们的装配者有效和可理解的新人。 我们可能无法在一个博客文章中覆盖整个汇编程序。 继续进行调整,因为我们继续揭开制定计划的程序。