Экстремальная оптимизация Крис Касперски Хакер, номер #091, стр. 091-120-5 Вообще же, динамическое выделение памяти посредством PUSH + фиктивный регистр — вполне законный примем, которым пользуются не только люди, но и компиляторы. Это намного компактнее, чем обращение к локальным/глобальным переменным, выделяемым классическим способом. Естественно, большие объемы памяти лучше всего выделять с помощью SUB ESP, XXh, но при этом следует помнить, как минимум, о двух вещах. Первое и главное — Windows-системы выделяют стековую память динамически, используя для этого специальную «сторожевую» страницу памяти (page guard). Как только к ней происходит обращение, система выделяет еще одну или несколько страниц памяти, перемещая сторожевую страницу наверх (в сторону меньших адресов памяти). При последовательном «росте» стека все работает нормально, но, если попытаться прыгнуть за сторожевую страницу, сразу же возникнет непредвиденное исключение (ведь никакой памяти по данному адресу еще нет) — и работа программы завершится в аварийном режиме. То есть, если у нас есть, к примеру, 1 Мб стекового пространства, то это еще не значит, что код SUB ESP, 10000h/MOV [ESP],EAX будет работать. Тут уж как повезет (или не повезет). Если ранее вызываемые функции выделяли стековую память планомерно, задвинув сторожевую страницу куда-то вглубь стекового пространства, то какие-то шансы у нас есть, но полагаться на них не стоит. Поэтому при выделении под локальные переменные более 4-х Кб необходимо выполнить цикл, последовательно обращающийся хотя бы к одной ячейке каждой из запрашиваемых страниц. Читать все ячейки необязательно, да и непроизводительно. Компиляторы делают это автоматически, а вот многие ассеблерщики о таком коварстве Windows зачастую даже и не подозревают, а потом упорно ищут бага в своей программе, не понимая, почему она не работает! Вот пример программы на Си, выделяющий 1 Мб памяти под локальные переменные и обращающийся к самой «дальней» ячейке: main() { char x[1024*1024]; // выделяем 1 Мб стековой памяти return *x; // обращаемся к наиболее «дальней» стековой ячейке } .text:00401000 _main proc near .text:00401000 mov eax, 100000h .text:00401005 call __alloca_probe .text:0040100A movsx eax, byte ptr [esp] .text:00401012 add esp, 100000h .text:00401018 retn .text:00401018 _main endp ... .text:00401020 __alloca_probe proc near ; CODE XREF: _main+5?p .text:00401020 .text:00401020 arg_0 = dword ptr 8 .text:00401020 .text:00401020 push ecx .text:00401021 cmp eax, 1000h .text:00401026 lea ecx, [esp+arg_0] .text:0040102A jb short loc_401040 .text:0040102C .text:0040102C loc_40102C: ; CODE XREF: __alloca_probe+1E?j .text:0040102C sub ecx, 1000h .text:00401032 sub eax, 1000h .text:00401037 test [ecx], eax .text:00401039 cmp eax, 1000h .text:0040103E jnb short loc_40102C .text:00401040 .text:00401040 loc_401040: ; CODE XREF: __alloca_probe+A?j .text:00401040 sub ecx, eax .text:00401042 mov eax, esp .text:00401044 test [ecx], eax .text:00401046 mov esp, ecx .text:00401048 mov ecx, [eax] |