Об утилитах для тестирования ОС на надежность
PC Magazine/RE logo
(С) СК Пресс 1/96
PC Magazine, September 26, 1995, p. 251

Операционные системы: тестирование на надежность

Матт Петрек


Утилиты для тестирования операционных систем на устойчивость к ошибкам и ограничения.

В статье "Операционные системы: что дальше" в этом же номере журнала показана сравнительная оценка нескольких операционных систем по таким показателям, как устойчивость к возникающим ошибкам и наличие ограничений. Для анализа были отобраны следующие ОС: Microsoft Windows 3.1x, Microsoft Windows 95, IBM OS/2 Warp Connect 3.0 и Microsoft Windows NT Workstation 3.51. Инструментом для проведени экспериментов стал набор тестовых утилит, специально подготовленных автором этих строк. В предлагаемой статье будут подробно рассмотрены алгоритмы, использованные для этих утилит; попутно будут даны некоторые пояснения относительно архитектуры перечисленных ОС. Все эксперименты можно условно разделить на следующие четыре категории:

Разработка 16- и 32-разрядных программ для Windows велась на базе пакетов Microsoft Visual C++ 1.5 и Visual C++ 2.0 соответственно. Затем 32-разрядный программный код был преобразован для работы в среде OS/2 (с помощью Borland C++ for OS/2 2.0). Учитывая то, что у обоих интерфейсов - Win32 API и Warp API - общий прародитель, OS/2 1.3 API, почти все вызовы в Win32 имеют эквиваленты в OS/2. (Тем не менее часть программы BOMB32, отвечающей за выполнение эксперимента Overwrite System DLL, пришлось видоизменить, чтобы воспроизвести адекватное обращение к системному компоненту, специфичному именно для OS/2.) Ситуация, исследуема программой THRDTEST, характерна только для Windows 95, поэтому перенос на другие платформы не производили.

Если проводимые нами эксперименты вас заинтересовали и вы решили их повторить у себя, то можно обратиться к титульному листу локального архива тестовой лаборатории журнала PC Magazine в World-Wide Web (http://www.zdnet.com/~pcmag/pclabs/pclabs.htm) или к каталогу PC Labs форума ZiffNet Utilities/Tips (GO ZNT:TIPS через службу CompuServe) и загрузить оттуда исходный текст программ и сами исполнимые файлы.

УМЕЕТЕ ЛИ ВЫ ПАДАТЬ?

Программа BOMB16 предназначена для оценки устойчивости операционной системы к ошибкам в программах. Функционально она делится на три части, в каждой из которых воссоздается свой тип исключительной ситуации. В первом случае непосредственно генерируетс ошибка GPF (general protection fault - общее нарушение защиты). Значение сегмента кода используется как селектор для указателя данных. Ошибка генерируется при попытке записи по указанному адресу. Во всех рассмотренных ОС сразу после нажатия кнопки Common GP Fault в программе BOMB16 появлялось сообщение об ошибке. Оно информировало пользователя о попытке выполнения запрещенной операции. Все без исключени операционные системы прекращали работу содержащей ошибку программы, а остальные программы оставались никак этим событием не затронутыми.

Следующий тест программы BOMB16 - Overwrite Conventional Memory Below Start of Windows - воспроизводит ситуацию, когда младшие блоки основной памяти в отдельных версиях Windows оказывались незащищенными от "злонамеренных" действий со стороны 16-разрядных прикладных программ. Если в Windows 3.1x и Windows 95 использовать селектор __0000h, то первые 64 Кбайт обычной памяти становятся доступными любой 16-разрядной программе Windows. С помощью такого селектора в BOMB16 создается far-указатель, и по этому адресу производится запись заполненного нулями блока размером 64 Кбайт. Такая операция приводит к мгновенному краху Windows 3.1x и Windows 95. Однако Windows NT и OS/2 устояли; Windows NT корректно завершила тест без какого бы то ни было ущерба дл других работающих программ, а Warp просто прекратила сеанс Win-OS/2, где работала данная 16-разрядна программа.

Аналогичный эффект демонстрируется в последней "бомбочке" из ассортимента программы BOMB16. В тесте Overwrite System DLL производится обращение к сегменту данных любой основной 16-разрядной системной библиотеки DLL и последовательная запись нулей на всю ее длину. Для того чтобы обратиться к сегменту данных системной DLL, не требуется особого труда: вызов функции LoadLibrary дает возможность получить дескриптор HINSTANCE выбранной DLL, который фактически имеет то же самое значение, что и селектор сегмента данных этой DLL. Если программа BOMB16 затирает указанным образом модуль KRNL386.EXE, то крах Windows 3.1x и Windows 95 неизбежен. Если затираются сегменты данных модулей USER.EXE или GDI.EXE, то результат также катастрофический. В Windows NT и OS/2 код прикладных Win16-программ изолирован от остальной части системы. Поэтому подобного рода эксперимент приводил просто к закрытию сеанса работы 16-разрядной программы.

ОШИБКИ В 32-РАЗРЯДНЫХ ПРОГРАММАХ

Программа BOMB32 - это аналог BOMB16 для платформы Win32. Однако если в первоначальной версии ошибка GPF генерировалась за счет записи в сегмент кода, то в варианте BOMB32 аналогичные разрушения вызываются в тесте Common Page Fault путем записи на одну из своих собственных страниц с исполнимым кодом. В данном случае возникает ошибка обработки страниц, так как для защиты разделов памяти, предназначенных только для чтения, в операционных системах Win32 вместо сегментации используется страничный доступ. Все три 32-разрядные ОС отреагировали на подобную ситуацию простым сообщением об ошибке и прекратили выполнение "дефектной" прикладной программы.

Для проверки защиты от записи в разделы памяти в пределах до 1 Мбайт в программе BOMB32 используются два независимых теста: Overwrite Conventional Memory Below Start of Windows и Overwrite Conventional Memory Above Start of Windows. Windows 95 удалось справиться только с одним из них. В Windows 95 сегмент данных дл 32-разрядных программ представляет собой не "истинно плоский" (неструктурированный, flat) селектор, а расширяемый (в область младших адресов) сегмент. Размеры такого сегмента ограничены максимально допустимым смещением в область младших адресов, при котором доступ к данным не приводит к возникновению ошибки GPF. Максимально возможный размер смещения дл 32-разрядных сегментов составляет 0xFFFFFFFF. Порог дл такого сегмента Windows 95 выставляет по наименьшему адресу в ее 16-разрядном глобальном хипе, который располагается в основной памяти в пределах адресного пространства до 1 Мбайт. Используя именно такой способ для выделения памяти этому сегменту, Windows 95 защищает тем самым всю отводимую под DOS память, находящуюся в адресном пространстве ниже Windows. Таким образом, когда тест Overwrite Conventional Memory Below Start of Windows программы BOMB32 выполняется под управлением Windows 95, она без всяких проблем прекращает выполнение ошибочной программы. Windows NT и OS/2 реагируют аналогично.

Однако область основной памяти от точки начала глобального хипа и выше оказывается незащищенной от вторжений с помощью 32-разрядного расширяемого селектора. Когда в программе BOMB32 запускается тест Overwrite Conventional Memory Above Start of Windows, его задача - отыскать границу этого 32-разрядного сегмента данных, чтобы четко знать, начиная с какого места размещены данные Windows. Для вычислени граничного значения этого Win32-селектора данных используется малоизвестная функци GetThreadSelectorLimit().

Эта функция копирует содержимое точки входа таблицы локальных дескрипторов (Local Descriptor Table) в буфер размером 8 байт, а BOMB32 декодирует его поля. Если эта программа обнаружит, что рассматриваемый сегмент является расширяемым, то она рассчитывает границу этого дескриптора. При работе под управлением Windows 95 производится округление границы расширяемого селектора до адреса ближайшей страницы. После этого организуетс цикл, который начинает переписывать каждую последующую страницу в пределах адресного пространства до 1 Мбайт. Поскольку в такой ситуации младшие блоки глобального хипа портятся, это ведет систему к немедленному краху. Если же какая-либо из страниц оказалась помеченной только для чтения, то в цикле используется блок обработки исключительных ситуаций _try/_except. Он перехватывает подобные ошибки при обработке страниц и восстанавливает последующую работу цикла. В Windows NT Win32-селектор данных не является расширяемым сегментом; поэтому затирание страниц при работе программы BOMB32 начинается по смещению 0. Тем не менее для Windows NT и OS/2 все попытки затереть блоки младших адресов памяти ни к каким неприятностям не привели.

Алгоритм выполнения теста Overwrite System DLL в программе BOMB32 не отличается от ее 16-разрядного аналога. Сначала с помощью функции GetModuleHandle отыскивается начальный адрес системной DLL KERNEL32. Затем, используя значения элементов структур IMAGE_DOS_HEADER и IMAGE_NT_HEADERS, определение которых содержится в файле WINNT.H, BOMB32 определяет позицию и начинает просматривать раскладку в памяти модуля KERNEL32 в поисках раздела .data. Далее выполняется запись нулей на всю длину найденного раздела. Для Windows 95 последствия самые сокрушимые. Windows NT удается пройти испытания с честью, благодар тому что системные DLL, такие, как KERNEL32, не допускают совместного использования блоков памяти с прикладными программами. Когда тест Overwrite System DLL выполняется под управлением OS/2, производитс попытка затереть страницы модуля PMWIN.DLL с записываемым кодом. Это ведет к моментальному нарушению работоспособности системы.

ПОТОКИ И ЗАПАС СВОБОДНЫХ РЕСУРСОВ

Среди всех рассматриваемых в этой статье тестовых утилит наибольшей элегантностью отличается THRDTEST. Она отображает на экране количество работающих системных потоков и указывает, сколько системных ресурсов осталось свободно; при этом пользователь по собственному желанию может генерировать дополнительные потоки. Такая ситуация специфична только дл Windows 95, и поэтому на других 32-разрядных платформах она не воспроизводится. Причина заключается в том, что само понятие "свободных системных ресурсов" не имеет смысла применительно к Windows NT и OS/2: в них не используются 16-разрядные хипы системных ресурсов размером 64 Кбайт, поэтому какие-либо ограничения на количество одновременно работающих потоков теоретически не установлены. В Windows 3.1x концепция потоков, конечно же, отсутствует вообще.

Для того чтобы в системе Windows 95 узнать, сколько потоков организовано, THRDTEST просто считывает значение KERNEL\Threads из ключа HKEY_DYN_DATA\PerfStats\StatDat базы данных реестра. (Интересно отметить, что для получения аналогичного значения из Windows NT придется указывать совершенно другое название ключа реестра. А ведь в компании Microsoft утверждают, что для Windows NT и Windows 95 используется одинаковый интерфейс Win32 API! Однако подобного рода разночтения сулят немало дополнительных хлопот для программистов.)

А вот для того чтобы узнать, сколько свободных ресурсов осталось для хипов USER и GDI, придетс приложить определенные усилия. Правда, в Windows 95 все еще присутствует функция GetFreeSystemResources, однако она записана в 16-разрядном модуле USER.EXE. Учтите, что THRDTEST должна уметь создавать собственные потоки, поэтому данная программа должна быть 32-разрядной. А обращаться ей придется к 16-разрядной функции GetFreeSystemResources, поэтому данная операция будет сопряжена с определенными сложностями. Обычно в таких случаях прибегают к использованию особой короткой процедуры thunk, которая обеспечивает преобразование между 16- и 32-разрядным кодом. Согласно документации ОС Windows 95, программный код thunk-уровня сохраняетс в отдельных, специально созданных 16- и 32-разрядных динамических библиотеках DLL. Однако если обратить внимание, как работают процедуры из 32-разрядных системных библиотек DLL, то оказывается, что они умеют программно вызывать функции из 16-разрядных DLL без обращения к каким-либо другим промежуточным библиотекам. Поэтому не стоит возражать, если THRDTEST будет уметь делать то же самое.

Для того чтобы вызвать 16-разрядную функцию GetFreeSystemResources из Win32-кода, необходимо соблюсти два важных условия. Во-первых, THRDTEST должна получить 16-разрядный far-указатель на эту функцию. В данной программе это делается через обращение к недокументированным функциям LoadLibrary16, FreeLibrary16 и GetProcAddress16, содержащимся в KERNEL32.DLL. В обычной, поставляемой фирмой Microsoft библиотеке импорта модуля KERNEL32.LIB ссылки на данные функции отсутствуют. Однако можно создать новую библиотеку, используя файл DEF и программу LIB фирмы Microsoft. Теперь, пользуясь новой библиотекой импорта, компоновщик в состоянии разрешить все обращения из кода нашей прикладной программы к внешним функциям, записанным в модулях DLL.

Теперь, когда функция GetProcAddress16 передала в вызывающую программу 16-разрядный сегментированный адрес функции GetFreeSystemResources, необходимо учесть второе важное условие: просто так выполнить far-вызов функции по указанному адресу нельзя. Во-первых, наша прикладная программа заполняет "плоский" стек 32-разрядными значениями, и именно таким он будет попадать к 16-разрядной функции; а ведь она собираетс выбирать из него значения как из 16-разрядного. Во-вторых, при прямом вызове с адресацией 16:16 семафор Win16Mutex не выставляется. В результате нарушаетс условие, в соответствии с которым при вызове из Win32-кода в Win16-код, Win16Mutex должен быть выставлен, чтобы избежать попытки одновременного доступа к 16-разрядному коду со стороны другого потока.

Решить обе перечисленные проблемы можно, если функцию GetProcAddress16 вызывать через посредство недокументированной системной функцию QT_Thunk, экспортируемую из модуля KERNEL32.DLL Windows 95. Дл этого достаточно просто записать ее аргументы в стек, загрузить регистр EDX 16:16 адресом нужной функции и затем командой CALL вызвать функцию QT_Thunk. В результате программа THRDTEST сможет получить сведени о свободных системных ресурсах для хипов USER и GDI, не обращаясь к двум дополнительным библиотекам DLL thunk-уровня. Правда, программа THRDTEST может выполняться только под управлением Windows 95, потому что функция QT_Thunk определена только в этой ОС. Покажем листинг на ассемблере той части кода, где производится извлечение информации о свободных системных ресурсах через функцию QT_Thunk: __asm { push 2 // GFSR_USERRESOURCES mov edx, [pfnFreeSystemResources] call QT_Thunk movzx eax, ax mov [user_fsr], eax }

Наконец, последнее. С помощью программы THRDTEST можно генерировать собственные дополнительные потоки, имеющие либо не имеющие очередей для сообщений. Как отмечалось в статье "Операционные системы: что дальше" этого номера, использование потоков с выделенными очередями значительно истощает запас свободных системных ресурсов модуля USER, тогда как без их выделения последствия не настолько пагубны. Программа THRDTEST создает потоки, представляющие собой обыкновенные бесконечные циклы с вызовом функции GetLastError. Однако для того, чтобы у потока появилась очередь для сообщений, необходимо добавить в этот цикл обращения к функции GetFocus. Окно фокуса предоставляется в Windows 95 и Windows NT под каждый поток. Учтите, что, когда в Windows 95 создается новый поток, очередь для сообщений ему выделяется не сразу; она создается лишь после первого обращения к некоторой функции, например GetFocus, которая запрашивает информацию по этому конкретному ресурсу. Реальный дл Windows 95 максимум количества потоков, имеющих выделенную очередь для сообщений, составляет около 450.

СЕМАФОР WIN16MUTEX

Хотя присутствие семафора Win16Mutex не оказывает заметного влияния на производительность 32-разрядных прикладных программ, при использовании 16-разрядных задач могут возникать проблемы. Их причина - задержка с возвратом управления процессором. Для того чтобы убедиться в этом, была составлена программа (W16MUTEX.C). После компиляции были получены как 16-, так и 32-разрядный исполнимые модули (W16MTX16.EXE и W32MTX32.EXE). Алгоритм работы обеих версий следующий: создаются два небольших окна с показаниями часов, которые обновляются один раз в секунду. Если наличие Win16Mutex не будет играть существенной роли, то показания обоих часов будут изменяться одновременно.

Если теперь в одном из окон выбрать команду "Блокировка на 10 секунд", то функция этого окна начинает в течение 10 с непрерывно вызывать GetTickCount. В результате при работе с Windows 3.1x процессорное время для обработки второго окна не выделяется. Причина состоит в том, что работа этой ОС строится на принципе невытесняющей (кооперативной) многозадачности. Для того чтобы второе окно смогло "пробудиться", функция обработки первого окна должна уступить управление процессором операционной системе. Это делается при вызове таких функций, как GetMessage или PeekMessage. Поскольку в Windows 95 организаци выполнения нескольких Win16-программ строится на том же невытесняющем принципе, диспетчеризация протекает здесь аналогичным образом. При этом ОС требует, чтобы кажда 16-разрядная прикладная программа при передаче ей управления выставляла Win16Mutex во включенное состояние. Следовательно, 16-разрядная программа W16MTX16 будет вести себя абсолютно одинаково под управлением Windows 95, Windows 3.1x и Win-OS/2. Пока функция первого окна циклически выполняет одну и ту же операцию в течение 10 с, функция второго окна не может начать работать, так как для обращения к 16-разрядному коду ей требуется, чтобы проход через thunk-уровень был открыт. Однако для Windows NT эта уловка с легкостью обходится, поскольку для выполнения каждой 16-разрядной прикладной программы здесь выделяется отдельна виртуальная машина Windows.

Когда в тестировании участвует 32-разрядный вариант программы, обращение к команде "Блокировка на 10 секунд" проявляется лишь в работе первого окна. Обновление показаний часов на втором окне продолжаетс без задержек. Причина состоит в том, что Windows 95 разрешает выполнение Win32-программ без учета состояни Win16Mutex. Такие 32-разрядные задачи выставляют этот семафор только случае, когда они обращаются через thunk-уровень к 16-разрядным модулям DLL, скажем USER или GDI. Если говорить о Windows NT, то у нее, конечно же, нет Win16Mutex как такового. Поэтому блокировка управления из 32-разрядной программы не оказывает никакого воздействия на работу второго окна. Если же говорить об OS/2, то ее система ввода аналогична Windows 3.1x. Поэтому если некоторые прикладные программы задерживают передачу управления, то это будет сказываться на производительности других программ. К счастью, OS/2 умеет распознавать ситуации, когда какая-то программа монополизирует использование процессора, и в этом случае перераспределяет управление сама. Поэтому при выполнении W32MTX32, когда первое окно блокирует управление, работа второго замедляетс (но не прекращается).

ТЕСТ НА ПРЕДЕЛ КОЛИЧЕСТВА ГЕНЕРИРУЕМЫХ ОКОН

Аналогично программам для оценки Win16Mutex, при компиляции WNDTST16 и WNDTST32 использовался один и тот же исходный модуль (WNDTEST.C). Алгоритм работы обеих версий состоит в следующем: происходит непрерывна генерация оконных объектов класса "кнопка", до тех пор, пока либо не будет оброзовано 1600 экземпляров таких объектов, либо до получения кода ошибки в ответ на обращение к функции CreateWindow. По окончании сообщается, сколько действительно было создано окон. Хотя для Windows 95 теоретический максимум составляет 16 384, а для Windows NT - количество окон не ограничено, в обе версии программы WNDTEST практически останавливались при значении 1600. Причина состоит в том, что выполнение операций создания и последующего удаления при таком изобилии окон приводит к катастрофическому замедлению работы программы. Свою лепту в замедление вносят также обращения к PeekMessage, происходящие после каждого вызова функции CreateWindow. Если отказаться от этого, то кнопки перестанут отображаться на экране, и ход самой программы нельзя будет наблюдать. Если же замедление процесса все-таки чрезмерно, следует исправить значени MAX_X и MAX_Y в исходном тексте WNDTEST.C и перекомпилировать программу заново; в этом случае быстродействие возрастет.

Хотя при бета-тестировании Windows 95 появлялись неблагоприятные сообщения на этот счет, создание окна происходит здесь абсолютно без выделения какой-либо памяти в главном хипе модуля USER размером 64 Кбайт. Для того чтобы убедиться в этом, 16-разрядная программа WNDTST16.EXE обращается к функции GetFreeSystemResources в следующих точках: в начале работы, после окончания генерации окон и после удалени всех окон. По окончании все три показателя об остатке свободных системных ресурсов выводятся на экран, причем все значения должны совпадать. Поскольку 32-разрядный вариант этой программы (WNDTST32.EXE) предназначен также для Windows NT и OS/2, та часть кода, котора отвечает за оценку свободных системных ресурсов при работе с Windows 95, не используется; в других ОС она лишена смысла.

Результаты выполнения программы WNDTEST приведены в статье "Операционные системы: что дальше". Подвод итоги, можно сказать, что при работе 16-разрядной версии WNDTST16 под управлением Windows 3.1x или Win-OS/2 удается создать не более 800 окон. При работе Windows 95 и Windows NT можно достичь максимума - 1600 окон. Аналогичный результат, как у WNDTST16, получаетс при тестировании WNDTST32 под управлением Windows 95, Windows NT и OS/2.