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

Ассемблерные извращения

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

Хакер, номер #096, стр. 096-140-2


.code

MOV EBP, ESP

MOV ESP, offset func_arg + 4

JMP my_func

here:

MOV ESP, EBP

.data

func_arg DD offset here, 696h, 999h, 669h

Кстати говоря, ни адрес возврата, ни аргументы функции вовсе не обязаны быть константами, известными на стадии компиляции, и они могут свободно модифицироваться в любой момент командами MOV и STOS. Также если аргументы хранятся в локальных переменных, то засылать их в стек не обязательно! Достаточно лишь скорректировать регистр ESP таким образом, чтобы переменные-аргументы оказались на вершине. Естественно, порядок размещения аргументов в памяти должен совпадать с порядком передачи аргументов, но на ассемблере, в отличие от языков высокого уровня, мы можем самостоятельно выбирать нужную схему размещения переменных, так что это не проблема.

Еще одна тонкость: «оптимизированный» вариант обладает всеми формальными атрибутами «передачи по значению», но де-факто аргументы передаются по ссылке. То есть совсем наоборот! Аргументы передаются по значению, но это значение после выхода из функции сохраняет свое состояние, ведет себя так, как будто бы оно было передано по ссылке. Иногда это экономит такты процессора и сокращает потребности в памяти, но иногда ведет к трудноуловимым ошибкам, лишний раз подтверждая тезис, что нет в мире совершенства.

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

Повторное использование кадра стека

При входе внутрь функции большое количество локальных переменных инициализируется константами или значениями, инвариантными по отношению к самой функции (то есть другими переменными, чаще всего глобальными). Причем инициализация обычно осуществляется командой MOV, а для обслуживания строковых переменных приходится прибегать к REP MOVSB. Все это медленно, громоздко и непроизводительно.

А почему бы не подготовить кадр стека еще на стадии трансляции?! В грубом приближении это будет выглядеть так:

Вызов функции с заранее подготовленными аргументами и локальными переменными

.code

MOV EBP, ESP

MOV ESP, offset func_arg

JMP my_func

MOV ESP, EBP

…

my_func:

MOV EBP,ESP

SUB ESP, offset func_locals - offset return_address

…

…

…

MOV ESP,EBP

RETN

.data

func_locals:

var_1 DB 66h

var_2 DD offset globalFlag

var_s DB "hello",0

var_x DD 0

var_y DD 0

return_address:

DD 00h

func_args:

DD 696h, 999h, 669h

В некоторых случаях достигается просто колоссальное ускорение, однако тут есть один подводный камень — при повторном вызове функции все «инициализированные» переменные сохраняют свои текущие значения и наступает полный облом. Фактически мы добились того, что превратили локальные стековые переменные в статические! Бесспорно, иногда это очень хорошо, но в 90% случаев нам нужно совсем другое. Вот и устроим себе это другое с помощью REP MOVS! Подготавливаем инициализированные локальные переменные на стадии создания ассемблерной программы, а затем копируем их в кадр функции при его открытии. Это намного быстрее, чем инициализировать каждую локальную переменную по отдельности командой MOV.

Назад на стр. 096-140-1  Содержание  Вперед на стр. 096-140-3