x86 código da máquina de 16/32/64 bits: 11 bytes, pontuação = 3,66
Esta função retorna o modo atual (tamanho padrão do operando) como um número inteiro em AL. Chame de C com assinaturauint8_t modedetect(void);
Código de máquina do NASM + listagem de fontes (mostrando como funciona no modo de 16 bits, pois BITS 16
instrui o NASM a montar os mnemônicos de origem no 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
Justificação :
O código de máquina x86 não possui oficialmente números de versão, mas acho que isso satisfaz a intenção da pergunta ao produzir números específicos, em vez de escolher o que é mais conveniente (que leva apenas 7 bytes, veja abaixo).
A CPU x86 original, 8086 da Intel, suportava apenas código de máquina de 16 bits. O 80386 introduziu o código da máquina de 32 bits (utilizável no modo protegido de 32 bits e, posteriormente, no modo compatível em um sistema operacional de 64 bits). A AMD introduziu o código da máquina de 64 bits, utilizável no modo longo. Essas são versões da linguagem de máquina x86 no mesmo sentido em que Python2 e Python3 são versões de linguagem diferentes. Eles são principalmente compatíveis, mas com alterações intencionais. Você pode executar executáveis de 32 ou 64 bits diretamente em um kernel do sistema operacional de 64 bits da mesma maneira que poderia executar os programas Python2 e Python3.
Como funciona:
Comece com al=64
. Desloque-o para a direita por 1 (modo de 32 bits) ou 2 (modo de 16 bits).
16/32 vs. 64 bits: os códigos de 1 byte inc
/ dec
são prefixos REX em 64 bits ( http://wiki.osdev.org/X86-64_Instruction_Encoding#REX_prefix ). O REX.W não afeta algumas instruções (por exemplo, a jmp
ou jcc
), mas, neste caso, para obter 16/32/64, eu queria aumentar ou diminuir em ecx
vez de eax
. Isso também define REX.B
, o que altera o registro de destino. Felizmente, porém, podemos fazer isso funcionar, mas configurar as coisas para que os 64 bits não precisem mudar al
.
As instruções que são executadas apenas no modo de 16 bits podem incluir a ret
, mas não achei isso necessário ou útil. (E tornaria impossível alinhar como um fragmento de código, caso você queira fazer isso). Também poderia ser um jmp
dentro da função.
16 bits vs. 32/64: os imediatos são 16 bits em vez de 32 bits. A mudança de modos pode alterar o comprimento de uma instrução; portanto, os modos de 32/64 bits decodificam os próximos dois bytes como parte da instrução imediata, em vez de uma instrução separada. Eu mantive as coisas simples usando uma instrução de 2 bytes aqui, em vez de descodificar a sincronização para que o modo de 16 bits decodificasse a partir de limites de instruções diferentes de 32/64.
Relacionado: O prefixo do tamanho do operando altera a duração do imediato (a menos que seja um imediato de 8 bits com extensão de sinal), assim como a diferença entre os modos de 16 e 32/64 bits. Isso dificulta a decodificação no comprimento das instruções em paralelo; As CPUs Intel têm barragens de decodificação LCP .
A maioria das convenções de chamada (incluindo os psABIs x86-32 e x86-64 System V) permite que valores de retorno limitados tenham lixo nos bits altos do registro. Eles também permitem derrotar CX / ECX / RCX (e R8 para 64 bits). IDK, se isso era comum em convenções de chamada de 16 bits, mas este é um código de golfe, então sempre posso dizer que é uma convenção de chamada personalizada de qualquer maneira.
Desmontagem 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
Desmontagem de 64 bits ( Experimente online! ):
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: minhas perguntas e respostas sobre código de máquina poliglota x86-32 / x86-64 no SO.
Outra diferença entre 16 bits e 32/64 é que os modos de endereçamento são codificados de maneira diferente. por exemplo lea eax, [rax+2]
( 8D 40 02
) decodifica como lea ax, [bx+si+0x2]
no modo de 16 bits. Esta é, obviamente, difícil de usar para o código-golf, especialmente desde e/rbx
e e/rsi
são, em muitos convenções de chamada preservado-call.
Também considerei o uso de 10 bytes mov r64, imm64
, que é o REX + mov r32,imm32
. Mas como eu já tinha uma solução de 11 bytes, isso seria no máximo igual (10 bytes + 1 para ret
).
Código de teste para os modos 32 e 64 bits. (Na verdade, não o executei no modo de 16 bits, mas a desmontagem mostra como será decodificado. Não tenho um 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 Linux sai com exit-status = modedetect()
, portanto, execute-o como ./a.out; echo $?
. Monte e vincule-o a um binário estático, por exemplo
$ 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 (pontuação = 2,33) se eu puder numerar as versões 1, 2, 3
Não há números de versão oficiais para diferentes modos x86. Eu apenas gosto de escrever respostas asm. Eu acho que violaria a intenção da pergunta se eu chamasse os modos 1,2,3 ou 0,1,2, porque o objetivo é forçar você a gerar um número inconveniente. Mas se isso fosse 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 no modo de 32 bits como
08048080 <detect123>:
8048080: b8 03 00 fe c8 mov eax,0xc8fe0003
8048085: 48 dec eax
8048086: c3 ret
e 64 bits como
00000000004000a0 <detect123>:
4000a0: b8 03 00 fe c8 mov eax,0xc8fe0003
4000a5: 48 c3 rex.W ret