Как один байт кода операции декодирует разные инструкции в зависимости от поля регистра/кода операции? Что это такое?

Как я могу определить, во что будет преобразован массив байтов в машинном коде?

Я понимаю, что если я вижу 0f в начале, это 2-байтовая инструкция, но я вижу другие префиксы и в некоторой разборке в моем отладчике x64 я вижу странные взаимодействия, такие как 48 83 C4 38, и я вижу в ссылке на код операции, что 48 говорит операнд 64 байта.

Но 83 говорит, что это может быть 7 разных инструкций в зависимости от поля, называемого "поле регистрации/кода операции"... что?

Может кто-нибудь объяснить логику того, как процессор использует эти байты для определения:

  1. Какая инструкция запущена
  2. Какие регистры и/или адреса использует инструкция (если есть)

person bcvdgfdag fewafdsaf    schedule 25.12.2018    source источник
comment
в основном дубликат Как читать нотацию Intel Opcode, но ответы там не разбирают это подробно.   -  person Peter Cordes    schedule 25.12.2018
comment
Как интерпретировать код операции вручную? охватывает декодирование ModRM. что делает код операции FF350E204000? является еще одним дубликатом этого, используя FF /6 push r/m64 в качестве примера. Расшифровка инструкций OpCode x86 является еще одним дубликатом. Все это я нашел в гугле на site:stackoverflow.com x86-64 register/opcode field   -  person Peter Cordes    schedule 25.12.2018
comment
Внутренности некоторых процессоров имеют эквивалент нескольких таблиц поиска для обработки кода операции, поля r/m, префикса rex и т. д.   -  person rcgldr    schedule 25.12.2018
comment
@peter cordes После некоторого обучения я пришел к пониманию, что мой вопрос на самом деле на 100% дублирует Как читать нотацию Intel Opcode, я на самом деле не настолько уважаю stackoverflow, чтобы смотреть на то, что я должен делать с этого момента, но, поскольку я вижу, что вы часто и, вероятно, увидите это, я прошу вашего совета о том, что я должен делать ( например, мне пометить этот вопрос как дубликат и закрыть его или что?)   -  person bcvdgfdag fewafdsaf    schedule 27.12.2018
comment
@bcvdgfdagfewafdsaf: я перенесу свой ответ туда и закрою его как дубликат. Спасибо, что сообщили мне, что вы согласны с тем, что это дубликат, вместо того, чтобы расширить этот вопрос, чтобы охватить больше того, что ответил old_timer.   -  person Peter Cordes    schedule 27.12.2018


Ответы (3)


0x48 — это префикс REX с полем W, установленным в 1, что подразумевает 64-разрядный размер операнда. (не 64 байта).

Многие коды операций для немедленных версий инструкций, включая 83, используют 3-битное поле /r в байте ModR/M как 3 дополнительных бита кода операции. Руководство Intel vol.2 документирует это, и таблица кодов операций в приложении, я думаю, включает это.

Вот почему большинство немедленных инструкций оригинального 8086, таких как and r/m, imm, по-прежнему допускают только 2 операнда, в отличие от shrd eax, edx, 4 или imul edx, [rdi], 12345, где оба поля ModRM используются для кодирования операндов, а также непосредственного операнда, подразумеваемого кодом операции. SHRD/SHLD и были добавлены с 386, а imul-immediate был добавлен с 186. Может быть, жаль, что копирование-и-И (and eax, edx, 0xf) не кодируется, но, по крайней мере, x86 может использовать LEA для копирования-и-добавления/подписки.


Собственные документы каждой инструкции, например. add (html-выдержка из руководства vol2), показывает такие кодировки, как
REX.W + 83 /0 ib для ADD r/m64, imm8, что у вас есть.

схема битовых полей ModRM с wiki.osdev.org

  7                           0
+---+---+---+---+---+---+---+---+
|  mod  |    reg    |     rm    |
+---+---+---+---+---+---+---+---+

0xc4 = 0b11000100, поэтому поле reg = 0. Таким образом, наш код операции — 83 /0 в нотации Intel.

Остальные поля ModRM:

  • mode = 0b11, поэтому поле rm кодирует регистровый операнд, а не базовый регистр для режима адресации.
  • гм = 0b100. регистр №4 = SPL/SP/ESP/RSP. (В данном случае RSP, потому что это 64-битный размер операнда). См. руководство Intel или https://wiki.osdev.org/X86-64_Instruction_Encoding#Registers для таблиц.

Итак инструкция add rsp, 0x38

ndisasm -b64 соглашается:

$ cat > foo.asm
db 0x48, 0x83, 0xC4, 0x38
$ nasm foo.asm     # create a flat binary with those bytes, not an object file
$ ndisasm -b64 foo
00000000  4883C438          add rsp,byte +0x38
person Peter Cordes    schedule 25.12.2018
comment
Вы действительно прошли лишнюю милю! +1 - person kabanus; 25.12.2018
comment
Повторно опубликовано с небольшим расширением вводного раздела Как читать нотацию Intel Opcode, и этот вопрос закрыт как дубликат. Думаю, я оставлю этот ответ здесь, а не удалю, потому что он все еще отвечает на этот вопрос. - person Peter Cordes; 30.12.2018

Это зависит от конкретной архитектуры, не только от x86-64, но и от фактического поставщика микросхем. Вы можете проверить, например, руководство Intel для разработчиков архитектурного ПО.

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

Формат архитектуры Intel

взято из вышеуказанного руководства. Это изменится, если вы, например, используете ARM.

Это то, что люди могут потратить годы на изучение, чтобы иметь возможность «бегло читать» байт-код, поэтому просто беглый просмотр может дать вам только общее представление о синтаксисе или хороший ресурс для поиска конкретной вещи.

person kabanus    schedule 25.12.2018
comment
Это бесполезно, это не дало мне никакой новой информации и фактически не ответило на мои вопросы. - person bcvdgfdag fewafdsaf; 25.12.2018
comment
@bcvdgfdagfewafdsaf Так и есть! См. разработку байта ModR/M, где биты 5–3 помечены как «Reg/Opcode», т. е. это поле может быть либо регистровым операндом, либо другими 3 битами кода операции. - person fuz; 26.12.2018

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

Так работал x86 и другой машинный код той эпохи, в частности, наборы инструкций, из которых он был непосредственно получен.

Во-первых, и это самое главное, если вы просто возьмете все байты программы и прыгнете в середину, это не будет иметь никакого смысла, очень легко встать не с той ноги. " что это такое? Процессор запускается и продолжает работу на основе правил набора инструкций, процессор довольно глуп, он следует правилам, определенным или, по крайней мере, задокументированным в руководствах по процессору. Пока программист и инструменты создали правильно построенную программу, она не потеряется, если это произойдет, это вина программиста/инструментов, а не процессора. Процессор начнет декодировать байт кода операции как байт кода операции. Этот байт может быть целой инструкцией или просто частью, зависящей от конкретного байта. Если дробь, то первый байт плюс следующий за ним байт могут определять всю инструкцию или быть дробью.

CISC, в частности, сами коды операций и частично следующие байты могут содержать или не содержать биты, которые означают что-то важное. В RISC, таких как mips, arm или другие, 0000 в конкретном Please означает регистр 0, 0001 означает регистр 1 и так далее. Но в некоторых, если не во многих инструкциях CISC, нет ничего, что отличало бы регистр x от регистра y, регистр a от регистра b. Весь код операции нужно было просмотреть в таблице, чтобы понять, что он означает.

x86 - это набор инструкций переменной длины, некоторые инструкции состоят из одного байта, без других операндов, другим требуется больше байтов, чем, возможно, сразу после этого. Хотите переместить непосредственное значение 0x12345678 в регистр EAX, не глядя ни на какую документацию, в которой говорится, что это либо 5-, либо 6-байтовая инструкция, либо код операции, который говорит, что загружать немедленно в ax, либо байт, который говорит, что загружать немедленно, и другой байт, который говорит это топор, то четыре байта непосредственного.

mov eax,0x12345678
mov ebx,0x12345678
mov ecx,0x12345678
mov edx,0x12345678

Disassembly of section .text:

00000000 <.text>:
   0:   b8 78 56 34 12          mov    eax,0x12345678
   5:   bb 78 56 34 12          mov    ebx,0x12345678
   a:   b9 78 56 34 12          mov    ecx,0x12345678
   f:   ba 78 56 34 12          mov    edx,0x12345678

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

Возможно, вы слишком усложняете это, и, к сожалению, Intel и другие документы по x86 не так хороши, как у некоторых других поставщиков. Но на самом деле это просто блок-схема, довольно легко декодируемая, первый байт сообщает вам, ищете ли вы другой байт или нет по его определению, следующий байт указывает, нужно ли вам искать дальше и так далее. Вы не декодируете x86, как вы декодируете mips или arm или другие, которые разработаны по-другому. Все они имеют декодирование, в котором говорится: посмотрите на эти биты и определите инструкцию или определите, нужно ли мне больше битов, но x86 делает это одним способом, mips — другим, arm — другим. У каждого есть свои плюсы и минусы.

CISC, как и x86, хотя и является скорее блок-схемой, первый байт говорит вам перейти на страницу X, на которой либо есть полный ответ, либо он говорит, что нужно получить следующий байт, и на его основе перейти на страницу Y в приложении X.

В некоторых домах есть один жилец, адрес/местоположение приводит вас к одному человеку. У некоторых их несколько, и как только вы доберетесь до дома по адресу, вам понадобится дополнительная информация, чтобы определить, какой человек или домашнее животное вас интересует. Первая часть информации, почтовый адрес, соответствует стандарту, но информация для изоляции человека/животного в этом доме соответствует стандарту для этого дома. Первый байт инструкции — это код операции. Но на основе кода операции, если есть дополнительные байты, то эти байты зависят от кода операции, как мы видели выше. b8 78 56 34 12 для 0xB8 второй байт является частью непосредственного значения. Есть много, вы можете посмотреть, где второй байт является дальнейшей расшифровкой инструкции.

mov eax,eax
mov eax,ebx
mov eax,ecx
mov eax,edx


   0:   89 c0                   mov eax,eax
   2:   89 d8                   mov eax,ebx
   4:   89 c8                   mov eax,ecx
   6:   89 d0                   mov eax,edx

для кода операции 0x89 второй байт в этих случаях не является данными, но определяет инструкцию.

Это правда, что декодирование второго байта не является уникальным только для этого кода операции, многие инструкции будут использовать одно и то же декодирование этих битов, например, для определения ah, al, ax, eax, bh, bl, bx... и т. д. И это задокументировано в документации Intel, а также в бесчисленном количестве других книг и веб-сайтов.

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

x86 — это в значительной степени последний набор инструкций, который вы хотите изучить, наличие одного не является уважительной причиной, для каждого x86, который у вас есть, внутри этой коробки есть много процессоров, отличных от x86, плюс на каждый x86, который у вас есть, у вас есть довольно много , десятки устройств, отличных от x86. И если целью является образование и обучение, вы все равно хотите начать с симулятора, который значительно повысит ваши шансы на успех, а аварии не так болезненны. Для начала есть гораздо лучшие наборы инструкций, такие как msp430 и pdp11, что явно повлияло на него. рука, большой палец, позже углубившись в мипс и его нюансы, то из 8 бит я бы не начал с х86 я бы пошел с чем-то еще 6502 или другими. Тогда, может быть, если любопытно, 8088/8086 с использованием эмулятора и старых документов в Интернете на обратном пути, то, наконец, x86, как в 80386, 80486 и x86-64. Погружение в x86-64 сначала должно быть связано с болью, действительно для людей, злоупотребляющих собой. Если вы все еще чувствуете, что вам нужно это сделать, менее болезненный путь этого болезненного пути - начать с 8088/8086, используя старые руководства и dosbox или bochs или ряд других эмуляторов. Как только вы получите основу, то, что они добавили на шаге к 32-битной, а затем 64-битной версии, может иметь больше смысла, и вас не должно смущать огромное количество защиты, добавляемой с течением времени, вы можете начать с чистого листа.

Дизассемблирование наборов инструкций переменной длины — это огромная проблема, которую нужно решить, и никто не решил ее, потому что они не могут полностью. Невозможно. Раньше я изучал все новые наборы инструкций, начиная с дизассемблера. В эти дни я, вероятно, вместо этого сделал бы симулятор. Единственный способ иметь хотя бы половину шанса на успех — начать с действующей точки входа. И декодировать в порядке выполнения, а не линейно через бинарник. Это только раскроет часть кода. Остальное, если оно есть, основано на данных, и вы можете попытаться подражать, но это тоже не будет идеальным. Во-первых, данные во время дизассемблирования могут измениться во время выполнения. Вы даже можете эмулировать программу и запускать ее в течение нескольких дней/недель, чтобы обнаружить различные значения данных в разных местах, на которые смотрит конкретная инструкция, и все еще не знать всех возможностей. Так что некоторые дизассемблеры просто ошибаются, но показывают это вам так, как будто это правильно, а другие правильно, просто говорят, что я не знаю, что это такое...

сегодня подавляющее большинство двоичных файлов скомпилировано, поэтому пути данных в основном в порядке и полны. Но возьми немного ромов со времен стендап-видеоигр, например, астероиды. вы увидите что-то похожее на этот псевдокод:

a = 0
if(a == 0) goto somewhere
b = 7

мы можем легко увидеть, что условный переход на самом деле является безусловным, дизассемблирование нам нужно было бы рассматривать инструкцию после условного перехода как возможный путь выполнения. Но то, что вы обнаружите в этом роме, это то, что следующая инструкция является фактическими данными, а не инструкцией. 1 представляет байт кода операции 2 и 3 представляют дополнительные байты для этой инструкции, больше псевдокода

1 a = 0;
2
1 if(a == 0) goto somewhere
2
3
1 b = 7.
2
3
1
2
3

Но когда мы продолжаем декодировать все предположительно допустимые пути выполнения, мы обнаруживаем, что

1 b = 7.
2 
3  <--- is a branch destination
1
2
3

это байт кода операции, а не последние байты в инструкции, так что теперь есть конфликт, и хороший дизассемблер скажет вам об этом. Затем человек должен изучить эти пути, чтобы определить, какой из них был действительным: путь a = 0.... или путь b = 7. Предполагая, что a = 0, а последующая условная ветвь была частью действительного дизассемблирования, тогда может показаться, что это действительно безусловная ветвь, и есть пара байтов данных или заполнение или что-то еще, а затем следует какой-то код. Это могло быть преднамеренным, как это было более распространено в то время, чтобы намеренно сбросить дизассемблер, или это могло быть результатом ручного взлома бинарного файла, а не пересборки всего проекта и записи ромов. (прочитайте, я думаю, что это был Defender, взламывавший двоичный файл в гостиничном номере в ночь перед выставкой, а затем на следующий день). Эти байты могли быть другими инструкциями, которые были изменены вручную, чтобы обойти ошибку. 6502 является хорошей отправной точкой, и в некоторых игровых ромах, если вы хотите написать дизассемблер, не так много инструкций, как, скажем, в z80 или 8088/8086, которые с помощью вторых байтов умножили первоначальный потенциал 256 инструкций на более длинный. список. Ранний PIC или msp430 был бы намного проще в качестве первого дизассемблера, поскольку у них всего дюжина или две инструкции. Msp430 имеет отлаженный/поддерживаемый бэкэнд gnu (llvm не отлаживается и не поддерживается, поэтому избегайте его), так что вам будет легко получить доступ к инструментам, если вас интересуют наборы инструкций для изучения.

Когда у вас есть фиксированная длина инструкции, такая как mips, когда 16-битная не используется, или активация, когда 16-битный большой палец не используется. (И в наборе инструкций сказано, что инструкции должны быть выровнены (не risc-v)) Вы можете линейно дизассемблировать через память, некоторые из «инструкций», которые вы найдете, не имеют смысла или не определены, но вы просто перемалываете, человек позже увидит их как данные, а не инструкции, но те, которые являются инструкциями, будут иметь смысл. К сожалению, mips и arm имеют вторичные наборы инструкций, которые декодируются совершенно по-разному и имеют разные правила, поэтому вы также не можете просто дизассемблировать двоичный файл arm, для чего-то, сгенерированного компилятором сегодня, вам также нужно делать это в порядке выполнения, у вас гораздо больше шансов получить большая часть инструкций декодирована, но будут некоторые таблицы переходов, которые сведут на нет все ваши усилия, оставив куски кода неразборчивыми должным образом.

Итак, хотя и многословный, краткий ответ — доверяйте дизассемблеру настолько, насколько вы можете его бросить. И инструкции довольно легко расшифровать, если перейти в порядке выполнения от заведомо верной точки входа и посмотреть документацию на процессор.

person old_timer    schedule 25.12.2018
comment
Вы начали писать эту обличительную речь до того, как я отредактировал вопрос, чтобы сделать его более конкретным и подчеркнуть, что он спрашивал конкретно о поле кода операции / r? Старый заголовок был мусором, но по-прежнему выглядел как довольно конкретный вопрос. (И я не вижу ответа на него в этой бессвязной тираде о x86 в целом.) Ничто в вопросе не упоминало проблему поиска границ инструкций с помощью статического анализа или какие-либо другие проблемы, вызванные набором инструкций переменной длины. . Это просто выглядит как тирада без ответа. - person Peter Cordes; 26.12.2018
comment
Хотя возможно, что биты этих байтов могут быть декодированы непосредственно в один из четырех регистров, это маловероятно, поскольку эти наборы инструкций были разработаны иначе. В отличие от 8080 и более ранних ISA только с парой регистров и разными версии одной и той же инструкции с другим неявным регистром, когда x86 имеет однобайтовый код операции, который включает явный регистр, это младшие 3 бита кода операции, которые кодируют регистр. например mov r32, imm32 is b8 + rd, где rd — код регистра назначения. - person Peter Cordes; 26.12.2018
comment
Вы получаете от меня голос только за то, что написали эту стену текста. - person fuz; 26.12.2018
comment
@Peter Cordes на данный момент это лучший ответ, и он отвечает на то, о чем я изначально спрашивал, я еще не дочитал его из-за его длины, но он точно указывает, что я ищу, как анализируется байт-код. Я не знал точно, как задать вопрос, когда я изначально написал его, к сожалению, это проблема, с которой я сталкиваюсь со многими вопросами о stackoverflow (то есть, не зная, как правильно написать свой вопрос). Но мой первоначальный вопрос может быть лучше сформулировать как байт-код разбирается на коды операций правильными дизассемблерами - person bcvdgfdag fewafdsaf; 26.12.2018
comment
@bcvdgfdagfewafdsaf: вы прокомментировали ответ \@kabanus, что он не дал вам никакой новой информации, поэтому я предположил, что вы уже поняли основы формата инструкций x86-64, показанного на этой диаграмме, и просто спрашивали об использовании поля /r в качестве дополнительного биты кода операции, если вы уже дошли до поиска байтов кода операции. Это единственное, на что был направлен ваш вопрос. Так что да, если этот ответ был полезен, тогда отлично, и да, вопрос, который вы написали, не ясно выразил то, что вы хотели знать. (Это было бы дублированием других вопросов, объясняющих декодирование длины инструкции.) - person Peter Cordes; 26.12.2018