Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #91, ИЮЛЬ 2006 г.

Экстремальная оптимизация

Крис Касперски

Хакер, номер #091, стр. 091-120-4


Инкрементная линковка обычно включается в режиме оптимизации, а в отладочном варианте  отсутствует. Сюрприз, да? При изменении ключей компиляции ассемблерные вставки изменяют свое поведение, причем безо всякого предупреждения!

Короче говоря, внешние функции из ассемблерных вставок лучше не вызывать, а если и вызывать, то очень осторожно.

Выделение памяти на стеке

На процессорах 8086/8088 существовала замечательная возможность — затолкать в стек аргумент-указатель с одновременным выделением памяти всего одной (!) однобайтовой (!) машинной командой PUSH ESP, которая сначала уменьшала значение ESP, а только потом заталкивала его в стек. То есть в стек попадало уже уменьшенное значение ESP, что способствовало трюкачеству.

Рассмотрим конкретный пример — функцию, одним из аргументов которой является указатель на переменную, принимающую возвращаемый результат: f(int a, word *x). Предельно компактный вызов (на 8086!) выглядел так:

push sp ; передаем указатель на x с одновременным выделением памяти под сам x

push si ; передаем переменную a

call f ; зовем функцию

Подвох в том, что переменная x возвращается к ячейке памяти, выделенной PUSH SP! То есть указатель на x указывает сам на себя, что хорошо видно в отладчике:

Начиная с 80286 логика работы инструкции PUSH ESP предательским образом изменилась, и теперь процессор помещает в стек такое значение регистра ESP, каким оно было до модификации (кстати, псевдокод команды PUSH, приведенный в руководстве Intel, содержит ошибку, из которой следует, что в стек помещается уменьшенное значение ESP, хотя на практике это не так!).

И пока программисты спорят, какое из двух решений идеологически более правильное, прежний код отказывается работать, потому что команда PUSH ESP вместо указателя, указывающего на себя, теперь заталкивает в стек указатель на следующее двойное слово!

Поэтому при переходе с 8086 на 286+ приходится добавлять «лишнюю» команду PUSH EAX, резервирующую ячейку на стеке, на которую будет указывать значение ESP, засланное в стек инструкцией PUSH ESP:

push eax ; выделяем память под переменную x (регистр может быть любым)

push esp ; передаем указатель на x как аргумент функции f

push esi ; передаем переменную a

call f ; зовем f

Несмотря на то, что 8086/8088 процессоры уже давно не встречаются в дикой природе (ну разве что в виде эмуляторов), многие программы, написанные под них, актуальны и сегодня. Это касается как уже откомпилированного машинного кода, так и различных ассемблерных библиотек, переносимых под современные процессоры. Одна из причин, по которой они могут не работать, — это различие в логике обработке команды PUSH ESP.

Назад на стр. 091-120-3  Содержание  Вперед на стр. 091-120-5