Windows для профессионалов


В 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 байтов) Нижняя часть стека {зарезервирована для перехвата переполнения стека)
Рис. 16-4. Так выглядит регион стека сразу после его создания под управлением Windows 98

Во-первых, размер региона на самом деле 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 байтов) Нижняя часть стека (зарезервирована для перехвата переполнения стека)
Рис. 16-5. Целиком заполненный регион стека потока в Windows 98


Содержание раздела