Экстремальная оптимизация Крис Касперски Хакер, номер #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. |