x86 16/32/64位机器代码:11个字节,得分= 3.66
此函数以AL中的整数形式返回当前模式(默认操作数大小)。用签名从C调用uint8_t modedetect(void);
NASM机器码+源代码清单(显示16位模式下的工作方式,因为它BITS 16
告诉NASM为16位模式组合源助记符。)
1 machine global modedetect
2 code modedetect:
3 addr hex BITS 16
5 00000000 B040 mov al, 64
6 00000002 B90000 mov cx, 0 ; 3B in 16-bit. 5B in 32/64, consuming 2 more bytes as the immediate
7 00000005 FEC1 inc cl ; always 2 bytes. The 2B encoding of inc cx would work, too.
8
9 ; want: 16-bit cl=1. 32-bit: cl=0
10 00000007 41 inc cx ; 64-bit: REX prefix
11 00000008 D2E8 shr al, cl ; 64-bit: shr r8b, cl doesn't affect AL at all. 32-bit cl=1. 16-bit cl=2
12 0000000A C3 ret
# end-of-function address is 0xB, length = 0xB = 11
理由:
x86机器代码没有正式的版本号,但是我认为通过生成特定的编号而不是选择最方便的版本(仅占用7个字节,请参见下文),可以满足该问题的意图。
最初的x86 CPU(英特尔的8086)仅支持16位机器代码。80386引入了32位机器代码(在32位保护模式下可用,后来在64位OS下以兼容模式使用)。AMD推出了可在长时间模式下使用的64位机器代码。这些是x86机器语言的版本,其含义与Python2和Python3是不同的语言版本相同。它们大多兼容,但有意更改。您可以直接在64位OS内核下运行32或64位可执行文件,就像运行Python2和Python3程序一样。
这个怎么运作:
从开始al=64
。向右移动1(32位模式)或2(16位模式)。
16/32与64位:1字节inc
/ dec
编码是64位的REX前缀(http://wiki.osdev.org/X86-64_Instruction_Encoding#REX_prefix)。REX.W根本不影响某些指令(例如a jmp
或jcc
),但是在这种情况下要获得16/32/64,我想使用inc或dec ecx
而不是eax
。这也会设置REX.B
,从而更改目标寄存器。但是幸运的是,我们可以做到这一点,但可以进行设置,因此不需要转换64位al
。
仅在16位模式下运行的指令可以包含ret
,但我认为没有必要或没有帮助。(如果您想这样做,则不可能内联为代码片段)。它也可以jmp
在函数内。
16位和32/64:立即数是16位而不是32位。更改模式可以改变指令的长度,因此32/64位模式将立即解码的后两个字节解码为立即数,而不是单独的指令。我通过在此处使用2字节指令使事情变得简单,而不是使解码不同步,因此16位模式将从不同于32/64的指令边界解码。
相关:操作数大小前缀会更改立即数的长度(除非它是带符号扩展的8位立即数),就像16位模式和32/64位模式之间的差异一样。这使得指令长度解码很难并行进行。英特尔CPU具有LCP解码停顿。
大多数调用约定(包括x86-32和x86-64 System V psABI)都允许狭窄的返回值在寄存器的高位中产生垃圾。它们还允许破坏CX / ECX / RCX(对于64位是R8)。IDK是否在16位调用约定中很常见,但这是代码高尔夫,所以我总是只能说这是自定义调用约定。
32位反汇编:
08048070 <modedetect>:
8048070: b0 40 mov al,0x40
8048072: b9 00 00 fe c1 mov ecx,0xc1fe0000 # fe c1 is the inc cl
8048077: 41 inc ecx # cl=1
8048078: d2 e8 shr al,cl
804807a: c3 ret
64位反汇编(在线尝试!):
0000000000400090 <modedetect>:
400090: b0 40 mov al,0x40
400092: b9 00 00 fe c1 mov ecx,0xc1fe0000
400097: 41 d2 e8 shr r8b,cl # cl=0, and doesn't affect al anyway!
40009a: c3 ret
相关:我在SO上的x86-32 / x86-64多语言机器代码问答。
16位和32/64之间的另一个区别是寻址模式的编码方式不同。例如lea eax, [rax+2]
(8D 40 02
)像lea ax, [bx+si+0x2]
16位模式一样进行解码。显然,这对于代码高尔夫球来说很难使用,尤其是因为在许多调用约定中,e/rbx
并且e/rsi
它们都保留了调用。
我还考虑过使用10字节mov r64, imm64
,即REX + mov r32,imm32
。但是由于我已经有一个11字节的解决方案,所以这最多等于(10个字节+ 1个ret
)。
32位和64位模式的测试代码。(我实际上并没有在16位模式下执行它,但是反汇编告诉您它将如何解码。我没有设置16位仿真器。)
; CPU p6 ; YASM directive to make the ALIGN padding tidier
global _start
_start:
call modedetect
movzx ebx, al
mov eax, 1
int 0x80 ; sys_exit(modedetect());
align 16
modedetect:
BITS 16
mov al, 64
mov cx, 0 ; 3B in 16-bit. 5B in 32/64, consuming 2 more bytes as the immediate
inc cl ; always 2 bytes. The 2B encoding of inc cx would work, too.
; want: 16-bit cl=1. 32-bit: cl=0
inc cx ; 64-bit: REX prefix
shr al, cl ; 64-bit: shr r8b, cl doesn't affect AL at all. 32-bit cl=1. 16-bit cl=2
ret
该Linux程序以exit-status =退出modedetect()
,因此以身份运行./a.out; echo $?
。组装并将其链接到静态二进制文件中,例如
$ asm-link -m32 x86-modedetect-polyglot.asm && ./x86-modedetect-polyglot; echo $?
+ yasm -felf32 -Worphan-labels -gdwarf2 x86-modedetect-polyglot.asm
+ ld -melf_i386 -o x86-modedetect-polyglot x86-modedetect-polyglot.o
32
$ asm-link -m64 x86-modedetect-polyglot.asm && ./x86-modedetect-polyglot; echo $?
+ yasm -felf64 -Worphan-labels -gdwarf2 x86-modedetect-polyglot.asm
+ ld -o x86-modedetect-polyglot x86-modedetect-polyglot.o
64
## maybe test 16-bit with BOCHS somehow if you really want to.
如果可以为版本1、2、3编号,则为7个字节(分数= 2.33)
没有用于不同x86模式的正式版本号。我只是喜欢写asm答案。我认为,如果我只是调用模式1,2,3或0,1,2,这将违反问题的意图,因为这样做的目的是迫使您生成一个不方便的数字。但是如果允许的话:
# 16-bit mode:
42 detect123:
43 00000020 B80300 mov ax,3
44 00000023 FEC8 dec al
45
46 00000025 48 dec ax
47 00000026 C3 ret
其中以32位模式解码为
08048080 <detect123>:
8048080: b8 03 00 fe c8 mov eax,0xc8fe0003
8048085: 48 dec eax
8048086: c3 ret
和64位为
00000000004000a0 <detect123>:
4000a0: b8 03 00 fe c8 mov eax,0xc8fe0003
4000a5: 48 c3 rex.W ret