декодирование префикса инструкции x86 - PullRequest
0 голосов
/ 26 февраля 2019

В настоящее время я занимаюсь разработкой дизассемблера для x86_x64 CISC.У меня есть 2 вопроса относительно декодирования префиксной инструкции:

  1. Для следующего потока:

    \x9b\x9b\xd9\x30
    

    GCC и objdump выходы

    fstenv [eax]
    

    Таким образом, они сначала читают все префиксы (не более 15), а затем приступают к проверке правильной инструкции, используя последний префикс, прочитанный \x9b с \xd9, чтобы сделать его fstenv инструкцией.

    Capstone, с другой стороны, выводит

    wait
    wait
    fnstenv dword ptr [eax] 
    

    Теперь очевидно, что ошибочны предположения о том, что он помещает 2 wait инструкции, а не только 1. Но следует ли вообще ставить wait инструкции илиGCC и objdump находятся справа здесь для использования всех дополнительных \x9b префиксов для инструкции fstenv?

  2. Для следующего потока:

    \xf2\x66\x0f\x12\x00
    

    GCC и objdump output

    data16 movddup xmm0,QWORD PTR [eax]
    

    Таким образом, они размещают префиксы в определенном порядке, поэтому \x66 интерпретируется перед \xf2, и поэтому они все ещеиспользуя последний префикс, прочитайте \xf2, чтобы определить инструкцию movddup.Так они здесь для использования этой логики аранжировки префиксов или они ошибочны?

    Capstone с другой стороны выводит

    movlpd xmm0, qword ptr [eax]

    Такони не упорядочивают префиксы в любом порядке, а просто берут последний префикс, прочитанный \x66, чтобы определить инструкцию movlpd, которая в данном случае выглядит более логичной, чем то, что делали GCC и objdump.

Как процессор на самом деле интерпретирует эти потоки?

Ответы [ 2 ]

0 голосов
/ 27 февраля 2019

9B 9B D9 30 Capstone корректен, и objdump fstenv также в основном корректен.

fstenv не является реальной машинной инструкцией , этопсевдоинструкция для fwait + fnstenv.Обратите внимание, что машинный код для fnstenv, указанный в ручном вводе , равен D9 /6, тогда как fstenv добавляет 9B перед этим.

9B равен не префикс инструкции, это отдельная 1-байтовая инструкция, которая называется wait aka fwait.На оригинальном 8086 + 8087 это было необходимо, потому что 8087 был действительно отдельным сопроцессором. Как интерфейс 8086 взаимодействовал с сопроцессором 8087 FPU? .Смотрите комментарии под верхним ответом там;до 286 они не были достаточно тесно связаны между собой, чтобы основной ЦП знал, были ли ожидающие исключения FPU.

Я не уверен в деталях, но fnstsw на 8086/186 может прочитатьстарая версия слова состояния, для которой не установлены последние флаги из замаскированного исключения.Или, может быть, это имеет значение только для немаскированных исключений, для получения исключения FP из умножения или чего-то еще до инструкции fnst*.Согласно комментариям Стивена Китта, 286 и новее «проверяют свою строку TEST перед выполнением инструкции NPX», автоматически FWAITing.

И, конечно, процессоры со встроенными FPU не имеют проблем с точными исключениями FP и синхронным поведением, поэтомуfwait - это пустая трата места.


Capstone's wait / wait / fnstenv dword ptr [eax], таким образом, более явный, потому что с точки зрения процессора, это действительно 3 инструкции,(Как показывает ответ Андреаса, запись современных счетчиков производительности x86).

Objdump рассматривает две предшествующие fwait инструкции как часть единого fstenv.Было бы точнее расшифровать его как fwait;fstenv dword ptr [eax], поскольку руководство Intel только документирует fstenv как включающий в себя один код операции fwait.Но дополнительный fwait не имеет архитектурного эффекта.


Часть 2

Как показывает ответ Андреаса, f2 66 0f 12 00 декодируется как movddup (64-битное вещание) на реальномаппаратное обеспечение с бессмысленным префиксом 66 (размер данных операнда). objdump корректно, по крайней мере для этого процессора .

Документированная кодировка для movddup равна F2 0F 12, где F2 - обязательный префикс, а 0F -escape-байт.

Мы могли бы ожидать, что он будет декодироваться как 66 0F 12 /r MOVLPD с бессмысленным префиксом F2 REP, но это не так; замковый камень неправильный .Существуют правила для обязательных байтов префикса: порядок кодирования байтов префикса инструкции x86 , включая «префикс 66 игнорируется, если используется либо F2, либо F3».

Я не уверен на 100%эта последовательность гарантированно для декодирования как movddup на всех аппаратных средствах, если это просто так, как это происходит в семействе Intel Sandybridge.Как прокомментировал @fuz, существует обязательный порядок обязательных префиксов, и неправильное его использование дает неопределенное поведение (т. Е. Определенный ЦП может декодировать его во что угодно, особенно в некоторый будущий ЦП, где другая последовательность префиксов является обязательной для некоторых других инструкций.)1083 *

0 голосов
/ 26 февраля 2019

То, как ваш процессор на самом деле интерпретирует эти потоки, может быть сравнительно легко протестировано.


Для первого потока вы можете использовать мой инструмент nanoBench .Вы можете использовать команду

sudo ./nanoBench.sh -asm_init "mov RAX, R14" -asm ".byte 0x9b, 0x9b, 0xd9, 0x30".

Эта команда сначала устанавливает RAX в действительный адрес памяти, а затем запускает поток несколько раз.На моем Core i7-8700K я получаю следующий вывод (для счетчиков производительности с фиксированными функциями):

Instructions retired: 3.00
Core cycles: 73.00
Reference cycles: 62.70

Мы видим, что ЦП выполняет три инструкции, поэтому Capstone кажется правильным.


Вы можете проанализировать второй поток, используя режим отладки nanoBench:

sudo ./nanoBench.sh -unroll 1 -asm "mov RAX, R14; mov qword ptr [RAX], 1234; .byte 0xf2, 0x66, 0x0f, 0x12, 0x00" -debug.

Это - внутри gdb - сначала выполнить asm код, а затем создать ловушку точки останова.Теперь мы можем взглянуть на текущее значение регистра XMM0:

(gdb) p $xmm0.v2_int64
$1 = {1234, 1234}

Таким образом, старшее и младшее четырехсловое слово XMM0 теперь имеют то же значение, что и память по адресу RAX, что указывает на то, что ЦП выполнилmovddup инструкция.


Вы также можете анализировать второй поток без использования nanoBench.Для этого вы можете сохранить следующий код ассемблера в файле asm.s.

.intel_syntax noprefix

.global _start
_start:
    mov RAX, RSP
    mov qword ptr [RAX], 1234   
    .byte 0xf2, 0x66, 0x0f, 0x12, 0x00
    int 0x03 /* breakpoint trap */

Затем вы можете построить его с помощью

as asm.s -o asm.o
ld -s asm.o -o asm

Теперь вы можете анализировать его с помощью GDBиспользуя gdb ./asm:

(gdb) r
Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000000000400088 in ?? ()
(gdb) p $xmm0.v2_int64
$2 = {1234, 1234}
...