Правильно ли работает ваш дизассемблер?

в 5:07, , рубрики: IA-32, LLVM, objdump, x86, x86_64, Блог компании Intel, дизассемблер, ненормальное программирование, префиксы, Процессоры, метки: , , , , , ,

Сегодня я хочу рассказать об одной интересной сложности декодирования/дизассемблирования IA-32 инструкций.

Перед прочтением этой статьи рекомендую обратиться в статье «Префиксы в системе команд IA-32», описывающей общую структуру IA-32 команды и существующие префиксы. В этой статье я подробнее расскажу про обязательные префиксы (англ. mandatory prefixes) и некоторые нюансы, связанные с ними.

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

Некоторые инструкции, такие как MULSS, MULSD и MULPD (инструкции векторного умножения) одинаковый опкод 0x0f 0x59, но различные обязательные префиксы (0xf2, 0xf3 и 0x66 соответственно). Появляется вопрос, а что же должно происходить, если коде инструкции присутствуют одновременно несколько таких префиксов? Наверно, логичнее было бы определять, что это за инструкция, по последнему префиксу. Но это не всегда так! Если последним префиксом является 0xf2 или 0xf3, то он считается обязательным, однако 0x66 является обязательным, только если префиксы 0xf2 и 0xf3 отсутствуют в кодировке данной инструкции. Примеры корректного вывода дизассемблера для этих инструкций можно найти в таблице:

Код инструкции Инструкция Обязательный префикс
66 f3 f2 0f 59 ff MULSD xmm7, xmm7 f2
66 f2 f3 0f 59 ff MULSS xmm7, xmm7 f3
66 0f 59 ff MULPD xmm7, xmm7 66
f2 66 0f 59 ff MULSD xmm7, xmm7 f2
0f 59 ff MULPS xmm7, xmm7 -

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

#include <stdio.h>

int main() {
    double a[2] = {2, 2}, b[2] = {0, 0};
    __asm__ __volatile__ (
         // Copy data from a to xmm7 register
         "movupd %1, %%xmm7n"
         //"mulsd %%xmm7, %%xmm7n"
         ".byte 0xf2, 0x66, 0x0f, 0x59, 0xffn"
         // Copy data from xmm7 register to b
         "movupd %%xmm7, %0n"
         :"=m"(*b)
         :"m"(*a)
         :
    ); 
    printf("%lf %lfn", b[0], b[1]);
    return 0;
}

Скомпилировав и запустив, его вы увидите следующее:

$ gcc -O0 -Wall mulsd.c
$ ./a.out
4.000000 2.000000

То есть умножился только первый элемент вектора, тогда как второй остался неизменным, что соответствует инструкции MULSD, а не MULPD.

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

Продукт, версия Результат Сообщенная мною ошибка
Wind River Simics, 4.8 успех -
XED успех -
objdump, 2.23 ошибка 16083
GNU GDB, 7.5 ошибка 16089
nasm, 2.09 ошибка 3392269
ODA, 0.2.0 ошибка Переписка по электронной почте
objconv, 2.31 ошибка Переписка по электронной почте
IDA, 6.4 (Evaluation Version) ошибка Переписка по электронной почте
llvm-objdump, 3.2 ошибка 17697

Следует отметить, что gdb и objdump входят в состав binutils и используют одну и ту же библиотеку для дизассемблирования. Один из разработчиков ODA – Anthony DeRosa – в ответ на мое сообщение об ошибке сказал, что они используют библиотеку libopcodes, входящую в состав binutils. То есть исправление в одном месте должно повлечь за собой корректировку как минимум трех продуктов сразу, но, к сожалению, ни кто из binutils мне пока что не ответил.

А правильно ли работает дизассемблер, которым пользуетесь вы?

Автор: yulyugin

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js