В Windows 98 стеки ведут
В Windows 98 стеки ведут себя почти так же, как и в Windows 2000. Но отличия все же есть.
На рис. 16-4 показано, как в Windows 98 может выглядеть регион стека (зарезервированный с адреса 0x00530000) размером 1 Мб.
Адрес | Размер | Состояние страницы |
0x00640000 |
16 страниц (65 536 байтов) | Верхняя часть стека (зарезервирована для перехвата обращений к несуществующей области стека) |
0x0063F000 | 1 страница (4096 байтов) | Переданная страница с атрибутом PAGE_READWRITE (задействованная область стека) |
0x0063E000 | 1 страница (4096 байтов) | Страница с атрибутом PAGE_NOACCESS (заменяет флаг PAGE_GUARD) |
0x00638000 | 6 страниц (24 576 байтов) | Страницы, зарезервированные для перехвата переполнения стека |
0x00637000 | 1 страница (4096 байтов) | Переданная страница с атрибутом PAGE_READWRITE (для совместимости с 16-разрядными компонентами) |
0x00540000 | 247 страниц (1 011 712 байтов) | Страницы, зарезервированные для дальнейшего расширения стека |
0x00530000 | 16 страниц (65 536 байтов) | Нижняя часть стека {зарезервирована для перехвата переполнения стека) |
Во-первых, размер региона на самом деле 1 Мб плюс 128 Кб, хотя мы хотели создать стек объемом всего 1 Мб. В Windows 98 система резервирует под стек на 128 Кб больше, чем было запрошено. Собственно стек располагается в середине этого региона, а по обеим его границам размещаются блоки по 64 Кб каждый.
Блок перед стеком предназначен для перехвата его переполнения, а блок после стека — для перехвата обращений к несуществующим областям стека. Чтобы понять, какая польза от последнего блока, рассмотрим такой фрагмент кода:
int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, PSTR pszCmdLine, int nGmdShow)
{
char szBuf[100];
szBuf[10000] - 0; // обращение к несуществующей области стека
return(0);
}
Когда выполняется оператор присвоения, происходит попытка обращения за конец стека потока. Разумеется, ни компилятор, ни компоновщик не уловят эту ошибку в приведенном фрагменте кода, но, если приложение работает под управлением Windows 98, выполнение этого оператора вызовет нарушение доступа.
Это одна из приятных особенностей Windows 98, отсутствующих в Windows 2000, в которой сразу за стеком потока может быть расположен другой регион. И если Вы случайно обратитесь за пределы стека, Вы можете испортить содержимое области памяти, принадлежащей другой части Вашего процесса, — система ничего не заметит.
Второе отличие: в стеке нет страниц с флагом атрибутов защиты PAGE_GUARD. Пocкoлькy Windows 98 такой флаг не поддерживает, при расширении стека потока она действует несколько иначе. Она помечает страницу переданной памяти, располагаемой под стеком, атрибутом PAGE_NOACCESS (на рис, 16-4 — по адресу 0х0063Е000). Когда поток обращается к этой странице, происходит нарушение доступа. Система перехватывает это исключение, меняет атрибут защиты страницы с PAGE_NOACCESS на PAGE_READWRITE и передает память новой "сторожевой" странице, размещаемой сразу за предыдущей.
Третье: обратите внимание на единственную страницу с атрибутом PAGE_READWRITE по адресу 0x00637000. Она создается для совместимости с 16-разрядной Win dows. Хотя Microsoft нигде нс говорит об этом, разработчики обнаружили, что первые 16 байтов cегмента стека 16-разрядной программы содержат информацию о ее стeке, локальной куче и локальной таблице атомарного доступа. Поскольку Win32 приложения в Windows 98 часто обращаются к 16-разрядным DLL и некоторые из этих DLL предполагают наличие тех самых 16 байтов в начале сегмента стека, Microsoft пришлось эмулировать подобные данные и в Windows 98. Когда 32-разрядный код обращается к 16-разрядному, Windows 98 отображает 16-битный селектор процессо ра на 32-разрядный стек и записывает в регистр сегмента стека (SS) такое значение, чтобы он указывал на страницу по адресу 0x00637000. И тогда 16-разрядный код, получив доступ к своим 16 байтам в начале сегмента стека, продолжает выполнение без всяких проблем
По мере роста стека потока, выполняемого под управлением Windows 98, блок памяти по адресу 0x0063F000 постепенно увеличивается, а сторожевая страница смещается вниз до тех пор, пока не будет достигнут предел в 1 Мб, после чего она исчезает так же, как и в Windows 2000.
Одновременно система смещает позицию страницы, предназначенной для совместимости с компонентами 16-разрядной Windows, и она, в конце концов, попадает в 64-килобайтовый блок, расположенный в начале региона стека. Поэтому целиком заполненный стек в Windows 98 выглядит так, как показано на рис. 16-5.
Адрес | Размер | Состояние страницы |
0x00640000 | 16 страниц (65 536 байтов) |
Верхняя часть стека (зарезервирована для перехвата обращений к несуществующей области стека) |
0x00540000 | 256 страниц (1 Мб) | Переданная страница с атрибутом PAGE_READWRITE (задействованная область стека) |
0x00539000 | 7 страниц (28 672 байта) | Страницы, зарезервированные для перехвата переполнения стека |
0x00538000 | 1 страница (4096 байтов) | Переданная страница с атрибутом PAGE_READWRITE (для совместимости с 16-разрядными компонентами) |
0x00530000 | 8 страниц (32 768 байтов) | Нижняя часть стека (зарезервирована для перехвата переполнения стека) |