我写了一个关键人员,每个人都能理解

2021-06-09 21:01:36

当我们写下我们的汇编程序时,我们制作了许多设计选择,帮助我们减少了汇编程序本身的复杂性。其中一个决定是将汇编程序直接输出可执行文件。这是一个不错的选择,因为它使我们能够快速测试,因为我们正在开发:一旦我们实现的操作码,我们可以写与操作码的汇编文件,通过我们的汇编程序运行它,而无需做任何运行产生的程序更多加工。

然而,这种方法的一个SignScatant缺点是我们被限制为仅编写单文件汇编程序。如果我们希望将我们的程序拆分为多个文件,或将已现有的库融入我们的代码中,我们的汇编程序无法执行此操作。如果我们想这样做,我们需要让我们的汇编程序生成可重定位的对象代码:可以生活的代码"在自己的"在自己的独立(虽然不是直接可执行的)对象文件中,后来与其他这样的文件组合以生成可执行文件。

在这篇博客文章中,我们将编写我们自己的目标代码格式能够产生CP / M和MS-DOS COM文件格式的可执行文件的链接,针对Intel 8080和Zilog公司Z80上的CP / M和MS英特尔8086 -dos。

我们有两个潜在的方法来创建此对象代码。一种方法是学习已经存在的东西的对象文件格式和目标。在CP / M天中,Microsoft通过对象文件格式引导方式,为其M80汇编程序和L80链接器创建rel文件格式。数字研究公司,谁在CP初期/ M没有自己的重定位目标格式,最后被迫使用Microsoft' S REL文件格式为他们的迟到到了党RMAC重定位汇编程序。

如果我们为我们的汇编程序选择了Rel文件格式,那么我们就可以以这种格式点击到所有已存在的库中。

第二种方法是设计自己可重定位的对象代码格式,并编写自己的链接器,了解这种格式。虽然我们不会立即使用已预先存在的格式的所有库,但我们将能够控制该格式。此外,设计自己的对象文件格式并编写自己的链接器可能很有趣。我肯定是我们学习有用的东西。

也许从这篇文章的标题中难以理解,而我已经告诉过你的事实是这样做的,我选择了第二个选择。

我将在这里至少有一个重要的自由。对于一个,我们将使对象格式简单。对于两个,我们不会担心对象代码文件的大小。由于我将在D中编写链接器,因此我们只需要照顾最终的二进制文件是正确的尺寸。如果对象代码文件效率低下并且臃肿,则可以。

我们的联系人需要做什么?这将有助于我们思考如何设计我们的对象文件格式。

更多关于链接器的这些要求的思考使我相信我们有两件事对我们的符号和其他一切感兴趣。但是那个'没有完整。我们真的将符号的概念分为两​​个:符号定义和符号引用。这让我们有三件事我们需要关心:符号定义,符号引用以及其他一切。现在,让' s标记这三件事。让' s调用定义类型1,引用类型2,以及其他类型0.我们所有的对象代码和库都应包含这三种,只有这三种类型。

符号定义名称将完全按照它们的源文件中出现的方式写出,以\ 001包装。

符号参考名称将完全按照它们的源文件中出现,以\ 002包装。

其他一切都将以字节字节写出字节。每个字节将由\ 000提前。

是的,与它们产生的最终可执行文件相比,生成的对象文件非常大,特别是如果您有长符号名称。但再次,它并不对我们有意义,因为这些工具本身不会在任何16位机器上运行。

对于未来,让' s还调用\ 000,\ 001和\ 002字节,因为我们在控制代码上方的列表中定义了它们。编写链接器时,我们将使用这些控制代码。

通常:OpenBSD上的GNU D编译器。使用Vim作为客观正确的编辑器。

由于对象文件格式如此明确且简单,因此在我调整汇编器之前,我最终结束了链接。我为链接器选择了令人难以置信的原始名称L80。显然它不是Microsoft链接器。我们的汇编程序名为A80,我们的反汇编程序被命名为D80,因此我只是遵循既定的主题。

链接器本身非常简单。像我们的汇编程序一样,我们需要两个通过。它遵循与汇编程序相同的组织原因:我们的第一个传递都没有,除了收集所有符号定义并将其名称存储在表中,并在表中存储它们的命名,并使用第二个传递替换所有符号引用使用我们可以在表中查找的该符号的计算地址。

其中二进制是最终可执行文件的名称,SANS .com扩展(L80为您自动添加.com后缀)和file1.obj等是包含可执行文件的对象文件。您可以在对象文件列表中使用.lib库。

最简单,您只有一个名为Hello.obj的单个独立的对象文件,您' d喜欢创建一个名为hello.com的可执行文件,调用l80会如下所示:

检查以查看我们具有适当数量的参数后,我们将直接移动到读取命令行上标识的所有目标文件和库到一个大缓冲区。我们的Foreach循环开始于2,因为我们不想读取链接器本身或输出文件(尚不存在)进入缓冲区。我们只想将输入对象文件读入缓冲区。正如我们所记得的那样,程序本身就在参数列表中作为第一个参数。

我们也可能还记得我们汇编程序的FindSplit函数。回想一下,它返回三个字符串:分裂点的左侧,分裂点本身和分裂点的右侧。正如我们检查文件扩展的存在,那就是拆分点,所以如果它不存在,这意味着文件没有与.obj结束。然后我们检查.lib,因为我们可能有一个图书馆。如果我们既没有,那么我们就会出错。

现在我们已准备好在所有这些对象代码中找到所有符号定义。这很容易。我们需要做的就是检查控制代码!对于第一次通过,如果我们看到\ 000的控制代码,那么我们知道这与符号完全无关,所以我们可以跳过此控制代码字节和以下字节。我们确实希望通过一个人递增地址柜台,因为我们需要知道我们的最终可执行文件进入多远,以便跟踪所符号的地址应该是什么。出于良好的衡量标准,让' s还检查我们的二进制文件是否太大,对于COM文件。

如果控制代码是\ 002,则我们可以跳过下一个\ 002的发生,因为我们在我们的目标文件格式设计中说,符号引用被包装在\ 002中。如果不知何故,你从来没有看到另一个\ 002,那个' s错误。我们确实希望通过符号参考地址和所有地址为16位,以确保我们递增两个地址计数器。再次,如果我们最终用地址计数器太大,那是一个错误。

但是,如果控制代码是\ 001,那么我们有一些真正的工作要做。我们继续读取字符,直到我们看到另一个\ 001,因为根据我们的对象文件格式设计,符号定义被包装在\ 001中。就像符号引用一样,如果我们从未看到关闭\ 001,那么' s错误。另外,如果有两个\ 001控制代码回来,那么我们实际上在这里有一个符号定义,并且它是错误的。

对于最后一次检查,我们需要确保我们之前没有看到此符号名称。如果我们有,那么我们有多种定义相同的符号,并且链接器无法知道用户想要的定义。不出所料,' s错误。

最后,如果一切都会检查,我们使用与我们的汇编程序一起使用的相同伎俩来创建我们的符号表:我们有一系列结构,每个结构都持有单个符号名称及其已解决的地址,这恰好是什么地址计数器在此时。我们为此符号创建新结构,并将其附加到结构数组。

如果由于某种原因,您读取了\ 000,\ 001或\ 002以外的控制代码,那么' s错误。

第二次通过负责用我们在第一次通过并写出最终二进制中的地址来替换符号引用。这次,我们可以忽略所有\ 001控制代码,因为我们已经在第一次通过时已经处理了它们。

如果我们看到一个\ 000控制代码,我们将以下字节写入输出。这一切都需要为每个控制代码进行所有何种控制代码。

遇到\ 002控制代码是在第二次通过期间需要一些工作的控制代码。我们在引用的符号名称中读取,确保它是一个空字符串,然后尝试在第一次通过期间找到我们创建的表中的符号。如果我们在表中找到符号,我们会输出其已解决的地址。如果我们没有在表格中找到符号,那就' s错误。

最后,就像在第一次通过期间我们检查它以来永远不会发生的理智检查,如果您看到了ISN' t 000,\ 001,或\ 002,那么' s错误。

相信它与否,'它。这是我们的整个联系人。它真的是每个人都能理解的联系人。

教导我们的汇编程序输出我们的新对象文件格式如下所示。简而言之,我们需要检测我们何时使用符号未使用equ伪操作设置为值。对于未设置为值的符号,当发现符号作为标签时,我们将其名称包装在\ 001中,因为它是我们的符号定义,并在找到符号作为符号时将其名称包装在\ 002中对Opcode的论点,因为它是我们的符号引用。最后,我们在所有剩余的字节前写一个\ 000,因为这些是我们的"其他一切。"

诚实地,使用对象文件格式这么简单,您可以将您的对象文件一起放入库中。但这对我来说感觉有点太尴尬了。所以我写了一个归档者......好吧,它会连接其输入文件。它检查每个输入文件在.obj中结束,并自动将.lib后缀附加到您的库中。也许这是值得两分钟来写它。

是的,这意味着我们的汇编程序无法再生成直接可执行代码。但是,考虑到我们现在所获得的东西也值得:任何高级编译器撰写者都可以针对我们的A80汇编语言,并保证模块编译只会工作,因为我们的链接器可以处理多个对象文件。它们也可以保证,库中只需工作,因为L80以及AR80,可以创建和链接从目标文件创建的库。

此外,如果有人写了一个8086汇编程序,它在我们的格式生成了对象代码,那么L80还将正常工作以创建MS-DOS COM文件。

我们极端简单的一个缺点是,未来的高级语言编译器作家将需要在编译器中为所有非全局符号实施编译器中的名称。否则,对于重复符号名称可能是非常容易的,这将导致链接器错误。至少L80可以处理任意长度的符号名称。

另一个缺点是您必须在命令行中以有意义这种线性方法的顺序排列目标文件。第一个目标文件必须是程序的入口点。对象文件的不同布置可以用于链接可执行文件,但将生成有机态不同的可执行文件。你可能会或可能不关心这一点。

除了针对MS-DOS的8086汇编程序外,还有一个我认为链接器所必需的一个附加功能。现在,链接器无法确定在最终程序中未使用符号,然后从可执行文件中删除该符号。这可以通过跟踪在传递期间的引用然后在通过两个过程中跳过所有全局符号的引用来完成。

链接器,就像我们用汇编者看到的一样,不需要是黑色盒子,只有预先预定的可以希望理解。至少出于仅需要符号解析的CP / M和MS-DOS COM文件等简单可执行格式,您自己创建的链接器可以是一个不错的下午项目。虽然我没有完全是时间,但我很确定你花了更长的时间来编写这个博客文章,解释了与我来说实际编写链接器的链接。

看看您是否可以在创建自己的对象文件格式并为其创建自己的链接器时做得更好。

它肯定可以实现我们的汇编程序和链接器作为本机二进制文件,尽管我们必须设计用于阅读和与文件一起使用的新策略,因为存储大缓冲区中的所有内容将无法在16位系统上工作。 但是可以完成将本实用程序移植到16位系统。 最后,我们'重新编写完整的开发环境。 除了许多高级语言编译器之外,我们缺少的最后一个大工具是一个调试器。 调试器呈现出一些非常有趣的挑战,因为我们需要它是一个原生二进制文件,并且是可以运行和监控其他二进制文件的二进制文件。 我想我们会在某一天解决它,虽然我不知道何时。 如果您'重新对链接器感兴趣并有兴趣阅读关于创建生产优质精灵链接器,请查看Ian Lance Taylor' S博客系列在GNU金色链接器上。