...

пятница, 15 ноября 2013 г.

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

Сегодня я хочу рассказать об одной интересной сложности декодирования/дизассемблирования 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, %%xmm7\n"
//"mulsd %%xmm7, %%xmm7\n"
".byte 0xf2, 0x66, 0x0f, 0x59, 0xff\n"
// Copy data from xmm7 register to b
"movupd %%xmm7, %0\n"
:"=m"(*b)
:"m"(*a)
:
);
printf("%lf %lf\n", b[0], b[1]);
return 0;
}


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



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


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


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




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

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


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Комментариев нет:

Отправить комментарий