如何在一个将自己缝合到自己的尾巴上的程序中环绕 64KB 代码段,无限循环?
How to wraparound in the 64KB code segment in a program that stitches itself to its own tail, ad infinitum?
If the sequential execution of instructions passes offset 65535, then the 8086 will fetch the next instruction byte from offset 0 in the same code segment.
下一个 .COM 程序利用了这一事实,并不断地将其整个代码(总共 32 个字节)拼接到自己的尾部,环绕在 64KB 的代码段中。你可以称其为 binary quine.
ORG 256 ; .COM programs start with CS=DS=ES=SS
Begin:
mov ax, cs ; 2 Providing an exterior stack
add ax, 4096 ; 3
mov ss, ax ; 2
mov sp, 256 ; 3
cli ; 1
call Next ; 3 This gets encoded with a relative offset
Next:
pop bx ; 1 -> BX is current address of Next
sub bx, 14 ; 3 -> BX is current address of Begin
More:
mov al, [bx] ; 2
mov [bx+32], al ; 3
inc bx
test bx, 31 ; 4
jnz More ; 2
nop ; 1
nop ; 1
nop ; 1
为了 call
和 pop
指令的好处,程序将在代码段外部设置一个小堆栈。我不认为 cli
真的有必要,因为我们确实有一个堆栈。
一旦我们计算出 32 字节程序的当前起始地址,我们就将它复制到内存中高 32 字节的位置。所有 BX
指针运算都会回绕。
然后我们在新编写的代码中失败了。
If the sequential execution of instructions passes offset 65535, then the 80386 will trigger exception 13.
假设我包括了异常处理程序的必要设置,仅执行远跳转到此代码段的开头(新编写的代码等待的地方)就足够了吗?
这样的解决方案在 post 80386 CPU 上是否仍然有效?
相关:Is it possible to make an assembly program that writes itself forever?
在 16 位模式(真实或保护)下,IP
寄存器将无任何错误地绕过 64KiB,前提是没有指令跨越 64KiB 边界(例如,位于 0xffff
).
的两字节指令
交叉指令会在 80386+ 上出错,不确定以前的型号会发生什么情况(读取线性地址中的下一个字节 space?从 0 读取下一个字节?)。
请注意,这是可行的,因为段限制与 IP
寄存器“限制”相同。
16位保护模式下可以设置小于64KiB的段限制,这样执行到最后会报错
简而言之(比喻),CPU 确保它需要的所有字节都在段限制内,然后将在没有溢出检测的情况下递增程序计数器。
所以你的程序应该可以运行。
说它是 quine 可能有点牵强,因为它正在读取自己的机器代码,这是作弊(就像读取源代码文件是针对高级语言的)。
我还没有测试过,但是一个“有点复制”本身的程序的最小示例可能是:
;Setup (assuming ES=CS)
mov al, 0abh ;This encodes stosb
mov di, _next ;Where to start writing the instruction stream
stosb ;Let's roll
_next:
这也不是 quine,因为只有 stosb
被复制了。
制作 quine 很难,存储必须是编码小于存储数据大小的指令,否则我们要写入的字节总是多于写入的字节。
If the sequential execution of instructions passes offset 65535, then the 8086 will fetch the next instruction byte from offset 0 in the same code segment.
下一个 .COM 程序利用了这一事实,并不断地将其整个代码(总共 32 个字节)拼接到自己的尾部,环绕在 64KB 的代码段中。你可以称其为 binary quine.
ORG 256 ; .COM programs start with CS=DS=ES=SS
Begin:
mov ax, cs ; 2 Providing an exterior stack
add ax, 4096 ; 3
mov ss, ax ; 2
mov sp, 256 ; 3
cli ; 1
call Next ; 3 This gets encoded with a relative offset
Next:
pop bx ; 1 -> BX is current address of Next
sub bx, 14 ; 3 -> BX is current address of Begin
More:
mov al, [bx] ; 2
mov [bx+32], al ; 3
inc bx
test bx, 31 ; 4
jnz More ; 2
nop ; 1
nop ; 1
nop ; 1
为了 call
和 pop
指令的好处,程序将在代码段外部设置一个小堆栈。我不认为 cli
真的有必要,因为我们确实有一个堆栈。
一旦我们计算出 32 字节程序的当前起始地址,我们就将它复制到内存中高 32 字节的位置。所有 BX
指针运算都会回绕。
然后我们在新编写的代码中失败了。
If the sequential execution of instructions passes offset 65535, then the 80386 will trigger exception 13.
假设我包括了异常处理程序的必要设置,仅执行远跳转到此代码段的开头(新编写的代码等待的地方)就足够了吗? 这样的解决方案在 post 80386 CPU 上是否仍然有效?
相关:Is it possible to make an assembly program that writes itself forever?
在 16 位模式(真实或保护)下,IP
寄存器将无任何错误地绕过 64KiB,前提是没有指令跨越 64KiB 边界(例如,位于 0xffff
).
交叉指令会在 80386+ 上出错,不确定以前的型号会发生什么情况(读取线性地址中的下一个字节 space?从 0 读取下一个字节?)。
请注意,这是可行的,因为段限制与 IP
寄存器“限制”相同。
16位保护模式下可以设置小于64KiB的段限制,这样执行到最后会报错
简而言之(比喻),CPU 确保它需要的所有字节都在段限制内,然后将在没有溢出检测的情况下递增程序计数器。
所以你的程序应该可以运行。
说它是 quine 可能有点牵强,因为它正在读取自己的机器代码,这是作弊(就像读取源代码文件是针对高级语言的)。
我还没有测试过,但是一个“有点复制”本身的程序的最小示例可能是:
;Setup (assuming ES=CS)
mov al, 0abh ;This encodes stosb
mov di, _next ;Where to start writing the instruction stream
stosb ;Let's roll
_next:
这也不是 quine,因为只有 stosb
被复制了。
制作 quine 很难,存储必须是编码小于存储数据大小的指令,否则我们要写入的字节总是多于写入的字节。