Código de máquina x86 de 16/32/64 bits: 11 bytes, puntaje = 3.66
Esta función devuelve el modo actual (tamaño de operando predeterminado) como un entero en AL. Llámalo desde C con firmauint8_t modedetect(void);
Código de máquina NASM + lista de fuentes (que muestra cómo funciona en modo de 16 bits, ya que BITS 16
le dice a NASM que arme los mnemónicos de fuente para el modo de 16 bits).
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
Justificación :
El código de máquina x86 no tiene oficialmente números de versión, pero creo que esto satisface la intención de la pregunta al tener que producir números específicos, en lugar de elegir lo que es más conveniente (que solo toma 7 bytes, ver más abajo).
La CPU x86 original, la Intel 8086, solo admitía código de máquina de 16 bits. 80386 introdujo el código de máquina de 32 bits (utilizable en modo protegido de 32 bits y más tarde en modo compatible bajo un sistema operativo de 64 bits). AMD introdujo el código de máquina de 64 bits, utilizable en modo largo. Estas son versiones del lenguaje de máquina x86 en el mismo sentido que Python2 y Python3 son versiones de idiomas diferentes. En su mayoría son compatibles, pero con cambios intencionales. Puede ejecutar ejecutables de 32 o 64 bits directamente en un núcleo del sistema operativo de 64 bits de la misma manera que podría ejecutar los programas Python2 y Python3.
Cómo funciona:
Comenzar con al=64
. Cambie a la derecha en 1 (modo de 32 bits) o 2 (modo de 16 bits).
16/32 vs. 64 bits: Las 1-byte inc
/ dec
codificaciones son prefijos rex en 64 bits ( http://wiki.osdev.org/X86-64_Instruction_Encoding#REX_prefix ). REX.W no afecta en absoluto algunas instrucciones (por ejemplo, una jmp
o jcc
), pero en este caso para conseguir 16/32/64 quería inc o DEC ecx
en lugar de eax
. Eso también establece REX.B
, lo que cambia el registro de destino. Pero afortunadamente podemos hacer que eso funcione, pero configurar las cosas para que no sea necesario cambiar de 64 bits al
.
Las instrucciones que se ejecutan solo en modo de 16 bits podrían incluir a ret
, pero no me pareció necesario o útil. (Y haría imposible alinearse como un fragmento de código, en caso de que quisiera hacerlo). También podría estar jmp
dentro de la función.
16 bits frente a 32/64: los inmediatos son de 16 bits en lugar de 32 bits. Cambiar los modos puede cambiar la longitud de una instrucción, por lo que los modos de 32/64 bits decodifican los siguientes dos bytes como parte de la instrucción inmediata, en lugar de una instrucción separada. Mantuve las cosas simples usando una instrucción de 2 bytes aquí, en lugar de descodificar la sincronización de modo que el modo de 16 bits decodificaría desde límites de instrucción diferentes a 32/64.
Relacionado: El prefijo de tamaño de operando cambia la longitud del inmediato (a menos que sea un signo de 8 bits inmediato extendido), al igual que la diferencia entre los modos de 16 bits y 32/64 bits. Esto hace que la decodificación de longitud de instrucción sea difícil de hacer en paralelo; Las CPU Intel tienen puestos de decodificación LCP .
La mayoría de las convenciones de llamadas (incluidas las psABI x86-32 y x86-64 System V) permiten que los valores de retorno estrechos tengan basura en los bits altos del registro. También permiten clobbering CX / ECX / RCX (y R8 para 64 bits). IDK si eso era común en las convenciones de llamadas de 16 bits, pero este es el código golf, por lo que siempre puedo decir que es una convención de llamadas personalizada de todos modos.
Desmontaje de 32 bits :
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
Desmontaje de 64 bits (¡ Pruébelo en línea! ):
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
Relacionado: mi x86-32 / x86-64 polyglot machine-code Q&A en SO.
Otra diferencia entre 16 bits y 32/64 es que los modos de direccionamiento se codifican de manera diferente. por ejemplo lea eax, [rax+2]
( 8D 40 02
) decodifica como lea ax, [bx+si+0x2]
en modo de 16 bits. Obviamente, esto es difícil de usar para el golf de código, especialmente desde entonces e/rbx
y e/rsi
se conservan en muchas convenciones de llamadas.
También consideré usar el byte 10 mov r64, imm64
, que es REX + mov r32,imm32
. Pero como ya tenía una solución de 11 bytes, sería, en el mejor de los casos, igual (10 bytes + 1 para ret
).
Código de prueba para el modo de 32 y 64 bits. (Realmente no lo he ejecutado en modo de 16 bits, pero el desmontaje le dice cómo se decodificará. No tengo un emulador de 16 bits configurado).
; 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
Este programa de Linux sale con exit-status = modedetect()
, así que ejecútelo como ./a.out; echo $?
. Ensamble y vincule en un binario estático, por ejemplo
$ 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.
7 bytes (puntuación = 2,33) si puedo numerar las versiones 1, 2, 3
No hay números de versión oficiales para diferentes modos x86. Solo me gusta escribir respuestas. Creo que violaría la intención de la pregunta si solo llamara a los modos 1,2,3 o 0,1,2, porque el punto es forzarlo a generar un número inconveniente. Pero si eso estaba permitido:
# 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
Que decodifica en modo de 32 bits como
08048080 <detect123>:
8048080: b8 03 00 fe c8 mov eax,0xc8fe0003
8048085: 48 dec eax
8048086: c3 ret
y 64 bits como
00000000004000a0 <detect123>:
4000a0: b8 03 00 fe c8 mov eax,0xc8fe0003
4000a5: 48 c3 rex.W ret