我一直在调试器中单步执行 Malbolge 解释器,以诊断这里发生了什么。我想说“恭喜,您在 Malbolge 解释器中发现了一个错误”,但鉴于规范和解释器在其他方面不匹配(通常将权威版本作为解释器),并且这是 Malbolge,据我所知,这是预期的行为。(好吧,它实际上可能不是预期的行为,但是其他许多功能也不是被视为重要的编程技术。)
Malbolge 将所有内容存储在一个大数组中,包括代码和数据。命令旨在在运行后自行修改(“加密”,在 Malbolge 术语中),但解释器并没有完全正确地实现它:它实际上做的是运行一个命令,然后查看由代码指针并对其进行加密。这就是为什么跳转会加密跳转目标之前的指令,而不是跳转指令本身。
如果您正在运行的命令超出 33 到 126(含)范围,则该命令不会运行(实际上,在我拥有的 Malbolge 解释器版本中,代码和数据指针也不会增加,看起来像这将不可避免地导致无限循环;也许还有其他版本可以解决该问题)。这是一项重要的检查,因为加密例程只是通过索引查找表来工作;33 到 126 范围之外的值最终会从数组之前或之后读取一些任意字节的内存。
不幸的是,由于代码和数据一起存储在一个大数组中,因此命令最终可能会在运行时修改自身:它可能在运行前处于 33 到 126 的范围内(从而导致安全检查成功),但在运行之后运行时,它超出范围,然后加密将最终执行查找表的越界索引。Malbolge 解释器是用 C 语言编写的,它对于越界读取具有未定义的行为,但对于非常长的越界读取,可能会出现分段错误(但不能保证)行为。
让我们看看代码发生了什么u&a
:
Command A C D memory
start 0 0 0 117, 38, 97, 29432, 98, 29431, 98, 29432, 97, ...
/ input 0 0 117, 38, 97, 29432, 98, 29431, 98, 29432, 97, ...
encrypt input 1 1 111, 38, 97, 29432, 98, 29431, 98, 29432, 97, ...
* 39378 1 1 111, 39378, 97, 29432, 98, 29431, 98, 29432, 97, ...
encrypt 39378 2 2 111, ???, 97, 29432, 98, 29431, 98, 29432, 97, ...
如您所见,您实际上并没有旋转加载到的输入A
;D
旋转操作从(而不是)指向的地址读取A
,因此您将旋转 38(*
内存位置 1 中命令的内存表示形式)以生成 39378。该值同时存储到A
内存位置 1 中。不幸的是,内存位置 1 是当前正在执行的命令,因此当需要对其进行加密时,解释器会越界读取查找表(试图在仅涵盖从 33 到 126 的范围),如果幸运的话,这将产生分段错误。
这种行为很可能出现在“简单”的 Malbolge 程序中,因为 C 和 D 以相同的值开始并以相同的速率增加。如果您希望旋转指令影响当前运行的命令以外的其他内容,则必须以某种方式使它们不同步。最简单的方法通常是一个j
命令(注意:使用生成的数据指针可能不是特别容易,但至少它可能在代码指针之外的其他地方)。
顺便说一句,与您给出的示例程序相比,实际旋转用户输入需要更多的努力。您必须首先将其存储到内存中,并且唯一能够根据 A 的值写入内存的操作是p
,这要求相关单元格已经具有适当的值(为避免丢失信息,这需要是 29524 ,您必须通过 Malbolge 的算术产生一个值,因为它不能作为原始程序的一部分输入,即使这样,您最终也会在输入值中将 0 个 trits 与 1 个 trits 交换)。然后您必须将数据指针发送回您写入输入的单元格,以便您可以在其上运行*
以旋转它。