Есть ли размер операнда по умолчанию в архитектуре x86-64 (AMD64)?

Это вопрос о префиксах переопределения размера операнда в архитектуре x86-64 (AMD64).

Вот набор инструкций ассемблера (nasm) и их кодировки; под новым я имею в виду регистры r8, …, r15:

                                                                   67: address-size override prefix
                                                                   |
                                                                   |  4x: operand-size override prefix
                                                                   |  |
   ;   Assembler                   ; | Dst operand | Src operand | -- --
       mov      eax,ecx            ; | 32-bit      | 32-bit      |       89 C8     |
       mov      r8d,ecx            ; | 32-bit new  | 32-bit      |    41 89 C8     |
       mov      eax,r9d            ; | 32-bit      | 32-bit new  |    44 89 C8     |
       mov      r8d,r9d            ; | 32-bit new  | 32-bit new  |    45 89 C8     |
       mov      rax,rcx            ; | 64-bit      | 64-bit      |    48 89 C8     |
       mov      r8,rcx             ; | 64-bit new  | 64-bit      |    49 89 C8     |
       mov      rax,r9             ; | 64-bit      | 64-bit new  |    4C 89 C8     |
       mov      r8,r9              ; | 64-bit new  | 64-bit new  |    4D 89 C8     |

       lea      eax,[ecx]          ; | 32-bit      | 32-bit      | 67    8D 01     |
       lea      r8d,[ecx]          ; | 32-bit new  | 32-bit      | 67 44 8D 01     |
       lea      eax,[r9d]          ; | 32-bit      | 32-bit new  | 67 41 8D 01     |
       lea      r8d,[r9d]          ; | 32-bit new  | 32-bit new  | 67 45 8D 01     |
       lea      rax,[rcx]          ; | 64-bit      | 64-bit      |    48 8D 01     |
       lea      r8,[rcx]           ; | 64-bit new  | 64-bit      |    4C 8D 01     |
       lea      rax,[r9]           ; | 64-bit      | 64-bit new  |    49 8D 01     |
       lea      r8,[r9]            ; | 64-bit new  | 64-bit new  |    4D 8D 01     |

       push     rax                ; |             | 64-bit      |       50        |
       push     r8                 ; |             | 64-bit new  |    41 50        |

Изучая эти и те же инструкции с другими регистрами, я пришел к следующему. Между «старым» и «новым» регистрами существует пара. Не исчерпывающе:

   AX <--> R8
   CX <--> R9
   DX <--> R10
   BX <--> R11
   BP <--> R13 

Игнорируя префикс размера, байты инструкции относятся не к конкретным регистрам, а к парам регистров. В качестве примера: байты 89 C8 указывают команду mov из источника, который является либо ecx, rcx, r9d, либо r9, в пункт назначения, который является либо eax, rax, r8d, либо r8. Учитывая, что операнды должны быть 32- или 64-битными, существует восемь допустимых комбинаций. Префикс переопределения размера операнда (или его отсутствие) указывает, какая из этих комбинаций является предполагаемой. Например, если префикс присутствует и равен 44, то исходный операнд должен быть 32-битным новым регистром (в этом примере затем сворачиваться до r9d), а местом назначения должен быть 32-битный старый регистр (здесь тогда сигнализация eax).

См. также:  нечетная ошибка компоновщика при создании программы: "множественное определение `fnames'; src/main.o:(.data.rel.local+0x0): сначала определено здесь'

Возможно, я не совсем правильно понял, но я думаю, что уловил суть. Тогда может показаться, что префиксы переопределения размера операнда переопределяют тот факт, что без них инструкция использовала бы 32-битные «старые» операнды.

Но наверняка есть кое-что, что ускользнет от меня, иначе: какой смысл тогда говорить о «версии x86-64 с размером операнда по умолчанию 64-бит» (например, здесь)?

Или есть способ, работающий на 64-битной машине, установить размер операнда по умолчанию на 32 или 64, и если это так, и если моя программа настроит машину соответствующим образом, я бы увидел разные кодировки?

Также: когда будет использоваться префикс переопределения размера операнда 66H?

Взгляните на руководства Intel по разработке программного обеспечения. Объясняется кодировка и значение префиксов. Я могу написать настоящий ответ позже.   —  person user1752563    schedule 07.07.2021

Префиксы от 40h до 4Fh называются префиксами REX. Они могут указывать 64-битный размер операнда. Они также могут указывать на использование одного из 8 верхних регистров для источника или назначения. Я считаю, что возможна любая комбинация этих вариантов.   —  person user1752563    schedule 07.07.2021

Префикс 66 изменяет размер операнда на 16 бит.   —  person user1752563    schedule 07.07.2021

См., Например, wiki.osdev.org/X86-64_Instruction_Encoding#Encoding бит REX смыслы.   —  person user1752563    schedule 07.07.2021

Да, в машинном коде по умолчанию используется 32-битная версия для большинства инструкций, 64-битная для стека и инструкций перехода / вызова. В источнике сборки нет значения по умолчанию, это должно подразумеваться регистром или указываться явно. (За исключением некоторых ассемблеров, имеющих значение по умолчанию для push / pop.) Обратите внимание, что 16-битный AX соответствует 16-битному R8W, а RAX и R8 — это пара, различающаяся префиксом REX.   —  person user1752563    schedule 07.07.2021

См. также:  Как создать проект Visual Studio с манифестом приложения из Qt?

@ecm В частности, префикс REX кодирует четыре бита состояния (один бит для размера операнда, три бита для старших регистров). Простое присутствие префикса REX дополнительно кодирует то, что вы хотите sil, dil, spl и bpl вместо ah, ch, dh и bh.   —  person user1752563    schedule 07.07.2021

@Peter Cordes Под «AX ‹—› R8» я имел в виду, что регистры, которые объединены в пары для целей кодирования, — это eax, rax с одной стороны и r8d, r8 с другой. Но Питер, на вопрос, который я связал, почему вы говорите о «версии x86-64 с размером операнда по умолчанию 64-бит»?   —  person user1752563    schedule 07.07.2021

Версия x86-64 с размером операнда по умолчанию 64-бит является чисто гипотетической. Питер говорит о том, как бы все работало, если бы был процессор, который вел бы такое поведение. Но в реальной жизни это не так.   —  person user1752563    schedule 08.07.2021

Понравилась статья? Поделиться с друзьями:
IT Шеф
Комментарии: 1
  1. user1752563

    Да, в 64-битном машинном коде размер операнда по умолчанию для большинства инструкций 32-битный, 64-разрядная для стека и инструкций перехода / вызова, а также 64-разрядная для loop и jrcxz. (И размер адреса по умолчанию — 64-битный, поэтому add eax, [rdi] — это 2-байтовая инструкция, без префиксов.) И нет, значения по умолчанию нельзя изменить, у вас не может быть 2-байтового add rax, rdx.

    Кодирование размера операнда в 64-битном режиме

    • 64-битный размер операнда сигнализируется REX.W (0x4? со старшим битом, установленным в младшем полубайте, 48..4f). Префикс REX с очищенным битом W никогда не может заменить размер операнда на 32-битный для кодов операций, где по умолчанию используется что-то другое. (Как push)
    • 16-битный размер операнда обозначается префиксом 0x66, например imul ax, [r8], 123
    • Для 8-битного размера операнда используются разные коды операций. (У 8086 были 8- и 16-битные размеры операндов; коды операций для 8-битных размеров операндов с тех пор не изменились. Коды операций 8086 для 16-битных размеров операндов по умолчанию зависят от режима и префикса.)

    (В других режимах REX отсутствует, и 66 устанавливает значение, отличное от значения по умолчанию.)

    Интересный факт: loop и jrcxz переопределяются для использования ECX. RCX неявно префиксом размера адреса, а не размером операнда. IIRC, это имеет некоторый смысл, потому что атрибут размера операнда ветви влияет на то, усекает ли он EIP до IP или нет.

    Например, разборка GNU .intel_syntax тех примеров синтаксиса NASM, приведенных выше.

    objdump -drwC -Mintel foo
      401000:       6a 7b                   push   0x7b
      401002:       66 6a 7b                pushw  0x7b
      401005:       03 07                   add    eax,DWORD PTR [rdi]
      401007:       66 03 07                add    ax,WORD PTR [rdi]
      40100a:       48 03 07                add    rax,QWORD PTR [rdi]
      40100d:       66 41 6b 00 7b          imul   ax,WORD PTR [r8],0x7b
    

    Обратите внимание, что в примере imul использовался регистр высокого уровня, поэтому для сигнала R8 требовался префикс REX, а не префикс 66 для сигнала 16-битного размера операнда. Бит .W не установлен в префиксе rex, это 0x41 не 0x49.

    Не имеет смысла иметь одновременно REX.W и префикс 0x66. Похоже, в этом случае префикс REX.W выигрывает. Одношаговое 66 48 05 40 e2 01 00 data16 add rax,0x1e240 в Linux GDB на i7-6700k (Skylake), при пошаговом режиме RIP указывает на конец всей этой инструкции (и добавляет полное немедленное выполнение в RAX), не декодируя его как add ax, 0xe240 и оставляя RIP, указывающий на середина 4-байтового сразу. (Префикс 66 изменяет длину для этого кода операции, как и большинство, у которых есть 32-битный немедленный, который становится 16-битным. См. https://agner.org/optimize/ re: киоски LCP.)

    Я попросил NASM выдать это из o16 add rax, 123456. Префиксы REX в целом нормальны и подходят с префиксом 66, например для кодирования add r8w, [r15 + r12*4], требуя, чтобы все 3 других бита были установлены в младшем полубайте REX.


    • 32-битный размер адреса обозначается префиксом 0x67, например add eax, [edx].

    Конечно, его можно комбинировать с размером операнда, полностью ортогональным.

    Обычно 32-битный размер адреса полезен только для Linux x32 ABI (ILP32 в длинном режиме для экономии места в кэше на структуры данных с большим количеством указателей), где вы можете усечь высокий объем мусора из указателя, чтобы убедиться, что математические вычисления адреса правильно переносятся, чтобы оставаться в низких 4 ГиБ, даже с 32-битными отрицательными числами.

      401012:       67 03 04 ba             add    eax,DWORD PTR [edx+edi*4]
    

    В других режимах 67 устанавливает размер адреса не по умолчанию. 16-битный размер адреса также подразумевает 16-битную интерпретацию байта ModRM, поэтому разрешены только [bx|bp + si|di], без байта SIB, чтобы обеспечить гибкость 32/64-битной адресации.


    Режимы и наборы настроек по умолчанию

    Нет, настройки по умолчанию нельзя изменить в 64-битном режиме. Различные биты в записи GDT, выбранные CS (или любым другим методом), не имеют значения. AFAIK, таблица в https://en.wikipedia.org/wiki/X86-64#Operating_modes — это полный список возможных комбинаций режимов и размеров операндов / адресов по умолчанию.

    Есть только один набор настроек, который вообще разрешает 64-битный размер операнда. Невозможно даже в любом устаревшем режиме иметь такую ​​комбинацию, как 16-битный операнд, 32-битный размер адреса.

    В этом есть смысл с точки зрения сложности оборудования. Чем больше различных комбинаций вещей он должен поддерживать, тем больше транзисторов может быть задействовано в и без того сложной и энергоемкой части процессора.

    (Хотя размер адреса стека по умолчанию, неявно используемый push / pop, выбирается независимо селектором SS, IIRC. Поэтому я думаю, что у вас может быть обычный 32-битный режим, где add eax, [edx] составляет 2 байта, за исключением push / pop / call / ret с использованием ss:sp вместо ss:esp. Я никогда не пробовал настраивать.


    Обратите внимание, что 16-битный AX соответствует 16-битному R8W, а RAX и R8 — это пара, различающаяся префиксом REX.


    В исходном коде сборки нет значения по умолчанию, это должно подразумеваться регистром или указываться явно.

    За исключением некоторых ассемблеров, имеющих значение по умолчанию для push / pop, или нескольких плохих ассемблеров, которые имеют значение по умолчанию для других случаев, включая ассемблер GNU для таких вещей, как add $1, (%rdi) по умолчанию dword, с предупреждением только в последних версиях. ГАЗ делает ошибку на неоднозначном mov, как ни странно. Встроенный ассемблер clang лучше, он ошибается при любом неоднозначном размере операнда.

    Обратите внимание, что 66 и 48 могут появляться одновременно с инструкциями SSE, где 66 выбирает другую организацию данных. IIRC, есть также несколько совсем недавних специальных инструкций с такой кодировкой, их нужно будет найти. person user1752563; 08.07.2021

    @fuz: о да, правда, когда 66 — это просто обязательный префикс, который по сути является частью кода операции. На этом этапе он на самом деле не действует как префикс размера операнда, хотя он все еще является префиксом и может отображаться в другом порядке, если хотите (хотя Intel рекомендует определенный порядок). person user1752563; 08.07.2021

    Однако на самом деле это не часть кода операции; у него просто другая функция (выбор организации данных). person user1752563; 08.07.2021

    @fuz: Вы имеете в виду ps против pd? Да, верно, но он также используется для целочисленных версий SSE2 / SSSE3 xmm и MMX-регистров, таких как paddb xmm vs. paddb mm. И даже внутри ps / pd SSE2 cvtps2pd xmm является (без префикса) NP 0F 5A в то время как SSE2 cvtpd2ps xmm равно 66 0F 5A. Для других обязательных префиксов, таких как rep, инструкции, перегруженные им, включают такие разные вещи, как F2 0F 38 F1 crc32 r, r/m32 vs . 0F 38 F0 movbe. person user1752563; 08.07.2021

    @fuz: Так или иначе, 66 может различать инструкции, которые выполняются на разных портах, если это имеет значение. person user1752563; 08.07.2021

    Большое спасибо за такой подробный ответ. Другой вопрос: при работе с 32-битными значениями до сих пор я предпочитал, например, mov eax, r8d, а не mov rax, r8, потому что я ошибочно полагал, что последний принимает префикс, а не первый. Но мне сейчас кажется, что не должно быть разницы, по производительности или как-то иначе? person user1752563; 08.07.2021

    @ user1752563: Правильно, постарайтесь сохранить 32-битные значения (и указатели, используемые в режимах адресации) в устаревших регистрах, чтобы избежать префиксов REX, например mov eax, esi составляет 2 байта, по сравнению с mov eax, r8d и mov rax, r8, оба имеют 3 байта и одинаковую производительность. Все еще есть случаи, когда 32-битный размер операнда быстрее по причинам, отличным от размера кода, например. div r8d намного быстрее на Intel до Ice Lake. И popcnt eax, r8d или imul eax, r8d быстрее на некоторых AMD. Также xor-zero на SMont. Преимущества использования 32-битных регистров / инструкций в x86-64 person user1752563; 08.07.2021

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: