最简单的方法是使用单字节inc 操作码,这些操作码在 64 位模式下被重新用作 REX 前缀。 REX 前缀对jcc 没有影响,所以你可以这样做:
xor eax,eax ; clear ZF
db 0x40 ; 32bit: inc eax. 64bit: useless REX prefix
jz .64bit_mode ; REX jcc works fine
另请参阅根据其执行模式返回 16、32 或 64 的 3 路多语言:codegolf.SE 上的Determine your language's version。
提醒:通常您不希望将其作为已编译二进制文件的一部分。在构建时检测模式,因此任何基于此的决策都可以优化而不是在运行时完成。例如使用 #ifdef __x86_64__ 和/或 sizeof(void*)(但不要忘记 ILP32 x32 ABI 在长模式下具有 32 位指针)。
这是一个完整的 Linux/NASM 程序,如果以 64 位运行,则使用 syscall 到 exit(1),如果以 32 位运行,则使用 int 0x80 到 exit(0)。
使用 BITS 32 和 BITS 64 可确保它以任何一种方式汇编成相同的机器代码。 (是的,我检查了objdump -d 以显示原始机器代码字节)
即便如此,我还是使用了db 0x40 而不是inc eax,以便更清楚地了解有什么特别之处。
BITS 32
global _start
_start:
xor eax,eax ; clear ZF
db 0x40 ; 32bit: inc eax. 64bit: useless REX prefix
jz .64bit_mode ; REX jcc still works
;jmp .64bit_mode ; uncomment to test that the 64bit code does fault in a 32bit binary
.32bit_mode:
xor ebx,ebx
mov eax, 1 ; exit(0)
int 0x80
BITS 64
.64bit_mode:
lea rdx, [rel _start] ; An instruction that won't assemble in 32-bit mode.
;; arbitrary 64bit code here
mov edi, 1
mov eax, 231 ; exit_group(1).
syscall ; This does SIGILL if this is run in 32bit mode on Intel CPUs
;;;;; Or as a callable function:
BITS 32
am_i_32bit: ;; returns false only in 64bit mode
xor eax,eax
db 0x40 ; 32bit: inc eax
; 64bit: REX.W=0
;nop ; REX nop is REX xchg eax,eax
ret ; REX ret works normally, too
经过测试和工作。我构建了两次以获得围绕相同机器代码的不同 ELF 元数据。
$ yasm -felf64 -Worphan-labels -gdwarf2 x86-polyglot-32-64.asm && ld -o x86-polyglot.64bit x86-polyglot-32-64.o
$ yasm -felf32 -Worphan-labels -gdwarf2 x86-polyglot-32-64.asm && ld -melf_i386 -o x86-polyglot.32bit x86-polyglot-32-64.o
$ ./x86-polyglot.32bit && echo 32bit || echo 64bit
32bit
$ ./x86-polyglot.64bit && echo 32bit || echo 64bit
64bit
(来自Assembling 32-bit binaries on a 64-bit system (GNU toolchain) 的构建命令,链接自x86 标签wiki 中的FAQ 部分)。