一个在运行时检测64位模式的x86-32 / x86-64多语言机器码片段是什么?

11

相同的机器码是否能够确定它们是在32位还是64位模式下运行,并执行不同的操作?

即,编写多语言机器码。

通常情况下,您可以通过#ifdef宏在构建时进行检测。或者在C语言中,您可以编写一个带有编译时常量作为条件的if(),并让编译器优化掉其另一侧。

这只对奇怪的情况有用,比如代码注入,或者只是为了看看是否可能。


另请参见:多语言ARM / x86机器码,可根据解码字节的体系结构分支到不同的地址。

1个回答

13
最简单的方法是使用一字节的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位结果:确定您语言的版本 在 codegolf.SE 上。

提醒:通常你不想将此作为编译二进制文件的一部分。在构建时检测模式,以便基于此的任何决策都可以优化掉而不是在运行时执行。例如,使用 #ifdef __x86_64__ 和/或 sizeof(void*)(但不要忘记 ILP32 x32 ABI 在长模式下具有 32 位指针)。


这是一个完整的Linux/NASM程序,使用syscall退出(1)如果以64位运行,或者使用int 0x80退出(0)如果以32位运行。
使用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

在64位系统上组装32位二进制文件(GNU工具链)中构建命令,链接自标签wiki中的FAQ部分。


小修正:syscall 在大多数 AMD CPU 的 32 位模式下是有效的。 - Jester
@Jester:谢谢,我一直在想为什么它在32位模式下没有投诉就反汇编了,在代码的早期版本中也可以汇编。但是对我来说(在Intel Merom上),它确实起作用,以确认我在32位模式下运行了错误的分支而收到了SIGILL。(lea只是解码为一个dec和一个具有不同但仍然有效的寻址模式的lea)。无论如何,已经修复了注释 :) - Peter Cordes
1
@Jester:没错,但我认为他们只是将其作为AMD64的一部分添加,而不是在32位模式下作为新指令添加。(我通常只查看英特尔的指令集参考手册,在兼容/遗留模式下只显示“无效”,没有脚注,所以我想这就是我得出这个结论的原因。) - Peter Cordes
1
我没有查过历史记录,但我认为syscall/sysenter是在64位之前出现的。 - Jester
1
显然,32位的syscall规范已经在1997年5月发布为_SYSCALL和SYSRET指令规范应用说明,订单号为21086_,比你链接的64位代码的邮件列表讨论早了三年,而且32位的AMD K6-2系列处理器自1998年以来就支持它。PS:是的,在32位模式下它的工作方式不同。 - Jester
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接