x86 16/32/64-bitowy kod maszynowy: 11 bajtów, wynik = 3,66
Ta funkcja zwraca bieżący tryb (domyślny rozmiar argumentu) jako liczbę całkowitą w AL. Zadzwoń do niego z C z podpisemuint8_t modedetect(void);
Kod maszynowy NASM + lista źródeł (pokazująca, jak to działa w trybie 16-bitowym, ponieważ BITS 16
mówi NASM, aby zebrał źródłowe mnemoniki dla trybu 16-bitowego).
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
Uzasadnienie :
Kod maszynowy x86 oficjalnie nie ma numerów wersji, ale myślę, że to spełnia cel pytania, ponieważ trzeba wytwarzać określone liczby, zamiast wybierać to, co jest najwygodniejsze (zajmuje to tylko 7 bajtów, patrz poniżej).
Oryginalny procesor x86, Intel 8086, obsługiwał tylko 16-bitowy kod maszynowy. 80386 wprowadził 32-bitowy kod maszynowy (dostępny w 32-bitowym trybie chronionym, a później w trybie kompatybilności w 64-bitowym systemie operacyjnym). AMD wprowadziło 64-bitowy kod maszynowy, który można stosować w trybie długim. Są to wersje języka maszynowego x86 w tym samym sensie, co Python2 i Python3 to różne wersje językowe. Są w większości kompatybilne, ale z celowymi zmianami. Możesz uruchamiać 32-bitowe lub 64-bitowe pliki wykonywalne bezpośrednio w 64-bitowym jądrze systemu operacyjnego w taki sam sposób jak w programach Python2 i Python3.
Jak to działa:
Zacznij od al=64
. Przesuń go w prawo o 1 (tryb 32-bitowy) lub 2 (tryb 16-bitowy).
16/32 a 64-bitowe: 1-bajtowe inc
/ dec
kodowanie to przedrostki REX w wersji 64-bitowej ( http://wiki.osdev.org/X86-64_Instruction_Encoding#REX_prefix ). REX.W nie wpływa w ogóle na niektóre instrukcje (np. A jmp
lub jcc
), ale w tym przypadku, aby uzyskać 16/32/64, chciałem ecx
raczej dodać lub zdecydować eax
. To także ustawia REX.B
, co zmienia rejestr docelowy. Ale na szczęście możemy to zrobić, ale konfigurujemy tak, aby 64-bit nie musiał się zmieniać al
.
Instrukcje, które działają tylko w trybie 16-bitowym, mogą zawierać a ret
, ale nie uważam tego za konieczne ani pomocne. (I uniemożliwiłoby wstawienie jako fragment kodu, na wypadek gdybyś chciał to zrobić). Może to być również jmp
funkcja.
16-bit vs. 32/64: natychmiastowe są 16-bitowe zamiast 32-bitowe. Zmiana trybów może zmienić długość instrukcji, więc tryby 32/64 bit dekodują kolejne dwa bajty jako część instrukcji bezpośredniej, a nie oddzielnej instrukcji. Uprościłem to, używając tutaj instrukcji 2-bajtowej, zamiast zsynchronizować dekodowanie, aby tryb 16-bitowy dekodował z innych granic instrukcji niż 32/64.
Powiązane: Prefiks wielkości operandu zmienia długość natychmiastowego (chyba że jest to 8-bitowe bezpośrednie rozszerzenie z rozszerzeniem znaku), podobnie jak różnica między trybami 16-bitowymi i 32/64-bitowymi. Utrudnia to równoległe dekodowanie długości instrukcji; Procesory Intel mają przeciągnięcia dekodujące LCP .
Większość konwencji wywoływania (w tym psABI x86-32 i x86-64 System V) pozwala na wąskie zwracane wartości, które zawierają śmieci w wysokich bitach rejestru. Pozwalają również na clobbering CX / ECX / RCX (i R8 dla wersji 64-bitowej). IDK, jeśli było to powszechne w 16-bitowych konwencjach wywoływania, ale jest to kod golfowy, więc zawsze mogę po prostu powiedzieć, że jest to zwyczajowa konwencja wywołań.
Demontaż 32-bitowy :
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
Demontaż 64-bitowy ( wypróbuj 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
Powiązane: mój kod maszynowy x86-32 / x86-64 poliglota - pytania i odpowiedzi dotyczące SO.
Kolejna różnica między 16-bitem a 32/64 polega na tym, że tryby adresowania są kodowane inaczej. np. lea eax, [rax+2]
( 8D 40 02
) dekoduje jak lea ax, [bx+si+0x2]
w trybie 16-bitowym. Jest to oczywiście trudne do wykorzystania dla code-golf, zwłaszcza, że e/rbx
i e/rsi
są zadzwonić zachowane w wielu konwencjach telefonicznych.
Zastanawiałem się również nad użyciem 10-bajtowego mov r64, imm64
, którym jest REX + mov r32,imm32
. Ale ponieważ miałem już rozwiązanie 11-bajtowe, byłoby to w najlepszym razie równe (10 bajtów + 1 dla ret
).
Kod testowy dla trybu 32 i 64-bitowego. (Właściwie nie wykonałem go w trybie 16-bitowym, ale deasemblacja mówi ci, jak będzie dekodować. Nie mam skonfigurowanego emulatora 16-bitowego).
; 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
Ten program Linux kończy działanie z kodem wyjścia = modedetect()
, więc uruchom go jako ./a.out; echo $?
. Złóż i połącz go w statyczny plik binarny, np
$ 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 bajtów (wynik = 2,33), jeśli mogę numerować wersje 1, 2, 3
Brak oficjalnych numerów wersji dla różnych trybów x86. Po prostu lubię pisać odpowiedzi na asm. Myślę, że naruszałoby to cel pytania, gdybym tylko wywołał tryby 1,2,3 lub 0,1,2, ponieważ chodzi o to, aby zmusić cię do wygenerowania niewygodnej liczby. Ale jeśli było to dozwolone:
# 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
Który dekoduje w trybie 32-bitowym jako
08048080 <detect123>:
8048080: b8 03 00 fe c8 mov eax,0xc8fe0003
8048085: 48 dec eax
8048086: c3 ret
i 64-bitowy jak
00000000004000a0 <detect123>:
4000a0: b8 03 00 fe c8 mov eax,0xc8fe0003
4000a5: 48 c3 rex.W ret