我的预编译 x86 代码可能运行在 16 位(实模式或 16 位保护模式)或 32 位(i386 保护模式)下。如何在运行时从代码中检测它?
我能找到这个 NASM 源代码:
bits 16
cpu 386
pushf
test ax, strict word 0 ; In 32-bit mode this is `test eax, ...', +2 bytes.
jmp short found_16
; Fall through to found_32.
found_32:
bits 32
popf
int 32 ; Or whatever code.
found_16:
bits 16
popf
int 16 ; Or whatever code.
但是我不喜欢它,因为它使用了堆栈。有没有一种解决方案,既不修改任何通用寄存器、段寄存器或标志,也不使用堆栈,而且可以在 8086(仅 16 位模式)和 386(两种模式)上运行?
我已尝试lea esi, [dword esi+0]
过 32 位模式,但在 16 位模式下则转换为非 nop。
请注意,我知道对于大多数程序来说,模式是在编译时决定的(作为架构和平台的一部分),并且它们不必能够在运行时检测模式。对于正常启动的程序,操作系统也会根据文件头选择正确的模式,因此几乎没有意外以错误模式运行完整程序文件的危险。但是,某些程序片段(例如漏洞利用 shellcode)可以从各种运行时检测中受益(包括架构和操作系统)。我还想到了一些其他不太明显的用例。
如果您对临时更改感到满意,
esi
则可以撤消类似的操作:在 16 位模式下解码为:
在 32 位中:
我意识到我可以改进我以前的解决方案。
JMP NEAR
,操作码 0xE9在 16 位模式下采用两字节 16 位立即位移,在 32 位模式下采用四字节 32 位位移。此外,此位移相对于下一条指令的开始。因此,如果 32 位位移的高 16 位为零,则意味着 16 位模式下的跳转目标比 32 位模式下的跳转目标低两个字节。这刚好足够短距离跳转到真正的 16 位目标。NASM 示例:
输出
ndisasm -b16 foo.bin
:输出
ndisasm -b32 foo.bin
:我之前的解决方案(仅供参考)是将其用作
0x0001
位移的高 16 位,这样在 32 位模式下,跳转目标就位于 64K+2 字节之外。这需要至少有 64K+ 的可用代码空间。输出
ndisasm -b16 foo.bin
:输出
ndisasm -b32 foo.bin
:想要这样做并希望保留包括 FLAGS 在内的原始架构状态是非常不寻常的。
test
我会使用您的指令长度差异检测方法。或者,mov reg, imm16/32
如果您想保留 FLAGS,但可以破坏您选择的寄存器。如果您的 CPU 支持长 NOP(
0F 1F modrm
),则可以使用它代替test eax, imm32
/imm16
操作码,以避免影响 FLAGS。至少从 P6(Pentium Pro / Pentium II)开始就存在长 NOP 支持,但可能不存在于 P5 Pentium 或更早版本中;如果您关心与复古硬件的兼容性,则应仔细检查或避免这种情况并使用 Jester 的答案。以16位模式解码:
以32位模式解码:
与汇编程序计算出的分支位移相同的代码
nop
因此您可以在/jmp short
和标签之间放置最多 127 个字节found16:
。半相关:https ://codegolf.stackexchange.com/questions/139243/determine-your-languages-version/139717#139717 -
11 个字节的机器代码,设置 AL =
16
,32
或64
(并破坏 FLAGS、CX 和 R8B)。