Ассемблерные извращения Крис Касперски Хакер, номер #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. |