SUBX:X86 ISA子集的简单汇编语言

2021-03-21 03:42:49

您可以使用它在裸露的Linux内核上运行它的本地ELF二进制文件。没有其他依赖项。

$ ./subx -map翻译examples / femirmal.subx -o examples / femiration $ ./subx --map --trace运行' last_run' $ ../browse_trace/browse_trace last_run #Text-Mode Debugger UI

您可以为您的装配程序编写测试。整个堆栈被自动化测试彻底覆盖。 Subx' s标记:在语法之前测试。

您可以使用它来了解(几乎肯定)运行计算机的X86处理器。 (见下文。)

您可以阅读其微小的零依赖性内部,并了解它们的工作原理。你可以破解它,当你打破某些东西时,它的彻底测试将提高警报。

最终,您将能够在更高级别的符号中编程。但是,你' ll始终将测试作为检查运行的护栏和痕迹。整个堆栈将始终为其他堆栈设计以理解。你' ll始终有权了解事情的工作,并改变什么' t为您工作。您' LL始终有望在升级期间进行小的变化。

每一行包含在大多数指令中。说明由WhiteSpace分隔的单词组成。单词可以是操作码(定义正在执行的操作)或参数(指定操作的数据行为)。任何单词都可以在/后附加到它的额外元数据。需要一些元数据(如上面的/ imm32和/ / / / / / / / / / / / / / / / / / / / / / / / / / /m)默默地跳过了无法识别的元数据,因此您可以将注释附加到单词(如上面的指令名称/复制到eax或/退出操作数)。

Subx Nond' t提供了很多语法(也有ant#39;甚至是Opcodes的通常助记符),但它确实提供错误检查。如果您错过了操作数或不小心为您添加额外的操作数' ll获得一个很好的错误。 SUBX WON' t任意将数据字节解释为指令,反之亦然。

这么多语法。所有这些数字实际上是什么意思? SUBX支持可能在计算机上运行的32位X86指令集的小子集。 (将名称视为短暂的" sub-x86")指示在一些寄存器上运行:

2其他32位寄存器:ESP和EBP(我建议您只使用这些来管理呼叫堆栈。)

SUBX程序由89 / COPY,01 / ADD,39 /比较和52 / PUSH-ECX组成的指令包括修改这些寄存器以及字节可寻址内存。有关受支持指令的完整列表,请运行Subx帮助操作码。

(Subx' t支持浮点寄存器。英特尔处理器支持8位模式,16位模式和64位模式。Subx永远不会支持它们。还有其他标志。Subx永远不会支持它们。还有更多的指令,Subx永远不会支持。)

它'值得区分指令' somands及其论点。参数直接提供说明。操作数是寄存器或内存中的数据,由指令运行。英特尔处理器以相当复杂的方式确定来自参数的操作数。

英特尔处理器手册是X86指令集上的最终的真相源,但它可能是禁止的,所以这里的意义'速度快。您需要熟悉二进制数,也许是其他一些东西。如果某些东西是NON' t清晰的话,请在任何时候给我发电子邮件。只要需要,我喜欢解释这种东西。坏消息是它需要一些习惯。好消息是,内化接下来的500个单词将为您提供对计算机的更深入了解。

大多数指令在寄存器或内存中的操作数上运行(' reg / mem')和寄存器中的第二个操作数。寄存器操作数使用3位/ R32参数相当直接指定:

但是,REG / MEM操作数变得复杂。它可以由1-7个参数指定,每个参数的大小从2位到4个字节范围。

' s始终存在于reg / mem操作数的关键论点是/ mod,寻址模式。这是一个2位参数,可以采用4个可能的值,并且它确定需要哪些其他参数,以及如何解释它们。

if / mod为3:操作数是3位/ RM32参数描述的寄存器与上面的/ R32类似。

if / mod为0:操作数是/ RM32所描述的寄存器中提供的地址。 C语法中的那个' s * RM32。

如果/ mod为1:操作数是通过将寄存器中的寄存器中添加/ rm32与(1字节)位移添加。其中C语法中的' s *(RM32 + DISP8)。

如果/ mod为2:操作数是通过使用(4字节)位移的/ rm32添加寄存器提供的地址。 C语法中的' s *(/ rm32 + disp32)。

在过去的三种情况下,当/ RM32参数包含4时发生一个异常。而不是编码寄存器ESP,它意味着以完全不同的方式由三个全新的参数(/基本,/索引和/比例)提供地址:

(更多的例外情况更多;见完整故事的英特尔手册的表2-2和表2-3。)

Phew,这是很多东西。在你重读和消化它时,有些例子通过它:

要直接从EAx寄存器读取,/ mod必须是3(直接模式),而/ RM32必须为0。必须没有/基础,/索引或/比例参数。

要从* eax读取(在c语法中),/ mod必须为0(间接模式),并且/ rm32参数必须为0。必须没有/基础,/索引或/比例参数。

要从*(eax + 4)读取,/ mod必须是1(间接+ disp8模式),/ rm32必须为0,必须没有sib字节,并且必须有一个包含4的单个位移字节。

要从*(eax + ECX + 4)读取,一个方法将设置为上面的/ mod到1,/ rm32至4(sib字节下一个),/ base至0,/索引到1(ECX)和单个位移字节到4。 (规模位应该是什么?你能想到另一种方法吗?)

该计划总结了前10个自然数。通过惯例,我使用水平Tabstops来帮助阅读指令,点击遵循长线,评论一组指令描述他们的高级目的,并在复杂指令结束时发表说明他们执行的低级操作。数字总是在十六进制(基地16)中; ' 0x'前缀是可选的,当数字看起来像十进制数或单词时,我倾向于将其作为提醒。

子句程序映射到传统的Linux系统使用的相同的ELF二进制文件。 Linux ELF二进制文件由一系列部分组成。特别是,它们区分代码和数据。相应地,Subx程序由一系列段组成,每个段是从标题行开始:==后跟名称。第一个段必须命名代码;第二个必须命名为数据。

在代码段内,每行包含注释,标签或指令。注释以#开头并被忽略。标签应该始终是一行中的第一个单词,它们以答:。

不同的指令(操作码)需要不同的参数。 SUBX将验证程序中的每个指令,并随时介绍您未命中或虚拟地添加参数的错误。

我建议您在您的程序中一致地订购参数。 SUBX允许任何顺序的参数,但仅是因为它最简单的解释/实施。从指令到教学的切换订单可能会增加读者'的负担。这里的订单I'在OPCODES之后使用的订单:

说明可以引用位移或立即参数中的标签,它们' ll基于标签的地址获取值:立即参数将直接包含该地址,而位移参数将包含地址与地址之间的差异目前的指示。后者主要用于跳转和呼叫指示。

使用标签定义函数。按照惯例,函数内部的标签(必须只跳转到)以$开头。只能调用任何其他标签,从未跳过。所有标签必须是唯一的。

特殊标签是条目,可用于指定/覆盖程序的入口点。它不必是唯一的,最新的定义将覆盖早期的定义。

(条目标签以及重复的段标题,允许逐个中逐个构建程序。)

数据段由标签组成以前和字节值。参考代码段指令或数据段值中的数据标签(使用IMM32元数据)产生它们的地址。

自动测试是Subx的重要组成部分,以及提供测试线束的简单机制:所有名为Run-Tests的特殊,自动生成的函数依次调用以测试的所有功能。你选择如何称呼它取决于你。

我试图保持简单的事情,以便在Subx中最终实现Subx时,在那里毫无努力。但是有一种便利性:说明可以在IMM32参数中提供由引号(")包围的字符串文字。 SUBX将透明地将其复制到数据段并用其地址替换它。字符串是允许SUBX Word包含空格的唯一位置。

这应该是编写Subx程序的信息。示例/目录为练习提供了一些饲料,为Subx功能提供了更逐步的介绍。此回购包括所有示例的二进制文件。在任何提交时,一个例子' s二进制文件应该是相同位的位,结果是翻译相应的.subx文件的结果。二进制也应该在Intel X86处理器上运行的Linux系统中自然可运行,其中32位或64位。如果这些不变性中的任何一个被打破它'我的一部分错误。

更高级别的符号。喜欢编程语言,但是您可以 - 并且预期较薄的实现! - 调整。

子句转换<输入文件> -O<输出ELF二进制> :将.subx文件转换为可执行的精灵二进制文件。

SUBX运行< elf binary> :模拟由Subx翻译发出的ELF二进制文件。对调试有用,并且还可以更彻底地测试转换。

请记住,并非所有32位Linux二进制文件都保证运行。我不是在这里构建一般基础架构的所有x86指令集。 Subx是关于用小,常规子集的32位x86的编程。

在Subx中编写课程令人惊讶和上瘾。阅读计划是一项正在进行的工作,希望广泛的单位测试有所帮助。但是,调试程序是一个真正面对Subx的低级性质的课程。即使是最小的修改也需要测试以确保他们的工作。在我的经验中,没有修改如此之小,我将它完成第一次尝试。当它不起作用时,没有明确的错误消息。机器代码太简单了。您可以使用调试器,因为Subx' S sallymenticeLF二进制文件不包含调试信息。因此,调试需要返回基础知识并练习一个新的,更粗糙的,但希望仍然是可行的工具包:

首先挖掘一个具体一组步骤,以便可重复地获得错误或错误行为。

如果可能,请将步骤转换为故障测试。它并非总是可能的,但Subx'初级目标是继续改善一个人可以写的测试。

单独开始运行单个故障测试。这涉及通过替换对适当的测试功能的调用来修改程序的顶部(或传递给Subx转换为Subx翻译)。

在仿真模式下在运行程序时生成故障测试的跟踪(Subx运行):

生成迹线的能力是存在子表达运行模式的基本原因。它还能够更好地了解程序内部,而不是自然地运行。

作为进一步的改进,可以通过将第二个标志添加到转换和运行命令来呈现跟踪中的标签名称:

$ ./subx --map翻译input.subx -o二进制$ ./subx --map --trace运行二进制arg1 arg2 2>跟踪

Subx -Map Translate将从标签发出映射到名为Map的文件中的地址。 Subx -map --trace在start的地图文件中运行读取,并打印出任何匹配的标签名称,因为它追踪每个执行的指令。

每个绿色框都显示出用于单个指令的痕迹。它以运行的线条:___,后跟操作码,指令前的寄存器状态,以及在执行期间推断的各种其他事实。有些说明首先打印匹配标签。在上面的屏幕截图中,红色框显示地址0x0900005E地图标记为$循环,并可能标记某些循环的开始。函数名称相似运行:==标签行。

这对于快速向您展示运行的控制流程以及在发生错误时执行的功能是有用的。我觉得从这个信息开始很有用,只能在I&#39之后看完整的痕迹;在控制流程上得到的。它刚刚修改的循环是否有所了解?它经过了多少次循环?

一旦您在迹线中显示了Subx,它会在迹线中显示标签,它' s一个简短的步骤来修改程序以插入更多标签只是为了获得更多洞察力。例如,考虑以下功能:

此功能包含一系列跳转指令。如果跟踪显示是-HEX-LOWS-BYTE?正在遇到,然后$ IS-HEX-LOWECASE-BYTE?:结束遇到,它仍然模糊的事情发生了。我们达到了早期退出,还是我们一直执行?要澄清这一点,请在每个跳转后添加临时标签:

现在,该迹线在达到了哪些标签中,并准确地说,当拍摄出出口时,该迹线应更详细。

如果您发现自己想知道,"此内存地址的内容何时更改?",Subx运行对观看点有一些基本的支持。只需在向地址写入的指令之前插入以$观察开始的标签,并且其值将在此后每条指令后开始转到跟踪。

一旦我们恰好有意义,我们想要看看哪些指示,它是一整个看痕迹的时间。键是每条指令之前的寄存器状态。如果函数接收到错误的参数,则检查在调用它之前,检查堆栈上是否在堆栈上推动了哪些值,从那里追溯,等等。

我偶尔希望看到堆栈段的精确状态,在这种情况下,我将注释的呼叫取消注释对VM.CC层中的dump_stack()。它使得跟踪更加冗长,更少密集,需要更多滚动,所以我将它保持在大部分时间。

希望这些提示足以让你开始。要记住的主要事情是不要害怕修改来源。一个很好的调试会话进入了生成迹线的漂亮节奏,盯着它一段时间,修改源,再生轨迹等。如果您' d喜欢另一种眼睛,或者如果您有疑问或投诉,请给我发电子邮件。

内核字符串:空终止字节数阵列。 不安全并避免,但需要与内核进行交互。 字符串:长度为前缀字节数组。 字符串内容以包含数组的长度的4个字节(32字节)。 切片:一对32位地址,表示半开[开始,结束)间隔,以实现具有一致寿命的内存。 流:字符串以32位写入为前缀,并分别读取下一个写入或读取的索引。 文件描述符(FD):内核用于跟踪程序打开的文件的低级别32位整数。 文件:32位值,包含FD或地址流(虚假文件)。 缓冲文件(缓冲文件):包含文件描述符和用于缓冲读/写的流。 每个缓冲文件必须专门执行读取或写入。

SUBX的主要目标是用于操作系统SYSCALL的可测试包装。这里'迄今为止建造了什么:

写入:占用两个参数,文件f和阵列s的地址。

write()接受缓冲区及其长度在单独的参数中,这需要呼叫者单独管理两个,因此可能会出错。 SUBX'包装器将两个人保留在一起,以增加我们从未意外地超出阵列界限的机会。

阅读:用两个参数,文件f和地址进行流。读取F的数据,可以适合(自由空间)。

与write()一样,Unix read()syscall周围的包装器增加了处理的能力'假'测试中的文件描述符,并减少了堵塞外部阵列界限的机会。

这里有一点怪异:在测试中,我们从一个流到另一个流冗余副本。请参阅实施前的评论以讨论备用接口。

ed是退出描述符的地址。退出描述符允许我们退出()生产中的程序,但返回测试内的测试线束。这允许测试在调用EXIT()时对其进行断言。

有关退出描述符的更多详细信息以及如何创建一个,请参阅实施前的注释。

为程序分配一个全新的内存段,与现有代码和数据(堆)段不连续。只是一种更具自以为是的MMAP形式。

分配连续的存储器范围,保证专门用于呼叫者。将起始地址返回到EAX中的范围。

在一些连续的存储器范围内分配V可用地址的分配描述符曲目。 int指定要分配的字节数。

在分配描述符中明确传递允许嵌套的内存管理,其中子系统获取块的内存并将其进一步划分为各个分配。特别有帮助(惊喜)测试。

(复合参数通常通过引用传递。在那里,结果是Don' t it在寄存器中的复合对象,呼叫者通常在分配的内存中传递给它。)

error-byte:类似于错误,但需要额外的字节值,它在消息的末尾打印出来。

下游线相等? :与String将流中的下一行与redlow(Read Index)进行比较 倒带 - 流:将流的读取索引重置为0而不修改其内容。 是-Hex-digit? :采用包含单个字节的32位字,返回eax的布尔结果。 from-hex-char:在eax中占用十六进制数字字符,返回其在eax中的数值 to-hex-char:在eax中占用单位数值,在eax中返回其相应的十六进制字符 下一个令牌从切片:开始,结束,分隔字节 - > 给定切片和分隔符字节的切片,返回在分隔符字节上结束的输入内的新切片。