16/32/64-битный машинный код x86: 11 байт, оценка = 3,66
Эта функция возвращает текущий режим (размер операнда по умолчанию) как целое число в AL. Звони из Си с подписью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, Intel 8086, поддерживал только 16-битный машинный код. 80386 представил 32-битный машинный код (можно использовать в 32-битном защищенном режиме, а затем в режиме Compat под 64-битной ОС). AMD представила 64-битный машинный код, который можно использовать в длинном режиме. Это версии машинного языка x86 в том же смысле, что Python2 и Python3 - это разные языковые версии. Они в основном совместимы, но с преднамеренными изменениями. Вы можете запускать 32- или 64-разрядные исполняемые файлы непосредственно под ядром 64-разрядной ОС, так же, как и программы Python2 и Python3.
Как это устроено:
Начните с al=64
. Сдвиньте его вправо на 1 (32-разрядный режим) или 2 (16-разрядный режим).
16/32 против 64-битных: 1-байты inc
/ dec
кодировки являются префиксами REX в 64-битных ( http://wiki.osdev.org/X86-64_Instruction_Encoding#REX_prefix ). REX.W совсем не влияет на некоторые инструкции (например, a jmp
или jcc
), но в этом случае, чтобы получить 16/32/64, я хотел бы включить или 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-битным режимами. Это затрудняет параллельное декодирование длины команды; Процессоры Intel имеют киоски декодирования LCP .
Большинство соглашений о вызовах (включая psABI x86-32 и x86-64 System V) позволяют узким возвращаемым значениям иметь мусор в старших битах регистра. Они также допускают удары CX / ECX / RCX (и R8 для 64-битных). 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
Связанный: мой машинный код полигона x86-32 / x86-64 Q & A на SO.
Другое различие между 16-битным и 32/64 заключается в том, что режимы адресации кодируются по-разному. eg 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.
7 байт (оценка = 2,33), если я могу нумеровать версии 1, 2, 3
Официальных номеров версий для разных режимов x86 нет. Мне просто нравится писать ответы на вопросы. Я думаю, что это нарушило бы намерение вопроса, если бы я просто назвал режимы 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