/
Текст
От лучшего автора, сотрудничающего
с Newsweek, PC Week и Microsoft Systems Journal
UMIMJilM
WINDOWS 95
Руководство разработчика, посвященное
исследованию основ Windows™ 95 -‘Chicago”
Эндрю
ШУЛЬМАН
НЕОФИЦИАЛЬНАЯ
WINDOWS 95
"Эндрю Шульман не пошел на поводу у Microsoft. Вместо этого он показал,
как э действительности работает Windows 95. Четко представляя, что же
находится э коробке Windows, вы сможете создавать лучшие приложения."
— Ричард Смит, президент Phar Lap Software
Windows 95 может
поразить вас.
Столь
нетрадиционно
представить
новую
операционную
систему может
только Эцдрю Шульман.
В попытке предугадать удар Windows 95
замерла вся индустрия.
Книга Неофициальная Windows 95
представляет собой первый критический и
реалистический взгляд на "Чикаго" и его
значение для индустрии программного
обеспечения и собственно программистов.
Вы сможете по достоинству оценить этот
новый продукт и его влияние на ваш
личный" способ создание программного
обеспечения.
Специальный раздел "Обновление индустрии"
позволит вам охватить проблему в целом и
подготовиться к восприятию Windows 95.
Вы должны четко представить, что же стоит
за Windows 95 и почему ее влияние на
индустрию столь ощутимо.
Ясность видения проблемы
и оригинальность
методов исследования
помогли
Эцдрю Шульману
завоевать признание
в глазах многснисленной
аудитории разработчиков
программного обеспечения
для Windows. Он писал
для Newsweek, PC Week и Microsoft
Systems Journal, а сейчас ведет
колонку "Undocumented Comer"
в Dr Bobb’s Journal
• ДвЯстштално m Mfrtiom 95 пересмеяна от нечем
до юнце?
• ДеДствитемно jm MMom 95 не нуж» DOS?
• ДвДстшгално м ММом 95 мпвтса потостыо
|* *гатвк>оавиюй операциомп* системой?
1 ДеДствитемно тм 32-битовое цфо совериежо
независимо от 16-бнтовоги?
'Умерлаm DOS?
1 Не окажется at WHom 95 просто НКйГ?
• ДеДстмттно м ММот 95 радеамио оимчвеня
отМгхкмвЗ.И?
1 Почему драгеры VxD гфвара^пт Mndom
о полноцмфо операцкмтую систему?
1 Почему ражим VB6 яамется ражпцдностъе
эанеацежого рехша?
Ди
мгерссующлхм
1|Г - I-
nluuWI
м|фогрмафоммем
Предисловие редактора
Книга о Windows 95
и не только
Программисты — тоже люди, и они хотят достаточно
хорошо прочувствовать операционную систему, с которой
работают.
— Эндрю Шульман, Неофициальная Windows 95
Windows 95 (она же “Chicago”)— безусловный феномен в индустрии программного обес-
печения PC. Задолго до появления полноценной коммерческой версии о ней уже напи-
саны десятки, быть может, даже сотни книг. Не остались и мы в стороне. Правда, книги
все больше переводные, но зато десяток их, а то и больше, уже появился на книжных полках.
Благодаря совместным усилиям киевского издательства “Диалектика” и московской фирмы “ICE”
вашему вниманию предлагается еще одна книга известного американского (компьютерного) журна-
листа и автора нескольких книг Эндрю Шульмана Неофициальная Windows 95.
Очевиден вопрос — зачем еще одна-то?
Американское название книги Unauthorized Windows 95. После долгих прений в русском изда-
нии слово Unauthorized решено трактовать как Неофициальная. Тем самым подчеркивается уни-
кальность данного издания: пожалуй, это первая книга о Windows 95, в которой взгляд на ново-
явленную операционную систему расходится с официальной версией Microsoft.
Как отделить рациональное зерно от всей шумихи вокруг Windows 95? Как не увязнуть в таком
потоке информации? (Недавно один мой знакомый программист, ознакомившись с очередным
компакт-диском Microsoft Developers Network, с горечью сказал: “Да я почти ничего здесь уже не
понимаю!” Сам Эндрю Шульман в одной из глав, насчитав И уровней дисковой подсистемы в
Windows 95, признался: “Лично моя голова не способна все это вместить”.) Где здесь правда, где
полуправда, где всего лишь рекламный трюк? Должны ли мы слепо верить Microsoft, если, как
справедливо утверждает Эндрю Шульман, “автор — это как раз тот человек, от которого требовать
объективное мнение о работе стоит в последнюю очередь”?
• Действительно ли Windows 95 переписана от начала до конца?
• Действительно ли Windows 95 не нуждается в DOS?
• Действительно ли Windows 95 является полностью интегрированной операционной системой?
• Действительно ли 32-битовое ядро совершенно независимо от 16-битового?
• Умерла ли DOS?
• Не окажется ли Windows 95 просто Win32s?
• Действительно ли Windows 95 радикально отличается от Windows 3.11?
• Почему драйверы VxD превращают Windows в полноценную операционную систему?
• Почему режим V86 является разновидностью защищенного режима?
Предисловие редактора
5
Вполне возможно, Шульман — далеко не первый, в чью душу закрались подобные сомнения.
Зато он, судя по всему, первый, кто предпринял попытку (и довольно основательную) докопаться
до самой сути подобных вопросов и решился обнародовать свои далеко не очевидные результаты.
Автор просто поражает своей основательностью, дотошностью, педантичностью, придирчиво-
стью (в том числе, и к самому себе) и желанием доказать все, что возможно, с математической
точностью. Он цитирует десятки источников десятков авторов, начиная от документации самой
Microsoft и заканчивая статьями из не-компьютерной прессы. Для своего исследования он написал
десятки программ, тексты которых приведены в книге. Он постоянно полемизирует с автором
другой популярной книги о Windows 95 Адрианом Кингом (в русском издании — Windows 95
изнутри), чья трактовка ближе к официальной (как пишет сам Шульман — полуофициальная).
Кстати, некоторые из цитируемых Шульманом изданий уже появились в русском переводе. К
сожалению, в терминологии наблюдается определенный разнобой, поэтому в большинстве случаев
мы предлагали свой перевод, а ссылку давали на англоязычное издание. Исключение составляет
лишь книга Windows 95 изнутри, — в некотором смысле, антипод Неофициальной Windows 95 —
которая цитировалась по русскому изданию (Врага нужно знать в лицо!).
Как следует из названия, книга в первую очередь посвящена Windows 95. Однако эта новая
операционная система изучается, так сказать, в историческом аспекте. Характер книги не позна-
вательный, а скорее, методологический. Фактически прослеживается весь путь развития Windows и
исследуются все сколько-нибудь значительные нововведения на этом пути. Вы откроете для себя
много интересного о предшественницах Windows 95. Вы поймете, в каком же направлении движется
индустрия программного обеспечения. Вы получите еще один шанс найти свое место под солнцем.
Вы больше не будете чувствовать себя пешкой в руках сильных мира сего.
Автор проводил исследование Windows 95 на версии Beta-1. При подготовке русскоязычного
издания редакция располагала Final Beta версией, в которую уже, несомненно, были внесены опре-
деленные изменения. Некоторые из них, наиболее существенные, отражались в примечаниях. Боль-
шинство, впрочем, не касалось принципиальных вопросов, затрагиваемых в книге, поэтому на них
внимание не акцентировалось (тем более, мы не преследовали цель отловить все такие нюансы).
Хотелось бы еще раз обратить ваше внимание на терминологию. Американский язык является
одним из наиболее (если так можно выразиться) “демократичных”, и в компьютерной терминологии
наблюдается буквально засилье сленга и неологизмов. Сленг же (как и поэзия) очень редко под-
дается однозначному переводу. Тем более, что (в отличие от поэзии) смысл желательно передать
очень точно. Поэтому при подготовке книги приходилось идти на определенные компромиссы. Взять
к примеру словцо thunk, типичный жаргонизм, который обозначает нечто, участвующее при сов-
местном использовании 16- и 32-битового кода, но которое может быть и существительным, и гла-
голом, и чем угодно — вплоть до ругательства. Кое-кто возразит: thunk он санк и есть. Вот на этих
санках мы и рванем в 32-разрядный код! Можно было бы это слово и не переводить (что иногда и
приходилось делать), но это скорее полумера; боюсь, что тогда можно было бы не переводить пол-
книги. Поэтому мы вполне осознаем, что во многих случаях наши трактовки могут оказаться не
самыми удачными, но надеемся, что читатели поймут наши трудности (а, главное, нашу книгу).
Книга, безусловно, в первую очередь рассчитана на читателя с основательной программистской
подготовкой. Я думаю, она окажется просто жизненно необходимой всем разработчикам програм-
много обеспечения (о хакерах даже не вспоминаю). Автор ни в коем случае не ставил перед собой
цели “опорочить светлое имя”, просто “программисты — тоже люди...” И потому эта книга явится
для вас не просто приятным времяпровождением, а программой борьбы за выживание в компью-
терном бизнесе. С другой стороны, любой, кто знает, что такое прерывание и защищенный режим,
кто работал с предыдущими версиями Windows, найдет в ней для себя много любопытного.
Итак, не преклоняемся перед авторитетами, ничего не принимаем на веру, во всем сомневаемся.
Приятного чтения.
11.08.95 И.И. Дериев
6
Неофициальная Windows 95
Моя книга Неофициальная Windows 95 также предлагает интерпретацию инфор-
мации, опубликованной Microsoft, а выводы основаны на результатах моих исследований
работы версии Chicago Beta-1 (май 1994 г.). В дополнение к этому я изучил коммер-
ческую версию Windows for Workgroups (WfW) 3.11. Ее 32-битовый доступ к файлам
был, как сказано в рекламе Microsoft, “усилен 32-битовой технологией проекта “Chicago”.
Я также изучил разработанную Microsoft 32-битовую “надстройку” к Windows 3.1 —
Win32s, которая гораздо больше похожа на Windows 95, чем утверждает (и, видимо, по-
лагает) Microsoft.
Представители Microsoft также утверждают, что Windows 95 стала “интегрирован-
ной”. Система не основана на MS DOS, переписана заново с самого начала и имеет 32-
битовое ядро. При этом Microsoft пытается противопоставить Windows 95 операционной
системе Apple Macintosh (легкость в работе с которой, по распространенному мнению,
вызвана ее “интегрированностью”) и приблизить ее к значительно менее распространенной
OS/2 фирмы IBM, ярые сторонники которой поднимают много шума вокруг того, что она
является полноценной — “от и до” — операционной системой, а не “надстройкой” вроде
Windows.
Но, как программист часто бывает удивлен поведением собственной программы, так и
компания часто питает иллюзии (отсюда и источник дезинформации) относительно качеств
своего собственного продукта. Вот почему опытные покупатели автомобилей обычно уде-
ляют больше внимания потребительским отчетам, а не рекламе производителей автомоби-
лей или их дилеров. Предпочтительнее узнать мнение об операционной системе у пользо-
вателей, а не у ее авторов.
Как правило, автор — это как раз тот человек, от которого требовать объективного
мнения о работе стоит в последнюю очередь.
Неудивительно, что многие заявления Microsoft по поводу Windows 95 не вполне соот-
ветствуют действительности. В этой книге показано, что Windows 95 не более интегрирова-
на, чем ее предшественницы, и не представляет собой “полностью переписанную” систему.
Несмотря на значительные недомолвки в заявлениях Microsoft о Windows 95, я не при-
соединяюсь к тем критикам Windows 95, которые работают под OS/2 или Windows NT.
Энтузиасты OS/2 и NT (их замечания удивительно похожи) набрасываются на архи-
тектурные компромиссы в Windows 95, такие как продолжающееся использование 16-
битового кода в ядре или базирование MS DOS, для доказательства того, что Windows 95
не является настоящей операционной системой. Но Windows 95 именно благодаря этим
компромиссам оказалась удивительно удачной операционной системой.
В этой книге будет показано, что Windows 95 по-прежнему связана с DOS, ее ядро
Win32 опирается на ядро Winl6, и при этом — вот удивительно! — с ней все в порядке.
Моя цель состоит не в доказательстве того, что Windows 95 продолжает использовать DOS
и поэтому не является настоящей операционной системой. Я хочу показать, что, даже если
Windows 95 и использует DOS (как и Windows 3.x в расширенном режиме), ее нельзя
считать “надстройкой”. Она опирается на осмысленную, разумную операционную архитек-
туру, основанную на виртуальном режиме (V86) микропроцессоров Intel.
Помимо прочего, есть надежда, что эта книга поможет преодолеть распространенные
предубеждения по поводу “правильности” архитектуры операционной системы. Windows и
DOS — наиболее удачные операционные системы за всю историю вычислительной тех-
ники. Несоответствие их некоторым принципам конструирования операционных систем
свидетельствует скорее о сомнительности этих принципов. Большинство компьютеров на
Земле работает под DOS и, вероятно, Windows. Поэтому, нравится вам это или нет,
Windows и DOS вместе определяют представления о современной операционной системе.
8
Предисловие
Отвергая некоторые притязания Microsoft по поводу Windows 95, я вовсе не ставлю
перед собой цель продемонстрировать ее недостатки. Поняв, что Windows 95 не полностью
переписана, все еще частично опирается на DOS, имеет 16-битовый код в своем ядре и
т.д., вы сможете лучше разобраться в том, как она на самом деле работает.
Надеюсь, книга поможет вам увидеть, что лучшие свойства Windows 95 берут начало в
расширенном режиме Windows 3.0, впервые поступившей в продажу в знаменательный
день 22 мая 1990 года (День великого переворота в индустрии программного обеспече-
ния). Появление Windows 95 можно было предсказать при внимательном чтении описания
таких функций VMM, как Hook_V86_Int_Chain, Set_PM_Int_Vector, Allocate_
V86_Call_Back, Install_V86_Break_Point и Call_Priority_VM_Event в книге Windows 3.0
Device Driver Kit.
Этот уровень VMM, являющийся подлинной операционной системой, и подключаемые
к нему дополнения, называемые VxD, известны уже около пяти лет, но, похоже, только
сейчас рядовой Windows-программист столкнулся с ними. Есть подозрение, что лишь не-
многие разработчики Windows могли бы подробно рассказать о таких компонентах
операционной системы Windows, как VMM, IFSMgr, VPICD, DOSMGR, VWIN32 или
IOS. Более того, книги о программировании в Windows также мало упоминают об этом
уровне Windows.
KERNEL32 GDI32 KERNEL USER GDI В большинстве книг о Windows (в том числе и Undocumented
COMMDLG OLE DDE USER32 и т.д. ^^^^^“Windows ) обсуждается этот уровень
VMM DOSMGR VWIN32 IFSMfir V86MMGR А этот уровень исследуется в
VFAT VXDLDR IOS и т.д. VPICD Неофициальной Windows 95
Хотя Windows часто называют скорее “средой”, чем операционной системой, ее мало-
известная часть действительно является полноценной операционной системой. Эта книга
охватывает именно эти компоненты операционной системы Windows.
Хотя Неофициальная Windows 95 большей частью описывает VMM и драйверы VxD
в Windows 95, она не предназначена для разработчиков драйверов устройств. Даже если
какой-то разработчик таких драйверов найдет здесь много полезной информации, я зара-
нее предполагаю, что читатель не собирается писать драйверы. И хотя здесь обсуждаются
многие важные недокументированные функции — особенно функции Win32 API, обеспе-
чиваемые KERNEL32.DLL (как, например, VxDCall и GetpWinl6Lock), — мне просто
хотелось дать вам почувствовать, как на самом деле работает Windows 95.
В этой книге я также попытался “интегрировать” (в понимании Microsoft) последние
технические детали Windows 95 с ролью Microsoft в индустрии программного обеспечения
и влиянием на нее со стороны Windows 95. Я никогда не интересовался деталями ради
них самих.
Изучение кода Windows (в некоторых случаях вплоть до уровня машинных команд)
важно хотя бы потому, что этот код каждый день выполняется на десятках миллионов
компьютеров. Не думаю, что следовало бы подвергать менее распространенные операци-
онные системы, такие как OS/2 или NT, столь же тщательному анализу.
Итак, вот что вы можете ожидать от Неофициальной Windows 95. А теперь — моя
любимая часть книги, где я хочу поблагодарить всех, оказавших мне помощь.
Предисловие
9
Прежде всего, мой редактор — Труди Нейхауз. В течение пяти лет Труди редакти-
ровала журнал PC Magazine, регулярно печатались такие известные авторы, как
Дуглас Боулинг, Рей Дункан, Чарлз Пицольд и Джеф Просайз. Известная как лучший
редактор в мире компьютерной прессы, Труди присоединилась к издательству IDG в ка-
честве старшего редактора их нового отдела Programmers Press, и сейчас является дирек-
тором-издателем в Programmers Press Professional. Мне невероятно повезло, что она была
редактором этой книги. *
Ричард Смит, президент фирмы Phar Lap Software, не только превосходный собесед-
ник при обсуждении как технических, так и других вопросов, он также оказал мне боль-
шую поддержку.
Джим Финнеган, редактор Microsoft System Journal (рекомендую вам прочесть его
превосходные статьи), был техническим редактором этой книги. Его орлиный глаз и ост-
рый ум спас меня от некоторых глупостей на страницах этой книги.
Джеф Чапелл помог мне понять внутренний механизм работы VMM и 32-битового
доступа к файлам. Пол Бонно, Рон Берк, Тим Фарли, Билл Льюис, Дэвид Маркан, Клаус
Мюллер, Томас Олсен, Уолтер Они, Мэтт Петрек, Бретт Солтер, Мюррей Сарджент,
Алекс Шмидт, Майк Спило и Келли Зитарук оказывали мне помощь по телефону и
электронной почте.
Джеф, Клаус, Том, Алекс и Келли обеспечили меня огромным количеством матери-
алов по VMM и VxD, большинство из которых, к сожалению, не вошло в эту книгу. Дуг
Боулинг написал превосходную версию 2-го поколения библиотеки WINIO, которую я
также не успел включить в книгу. Я обещаю, что все эти вещи увидят свет.
Фирма Nu-Mega Software выпустила потрясающий отладчик Soft-ICE/Windows,
благодаря которому появление этой книги стало возможным.
Мой друг Крис Вильямс, вице-президент и издатель IDG Programmers Press, который
предложил название Неофициальная Windows 95, часто, казалось, понимал некоторые
разделы этой книги лучше, чем я.
Эмми Педерсен настойчиво проталкивала эту книгу на рынок задолго до того, как я ее
написал. Спасибо, Эмми!
Тереза Фрейзер и Сьюзен Пинк преобразовали мою хаотическую рукопись в читае-
мую и понятную книгу. Мне очень повезло, что они вошли в команду Неофициальной
Windows 95.
Волшебники настольных издательств Ронни Буччи и Бет Робертс создали макет книги,
включая копии экранов и диаграммы, и аккуратно, с чувством юмора, исправляли то, что
казалось уже исправленным.
Сет Мейслин вычитывала книгу, а Лиз Каннингем создала содержание и всякие
указатели, причем в рекордные сроки. Спасибо вам за ваши острые глаза!
Кейт То лини делала предварительные заказы, следила за изданием, направляла курье-
ров и выполняла много другой важной работы. Берта Хикен, обеспечи-вая аварийные
поставки восточных блюд, не позволила мне полностью отказаться от острой пищи.
Джон Эриксон, мой редактор в течение долгих лет, позволил мне на два месяца отойти
от моей рубрики “Undocumented Corner” в Dr. Dobb's Journal. Он познакомил меня со сво-
им вторым “Законом консервации прозы”, согласно которому я задолжал ему две статьи.
Ларри Зельцер, Венди Голдман Ром, Стивен Мейнс, Пол Эндрюс и Джон Марков
иногда звонили мне по поводу написанных ими статей — и я всегда получал от
переговоров с ними гораздо больше пользы, чем они.
Джин Лэнди из фирмы Shapiro, Israel and Weiner (автор великолепной книги Software
developer’s and Marketer’s Legal companion), Ричард Де Бодо из фирмы Irell and
10
Предисловие
Manella, Дэн Сильвер из Федеральной торговой комиссии и Стюарт Тейлор из журнала
ТЛв American Lawyer помогли мне попытаться понять суть американского антимонополь-
ного и торгового законодательства.
Рей Вальдес, кроме того, что обучал меня программированию на С в середине 80-х,
просто прекрасный друг. Его чудесные заметки в Dr. Dobb's Journal (раздел “Developer's
Update”) просто вдохновляли меня.
Клодетт Мур из Moor Literary Agency свела меня с издательством IDG Books. Когда я
был младшим инженером в Lotus, Клодетт посоветовала мне писать компьютерные книги.
Мой сын, Мэтью Джейкоб Шульман, должен был смириться с моим отсутствием в
течение почти двух месяцев, пока я работал над книгой. Мэтью, прости, я обещаю больше
не пропадать так надолго.
Моей жене, Аманде Сьюзен Клэйборн, в эти трудные два месяца пришлось быть
матерью-одиночкой. Спасибо за это, за одиннадцатичасовую вычитку этого предисловия, и
за напоминания, что в жизни есть более важные вещи, чем VxD.
Оксидентал, Калифорния,
23 октября 1994 года.
Предисловие
11
Обновление индустрии
Влияние Windows 95
“Если кто-нибудь думает, что мы не идем по стопам
Lotus, WordPerfect или Borland, то он глубоко заблуж-
дается. Моя работа состоит именно в том, чтобы получать
хороший доход от продажи приложений, и я убежден в
этом на 100%”.
— Майк Мэплз (Mike Maples), вице-президент по продаже
прикладного программного обеспечения (цитата из Jane
Morrissey, “Microsoft applications unit seeks market
dominance,” PC Week, November 18, 1991).
В типичной для Microsoft манере — под звуки фанфар — появилась Windows 95. Когда глава
фирмы Microsoft Билл Гейтс (Bill Gates) объявил, что следующая версия Windows под
кодовым названием “Chicago”, появится приблизительно на пять месяцев позже (возможно, в
апреле 1995 года1 , а не в декабре 1994), рейтинг Microsoft мгновенно подскочил почти на три
пункта.
Почему, когда Билл Гейтс объявляет, что выпуск новой версии задерживается, рейтинг повы-
шается? Потому, что Гейтс заявил: “Chicago станет самым замечательным феноменом всего систем-
ного программного обеспечения всех времен и народов” {New York Times, July 23, 1994). Для
Уолл-стрит этого оказывается достаточно! Microsoft Windows 3.0, выпущенная в мае 1990 г. (и
называемая иногда “матерью всех рекламных компаний”), изменила лик индустрии программного
обеспечения. Microsoft потратила около $2 млн. на рекламу Windows 3.0. Для Windows 95 она, по-
видимому, планирует рекламную кампанию в $40 млн. Microsoft ожидает получить за два года ни с
чем несравнимый доход (около миллиарда долларов) только от модификаций Windows. (Paul
Andrews, “The Winds of Chicago”, Marketing Computers, May 1994).
Windows 95 будет иметь огромное влияние на индустрию программного обеспечения и почти на-
верняка станет самым заметным явлением в мире современного программного обеспечения. Однако,
по-видимому, значение продукта и его вероятное влияние на индустрию будут немного отличаться от
той картины, которую рисует Microsoft. Microsoft провозглашает, что ее продукт — Windows 95 —
это новенькая “с иголочки” операционная система, полностью заменяющая MS DOS и переписанная
“до основания”.
Но, в действительности, приложения Windows, выполняемые под управлением Windows 95, все
же будут использовать MS DOS. Windows 95 базируется на той же самой архитектуре, что и
Windows 3.x в расширенном режиме, которая стала доступной с 1990 г. (мы могли бы назвать ее
Windows 90). С технической точки зрения, Windows 95 не произвела революции. Она — всего
лишь Windows 90+5.
Хотя архитектура Windows 95 не нова, продукт будет иметь такое же большое влияние на инду-
стрию программного обеспечения, какое оказала Windows 3.0 пять лет назад. Впрочем, в отличие от
Windows 3.0, Windows 95, по-видимому, не будет революционным продуктом." Опыт OS/2 и
Windows NT говорит о том, что, увидев совершенно новую операционную систему, подавляющее
1 Уже после выхода книги появилась более точная дата — август 1995 г. — Прим. ред.
12
Обновление индустрии
'<5олаЙн№Ш> пользователей ПК выберут вместо нее хорошо знакомых “старых дьяволов”: комби-
нацию MS DOS и Windows. Архитектура расширенного режима Windows 90 представляла собой
гораздо более радикальный отход от старой операционной системы DOS в реальном режиме, чем мы
это себе представляли. И хорошо, что Windows 95 это сохранила.
Microsoft также заявляет, что Windows 95 “интегрирована”. Это не совсем точно. Windows 95
не интегрирована. Вместо этого она “только растет”. Windows 95 имеет намного больше функцио-
нальных особенностей и возможностей, чем ее предшественники. Она выполняет много задач, кото-
рые до сих пор требовали приложений и утилит, производимых независимыми поставщиками. Но
истинной интегрированности на техническом уровне совсем немного. Было бы очень хорошо, если
бы единственной интеграцией, которую нам предстоит увидеть в Windows 95, оказалась верти-
кальная интеграция. В действительности, вместо этого происходит дальнейший захват компанией
всей индустрии программного обеспечения. Причем, буквально сейчас.
Включение все большего числа приложений/утилит в Windows 95, растущая связь между Mic-
rosoft Windows и Microsoft Office, торговая лихорадка, охватившая программный бизнес, возня
Microsoft и U.S. Department of Justice (Департамент юстиции — DOJ) вокруг антимонопольного за-
конодательства — все это признаки зарождающейся индустрии; признаки перестройки структуры про-
мышленности от сотен, если не тысяч фирм всех размеров, до небольшого количества крупных фирм.
Индустрия программного обеспечения PC переживает встряску и консолидацию. Подобное уже
происходило в других базовых промышленных технологиях. Например, в давние 20-е годы только в
США было 75 автомобильных фирм. Сегодня их осталось совсем немного. То же самое происходит
сейчас и в индустрии программного обеспечения.
Есть и другой аспект этого процесса — движение от технологии средств производства к
потребительски ориентированной технологии. И именно Windows 95, предлагая такие решения,
будет играть ведущую роль. Если программа Windows обратится к функции GetVersion в Windows
95, она получит в ответ 4.0. Программа DOS получит в ответ 7.0. Следовательно, Windows 95 —
это Windows 4.0 плюс MS DOS 7.0. Однако для названий своих новых продуктов Microsoft решила
использовать схему нумерации версий, применяемую в автомобилестроении и виноградарстве (вер-
сия х.О всегда приносила компании многочисленные проблемы). Windows 95 не является передовой
технологией или операционной системой — она является продуктом и предназначена не для разра-
ботчиков или конечных пользователей, а для потребителей.
Как Microsoft Windows 95 видит ваше участие в программном бизнесе? Краткий ответ: если вы раз-
работчик программного обеспечения или коммерсант, Windows 95 заставит вас поволноваться (если
вы не работаете в самой Microsoft или не ее инвестор или же — и то и другое вместе). По крайней
мере, Windows 95 изменит ваши представления о продаже и разработке программного обеспечения.
Пользователи Windows (я имею в виду потребителей) поначалу могут приветствовать Windows
95 как значительное усовершенствование предыдущих версий. Но это будет первое впечатление, они
слишком обеспокоены перспективой растущего преобладания Microsoft в индустрии программного
обеспечения. Windows 95 — это большой шаг Microsoft в направлении к 100%-ному удовлетворению
потребностей в программном обеспечении. Даже если Microsoft, по мнению Department of Justice, и
не нарушает серьезно антимонопольные законы США и если цель компании по монопольному вла-
дению индустрией не отличается от цели любой другой компании, находящейся в подобном положе-
нии, разработчики программного обеспечения и коммерсанты должны полностью осознавать особую
роль Microsoft в индустрии программного обеспечения. Microsoft является не просто поставщиком
вашей операционной системы. Она становится вашим конкурентом.
Постоянно расширяющаяся
операционная система
Microsoft не только производит операционные системы Windows и MS DOS, на которых рабо-
тает большая часть коммерческого программного обеспечения мира, она также создает такие прило-
жения, как Microsoft Word, Excel, PowerPoint, Access и Mail, которые включены в пакет Microsoft
Влияние Windows 95
13
Office. Эти пакеты непосредственно конкурируют с такими “не-микрософтовскими” приложениями,
как 1-2-3, WordPerfect, Quattro Pro и DBase. Разработчики этих приложений — компании Lotus,
Novell и Borland — беспокоятся, не дает ли монопольное владение Microsoft операционной системой
привилегий ее приложениям, а если да, то не будут ли эти привилегии неоправданными.
Однако это лишь часть проблемы. Даже сами операционные системы Microsoft, такие как Win-
dows и MS DOS, конкурируют с приложениями и утилитами независимых фирм, по крайней мере
со времени объединения нескольких основных утилит в MS DOS 5.0. Уже в июне 1991 г. Microsoft
привнесла в свою операционную систему те возможности, которые традиционно принадлежали
программам независимых разработчиков. Windows 95 — очевидная демонстрация этой тенденции.
Другими словами, Microsoft конкурирует со своими собственными покупателями. Independent
Software Vendors (независимые поставщики программного обеспечения — ISV), разрабатывающие
приложения и утилиты под Windows, являются основными покупателями Microsoft. Они зависят от
инструментальных средств Microsoft и информации, необходимой для написания программного
обеспечения под Windows.
Microsoft думает расширять Windows в значительной степени за счет ISV и, как предполага-
ется, с их помощью. Вот только несколько таких функциональных возможностей, включенных в
Microsoft Windows 95:
> Работа с сетью
> Встроенный информационный центр (электронная почта, факс)
> Explorer (значительно улучшенная оболочка)
> Wordpad (полноценная система подготовки текстов)
> Microsoft Paint (полноценная программа подготовки иллюстраций)
> Гипертерминал (полноценный телекоммуникационный пакет)
Операционная система разрастается, а прикладная область сужается. Будущие операционные
системы будут включать даже большие функциональные возможности по сравнению с такими сов-
ременными приложениями, как текстовые процессоры и базы данных.
Сейчас Билл Гейтс не поступит, как в свое время Никита Хрущев. Он не будет стучать башма-
ком по трибуне и заявлять на собрании представителей ISV: “Мы вас похороним!” Вместо этого
Гейтс скажет: “Смотрите, все это мы делаем для вас”. В глазах Microsoft экспансия операционной
системы не вызывает сокращения прикладной области. Взяв на себя всю основную работу, опера-
ционные системы Microsoft позволят разработчикам заниматься творчеством.
Однако беспристрастный наблюдатель может интерпретировать подход Microsoft по-другому: не
“Смотрите, все это мы делаем для вас”, а “Смотрите, все это мы для вас закрываем”.
Во взаимоотношения между приложениями и операционной системой вовсе не обязан сраба-
тывать закон сохранения: что приобретается одним, обязательно теряется другим. И нет ничего
бесчестного в желании Microsoft все больше и больше наращивать Windows. Я уверен, любой из нас
на месте Гейтса стремился бы к власти или, по крайней мере, к тотальному контролю над инду-
стрией программного обеспечения. Именно поэтому понимание дальних целей Microsoft помогает
поместить Windows 95 в правильный контекст.
Президент одной крупной компании, читая в Microsoft's “Chicago” Revier’s Guide обо всех
этих новых функциональных особенностях, начал составлять список компаний, которые, по его
мнению, должны выйти из бизнеса вскоре после начала продажи Windows 95. Да, это немного
мелодраматично. Однако даже трезвая Business Week утверждает, что Windows 95
окажет гораздо большее влияние на судьбу Microsoft и компьютерной индустрии, чем предполагают
блюстители закона (в DOJ)...
Если производители других операционных систем имеют хоть слабую надежду, то производители при-
кладного программного обеспечения не имеют ее вовсе. С появлением Chicago Microsoft устремляет свои
усилия на разработку таких функций, которые обычно поставляются отдельно, например электронной
почты. Когда Гейтс описал функциональные возможности Chicago в Electronic Mail Assn., настроение
14
Обновление индустрии
было мрачное. “Это было подобно пробуждению, — говорит Стивен Холдшилд (Steven Holdschild,
директор по связям с разработчиками) из фирмы Lotus, которая продает cc:Mail. —
Многие считают, что, когда Chicago начнет продаваться, нынешняя индустрия передачи сообщений
уйдет в прошлое”.
— Amy Cortese, “Next Stop, Chicago”, Business Week, August 1, 1994.
В статье в InfoWorld под названием “ISVs: Wake up”, (June 27, 1994) энтузиаст Windows 95
Стив Гибсон (Steve Gibson) нарисовал подобную картину влияния Windows 95 на некоторых неза-
висимых производителей программного обеспечения:
“Система, не оборудованная ничем, кроме Chicago, может выполнять всю полезную работу. Сегодня я
по-прежнему использую старую надежную DOS-версию cc:Mail Remote, устаревшую версию Procomm
для DOS и превосходное программное обеспечение Winfax Pro фирмы Delrina Corp. Программы ие свя-
заны ни между собой, ни с операционной системой. Следовательно, я вынужден постоянно возиться с
этими приложениями, тогда как Chicago каждый день будет все это делать гораздо проще.
Для тех, кто предлагал подобные решения вчера, — это как неожиданное пробуждение. Разработчики
программных продуктов прямо сейчас должны серьезно рассмотреть Chicago и определить, куда лучше
направить свои возможности в этой новой области. Тот, кто не сделает этого, обречен на неудачу”.
Windows 95 — громогласный предвестник этого “пробуждения”. Индустрия программного обес-
печения для PC быстро вырождается, остаются только три главных игрока: Microsoft, Novell и
Lotus. Когда Novell купила WordPerfect, корреспондент PC Week четко заметил, что ISV почув-
ствовали, как их заманили в ловушку, и начали искать пути бегства:
“1.5 млрд, долларов, выложенных Novell, предвозвестили большую перестройку в индустрии програм-
много обеспечения и заставили многих независимых производителей совершать подвиги в борьбе за вы-
живание. Microsoft, Novell и Lotus — не просто три больших игрока (они даже больше, чем нужно),
оии стали господствовать в производстве офисного ПО, как в свое время Форд, GM и Крайслер господ-
ствовали в автомобильной промышленности... Мораль: атмосфера в бизнесе программного обеспечения
накаляется. “Модель бизнеса из прошлого уже не состоятельна”, — говорит Роджер Мак-Нейми (Roger
Mcnamee), финансовый менеджер, специализирующийся на инвестировании технологий.
Но соберитесь с духом. Несмотря на продолжающуюся волну консолидаций, существуют и альтернати-
вы для продаж...
У вас текстовый процессор? Забудьте и думать. Вместо борьбы с Большой тройкой модифицируйте про-
дукт во “всплывающую записную книжку” для обмена сообщениями по Internet. Вас пугает такая ком-
бинация? Расслабьтесь. Думайте о своем продукте, как о платформе для симбиотического программного
обеспечения host-компьютеров. Вас вышвырнули из сферы настольных систем? Нацельтесь на серверы.
Такие изготовители утилит, как Symantec и McAffee, продолжают существовать, и особенно потому, что
Microsoft добавляет все больше и больше функциональных возможностей в операционную систему”.
— Bill Snyder, “The Great Escape”, PC Week, April 4, 1994.
Статья продолжается замечанием, что “доходы по продажам основных утилит Symantec снижа-
ются из-за все удлиняющегося списка встроенных в ОС функций”.
Теперь необходимо сделать некоторые выводы из этого предупреждения. Большая часть функ-
циональных возможностей, которые Microsoft поместила в Windows 95, не встроена в операционную
систему. Связаны — да. Но встроены или интегрированы? — Нет. Microsoft передала большинство ра-
бот по этим дополнительным возможностям независимым фирмам. Большинство из “связанных” ути-
лит Microsoft — это не столько полноценные, сколько изолированные коммерчески-досгупные
продукты.
Однако, если даже Windows 95 не делает всего того, что могут делать продукты независи-
мых фирм, можно предположить, что большинство покупателей не захотят приобретать полноцен-
ные продукты. В большинстве случаев запросы потребителей довольно ограничены, и они вполне
удовлетворятся программным обеспечением, уже установленным в их машине (т.е. Windows 95 —
Прим. ред.). Итак, если вы производитель (пусть даже самый искусный!) коммуникационного
пакета или пакета, обслуживающего факс, E-mail, Internet, настала пора поволноваться, — а я
думаю, даже потерять сон — из-за включения этих возможностей в каждую копию Windows 95.
Влияние Windows 95
15
Уроки истории: MS DOS 5.0 и 6.0
Если атмосфера в бизнесе программного обеспечения, как мы знаем, “накаляется”, то Microsoft
начала, так сказать, “нагнетать обстановку”, когда в конце июня 1991 г. выпустила MS DOS 5.0.
Эта версия DOS включала менеджер памяти для 386 — нечто подобное тому, что продавали незави-
симые фирмы Quarterdeck и Qualitas в качестве “довеска” к DOS, и множество дисковых утилит,
на которые Microsoft “получила лицензию” от Central Point Software (теперь часть Symantec). Я беру
слова “получила лицензию” в кавычки, потому что почти все, что Central Point получила взамен, —
лицензия от Microsoft на использование внешнего вида оболочки DOS (.Newsbytes, June 12, 1991).
Как интересный комментарий по поводу потребности в конкуренции на рынке операционных си-
стем приведем следующее: Microsoft произвела MS DOS 5.0 в значительной степени потому, что DR
DOS 5.0 фирмы Digital Research, выпущенная в августе 1990 г., обладала аналогичными возможно-
стями. Розничные продажи DR DOS 5.0 осуществлялись по обычным каналам, чего Microsoft ни-
когда не пыталась сделать с MS DOS. По-видимому, без конкуренции с Digital Research застой MS
DOS продолжался бы (прошел уже год с момента выпуска ужасной DOS 4.0). С другой стороны, мно-
жество особенностей MS DOS 5.0, включая “связанные” программы управления памятью и диско-
вые утилиты, целиком позаимствованы из DR DOS 5.0. Итак, может быть, именно Digital Research
мы должны обвинять (или благодарить) за начало экспансии операционной системы Microsoft.
Интересно было бы узнать, какую реакцию вызвало появление MS DOS 5.0 у компаний, изго-
товляющих менеджеры памяти (таких как Quarterdeck). Давайте вернемся назад по времени и по-
смотрим, как тогда смотрелась новоявленная DOS 5.0. Если вы удивлены тем, что я затрагиваю эту
тему, имейте в виду: тот, кто не помнит истории, вынужден повторять ее вновь. Существует много
близких параллелей между ожидаемым влиянием Windows 95 и известным влиянием MS DOS 5.0.
Давайте посмотрим, сможет ли история нас чему-нибудь научить.
“Читая о новом и великом в DOS 5.0, вспомните, что случилось с такими независимыми производи-
телями, как Qualitas (386МАХ), Quarterdeck (QEMM386), Symantec (Norton Utility), Central Point
Software (PC Tools) и другими, которые заработали миллионы, восполняя пробелы в DOS. Короче
говоря, им был нанесен ущерб — одним больше, другим меньше. Однако многие сообразительные про-
ектировщики утилит уже нарастили возможности своих продуктов над новыми возможностями DOS
5.0... В действительности, Symantec и Central Point могут даже выгадать от появления DOS 5.0...
Наибольший удар пришелся по изготовителям 38б-х менеджеров памяти. Программа управления памя-
тью DOS 5.0 EMM386.EXE не поднялась до высот 386МАХ или QEMM386. Например, она не умеет
отображать медленную ROM в быструю RAM (для увеличения производительности системы) или авто-
матически оптимизировать конфигурацию вашей системы (для эффективного использования верхней па-
мяти). Однако она выполняет две самые важные процедуры, которые делают эти продукты: загружает
резидентные программы и драйверы в верхнюю память и эмулирует расширенную память LIM 4.0 в до-
полнительной. Покупая сейчас какой-либо 386-й менеджер памяти, вы платите приличные деньги всего
лишь за дополнительные возможности. Правда, поставщики пакетов управления памятью постоянно
совершенствуют свои продукты и добиваются все больших преимуществ”.
— Jeff Prosise, “DOS 5: What's in it for you?”, PC Magazine, September 24, 1991.
С тех пор ситуация прояснилась: Central Point уже давно куплена Symantec. А доходы самой
Symantec в 1993 г. сократились на $11.5 млн.
Что касается таких поставщиков программ управления памятью, как Quarterdeck и Qualitas, су-
ществовала надежда, что DOS 5.0 поможет им узаконить рынок своих продуктов. Пол Шерер (Paul
Sherer) отмечал в свое время в PC Week:
Поставщики программ управления памятью, стоящие перед лицом неизбежной конкуренции с DOS 5.0
корпорации Microsoft, заявляют со всей уверенностью, что дефекты новой системы будут работать иа них.
Согласно отзывам участников бета-тестирования и документации на MS DOS, DOS 5.0, в отличие от
уже существующих менеджеров памяти, не умеет автоматически перемещать драйверы устройств и
резидентные программы в верхнюю память и размещать их там оптимальным образом.
Продукты независимых поставщиков в настоящее время не поддерживают эту особенность DOS 5.0, од-
нако поставщики считают, что новые возможности DOS 5.0 помогут расширить рынок менеджеров па-
мяти. Это также поощрит пользователей “оседлать” DOS 5.0 с помощью существующих программ.
16
Обновление индустрии
“DOS 5.0 узаконила то, что существовало уже давным-давно, — сказала Мэри Стэнли (Mary Stanley),
президент Qualitas Inc. в Bethesda, Md. — Больше миллиона человек используют технологию управле-
ния памятью Qualitas либо с помощью 386МАХ, либо с помощью OEM продуктов”.
“Наша самая большая проблема — это подготовка и образование пользователей. Microsoft как раз и бу-
дет знакомить людей с этой технологией, — добавила Стэнли. — Нынешние поставщики утверждают,
что, попробовав DOS 5.0, пользователи обратятся к более совершенным решениям независимых фирм.”
— Paul М. Sherer, “DOS 5.0 shortcoming may help memory management vendors”, PC Week, May 20, 1991.
Вопреки радужным прогнозам 1991 года, сегодня фирма Quarterdeck несет убытки. В другой ста-
тье PC Week (датированной 1994 г.) сообщается:
“После сокращения в трех кварталах продаж и доходов Quarterdeck Office System Inc. предприняла
решительные меры для снижения стоимости своих продуктов, надеясь вернуться к процветанию.
Две недели назад это вылилось в увольнение 55 служащих (25% американских сотрудников) и привело
к отставке учредителя, президента и исполнительного директора Терри Майерс (Terry Myers)
Аналитики сообщают, что Quarterdeck находится в критическом состоянии, ее флагманский корабль —
менеджер памяти QEMM — получил пробоину.
“Этот рынок опустел”, — сказал Ричард Дэвис (Richard Davis), аналитик из Luis Nicoud Associates в
Сан-Франциско. Дэвис не убежден, что стратегия компании, направленная на Internet (Mosaic и про-
дукты для World-Wide Web), способна поддержать ее на плаву.
Надзор за выходом из кризиса возложен на ветерана промышленности Кинга Ли (King Lee), который
вошел в правление Quarterdeck 1 августа в качестве временного исполнительного директора. Ли взял на
себя обязательство вернуться к истокам и уже закрыл несколько офисов компании, сократив на $1 млн.
квартальное финансирование...
Хотя Ли в прошлом успешно создавал и перестраивал такие компании по выпуску сервисного програм-
много обеспечения, как Fifth Generation Systems Inc. и XTree Co. (обе позже были проданы), офици-
альные лица Quarterdeck заявили, что никакой распродажи не планируется”.
— Jane Morrissey, “Quarterdeck CEO resigns over drastic cost-cutting”, PC Week, August 29, 1994.
Microsoft не виновата, что Quarterdeck потратила несколько лет на работу над проектом
DESQview/X. В свое время это выглядело как разумная стратегия конкуренции с Microsoft. В
1991 г. Forbes определил это явление как “маркетинг юркой тележки”:
“Quarterdeck Office System, маленькая фирма (объем продаж $48 млн.) в Санта Моника, Калифорния,
живет благодаря пробелам в продуктах Microsoft. Почему Microsoft оставляет место для такого крошеч-
ного конкурента? Как выживает Quarterdeck? Ответы на эти вопросы позволяют понять, как небольшие
фирмы конкурируют с большими...
К сожалению, Quarterdeck, DESQview и QEMM все же вытесняются конкурирующими продуктами
Microsoft...
Очевидный вопрос: как же Quarterdeck умудряется продавать свои продукты? Почему уровень прибы-
лей фирмы остается на уровне 23%? Ответ состоит в том, что программы, написанные учредителем
Quarterdeck Гарри Поупом (Gary Pope), работают со старыми версиями прикладных программ намного
лучше, чем Windows 3.0. Windows 3.0, напротив, лучше работает с последними версиями электронных
таблиц, текстовых процессоров и другими прикладными программами, которые специально разработаны
совместимыми с Windows.
Таким образом, Quarterdeck обслуживает пользователей, которые работают на старых вычислительных
системах, а не на современных машинах с новыми приложениями. Quarterdeck продает новые, усовер-
, шенстврванные тележки, а большие фирмы занимаются маркетингом безлошадных карет...
Легкая тележка — прекрасная временная ниша, но что делать, когда последний пользователь продаст
свою “лошадку” ради мощного “автомобиля”? Quarterdeck ищет другие ниши. Ее очередной продукт,
проходящий в настоящее время предпродажное тестирование, позволит пользователям PC запускать
прикладные программы, созданные для рабочих станций UNIX. Со временем, когда DOS “уйдет” и на
новых машинах будут работать новые операционные системы, вполне возможно, что Quarterdeck будет
продавать эти продукты. Без сомнения, некоторые предприниматели продолжают продавать запчасти
для Model Т (устаревшая модель форда. — Прим, ред.), и прекрасно этим живут”.
— Julie Pitta, “Buggy whip marketing: tiny Quarterdeck sells enhancements to giant Microsoft's operating
systems. How does it survive?”, Forbes, November 25,1991.
Влияние Windows 95 17
Давайте теперь переведем стрелки часов вперед, на сентябрь 1992 г., когда новость о предстоя-
щей MS DOS 6.0 (которая не была выпущена до апреля 1993 г.) появилась в компьютерной прессе:
“Корпорация Microsoft еще раз использует свою знаменитую операционную систему для захвата чужих
территорий. На этот раз полем битвы становятся сервисные программы.
Судя по отзывам, DOS 6.0, которая появится в конце этого года или начале следующего, будет включать
антивирусное программное обеспечение, усовершенствованную программу резервного копирования и
программы дефрагментации диска и сжатия данных.
Эти утилиты являются неотъемлемой частью стратегии Microsoft Windows. А вся дея-тельность фирмы
подтверждает движение, начавшееся в июне 1991 г. с разработки DOS 5.0, оснащенной встроенными
макросредствами, оболочкой DOS, мощным текстовым процессором и инструментальными средствами
резервного копирования и восстановления.
Многие корпорации, получив аналоги популярных утилит прямо в операционной системе, перестают
приобретать продукты независимых фирм ради уменьшения расходов.
Если уровень возможностей утилит в DOS 6.0 составит от 15 до 20% по сравнению с пакетами незави-
симых фирм, “я буду, как правило, использовать DOS 6.0 и сделаю исключение лишь для редких
пользователей, которым действительно нужны полноценные утилиты", — сказал Билл Рэмэдж (Bill
Ramage), системный архитектор из Bechtel Corp., фирмы по проектированию и конструированию, име-
ющей 6.000 ПК во всем Мире.
“Откровенно говоря, они ведь достаются бесплатно (в DOS 6.0), — сказал Рэмэдж. — К тому же, я
предпочитаю более тесную интеграцию утилит в операционной системе”.
Рэмэдж делает выводы из своего опыта. Бехтель (Bechtel) в прошлом году принял решение не в пользу
менеджера памяти QEMM фирмы Quarterdeck Office System Inc. просто потому, что DOS 5.0 уже пред-
ложила подобные возможности.
Официальные лица в Microsoft, однако, отрицают свои попытки овладеть рынком утилит, утверждая,
что компания не претендует на подобную тотальность.
“Мы обеспечим 80% (возможностей утилит)”, — сказал Брэд Чейз (Brad Chase), главный менеджер
MS DOS компании Redmond, Вашингтон. — Следовательно, поставщики утилит могут по-прежнему
развивать дополнительные возможности”.
Такие компании, как Stac Electronics и Qualitas Inc., ведут свои дела достаточно хорошо, разрабатывая
одну главную утилиту, но многие коммерческие наблюдатели сообщают, что времена меняются.
“Ясно, что это риск, — сказал Бэрн Херцог (Bernd Harzog), директор по компьютерной технике из
Gartner Group Inc., фирмы по исследованию рынка. — Системное программное обеспечение будет
включать все больше таких вещей, которые сегодня мы считаем приложениями, и те, кто работает в
этом бизнесе, должны быть проворными... И искать все время новые решения”.
— Paula Rooney and Paul M. Sherer, “DOS 6.0 threatens utility developers; users, however stand to benefit”,
PC Week, September 14, 1992.
Знакомые слова? To, что Брэд Чейз хочет захватить 80% рынка, лучше, чем то, что Майк
Мэплз хочет 100%, но прочие соображения, сопутствующие появлению DOS 6.0 (ОС начинает
включать то, что раньше называлось прикладным программным обеспечением; компании, выпус-
кающие всего один продукт, сильно рискуют; время подумать о переменах), напоминают слегка за-
вуалированную репетицию перед появлением Windows 95.
Итак, что же случилось с этими поставщикам после выпуска MS DOS 6.0 в апреле 1993 г.? Мы
уже знаем, что фирма Central Point была куплена Symantec и что Symantec и Quarterdeck понесли убыт-
ки. Другой пример: Stac Electronics уволила 40 человек или 20% своей рабочей силы в мае 1993 г.
Многие мероприятия, сопутствующие появлению Windows 95, предварительно обыгрывались на
DOS 5.0 и 6.0. Если принять во внимание этот горький опыт, то надежды, что Windows 95 “уза-
конит”, например рынок программного обеспечения электронной почты или факс-машин, практи-
чески исчезают, а следующая версия (Windows 96?) нанесет еще больший удар. С другой стороны,
Microsoft многому научилась после выпуска MS DOS 5.0 и Windows 3.0. По крайней мере, она
поняла, что “х.0” следует удалить из имени продуктов. Итак, с выходом Windows 95 мощь
Microsoft возрастет, что представляет серьезную опасность для других компаний, производящих
программное обеспечение (которые, следует пойнить, являются не только конкурентами, но и потре-
бителями ее операционной системы).
18f
Обновлениё'йндуёУрии
Что принадлежит операционной системе?
Я не призываю вас рыдать над трудностями, с которыми столкнутся такие компании, как
Symantec и Delrina, из-за внедрения основных возможностей их продуктов в Windows 95. Когда
фирма Microsoft включила программу уплотнения диска в MS DOS 6.0, это привело к почти немед-
ленным падениям доходов Stac. Большая часть разработчиков (включая меня) прореагировала на
это примерно так: “Черт возьми, уплотнение диска уже принадлежит ОС. Значит фирма Stac может
на время отдохнуть. И пусть она поищет теперь себе другую нишу в бизнесе. Это ее проблемы”.
В самом деле, быть может, все, что Microsoft поместила в Windows 95, и все, что Гейтс захочет
увидеть в будущей версии Windows, действительно должно принадлежать ОС. Текстовые процес-
соры, базы данных, электронные таблицы и графические пакеты — все объединяется в одно ос-
новное, хорошо определенное множество стандартных функциональных возможностей. Возможно,
это программное обеспечение будет принадлежать ОС либо в виде связанных с ней программ, либо
в виде динамически подгружаемых библиотек (DLL). Действительно ли мир нуждается в таком
количестве различных текстовых процессоров?.. А как насчет компиляторов C++?.. Телекоммуни-
кационных программ?.. Программ восстановления диска?.. Если вы независимый производитель, не
стало бы пользователям лучше, когда возможности ваших продуктов будут бесплатно обеспечивать-
ся каждой копией Windows 95?
Если независимый производитель выпускает продукт, который стремятся приобрести большин-
ство пользователей ПК, то — почти по определению — функциональность этого продукта стано-
вится частью ОС. База данных не должна включать дублирования данных, а программное обес-
печение, в идеале, не должно допускать дублирования кода.
Вопрос только во времени, когда Microsoft поместит очередное общецелевое приложение или
утилиту в свою ОС и определит то, чему они в действительности “принадлежат”: Windows.
Windows поддерживает общецелевые “не-микрософтовские” приложения, как веревка поддерживает
того, кто скоро будет повешен. Дайте только время, и ваш продукт превратится в Microsoft DLL.
Одна из причин такой экспансии Windows, возможно, связана с ранними заблуждениями отно-
сительно перспектив развития рынка программного обеспечения. Операционные системы Microsoft
предоставили пользователям самый минимум того, что необходимо для использования их PC, а мно-
гочисленные независимые фирмы заполнили массу пробелов, оставленных Microsoft в DOS и позже
в Windows. Всевозможные программы, вроде оболочек, диспетчеров файлов, дисковых утилит, от-
ладчиков и менеджеров памяти, на большинстве других компьютеров уже были частью ОС в то вре-
мя, как для PC они продавались как отдельные продукты независимых фирм.
Это заблуждение сыграло большую роль в становлении рынка ПК. Вот только один пример.
Соревнование при разработке программного обеспечения для уплотнения диска, несомненно, приве-
ло к производству лучших программ, чем можно было бы ожидать в случае включения этой воз-
можности в ОС. Тезис, обратный известному “Вы получаете то, за что заплатили”, звучит так: “Ес-
ли вы за что-то не заплатили, то, вероятно, ничего подобного и не получите”. Программное обеспе-
чение, связанное с ОС, вероятно, страдает от самодовольства. (Ладно, само программное обеспе-
чение — нет,., но вы знаете, что я имею в виду). Высокое качество и низкая цена большей части
программного обеспечения PC в значительной степени были вызваны отсутствием связи с операци-
онной системой.
Таким образом, если даже какой-то элемент программного обеспечения “принадлежит” ОС в не-
котором узком техническом смысле, то экономические вопросы производства высококачественного
программного обеспечения приводят к усложненному представлению о его принадлежности. Реше-
ние интегрировать элемент программного обеспечения (или по крайней мере связать его) с ОС или
предоставить его разработку независимым фирмам подобно выбору “производить или покупать”? А
ответ на этот вопрос нельзя получить, используя только узкий технический критерий. По этой при-
чине не следовало бы считать, что, скажем, программы уплотнения диска, восстановления диска или
даже командная оболочка принадлежат ОС.
Для Microsoft вопрос о принадлежности чего-либо ОС не является только техническим, это так-
же вопрос бизнеса. Windows продается в розницу. Microsoft постоянно нуждается в новых функци-
BAHflHHeAWindows 95
19
опальных возможностях, которые можно анонсировать на коробке. Кроме того, как мщ «увидим
позже (в разделе “Является ли Microsoft Office операционной системой?”), Microsoft не добавила в
DOS и Windows никаких липших возможностей, кроме тех, что сделали их лучшей платформой для
пакета приложений Microsoft Office.
Итак, по различным причинам Microsoft готовится сейчас привнести множество новых возмож-
ностей в Windows. И это понятно. По крайней мере, большая часть функциональных возможностей
Windows 95 действительно принадлежит ей в техническом смысле (впрочем, это по-прежнему толь-
ко часть истории). Однако объединение этих функциональных возможностей в ОС связано с наме-
рением оказать влияние на индустрию программного обеспечения. Windows 95, вне всякого сомне-
ния, — это повод для “пробуждения”.
Комментируя утилиты, включенные в MS DOS 6.0, журнал PC Magazine (September 14, 1993)
отмечает, что “добавление этих утилит демонстрирует неясное представление Microsoft, какой же
именно должна быть операционная система”.
Если определение ОС, данное Microsoft, было покрыто туманом еще во времена объявления
DOS 6.0, то ко времени реализации Windows 95 возникают некоторые подозрения: ведь Microsoft
не только имеет почти полную монополию на операционную систему, но и постоянно расширяет
трактовку принадлежности к операционной системе. Возможно, сегодня это только 80% рынка, но в
итоге Microsoft хочет все 100%.
Некоторые комментаторы видят эту экспансию, a DOJ отказывается затрагивать данную тему,
считая, что все в порядке. Например, New York Times (July 18, 1994) привела такую цитату Стю-
арта Алсопа (Stewart Alsop): “Если вы действительно заботитесь об улучшении работы персо-
нального компьютера, вы согласитесь, чтобы Microsoft собрала все под свое начало”.
В этом существует определенная логика. Например, одна из причин более простого исполь-
зования ПК фирмы Apple Macintosh состояла в том, что система Apple имела замкнутую
архитектуру, полностью доминировала на рынке и гарантировала покупателям, что почти все будет
приходить от единственного производителя. Монополия имеет некоторые явные преимущества. В
определенных ситуациях монополия может быть единственной жизнеспособной индустриальной
структурой, ведущей к так называемой естественной монополии.
Говоря об этом, великолепные биографы Гейтса Стивен Мейнс (Stephen Manes) и Поль Эндрюс
(Paul Andrews) (Gates, р. 202) цитируют его утверждение о том, что объем и стандарты в програм-
мном обеспечении ПК могут вести к естественной монополии.
“Почему нам требуются стандарты?.. Только благодаря большим объемам продаж можно предлагать
приемлемое программное обеспечение по низкой цене. Стандарты увеличивают базовые возможности
продукта, который можно продать...
Я не должен говорить об этом, но в некоторых случаях это ведет, в отдельной категории продуктов, к ес-
тественной монополии: когда кто-то правильно документирует, правильно настраивает, правильно про-
двигает конкретный программный пакет и, пользуясь моментом, доверием пользователя, своей репута-
цией, рыночными механизмами и ценой, формирует наиболее сильную позицию для своих продуктов”.
Гейтс, видимо, понял эту связь между количеством и качеством раньше, чем кто-либо другой в
индустрии программного обеспечения (эту индустрию, конечно, он в значительной степени и опреде-
ляет). Гейтс твердит о монополии с 1981 г. Если операционная система PC становится естественной
монополией, если Microsoft прибирает к рукам куски все более ценного пирога, напомним, что от
компаний в столь благоприятных условиях требуют определенных компромиссов. Так называемая
естественная монополия вообще-то регулируется, ей не позволяют распространяться в новые об-
ласти и т.д.
Microsoft уже установила MS DOS на более чем 120 млн. ПК в мире, a Windows — на 50 млн.
Достигнув льготного антимонопольного соглашения с Department of Justice, Microsoft еще быстрее
сможет продвигаться к своей цели, становясь бесконтрольным предприятием, одним махом удов-
летворяющим все ваши потребности в программном обеспечении.
20
Обновление индустрии
Логотип совместимости с Windows 95
Давно отмечается, что Microsoft поднимает ставки, чтобы остаться в бизнесе программного обес-
печения. Например, статья в New York Times (December 14, 1992) начиналась так: “Может ли кто-
нибудь, кроме корпорации Microsoft, отныне делать деньги в программном обеспечении?” (ответ:
да, если вы нашли нишу, “в которой еще не выступает Microsoft”). И продолжается цитированием
объяснений одного финансового аналитика, почему он в настоящее время не рекомендует каких-
либо инвестиций в программное обеспечение:
I “Пользователь ожидает, что программное обеспечение обновляется очень медленно по цене и быстро по
своим возможностям... Проблема в том, что расходы на маркетинг получаются столь высокими, что
Microsoft вынуждена поднимать ставки”.
В Windows 95 Microsoft буквально поднимает ставки, чтобы остаться в игре при разработке
приложений Windows. В качестве первого пробного шага по направлению к сертификации программ
в стиле Nintendo Microsoft опубликовала новое руководство о том, что должны делать приложения
для установки логотипа совместимости с Windows 95. Согласно статье “How to adapt an app for
Chicago” (Microsoft Developer Network News, July 1994), требования достаточно строгие. Цитата
слишком большая, чтобы приводить ее здесь, но приведем некоторые выдержки:
> Приложение должно быть выполнимым под WIN32
> Программа должна также успешно запускаться в Windows NT 3.5 (а не только
под Windows 95)
> Если программа имеет дело с файлами, она должна поддерживатв OLE 2.0
> Если программа имеет дело с файлами, она должна иметь возможности обработки почты,
обеспечивая по крайней мере команду Send или Send Mail в меню File.
По словам менеджера программы совместимости с Windows, “мы поднимаем ставки в игре и
говорим, что эти продукты (с логотипом Windows 95) не только действительно работают, но и
создают ценный синергизм с операционной системой” (InfoWorld, August 29, 1994, р. 27). Ценный
для кого? Большая часть требований, оказывается, касается желаний Microsoft, а не потенциальных
конечных пользователей. Например, хотя интерфейс прикладного программирования (API) WIN32
прекрасен, многие программы просто становятся больше при переходе на 32-битовый код. Требова-
ния совместимости с NT кажутся не более чем попыткой Microsoft контролировать предполагаемый
рынок Windows 95 и помочь своим “тусклым” программным продуктам из Windows NT. Требования
OLE 2.0 по меньшей мере странны при том, что сама Microsoft не использовала OLE для оболочки
Windows 95 (см. в главе 2).
Microsoft просто поднимает стоимость разработки приложений для Windows, и это совершенно
не обязательно принесет пользу конечным пользователям.
Является ли Microsoft Office
операционной системой?
Помимо происходящей экспансии Windows, направленной на поглощение возможностей прило-
жений независимых фирм, возрастает также интеграция между Microsoft Office и Microsoft Win-
dows. Большинство ПК сегодня поставляются не только с MS DOS и Windows, заранее установлен-
ными на жестком диске, но и с Microsoft Office. MS Office успешно продавался в 1993 г. Годовой
доход составил около $500 млн. Microsoft Office превосходил по продаже своего самого близкого
конкурента Lotus Smartsuite в соотношении 4:1. По оценкам Microsoft, 50% ее годового дохода по-
лучено от продажи Office (Information Week, June 27, 1994).
Microsoft Office становится все больше и больше похожим на часть Windows. Что это, новый
обходной маневр? Пользовательский интерфейс Windows 95, оказывается, существенно позаимст-
вован из Microsoft Office:
Влияние Windows 95 21
“Возможно, Department of Justice не увидел связи между методами развития операционной системы
Microsoft и ее приложений, но пользователи Windows эту связь увидят, как только Chicago поступит на
рынок... Многие изменения в пользовательском интерфейсе, проведенные Microsoft, можно уже увидеть
в Office 6.0...
Одно изменение в операционной системе позаимствовано прямо из MS Office. Это — использование
правой кнопки мыши как инспектора свойств...
В следующей версии Windows диалоговые окна будут похожи на аналогичные части пакета MS Office”.
— Randall С. Kennedy, “Like Office? You'll Love Chicago!”, Windows Sources, October 1994, p. 23—24.
Windows 95 — это не коварный Троянский конь для Microsoft Office. (Прошел слух, что
Microsoft преднамеренно задерживала Windows 95, чтобы дать возможность своим подразделениям
по приложениям Windows завершить работы по следующей версии Office!) Но иногда кажется, что
Windows — только платформа для Microsoft Office.
Microsoft даже добавила определенные функциональные возможности в DOS и Windows, чтобы
сделать их лучшей платформой для приложений Microsoft Office. Например, во время судебных
разбирательств между Stac и Microsoft оказалось, что одна из основных причин, заставивших Билла
Гейтса включить утилиты уплотнения диска, заключалась в том, что без них Microsoft имела бы
меньшие шансы на продажу MS Office, предъявлявшего повышенные требования к работе с диском.
MS DOS 6.0 требует уплотнения диска не только из-за конкуренции с DR DOS 6.0 (которая уже
включила эту возможность) и не только ради внесения симпатичного заголовка в рекламный про-
спект (“Легчайший способ удвоить емкость вашего жесткого диска”), а ради того, чтобы “смазать
тормозные колодки” (или по крайней мере жесткие диски) для Microsoft Office.
Вот другой пример: широко разрекламированная (но не очень практичная) технология Object
Linking and Embedding (связывание и внедрение объектов — OLE) Microsoft Windows появилась в
компонентах Microsoft Office раньше, чем в других приложениях. Брайан Ливингстон (Brian
Livingston, More Windows 3.1 Secrets, p. 194) говорит, что Microsoft PowerPoint 2.0, поступивший
в продажу в июне 1990 г., включал поддержку OLE за шесть месяцев до того, как документация по
OLE была предоставлена разработчикам. OLE была разработана в значительной мере для удобства
Microsoft Office.
С помощью Office Developers Kit (ODK) и Visual Basic for Applications (VBA) Microsoft по-
ощряет разработчиков писать приложения Windows, нацеленные специально на Office. Конечно,
Lotus и Novell предпринимают подобные действия, поощряющие разработку приложений, пред-
назначенных специально для их пакетов. Но Microsoft, имея большую долю на рынке, получила хо-
роший шанс убедить разработчиков рассматривать ее пакеты как платформы для своих разработок.
Кроме логотипа совместимости с Windows 95, Microsoft установила также логотип сов-
местимости с MS Office. Пол Бонэр (Paul Bonner), пространно писавший о настройке Windows, де-
лится некоторыми интересными наблюдениями относительно этого:
“Если вы работаете с пакетом и выбираете приложения и утилиты, основываясь на качестве их работы с
этим пакетом, нельзя даже думать о Windows как об операционной среде. Пакет — вот ваша опера-
ционная среда, и ваши приложения должны “жить” по правилам пакета, а не Windows...”
В этом году Microsoft представила целую программу работ по совместимости с Office. За $1.000
производители независимых фирм приобретают право заимствовать интерфейс2 таких приложений MS
Office, как Word и Excel, включая их инструментальные панели и структуры меню...
Существует, правда, одна ловушка. Если ваше приложение конкурирует с каким-нибудь приложением
или инструментарием, которые предлагает Microsoft, вы не сможете участвовать в Программе совме-
стимости с Office. Это и не удивительно. Microsoft Office и отметки совместимости с MS Office
являются, в конце концов, торговыми марками корпорации Microsoft. Программа обеспечения совме-
стимости с MS Office позволяет ее участникам использовать все виды защищенных авторским правом
пиктограмм, дизайн инструментальных меню и другую необходимую информацию о MS Office. Ведь
Microsoft не обязана распространить эти привилегии и на продукты конкурентов?
Нет! Microsoft Office не является посторонним приложением, а программы, совместимые с Office, не
просто “довески”. Когда совместимость с Office становится важной для покупателей характеристикой,
2 В оригинале используется новое устойчивое словосочетание: look and feel. — Прим, ped.)
22
Обновление индустрии
определяет интерфейс приложений и привносит отсутствующие до сих пор возможности, тогда Microsoft
Office сам становится операционной системой со своим собственным API — завершенным интерфейсом
прикладного программирования...
Как только вы начинаете думать о Microsoft Office как об операционной системе, программа обеспе-
чения совместимости с Office предстает совершенно в новом свете... Статья об отсутствии конкуренции в
контракте совместимости с Office означает, что приложениями, совместимыми с Office, будут только те,
которые захочет Microsoft. Другими словами, только приложения, дополняющие Redmond-овские и
никакие другие, способные с ними конкурировать...
Microsoft несомненно будет убеждать множество независимых разработчиков и маленькие компании по
выпуску программного обеспечения производить совместимые с Office приложения, если только она
сможет предложить поддержку маркетинга”.
— Paul Bonner, “Will the real operating system please stand up?”, Computer Shopper, October, 1994.
Мой друг, президент компании среднего размера, сообщил мне, что “Теперь Microsoft Office —
операционная система, a Visual Basic — ее API”. Я думаю, в этом есть большая доля истины. Micro-
soft действительно имеет намерения захватить большую долю рынка приложений, a Windows 95
становится ключевой частью этой стратегии. Отсюда следует, что адаптация MS Office представляет
собой растущую отрасль индустрии, так же как адаптации 1-2-3 и Dbase были популярнейшей
сферой разработки программного обеспечения. Интересный вопрос, который мы рассмотрим позже
(в разделе “Windows 95: опасности и возможности”), состоит в том, жизнеспособна ли индустрия
программного обеспечения, основанная на дополнительных приложениях к Microsoft Office?
Microsoft и Justice Department
Теперь можно вспомнить, что дело Microsoft о возможных нарушениях антимонопольных зако-
нов рассматривалось сначала U.S. Federal Trade Commission (Федеральная комиссия по торговле —
FTC) , а затем, когда FTC зашла в тупик, — Antitrust Division of the U.S. Department of Justice
(антимонопольное подразделение департамента юстиции)... Вы могли бы ожидать, что антимо-
нопольная комиссия обратит внимание на прогрессирующее доминирование Microsoft в области
программной индустрии. Да, она действительно обратила на это внимание. Однако, после доро-
гостоящего четырехлетнего расследования, антимонопольная комиссия завершила дело, предложив
Microsoft сместить несколько незначительных акцентов. По существу, правительство США дало
корпорации Microsoft зеленый свет.
15 июля 1994 г. Microsoft подписала соглашение с Antitrust Division DOJ, покончив с прави-
тельственным расследованием ее торговой практики. В то же время Microsoft подписала почти
идентичное соглашение с Directorate-General for Competition of the European Commission (Главное
управление по конкуренции европейской комиссии). Судебный процесс длился в течение 6.5 лет в
Соединенных Штатах, 4.5 лет в Европе.
Microsoft согласилась на немедленное прекращение нескольких проектов лицензирования опера-
ционных систем MS DOS и Windows для производителей аппаратного обеспечения PC, а также
согласилась на снятие некоторых “необязательных ограничений” для опытной эксплуатации бета-
версии Chicago. Windows NT исключена из соглашения.
Сначала соглашение рассматривалось как победа DOJ и конкурентов Microsoft. New York
Times (July 17) вывела в заголовке страницы: “Власть Microsoft на рынке программного обес-
печения ослаблена антимонопольным процессом”, и добавила, что “соглашение может изменить мир
компьютерных вычислений... Соглашение может ослабить контроль со стороны Microsoft рынка
операционных систем”. Заголовок в Boston Globe был столь же восторженным: “ Microsoft согласна
на конкуренцию в США и Европе”.
Действительно, соглашение звучало сначала так, как будто влияние Microsoft ограничивается и
в индустрии программного обеспечения PC начинается конкуренция. В течение ряда лет Microsoft
снабжала производителей аппаратного обеспечения PC (оригинальные изготовители оборудования
— Original Equipment Manufacturers, или OEM) лицензиями на установки MS DOS и Windows.
Согласно этим лицензиям изготовители платили Microsoft в зависимости от числа производимых
Влияние Windows 95
23
компьютеров, а не копий DOS или Windows, которые фактически использовались. В 1993 г. объемы
продаж по соглашениям с соглашения OEM давали около 60% для MS DOS и 43% для Windows.
Согласно DOJ, “в таких контрактах Microsoft запрещает OEM устанавливать “не-микрософ-
товские” операционные системы в течение действия договоров. Следовательно, OEM, подписавшие
контракты с Microsoft, должны отказаться от конкурирующих альтернативных операционных си-
стем”. Соглашение немедленно прекращает эту практику и позволяет надеяться, что устанавливать
“не-микрософтовские” операционные системы получат шанс в сфере настольных компьютеров.
Но на следующее утро почти все осознали, что прекращение DOJ процесса Государство против
Microsoft обернулось победой Microsoft. Джон Марков (John Markoff), аналитик из New York
Times (July 18), сообщает в статье под заголовком “Microsoft’s Barely Limited Future”: “Вместо
ограничения власти, соглашение... дает Microsoft полную свободу определять основные законы
компьютерной индустрии до конца десятилетия”.
В первый же день после соглашения Уолл-стрит выразил свое мнение по этому поводу: цена
акции Microsoft возросла с $1.87 до $50.50. Рик Шэрлунд (Rick Sherlund), аналитик из фирмы
Goldman Sachs, утверждает, что по этому соглашению Microsoft “должна господствовать на рынке
программного обеспечения настольной аппаратуры в течение следующих 10 лет”. Другой часто
цитируемый аналитик Ричард Шафер (Richard Shaffer) объявил что “войны операционных систем
завершены — Microsoft вышла победителем... Microsoft — это сегодняшняя Standard Oil”.
Windows 95 нужно рассматривать в контексте победы Microsoft над DOJ: Windows 95 и ее
последующие версии станут тем механизмом, с помощью которого Microsoft будет контролировать
рынок настольных компьютеров и определять основные правила игры в индустрии ПО.
Но почему запрет на важнейшую практику Microsoft может рассматриваться как укрепление ее
положения?
Во-первых, поворот от практики “оптового” лицензирования инсталяций ПО на компьютеры
QEM к лицензированию каждой копий, видимо, несколько запоздал. Анна Бингамен (Anne
Bingaman) утверждает: “Это должно было произойти еще пять лет назад. Теперь уже слишком
поздно”. (JnfoWorld, August 15, 1994, р. 46). Кажется маловероятным, что эти изменения приведут
к увеличению доли OS/2 на рынке операционных систем, даже несмотря на бравую реакцию IBM и
Novell, последовавшую непосредственно за подписанием соглашения. Со своей стороны, Novell
почти мгновенно последовала указаниям DOJ, стремясь укрепить свою Novell DOS 7.0 — это умно
в .любом случае. Однако, как отметил представитель фирмы Compaq (который уже предлагает
OS/2 своим покупателям), “Windows — это стандарт, и ничего не изменится”.
Гораздо важнее, что соглашение не касается ключевых вопросов о роли Microsoft в индустрии
программного обеспечения. Такие компании, как Lotus и Borland, которые конкурируют с Microsoft
на рынке текстовых процессоров, электронных таблиц и других приложений, давно утверждали, что
Microsoft поступает несправедливо, устанавливая свой контроль над операционной системой и созда-
вая тем самым привилегированное положение своим приложениям, в частности Microsoft Office.
Microsoft продолжает отрицать, что она монополизировала индустрию программного обеспе-
чения. Никто не признает ее вины до окончательного приговора суда. Все остается на своих местах
“до опубликования судебного решения по какому-либо спорному факту или нарушению закона; и до
окончательного приговора, фиксирующего любое нарушение или признание какой-либо стороной
того, что касается всякого спорного факта или нарушения закона”.
Тем не менее некоторые высокие правительственные чиновники порадовали-таки мир програм-
много обеспечения, разоблачив торговую практику Microsoft. Сразу после подписания пресловутого
соглашения Генеральный прокурор США Жанет Рено (Janet Reno) заявила: “Несправедливая прак-
тика Microsoft уничтожила шансы других компаний, лишила потребителей нормальной альтернати-
вы выбора среди конкурирующих операционных систем PC и замедлила инновационный процесс”.
Заместитель Генерального прокурора США по антимонопольному законодательству Анна
Бингамен отметила, что “Microsoft — это типично Американская история успеха; хотя любая
компания, пытающаяся построить свой успех на недостатках закона, — как это сделала Microsoft со
своей контрактной практикой — не может быть оправдана”.
“Microsoft использовала силу свой монополии, в сущности, чтобы обложить налогом произво-
дителей PC, которые в ином случае могли бы обратиться к альтернативным системам, — сказала
24
Обновление индустрии
Бингамен. — В результате возникли препятствия для конкуренции соперничающих операционных
систем, замедлился инновационный прогресс и сократились возможности выбора для потребителей”.
Согласно пресс-релизу DOJ, Microsoft не снижала цену на свои операционные системы даже тогда,
когда цена других программных продуктов резко падала. Начиная с 1988 г. доля Microsoft на рынке
ПО никогда не была ниже 70%.
Это просто удивительно. Руководитель антимонопольного ведомства США сообщает в точности
то, о чем уже в течение нескольких последних лет говорят в индустрии программного обеспечения
PC: существует “налог” Microsoft и, несмотря на то, что цены на ее операционные системы кажутся
потребителям низкими, Microsoft, по существу, занята “выбиванием” доходов.
Хотя многие разработчики программного обеспечения смогли сделать выводы из обвинений в
прекращенном деле Государство против Microsoft, факт остается фактом: согласительное поста-
новление касается очень узкой проблемы — сбыт OEM представляет меньше 25% годового дохода
Microsoft.
В деле отмечается: “По крайней мере 50.000 приложений сейчас работают в MS DOS и около
5.000 написаны для работы под Windows. Microsoft сама весьма успешно продает ряд собственных
приложений”. И это все, что сказано о приложениях!
Указывается, что “все версии Windows, написанные до сих пор, требуют присутствия базисной
операционной системы, MS DOS или близкой ей”, но ничего не говорится по поводу связи между
Windows и MS DOS (см. Undocumented DOS, 2d. edition, p. 3-18).
Аналогично указывается, что “критическая информация относительно интерфейсов операцион-
ной системы с ее приложениями — это информация, которая нужна ISV для разработки
приложений, работающих под управлением операционной системы”. Но ничего не говорится о том,
что Mibrosoft несправедливо утаивает часть’этой критической информации, чтобы дать собственным
разработчикам эксклюзивное право на использование неописанных свойств интерфейсов.
Сейчас уже не кажется, будто DOJ не осознавал этих проблем. Благодаря моим собственным
встречам с представителями DOJ Сэмом Миллером (Sam Miller), Доном Расселом (Don Russell) и
Лэрри Фрэнкелем (Larry Frankel) я знаю, что они в высшей степени интересовались проблемами
вокруг неописанных интерфейсов. Мы проговорили несколько часов по поводу возможного возмеще-
ния ущерба и того, что они упоминали, как “обязательное раскрытие интерфейса”.
Подобным же образом, “Civil Investigative Demand”, которые я получил от DOJ, требовали
“всей корреспонденции корпорации Microsoft, включая сообщения по электронной почте... всего,
что относится к конкуренции в разработке или продаже операционных систем для персональных
компьютеров или графических пользовательских интерфейсов, совместимости и несовместимости
любых продуктов Microsoft или любых “не-микрософтовских” продуктов, раскрытию и не-
раскрытию информации по интерфейсу программного обеспечения”. Я мог бы предложить некото-
рую очаровательную информацию от вице-президента Microsoft Брэда Сильверберга (Brad
Silverberg). Мие особенно нравится объяснение Брэда в октябре 1993 г., почему он был обязан
расширять Windows API: “Как только Windows будет заморожена и не станет больше продвигаться
вперед, ее можно будет легко клонировать и таким образом перевести в ранг ширпотреба. Microsoft
не нравится ситуация, которая сложилась вокруг BIOS”.
DOJ был достаточно в курсе проблем, связанных с монопольным использованием Microsoft
своих важных стандартов для DOS и Windows. Правда, в постановлении ни о чем подобном не
упоминалось. Все завершилось так же, как и предыдущая тяжба Microsoft с FTC. Даже Билл Гейтс,
охарактеризованный умеренной FTC и DOJ как “коммунист” и “социалист”, был вынужден при-
знать, что окончательный приговор не был для него серьезным. После нескольких лет расследо-
ваний он сказал: “Это все, чего они добились”.
Почему же DOJ согласился на столь малое? Как члены DOJ могли игнорировать мольбы мно-
гих поставщиков программного обеспечения PC?
Одна из теорий состоит в том, что администрация Клинтона рассматривает Microsoft как
“национальное сокровище” и оказала давление на DOJ. Давление, возможно, было оказано на
встрече между Биллом Гейтсом и экономическим консультантом Клинтона Робертом Рабином
(Robert Rubin) 25 мая. Дата- знаменательна тем, что как раз неделей позже Гейтс свидетельствовал
под присягой перед DOJ. Согласно одному анонимному источнику, Гейтс подчеркнул Рабину, что
Влияние Windows 95
25
Microsoft ответственна за существенную часть экспортных поставок программного обеспечение США
{Information Week, June 27, 1994).
Но трудно рассматривать давление со стороны администрации Клинтона в качестве единст-
венной причины ограниченности постановления DOJ. Microsoft, возможно, и на виду, но все это не
столь важно для экономики США, по крайней мере, когда ее сравнивают с такими компаниями, как
IBM и GM, которые производят осязаемо-ощутимые товары. Хотя производство программного
обеспечения — это стратегическая отрасль мировой экономики, даже такой “гигант”, как Microsoft,
имеет всего около 15.000 служащих, а ее квартальный доход в $1.25 млрд, явно не смотрится по
сравнению с $13 млрд. IBM и даже $2 млрд, фирмы Apple.
Что отличает Microsoft, так это то, что она имеет чрезвычайно низкие издержки производства.
Это прекрасно для Microsoft, но грустно видеть, как это сказывается на экономике США, особенно
когда 45% ресурсов Microsoft находится во владении ее сотрудников. DOJ мог бы сделать умерен-
ный выбор в пользу американского народа, а не Microsoft, далекой от “национальных ценностей”,
представляющей по сути монополиста, распределяющего прибыль только среди нескольких своих
служащих и нескольких акционеров.
Другое объяснение осторожности DOJ заключается в том, что он опасается повторить опыт с де-
лом IBM, которое тянулось в течение 13 лет, а закончилось формулировкой “отсутствие состава
преступления”. Легко представить себе юристов DOJ, не желающих краха своей карьеры из-за
проигрыша дела, и тем удивительнее, как IBM вышла сухой из воды. Но даже это безобидное окон-
чание процесса оказывало серьезное влияние на IBM в течение ряда лет.
В одной из последних книг говорится, что падение IBM и подъем ее поставщиков, Microsoft и
Intel, заставили IBM ограничить разработку программного и аппаратного обеспечения. Это открыло
дорогу независимому рынку программного обеспечения, освободив место для таких “выскочек”, как
Microsoft.
Процесс 1969 г. ожидался в течение длительного времени, и IBM уже приоткрыла цены на свои
системы, облегчая другим компаниям продажу совместимых устройств и программного обеспечения...
Многие действия IBM в 70-х и 80-х годах, в частности ее ленивую позицию по отношению к малым по-
ставщикам программного обеспечения, можно объяснить только отражением прочно укоренившегося об-
раза ослепительного судебного зала...
После коротких переговоров представители фирмы IBM согласились, что Гейтс является владельцем
системы, а оплата будет проводиться за счет отчислений от продаж, а не единовременно. (IBM прово-
дила более сдержанную политику в отношении монопольного использования системного программного
обеспечения из-за боязни соответствующих мер со стороны авторов).
— Charles Ferguson and Charles Morris, Computer Wars: The Fall of IBM and the Future of Global
Technology, 1993, p. 10-11, 26.
Иначе говоря, Microsoft смогла извлечь выгоду в деле Государство против IBM. “Нынешняя
Microsoft” добилась того же в деле Государство против Microsoft, и поздно уже что-либо менять.
Я думаю, что DOJ не выдвигал больше ничего против Microsoft по одной простой причине: он
чувствовал, что не сможет победить еще раз. Анна Бингамен, отвечая на многочисленную критику
по поводу постановления DOJ, заявляет: “Господа, мы рассмотрели все аспекты данного случая и
приняли решение, которое должны были принять”. Мнение DOJ: “Все, что мы могли — это исхо-
дить исключительно из закона”.
К сожалению, это правда. Закон, как и политика, является искусством возможного. Постанов-
ление “дает зеленый свет” расширяющейся монополии Microsoft, но ничего другого, как это ни пе-
чально, DOJ сделать не смог. Обязанность DOJ — приводить в действие антимонопольные законы, а
не содействовать развитию конкуренции в индустрии программного обеспечения, что не одно и то же.
И это означает, что действия Microsoft, изученные DOJ, но не вошедшие в постановление, либо
не являются незаконными, либо (что то же самое) доказать их незаконность слишком трудно.
Microsoft должна почувствовать воодушевление от того, что действие постановления столь огра-
ничено. Поэтому компания сможет пойти вперед полным ходом, расширяя горизонты операционной
системы в Windows 95. Microsoft Office будет все больше и больше выглядеть как основная часть
Windows.
26
Обновление индустрии
Windows 95: опасности и возможности
Хотя DOJ и отметил некоторую негативную роль Microsoft в индустрии программного обеспе-
чения, гегемония компании, вероятно, продлится еще некоторое время и в следующем веке.
Windows 95 будет платформой, а WIN32, возможно, будет ее API, так же, как и API был платфор-
мой Windows 3.x, которая определяла направление развития программной индустрии в первой поло-
вине 90-х годов. Либо Microsoft Office будет платформой, a Visual Basic for Applications будет API.
В любом случае, Microsoft не сойдет со сцены, по крайней мере, в течение следующих пяти лет.
Цель Microsoft ясна: охватить производство всего программного обеспечения для настольных
компьютеров. Стратегия Microsoft заключается в поддержке развития функциональных возмож-
ностей Windows и продвижении на рынок Microsoft Office, как “интегрированного” прикладного
уровня Microsoft Windows.
Как другие фирмы должны смотреть на изменения в индустрии программного обеспечения?
Становится все более очевидным, что им остается совсем немного возможностей для конкурен-
ции с Microsoft. Фирма Novell на постановление DOJ отреагировала почти мгновенно: решила сни-
зить цены на свои продукты. Такие проекты, как Novell DOS 7.0, OpenDoc и AppWare, оказались в
прямой конкуренции с Microsoft.
Аналогично общецелевые приложения и пакеты программ становятся плохой областью для вло-
жения капитала. В частности, Microsoft Office из-за его “связывания” с Windows на многих новых
компьютерах быстро становится квазичастью самой Windows. Даже фирма Lotus имеет здесь очень
небольшие шансы. Microsoft Office всюду и везде.
Поэтому забудьте про написание больших систем подготовки текстов. Подготовка текстов, впол-
не возможно, не является приложением, а подготовка юридических документов или квартальных
отчетов по продажам — является. База данных также не является приложением, а система учета
прихода/расхода для стоматологической поликлиники — является. Так или иначе, но некая компа-
ния — а ею стала Microsoft — получила виртуальную монополию на горизонтальном рынке обще-
целевых программ и стала перемещать их в операционную систему.
Возможно, осталось еще немного пространства на рынке графических систем и программного
обеспечения потребительского кредита. Система Microsoft Money пострадала от многочисленных
! публикаций и потерпела поражение от таких хорошо работающих продуктов, как Intuit's Quicken,
да и CorelDraw! чувствует себя отлично.
Какие еще компании могут чувствовать себя уверенно? Во время презентации в конце 1993 г.
Билл Гейтс постоянно упоминал о списках маленьких компаний, которые, по его мнению, имели
реальные шансы стать поставщиками программного обеспечения сегодня:
Altamira Редактор изображений, финансируемый фирмой Autodesk; недавно куплен Microsoft
Caligar Truespace — программное обеспечение трехмерной иллюстрации/моделирования
EJ Bilingual Insoft ShapeWare Японский EZ Телеконференции Программное обеспечение машинной графики; визуальные дополнения к Microsoft Office
Reportsmith Watermark Генератор отчетов клиент/сервер; приобретен Borland Обеспечение возможности работы с графическими образами для Microsoft Exchange; сервер изображения для NT
Оптимальное решение — это найти область рынка, где Microsoft не имеет продуктов и где есть
шансы быть единственным производителем в течение нескольких лет. С другой стороны, единствен-
ный рынок, в который, как я слышал, Microsoft не хотела внедряться, — это производство порно-
Влияние Windows 95
27
графических хранителей экрана и мультимедиа для совершеннолетних. Служащий одной компании
сообщил мне: “Мы тщательно изучали программное обеспечение для совершеннолетних, но решили
пока что воздержаться”. Трудно представить себе какую-либо область ПО, в которой Microsoft ре-
шила бы “воздержаться”.
Как отмечалось, существует большая надежда, что так называемое downsizing (уменьшение разме-
ров) создаст новое направление на рынке систем клиент/сервер. Многое из “downsizing” выглядит
как прямое следствие усовершенствований компьютерной технологии. Ожидается, что индустрия пер-
сональных компьютеров получит от этого выгоды. (Обратитесь к книге Стратегии клиент/сервер:
руководство по выживанию специалистов по перестройке компаний, Дэвида Васкевича.)
Однако считать так было бы в высшей степени близоруко. Перестройка корпораций происходит
не только из-за быстро понижающейся стоимости ПК, но и по причине долговременного упадка в
экономике США. “Downsizing” ^асто оказывается только предлогом для сокращения производства и
урезания бюджета. Видимо, это не очень хорошая основа для долгосрочных программ в индустрии
программного обеспечения. Во всяком случае, далеко не очевидно, что замена больших ЭВМ сетью
ПК действительно сокращает расходы компаний. В одном отчете сообщалось, что стоимость покупки
и установки сети ПК составляет всего около десяти процентов от общей стоимости системы.
А что слышно на рынке домашних ПК? В последнее время наблюдалось увеличение продажи
компьютеров и программного обеспечения для дома. К сожалению, ажиотаж на рынке домашних
ПК в меньшей степени обусловлен фактическим ростом числа семей, решивших автоматизировать
хранение своих рецептов, коллекций почтовых марок и чеков, и в большей степени вызван пустыми
мечтаниями. Сейчас говорят, что на этом рынке нужна “свежая струя”.
Давайте, однако, предположим, что рынок домашних ПК переживает период резкого подъема.
Действительно ли это та чаша Святого Грааля, которую искали производители программного обес-
печения? Уровень прибыли от продажи “домашних” программных продуктов очень низок. Как ска-
зал один служащий из Microsoft по поводу маркетинга мультимедиа, уровень прибыли столь низок,
что если будет даже один-единственный звонок по поводу технической поддержки, от прибыли не
останется и следа. (Его предложение: поместите на упаковочной коробке номера телефонов всех по-
ставщиков аппаратного обеспечения, которые придут вам в голову, большими буквами, а ваш собст-
венный номер телефона — самым маленьким шрифтом.) Можно иметь огромный успех в этой обла-
сти — посмотрите на популярность игры “Doom”, — но это не обязательно приводит к прибыли.
И последнее замечание относительно рынка домашних ПК: Microsoft уже здесь! Как отмечалось
ранее, Microsoft Home распространяется со скоростью один пакет в неделю. Компания предполагает
иметь около тридцати различных пакетов (включая Microsoft Dangerous Creatures) к рождест-
венской распродаже 1994 г.
Ну довольно об общем состоянии производства. Какие опасности и возможности ожидаются в
бизнесе в связи с выпуском Windows 95? Опасности, возникающие в связи с Windows 95, вполне
понятны. Объединение большого количества возможностей в Windows 95 сделает программные
продукты некоторых независимых фирм (и сами эти фирмы) устаревшими.
Windows 95 откроет перспективы разработки огромного числа маленьких функциональных
возможностей.
После выпуска Windows 95 многие поставщики программного обеспечения ожидают увеличения
сбыта своей продукции. Задержки в поставках Windows 95 дорого обходятся не только Microsoft,
но и многим другим поставщикам, чьи доходы уменьшаются, поскольку покупатели ждут Windows
95, чтобы принять решение о дальнейшем приобретении какого-нибудь программного обеспечения
для Windows. После выхода Windows 95 накопленный спрос “хлынет мощным потоком".
Не ясно, приведет ли это к увеличению доходов кого-нибудь, кроме Microsoft. Еще раз обра-
тимся к Роджеру Мак-Нейму, который утверждает, что бизнес обновления, по-видимому, основан
на модели, предложенной Мило Миндербиндером (Milo Minderbinder): “Продавайте с потерями, но
увеличивайте объемы”.
Как всегда, есть еще одна интересная область — это залатывание прорех в операционной систе-
ме. Несколько компьютерных журналистов были в полном восторге относительно возможностей, пре-
доставляемых Windows 95 в этой области. Например, Джесси Берст (Jesse Berst) сообщает: “Для
разработчиков Chicago станет рогом изобилия новых возможностей” (PC Week, August 29, 1994).
28
Обновление индустрии
Несмотря на то, что новый интерфейс может разочаровать опытных пользователей, он станет сбывшейся
мечтой для поставщиков мелких утилит. Он даст ряд возможностей для расширений, усовершенство-
ваний и мини-утилит.
Как один из примеров этих возможностей, Берст упоминает необходимость утилиты управ-
ления шрифтами. А в более поздней статье (“Chicago's a great place to build products on”, PC Week,
September 5, 1994) Берст пишет, что “самая большая благоприятная возможность Chicago лежит в
компьютерной телефонии”.
Возможно, это только мое мнение, но я не вижу, каким образом компьютерная телефония долж-
на появиться из причудливой фразы “Здорово свистит, значит круто”. Если эта сомнительная об-
ласть действительно образует тот базис Windows 95, на котором строятся продукты, будущее дейст-
вительно выглядит мрачно.
Что касается утилит, Windows 95 предоставляет ряд возможностей для расширения оболочки,
совершенствования регистрации и т.д. Но это, вероятно, временное явление (все более-менее ценное
будет оприходовано Microsoft в следующих версиях). Во всяком случае, нельзя поддерживать ком-
панию в 1000 или даже 50 человек, разрабатывая утилиты по $79.95 (хотя это возможно для
компаний в 10 человек).
Интерфейсы драйверов виртуальных устройств (Virtual Device Driver — VxD) предоставляют
многочисленные способы расширения Windows 95. Возможно, некоторые независимые поставщики
увидят среди сотен функций VxD ингредиенты для важного и выгодного расширения Windows,
которое, по какой-либо причине, Microsoft не станет реализовать в течение нескольких лет. Так бы-
ло с бизнесом расширений DOS. Компании вроде Phar Lap процветали просто потому, что Microsoft
по ряду причин не хотела переводить MS DOS в защищенный режим.
Стив Гибсон (Steve Gibson) привел превосходный пример того, каким образом распространи-
тели существующих утилит могут приспособиться к Windows 95. Учитывая возможности Chicago
“выполнять всю полезную работу” и то, что “для тех, кто предлагал подобные решения вчера, —
это как неожиданное пробуждение”, Гибсон констатирует:
Это не означает упразднения возможностей разработки дополнений к Chicago. Существует множество
различных способов зацепиться за Chicago. Но возможности изменились. Фирма Delrina, например,
недавно заявила о разработке системы обработки факс-сообщений. Руководство компании должно знать,
что Chicago резко потеснит продажи Winfax Pro, поскольку любой пользователь Chicago автоматически
получает подобные возможности.
- Steve Gibson, “ISVs: Wake up”, InfoWorld, June 27, 1994.
С технической точки зрения это звучит правильно. Но я могу задать вопрос, может ли производ-
ство “довесков” к Windows 95 действительно служить основой для существования компании? В про-
цитированной ранее статье PC Week указывается: если вы делаете текстовый процессор, превра-
щайте его во всплывающую записную книжку для Internet. Статья приводила в качестве примера
фирмы Q+E и Shapeware, которые успешно превратили свои автономные продукты в компоненты.
В индустрии ПО существует большое волнение относительно создания компонентов. Джон Удел
(Jon Udell) написал об этом несколько замечательных статей в журнале Byte (“Visual Basic Custom
Controls Meet OLE”, March 1994 и “Componentware”, May 1994 ). Как указывает Удел, любо-
пытно, что настоящая индустрия компонентов произошла не из C++ или других объектно-
ориентированных языков, а из Visual Basic. Как это было с 1-2-3 и DBase в 80-х годах, Visual Basic
Custom Controls (VBX) породили подындустрию построения компонентов. В обзоре “VBX Controls
as Software Components” в PC Techniques (October-November 1994) приводится список компаний,
которые сделали VBX своим бизнесом, куда входят Crescent Software, Desaware, Farpoint Techno-
logies, Microhelp и Sheridan.
Это действительно перспективное направление. Однако совершенно неизвестно, какого рода биз-
нес можно построить на компонентах, даже без оплаты лицензий. Не менее важно помнить следу-
ющее: что Microsoft получила — то Microsoft может и отдать. Из-за недостатков в архитектуре VBX
Microsoft продвигает новый интерфейс — OLE Custom Controls (OCX), который не совместим со ста-
рым интерфейсом VBX. Если встроенные компоненты VBX или OCX становятся крупным капи-
талом, можно ли ожидать, что Microsoft останется в стороне? Уже Visual Basic Professional Edition
3.0 включает большое количество VBX-независимых фирм Sheridan, Crescent, Microhelp и других.
Влияние Windows 95
29
Как отмечалось раньше, существуют также хорошие возможности для ада<Гг(Ции-ракета
Microsoft Office, являющегося, по сути, операционной системой для все большего и большего 'числа
пользователей. Роджер Мак-Нейм в статье по экономике программного обеспечения PC прекрасно
написал об этом:
“Большинство руководителей фирм по разработке программного обеспечения рассматривают Microsoft
как самую алчную и подлую акулу в океане. Это ошибка. Microsoft — больше не акула. Microsoft сама —
океан, в котором должна жить всякая другая рыба... Если бы я был поставщиком прикладного
программного обеспечения, я бы рассматривал продукты Microsoft как платформу для формирования
своего бизнеса. Я думаю о бизнесе, который построила Funk Software на платформе 1-2-3, или который
построили поставщики утилит вокруг MS DOS. Думаю — это только вопрос времени, когда предпри-
ниматели построят бизнес на Word, Excel и других продуктах Microsoft.
— Roger McName, “Sobering Up,” Upside, March 1993.
Вспомните, такой же совет давала PC Week: “Вас пугает такая комбинация? Расслабьтесь. Ду-
майте о своем продукте, как о платформе для симбиотического программного обеспечения”. Идея на-
писания всяческих довесков, макросов, продуктов вертикального рынка и т.д. для пакетов прило-
жений, главным образом для Microsoft Office, приятно согласуется с энтузиазмом, наблюдаемым в
деловых книгах по поводу “массовой адаптации”. (См., например, William Н. Davidow and Michael
S. Malone, The Virtual Corporation, p. 40-42, где обсуждается всегда популярный пример ASIC —
специфичных интегрированных схем). Остается неясным то, какие же компании смогут работать на
рынке адаптации массовых продуктов. Как указывает Мак-Нейм, 1-2-3 действительно вызвала по-
явление Funk Software. Но единственным большим результатом внедрения dBase, я думаю, является
программа SBT, генератор модулей бухгалтерского учета для dBase. Вызвал ли HyperCard для
Macintosh разработку каких-нибудь больших успешных проектов? Появилась масса программного
обеспечения, но ни одной ведущей компании... Правда, EDS Росса Перо (Ross Perot) начинала, в
сущности, в бизнесе адаптаций, поэтому существует некоторая надежда.
Какие еще уроки можно извлечь из прошедших нескольких лет “жизни под Windows”?
Во-первых, Microsoft явно ведет маркетинг в духе театрального представления. Я видел разра-
ботчиков, принимающих решение использовать Borland OWL вместо Microsoft MFC просто потому,
что они ненавидят Microsoft. Поэтому существуют компании, продукты которых просто пред-
ставляют собой CASE-технологии для AppWare. Или компании, чьи продукты основаны полностью
на OpenDoc или OS/2. К сожалению, все это бессмысленно. Я полагаю, можно обижаться на
Microsoft, но “не отрезайте себе нос из-за досады на лицо”.
Во-вторых, большинству из нас в бизнесе программного обеспечения для PC важно понимать,
что системы типа Windows 95 будут базовыми, а системы типа Windows NT — нет. Эволюционные
изменения гораздо легче принимаются рынком. Для того чтобы революционные изменения были
приняты, они должны быть на порядок лучше, чем те, которые они заменяют. Не на 25 или 33%
лучше, а по крайней мере в 10 раз лучше. Иными словами, изменения должны быть постепенными,
как это наблюдается в Windows 95. Совершенно ясно: такие продукты, как NT, Daytona (NT 3.5) и,
возможно, даже Cairo, занимают слишком маленькую нишу, чтобы представлять существенный ин-
терес. И даже цродажи NT ни к чему не приводят. Сейчас я работаю в сети с NT-сервером, но, веро-
ятно, никакого программного обеспечения для этого сервера купить не смогу. Это не та платформа,
которой можно доверить вашу судьбу.
В-третьих, если вы выбираете платформы для развития программного обеспечения, помните,
что в итоге дело не в технологически высоком качестве, а в проницательной рыночной политике.
Они редко идут рука об руку. Это не означает, что нужно просто кланяться глупым прихотям рын-
ка. Маркетинговая проницательность ведет к стандартизации, а стандарты имеют осязаемо-ощути-
мые выгоды, которые более важны, чем “крутейшие” технические возможности. Да, Windows 95 все
еще использует MS DOS. Она не является полноценной системой WIN32. Она не является пол-
ностью интегрированной. Она не была переписана “от и до”. Да, ей недостает некоторых пре-
красных возможностей, присутствующих в Windows NT или OS/2. Но ни один из этих компро-
миссов не помешает Windows 95 добиться успеха, скорее — поможет. Windows 95 будет
стандартной настольной компьютерной платформой на следующие пять лет. И одно это намного
ценнее, чем самая высокая технология.
30
Обновление индустрии
Глава 1
Добро пожаловать
в Windows 95
Когда вы включаете ПК, работающий под управлением MS DOS версии 6.0 (даже если в после-
дующем намерены запустить Windows 3.1), то первое, что увидите на экране, — это
сообщение “Starting MS-DOS...” (“Стартует MS DOS...”).
Когда же вы включаете ПК, запуская Windows 95, вас приветствует сообщение “Starting
Windows” (“Стартует Windows...”).
Это — наглядная демонстрация (настолько наглядная, насколько может представить ПК) того,
как Microsoft хочет преподнести вам Windows. Microsoft хочет, чтобы вы рассматривали именно
Windows, а не MS DOS, как операционную систему: вы включаете ПК, и он сообщает вам о запус-
ке Windows.
Одна из целей Windows 95 (“Chicago”) — превратить PC в машину, которая больше не будет
вызывать у пользователя недоуменный вопрос, почему он не использует Macintosh (особенно сейчас,
когда цены на машины фирмы Apple упали достаточно низко и машины Мас конкурируют на одном
рынке с PC). Одна из основных задач Windows 95 — создать то, что Microsoft называет стандартом
“NO EXCUSES” для аппаратных средств PC. Адриан Кинг (Adrian King) в своей книге Inside
Windows 95 (р. xxviii) многообещающе заявляет, что с появлением Windows 95 “...фирма Apple
Computer не сможет больше порочить Windows в своих рекламных роликах”. Он, конечно же, имел
в виду телевизионную рекламу Apple, которая представляет Windows чрезвычайно сложной для
настройки и использования. Эта рекламная компания, по-видимому, произвела большое впечатление
на Microsoft. Вполне возможно, что она была одним из мотивов создания так называемой техно-
логии Plug-and-Play.
Многие специалисты (и обозреватели) были поражены тем, как близко Windows 95 подошла к
своей цели — превратить PC в высокопродуктивное и при этом достаточно дешевое Windows-
приспособление. В августовском номере 1994 г. PC World пишет:
“Запуск компьютера, управляемого Chicago, похож на включение тостера — достаточно только нажать
кнопку. Конечно, вы по-прежнему увидите информацию о BIOS и другие иероглифические машинные
сообщения, бегущие по экрану, но их гораздо меньше, чем во времена Windows 3.1. А затем, вместо
старого, опостылевшего приглашения “С>“, ваш ПК будет загружен непосредственно в среду Chicago.
Слева внизу ваше внимание сразу привлечет пиктограмма, изображающая логотип Windows и слово
“Start”. Я говорю “привлечет внимание” потому, что справа установлена стрелка, даже сопровождаемая
надписью “Click here to begin” (Для начала работы щелкните мышью здесь). Эта внешняя
привлекательность быстро надоедает и становится избитой, но зато начинающие поль-зователи никогда
не будут недоумевать, что же делать дальше”.
Действительно, способность при загрузке попадать непосредственно в среду Windows произ-
водит глубокое впечатление. Рис. 1.1 наглядно демонстрирует, что же Microsoft заложила в пользо-
вательский интерфейс Windows 95. Тем не менее во время работы с Windows 95 (начиная с мая
1994 г.) я ловил себя на мысли, что по-прежнему завидую возможностям компьютера Macintosh
Quadra 660 AV моего семилетнего сынишки Мэттью (отличная машина, и стоит менее $2000)/
Windows 95 не сможет превратить пролетарский PC в машину класса Мас. Однако Windows 95
Глава!. Добро пожаловать в Windows 95
31
Рис. 1.1. Как видно из оболочки Chicago, его пользовательский интерфейс существенно лучше, чем в предыдущих
версиях Windows. Даже старые приложения Windows, такие как Microsoft Word 6.0 (показано здесь), в Chicago
смотрятся лучше
определенно повысит долговечность, работоспособность, имидж и репутацию PC. И простая загруз-
ка Windows при включении машины оказывается несомненно важной частью в превращении ПК в
совершенное компьютерное устройство или, по крайней мере, в нечто подобное.
Что же можно сказать об архитектуре Windows, учитывая эту способность непосредственной за-
грузки?
Конечно, вы могли заставить ПК загружать и ранние версии Windows, просто включив команду
WIN в последнюю строку файла AUTOEXEC.BAT. Но Windows 95 должна делать значительно
больше этого. Windows больше не связана с DOS через командный файл, она полностью заменила
DOS. Машина после запуска сообщает не “Starting MS-DOS”, a “Starting Windows”.
Старое сообщение “Starting MS-DOS” осуществлялось скрытым файлом MS DOS, называемым
IO.SYS. Этот файл и второй, под названием MSDOS.SYS, формируют ядро операционной системы
MS DOS.
C:\>dir *.sys /a:h
10 SYS 40.566 09-30-93 6:20a
MSDOS SYS 38.138 09-30-93 6:20a
Как видно из следующего 16-ричного дампа DOS-утилиты debug, стартовый блок (блок началь-
ной загрузки) занимает первый сектор загрузочной части диска и содержит код, предназначенный
для загрузки файла IO.SYS:
32
Неофициальная Windows 95
C:\WINDOWSXfebug
- L 100 2 0 1 ;;; загрузить: устройство. С:, sector 1, адрес 100h
- d 100 300 ;;; выдать дамп по адресу 100h
7431:0100 ЕВ ЗС 90 4D 53 44 4F 53-35 2Е 30 00 02 10 01 00 .C.MSD0S5.0....
7431:02Е0 61 64 79 0D 0А 00 49 4F-20 20 20 20 2D 20 53 59 ady...I0 SY
7431:02f0 53 4D 53 44 4F 53 20 20-20 53 59 53 00 00 55 АА SMSDOS SYS..U.
В Windows 95 сообщение “Starting Windows...” выдается значительно большим файлом
WINBOOT.SYS, который играет в Windows 95 роль, аналогичную роли IO.SYS и MSDOS.SYS в
ранних версиях MS DOS:
C:>dir *.sys /a:h
WINBOOT .SYS 288.030 06-10-94 4:22a
Как нетрудно догадаться, блок начальной загрузки в Windows 95 ищет файл WINBOOT.SYS, а
не файлы IO.SYS и MSDOS.SYS:
C:\WINDOWS>debug
- 1 100 2 0 1
- d 100 300
77АВ:0100 ЕВ ЗС 90 4D 53 57 49 4Е-34 2Е 30 00 02 08 01 00 .C.MSWIN4.0.
77AB:02F0 00 57 49 4Е 42 4F 4F 54-20 53 59 53 00 00 55 АА .WINBOOT SYS..U.
Заметим, что файл WINBOOT.SYS не только заменил скрытые файлы IO.SYS и MSDOS.SYS
ядра MS DOS, но и имя OEM (как это называет Microsoft) в блоке начальной загрузки тоже
изменено с “MSDOS5.0” на “MSWIN4.0”. (См. Undocumented DOS, 2-d ed, p. 408-411 для более
полного знакомства с блоком начальной загрузки.)
Это лишь два примера того, что всюду, где только можно, имя DOS было вычеркнуто, а впи-
сано имя Windows. Судя по реакции компьютерной прессы на Beta-1 (май 1994) версию Chicago,
этот факт, да еще подкрепленный новым интерфейсом Windows 95, произвел на нее неизгладимое
впечатление:
При запуске Chicago — без DOS, поскольку она является полноценной и полноправной операционной
системой — вы увидите, что имеете дело с совершенно другой Windows.
— Windows Magazine, June 1994, p. 184—185.
Chicago обходит DOS и работает полностью в защищенном режиме, хотя и может при запуске оста-
ваться в течение небольшого промежутка времени в реальном режиме, чтобы обработать теперь уже
необязательные файлы CONFIG.SYS и AUTOEXEC.BAT для запуска TSR и старых драйверов
устройств.
— PC/Computing, Jule 1994, р. 60.
Прежде всего, DOS не скрывается более под Windows. Ради совместимости с существующими при-
ложениями Chicago будет признавать и обрабатывать файлы CONFIG.SYS и AUTOEXEC.BAT, но ей
они не требуются.
— Стив Гибсон, InfoWorld, Jule 4, 1994, р. 45.
Все предыдущие версии Windows просто “скрывали” работающую в реальном режиме DOS,
теперь же Windows 95 исключает ее. Вышеприведенные утверждения свидетельствуют о том, что
для запуска Windows 95 больше не нужны файлы CONFIG.SYS и AUTOEXEC.BAT. Windows 95
автоматически загружает все необходимые для запуска Windows файлы — не только WIN.COM, но
и два драйвера: HIMEM.SYS и IFSHLP.SYS — без какой-либо директивы из CONELG.SYS или
AUTEXEC.BAT.
Это аналогично способности MS DOS 6.0 автоматически (по умолчанию) осуществлять пред-
загэузку утилиты сжатия диска DoubleSpace. Все предыдущие программы дискового сжатия, такие
<_
Глава!. Добро пожаловать в Windows 95 33
2 Неофициальная Windows 95
как Stacker, требуют помощи (иногда довольно значительной) от файлов инициализации DOS.
Подобным жеобразом, до появления Windows 95, файлы CONFIG.SYS и AUTOEXEC.BAT требо-
вались для запуска и работы Windows. И так же, как интерфейс предзагрузки, недокументирован-
ный в DOS 6.0 (из-за которого происходил интересный судебный спор между фирмами Microsoft и
Stac Electronics), делает незаметным дисковое сжатие, так и в Windows 95 автомати-ческая загрузка
HIMEM.SYS, IFSHLP.SYS и WIN.COM делается незаметно. Вы просто включаете ПК, а он пре-
подносит вам Windows.
Обход COMMAND.COM...
Имея Windows 95, вы можете обнаружить свойство непосредственной загрузки Windows, даже
если используете файлы CONFIG.SYS или AUTOEXEC.BAT: достаточно после включения машины
на мгновение нажать клавишу <F5>, которая инициализирует так называемый отказоустойчивый
(fail safe) режим, в котором WINBOOT.SYS игнорируем CONFIG.SYS и AUTOEXEC.BAT и
загружает HIMEM.SYS, IFSHLP.SYS, а в некоторых случаях SETVER.EXE и EMM386.EXE.
Затем WINBOOT.SYS загружает WIN.COM, который в Windows 95, как и в предыдущих версиях
Windows, загружает уровень Virtual Machine Manager (VMM) и Virtual Device Driver (VxD), а он,
в свою очередь, загружает графический интерфейс пользователя Windows.
Из всех файлов, загруженных WINBOOT.SYS, потерян один хорошо известный файл:
командный процессор DOS COMMAND.COM. Действительно, WINBOOT.SYS требуется
COMMAND.COM для обработки AUTOEXEC.BAT. Поэтому, если Windows 95 загружается без
AUTOEXEC.BAT, то WINBOOT.SYS будет непосредственно загружать Windows — без копи-
рования интерпретатора команд DOS, находящегося под ним.
Эту достаточно важную особенность подтвердим с помощью простого эксперимента. Если на
машине, на которой выполняется Windows 95, есть файл AUTOEXEC.BAT, то давайте его временно
переименуем во что-то наподобие AUTOEXEC.FOO. Независимо от того, использовался он или не
использовался прежде, создадим фиктивный файл AUTOEXEC.BAT всего с одной строкой, на-
пример “ECHO Hello world” и перезагрузим машину. Для данного эксперимента вовсе не обя-
зательно временно переименовывать CONFIG.SYS в нечто наподобие CONFIG.FOO, но это сделает
нашу демонстрацию еще убедительнее.
После загрузки Windows откройте окно DOS и запустите утилиту Microsoft МЕМ. Взгляните
на рис. 1.2. (Обратите внимание, что имя каталога в приглашении пишется как “Windows”, а не
“WINDOWS’*. Файловая система Windows 95 не только обеспечивает работу с длинными именами
файлов и каталогов, но еще и учитывает регистр букв.)
Теперь давайте посмотрим, что же произошло. Во-первых, если был убран или переименован
файл CONFIG.SYS, то Windows загрузилась самостоятельно, без каких-либо посторонних инст-
рукций. Очевидно, именно WINBOOT.SYS (интересно — и существенно, — что МЕМ распознает
его как MSDOS) обеспечивает загрузку HIMEM.SYS, IFSHLP.SYS и SETVER.EXE. Затем мы
видим, что COMMAND.COM был загружен в память до WIN (и до vmm32, который будет детально
рассмотрен в главе 2). Но присутствует и другая койия COMMAND.COM, загруженная после WIN:
она появилась в результате запуска МЕМ. И наконец, посмотрите на количество доступной памяти:
это 592 Кбайт.
Теперь запустите Windows 95 без файла AUTOEXEC.BAT. Этого можно добиться несколькими
способами: удалить файл AUTOEXEC.BAT, или нажать <F5> для загрузки, или нажать <F8> для
“интерактивной загрузки” и ответить Y (Да) на вопрос “Load Graphical User Interface?” (Загрузить
графический пользовательский интерфейс?). Когда Windows 95 запустится, снова откройте окно
DOS и запустите МЕМ/С. Результат показан на рис. 1.3. ,
34
Неофициальная Windows 95
Рис. 1.2. Утилита МЕМ в окне DOS под Windows 95 демонстрирует, какое программное обеспечение DOS исполь-
зуется для загрузки Windows. В этой конфигурации присутствует файл AUTOEXEC.BAT, поэтому первая копия
COMMAND.COM загружена для его обработки
Давайте внимательно посмотрим, что же получилось. После удалении файла AUTOEXEC.BAT
первая копия COMMAND больше не появляется: МЕМ показывает, что MS DOS загрузила
ШМЕМ, IFSHLP и SETVER, а затем запустила WIN, который, в свою очередь, загрузил VMM32
(см. главу 2). И снова копия COMMAND, загруженная после WIN и VMM32, представляет окно
DOS, из которого мы запустили МЕМ /С. Теперь доступны 601 Кбайт памяти DOS: добавилось 9
Кбайт стандартной памяти.
Итак, отсутствие AUTOEXEC.BAT избавляет от COMMAND.COM. Однако при выполнении
этого небольшого эксперимента возникает иная проблема: мы должны открыть окно DOS и
загрузить копию COMMAND.COM лишь для того, чтобы выполнить МЕМ /С. Несколько
обескураживает тот факт, что приходится загружать COMMAND.COM для выполнения теста,
который пытается доказать, что WINDOWS не нуждается в COMMAND.COM. Можно было бы
запустить МЕМ из PIF-файла и тем самым устранить потребность в COMMAND.COM. Но можно
предположить, что открытие окна DOS для выполнения теста будет иметь и несколько другой
эффект, способный исказить получаемые результаты. Для исследования памяти DOS было бы
значительно лучше применять программу Windows: тогда бы отпала необходимость открывать окно
DOS.
Глава!. Добро пожаловать в Windows 95
35
Рис. 1.3. Если AUTOEXEC.BAT отсутствует, Windows 95 может загрузить графический интерфейс пользователя без
загрузки первоначальной копии COMMAND.COM
Программа WINPSP, описанная в главе 13, идеально подходит для этой цели. PSP (Program
Segment Prefix — префикс программного сегмента) представляет структуру данных реального
режима DOS. Каждая работающая в реальном режиме программа DOS имеет PSP и адрес (в
реальном режиме) программы PSP служит идентификатором процесса. В любое время в реальном
режиме DOS существует единственный текущий PSP, и DOS осуществляет файловый ввод-вывод и
распределение памяти в контексте этого текущего PSP: каждый дескриптор файла сохраняется в
специальной таблице (Job File Table), указатель на которую хранится в PSP, и каждый блок
памяти DOS отмечается в PSP своего владельца.
WINPSP — это программа защищенного режима Windows, которая выводит определенную ин-
формацию о каждом PSP: адрес в реальном режиме и имя владельца (для лучшего понимания обра-
титесь к исходному коду WINPSP.С в главе 13). На рис. 1.4 показаны результаты работы WINPSP,
когда Windows 95 была загружена с фиктивным файлом AUTOEXEC.BAT: COMMAND.COM
присутствует.
На рис. 1.5 отражены результаты работы WINPSP, полученные при отсутствии файла
AUTOEXEC.BAT; COMMAND.COM, естественно, не использовался. Загрузчик Windows
WIN.COM замещает командный интерпретатор DOS COMMAND.COM: COMMAND.COM
загружался по адресу 05F7h, WIN теперь загружается почти по тому же самому адресу — 05F9h.
Прекрасно!
36
Неофициальная Windows 95
Рис. 1.4. WINPSP — 16-разрядная программа Windows, которая обнаруживает DOS-структуры PSP, присутст-
вующие в Windows 95. В этом примере Windows 95 загружена с файлом AUTOEXEC.BAT, поэтому в памяти
присутствует COMMAND.COM
...но еще и обход DOS?
Способность Windows 95 обходиться без COMMAND.COM является источником многих недо-
разумений. Например, один плохо информированный писатель утверждал, даже до появления бета-
версии Windows 95, что:
|Если драйверы устройств или резидентные программы реального режима DOS не загружаются из
CONFIG.SYS или AUTOEXEC.BAT, то Windows 4 может полностью выйти и из реального режима
DOS, полагаясь на операционную систему VMM/VxD.
— Эндрю Шульман, Dr. Dobbs Journal, February 1994.
Даже забыв о стилистических погрешностях, следует признать, что этот автор заблуждался.
COMMAND.COM — это еще не MS DOS. Если нет файла AUTOEXEC.BAT, Windows 95 может
обойтись без COMMAND.COM, но это едва ли означает, что Windows 95 может обойтись без
реального режима DOS.
Глава!. Добро пожаловать в Windows 95
37
Рис. 1.5. WINPSP снова отображает DOS-структуры PSP в системе Windows 95. Однако на этот раз файла
AUTOEXEC.BAT нет и, следовательно, отпадает необходимость в файле COMMAND.COM
1 ' WINPSP Fit 'f x|
,E?W d«ip •t k ...
DOS PSPs (froa HCB chain): §
Real Naas Paras я
1292 BIN eeci
1361 una32 014B
14ВЙ KRNL386 8АЧ6
Windows PSPs (froa task list):
Real Prot Naas Task Size
I202E 1DC7 WNCfiP 1DCE 120 WIN32
I2B1C 1F77 WINBEZMT 1E7E 120 WIN32
2О0Й 1F47 CLOCK 1F36 120 WIN32 •
1FD6 12DF CAB32 206E 120 WIN32
1FC5 137F TIMER 1387 110 fc
2627 145F HSGSRU32 1467 110
14ВЙ BBAF KERNEL32 009F 166 WIN32 ч ч
2061 1C57 WINPSP 1C5F 110 atTI
’O
аде , V. ® 1 * • • •
r в -
Достаточно широко известно, что COMMAND.COM и приглашение С:\> не являются сино-
нимами в MS DOS — вы можете заменить их, просто указав какую-нибудь другую программу в
CONFIC.SIS в команде SHELL=. Но многие, кто действительно понимают разницу между MS DOS
и COMMAND.COM, продолжают усматривать в способности Windows 95 обходить
COMMAND.COM нечто синонимичное обходу реального режима DOS. Например, отвечая на
письма читателей, которые сомневались в том (как утверждалось в июле 1994 г.), что Windows 95
является законченной операционной системой, журнал Windows Magazine (October, 1994, р. 20) в
доказательство своей правоты предложил следующее:
|С бета-версией Chicago можно провести такой эксперимент: переименуйте ваши файлы
AUTOEXEC.BAT и CONFIG.SYS в AUTOEXEC.OLD и CONFIC.OLD, затем перезапустите Win-
dows 95. Вы снова очутитесь в Windows. Вы при этом не увидите командной строки... Это скорее похо-
же на полноценную операционную систему, а не на “среду”, поверх которой работает такая система.
Способность Windows 95 непосредственно загружать Windows, конечно, производит большое
впечатление. Но, поскольку подсказка С:\> не является частью ядра реального режима DOS, этот
впечатляющий трюк на самом деле ничего не говорит о статусе Windows 95 как “полноценной опе-
рационной системе”.
В качестве последнего примера постоянной путаницы в понимании связи между Windows 95 и
DOS приведем замечание одного читателя рукописи данной книги о том, что “Chicago поставляется
с MS DOS 7.0, которая загружается, если пользователь использует файлы CONFIC.SIS и/или
AUTOEXEC.BAT (это означает, что пользователь нуждается в поддержке драйверов устройств и
38
Неофициальная Windows 95
TSR-программ, не рассчитанных на Chicago, а базирующихся на DOS)”. Цосле нескольких часов
экспериментирования (и нескольких бутылок пива) мы обнаруживаем, что Windows 95 всегда
использует MS DOS 7.0, и это выражается не более (но также и не менее) чем в использовании
COMMAND.CON при наличии AUTOEXEC.BAT.
Способность Windows 95 обходить COMMAND.COM стала причиной расхожего мнения, что, в
отсутствие любой TSR-программы или драйверов устройств для реального режима, Windows 95
может “вытеснить” реальный режим DOS. Например, на с. 71 своей полуофициальной книги
Windows 95 изнутри Адриан Кинг (бывший директор программных продуктов Microsoft и очень
хороший писатель) сказал:
“На этапе инициализации Windows 95 незначительно отличается от Windows 3.1. В Windows 3.1 поль-
зователю достаточно набрать команду Win и тем самым начать инициализацию системы Windows.
Windows 95 немедленно получает управление и переходит в защищенный режим, чтобы закончить
процесс инициализации после загрузки, — команда Win не требуется. В любом случае, когда Windows
переключается в защищенный режим, она вытесняет код реального режима и берет на себя управление
компьютером”.
Достаточно сложно понять, что может означает “вытеснение кода реального режима”, кроме как
способность обходить COMMAND.COM. Но вполне очевидно, что имеют в виду компьютерные
журналисты, когда говорят (как мы видели раньше), что “Chicago обходит DOS и полностью вы-
полняется в защищенном режиме” или что “DOS больше не скрывается под Windows”.
Конечно, приятно, что Windows больше не привязана к COMMAND.COM. А загрузка Windows
непосредственно из WINBOOT.SYS (вот, оказывается, откуда такое название!) заставит пользова-
телей по-иному воспринимать свои ПК. Но утверждения о том, что Windows 95 “вытесняет код
реального режима”, “обходит DOS и выполняется полностью в защищенном режиме” или “в
состоянии полностью выйти из реального режима DOS”, безосновательны для любых версий
Chicago, которые появлялись до сих пор, и очень маловероятно, что они получат обоснование в
окончательной коммерческой версии Windows 95.
Как утверждалось ранее, COMMAND.COM не есть MS DOS. Он — не более чем пользовате-
льский интерфейс MS DOS, поставщик знакомого, но презренного приглашения С:\>. Windows 95
позволяет запустить Windows, даже не увидев приглашения С:\>. Замечательно!
Но означает ли это, как пишет Адриан Кинг в Windows 95 изнутри (с. 112), что в Windows 95,
“используя только приложения Windows, вы никогда не выполните какой-либо код MS DOS” и что
“Windows 95 окончательно прервала все связи с кодом реального режима MS DOS”? Нет, это не так.
Существуют многочисленные способы убедиться, что Windows 95 не избавилась от реального
режима DOS, не обходит DOS, не выполняется полностью в защищенном режиме, не порвала все
связи с кодом реального режима DOS и не избавилась полностью от реального режима DOS (что
под этим не подразумевали бы).
Например, в качестве быстрого теста можно вновь запустить утилиту МЕМ, но на этот раз с
опцией /D (Отладка). Следующий результат получен на “чистой” системе Windows 95 без файлов
CONFIG.SYS и AUTOEXEC.BAT. Файл COMMAND.COM отсутствует, поскольку МЕМ
запускалась из PIF-файла:
Adress Name Size Type
000000 000400 Interrupt Vector
000400 000100 ROM Communication Area
000500 000200 DOS Communication Area
000700 10 000360 System Data
CON System Device Driver
AUX System Device Driver
PRN System Device Driver
CLOCKS System Device Driver
A: - C: System Device Driver
C0M1 System Device Driver
Глава]. Добро пожаловать в Windows 95
39
LPT1 System Device Driver
LPT2 System Device Driver
LPT3 System Device Driver
CONFIGS System Device Driver
COM2 System Device Driver
COM3 System Device Driver
COM4 System Device Driver
000А60 MSDOS 0014E0 System Data
001F40 10 003F30 System Data
HIMEM 000480 DEVICE=
XMSXXXXO Installed Device Driver
IFSHLP 000FD0 DEVICE=
IFS$HLP$ Installed Device Driver
SETVER 0002A0 DEVICE=
SETVERXX 000220 Installed Device Driver
000130 OOOCCO FILES=
000100 FCBS=
000200 BUFFERS=
0008F0 LASTDRIVE=
000BA0 STACKS=
005Е80 MSDOS 000400 System Program
005ED0 WIN 0000A0 Environment
005F80 WIN 000B90 Program
006В20 vmm32 0000C0 Environment
006BF0 vmm32 000400 Program
007000 MEM 0000B0 Environment
0070С0 MEM 014550 Program
01В620 MSDOS 0839D0 -- Free --
651,264 bytes total conventional memory
651,264 bytes available to MS-DOS
622,384 largest executable program size
На этой чистейшей из чистых систем Windows 95 (за исключением того факта, что для запуска
МЕМ необходимо открыть окно DOS, — но это можно сделать в один момент) MEM /D не только
подтверждает присутствие DOS, но и убедительно демонстрирует, что об этом и речь не может идти.
J МЕМ (которая является версией утилиты МЕМ, поставляемой с Windows 95) сообщает о
присутствии “Ю” и “MSDOS”. WINBOOT.SYS - это бывшие IO.SYS и MSDOS.SYS, допол-
ненные некоторым новым кодом, симпатичным логотипом и новым именем. WINBOOT.SYS
содержит обработчик прерывания реального режима INT 21h. Программам DOS Windows 95
представляется как MS DOS 7.0 (и 30h функция прерывания INT 21h, и функция 3306b
возвращают 7).
J Присутствуют обычные драйверы устройств DOS, такие как CON, AUX, PRN, CLOCKS.
Достаточно воспользоваться любой утилитой, выдающей шестнадцатеричные или строковые
дампы, и убедиться, что эти драйверы устройств встроены в WINBOOT.SYS так же, как в
предыдущих версиях DOS они были встроены в IO.SYS. Существует также новое встроенное
устройство DOS CONFIGS, которое работает с менеджером конфигурации Plug-and-Play
(CONFIGMG VxD).
J IFSHLP.SYS — это драйвер устройства для реального режима DOS, необходимый для
Windows 95 Installable File System Manager (iFSMGR) VxD, являющегося основой 32-
разрядного файлового доступа (32BFA), длинных файловых имен и встроенной сетевой
х поддержки Windows 95. В главе 8 IFSHLP.SYS рассматривается детальнее.
40
Неофициальная Windows 95
Сейчас же лишь заметим, что один из весьма важных VxD в Windows 95 зависит от драйвера
устройства реального режима DOS. Если IFSMGR не может найти IFSEILP.SYS, то он
прекращает выполнение и сообщает об ошибке: “The Microsoft Installable File System Manager
(IFSMGR) cannot find the helper driver. Please ensure that IFSHLP.SYS has been installed”
(Инсталируемый менеджер файловой системы не может найти вспомогательный драйвер.
Пожалуйста, обеспечьте загрузку файла IFSHLP.SYS).
•f Отображение MEM/D информации о FILES=, LASTDRIVE= и т.д. подтверждает присут-
ствие традиционных для реального режима DOS структур данных.
Небольшое расследование с помощью отладчика DEBUG показывает, например, что струк-
тура текущего каталога DOS (CDS), размер которой можно установить с помощью команды
LASTDRIVE=, при этой проверке была расположена по адресу 049Е:0000. A CDS реального
режима DOS указывает текущий каталог. Я выяснил это таким образом: используя при-
ложение Windows, чтобы прочесть папку (folder) дисковода А:, названную “This is a long
directory name on a floppy”, я открыл окно DOS и воспользовался отладчиком DEBUG для
просмотра CDS по адресу 049Е:0000. Понятно, что структура отражала текущий каталог
дисковода А:
-d 49е:0
049Е:0000 41 ЗА 5С 54 48 49 53 49-53 41 4С 00 00 00 00 00 A:\THISISAL
Конечно, CDS реального режима DOS не знает, как обращаться с длинными именами каталогов
(извините, я, разумеется, имел в виду папку — “folder”), но, во всяком случае, структура данных
присутствует и достаточно понятном виде.
Но выполнение МЕМ требует окна DOS. Возможно, мы наблюдаем реальный режим, потому
что выполняем приложение DOS. Однако, если вы когда-либо запускали Windows NT или OS/2,
то знаете, что окно DOS и программный интерфейс DOS INT 21h могут существовать и без реаль-
ного режима DOS.
Итак, то, что мы видим при выполнении МЕМ, будет лишь иллюзией реального режима DOS.
В конце концов, если МЕМ запускается в окне DOS в Windows NT или OS/2, вы также увидите
подобную информацию.
В качестве примера здесь представлен вывод MEM/D из Windows NT 3.1.
Adress Name Size Type
000000 000400 Interrupt Vector
000400 000100 ROM Communication Area
000500 000200 DOS Communication Area
000700 10 000370 System Data
CON System Device Driver
AUX System Device Driver
PRN System Device Driver
CLOCKS System Device Driver
C0M1 System Device Driver
LPT1 System Device Driver
LPT2 System Device Driver
LPT3 System Device Driver
COM2 System Device Driver
COM3 System Device Driver
COM4 System Device Driver
000А70 MSDOS 0015C0 System Data
002030 10 001C90 System Data
i KBD 000C10 System program
HIMEM 0004E0 DEVICE=
Глава]. Добро пожаловать в Windows 95
41
- XMSXXXXO Installed Device Driver FILES= FCBSx LASTDRIVE= STACKS=
000200 000090 0000Е0 0007D0
003CD0 COMMAND 000950 Program
004630 MSDOS 000070 -- Free --
0046В0 COMMAND 000150 Environment
004810 MEM 000170 Environment
004990 MEM 017550 Prog ram
01BEF0 MSDOS 0840F0 -- Free --
09FFF0 SYSTEM 028000 System Program
0С8000 MSDOS 000170 -- Free --
0С8180 MSCDEXNT 0001C0 Prog ram
0С8350 REDIR 000A60 Program
0C8DC0 DOSX 008950 Prog ram
0D1720 DOSX 000080 Data
0D17B0 MSDOS 00E830 -- Free --
ODFFFO SYSTEM 008000 System Program
0Е8000 10 003620 System Data
MOUSE 003610 System Program
0ЕВ630 MSDOS 0049C0 -- Free --
655360 bytes total conventional memory
655360 bytes available to MS-DOS
636496 largest executable program size
1048576 bytes total continuous extended memory
0 bytes available continuous extended memory
925696 bytes available XMS memory
MS-DOS resident in High Memory Area
Это смотрится, конечно, так же, как будто здесь присутствует реальный режим MS DOS. Одна-
ко, в случае Windows NT, мы знаем, что это не так. Быть может, это справедливо и для Windows
95. Может быть, Windows 95 эмулирует DOS точно так же, как это делают NT и OS/2?
По ходу этой книги мы увидим, что Windows 95 эмулирует DOS в значительно большей сте-
пени. Но мы также увидим, что эта эмуляция не оказывается ни полной, ни новой. По большей
части, но не всегда, Windows 95 эмулирует, а не полагается на код реального режима DOS; точно
так же поступает Windows for Workgroups 3.11 с 32-разрядным файловым доступом.
Еще более важной особенностью, чем сильное сходство между Windows 95 и WfW 3.11, явля-
ется тот факт, что впечатляющая технология эмуляции (хотя и частичная) DOS в Windows 95 про-
исходит из расширенного режима Windows 3.0, представленной в 1990 г. (с тем же успехом можно
назвать ее Windows 90). Хотя 32-битовый доступ к файлам является самой зримой демонстрацией
возможности Windows эмулировать DOS, предпосылки для организации 32-битового доступа к
файлам и Windows 95 можно найти в хорошо документированном, но малоизвестном сервисе VMM,
под названием Hook_V86_Int_Chain. В данной книге^большое внимание будет уделено рассмот-
рению Hook_V86_Int_Chain и выяснению его внутреннего содержания. Короче говоря,
Hook_V86_Int_Chain является основным средством, с помощью которого фирма Microsoft превра-
щает MS DOS в 32-битовую операционную систему в защищенном режиме. И все это прошло мимо
нас (под нашим носом) со времени дебюта Windows 90 — я имею в виду расширенный режим
Windows 3.0. ,
Хотя Windows и может эмулировать некоторые функции DOS в защищенном режиме, все же
необходимо выяснить, опирается ли Windows 95 на реальный режим DOS или нет. Давайте
закроем окно DOS и вернемся к программе WINPSP. Это Win 16-программа. Если “Windows 95
обходит DOS” и “DOS более не скрывается под Windows”, то это должно отображаться в
42
Неофициальная Windows 95
выходных данных WINPSP. Но это не так. Я ничего не говорил раньше об этом, но, когда мы
использовали WINPSP, чтобы убедиться, что Windows 95 может работать без COMMAND.COM,
удалось также показать, что все Windows-программы, выполняемые под Windows 95, имеют PSP
реального режима DOS.
Windows PSPs (from task list):
Real Prot Name Task Size
1314 1FFF CAB32 2006 120 WIN32
12Е1 12EF TIMER 12F7 110
1943 13CF MSGSRV32 13D7 110
1347 1E57 GROWSTUB 1E5F 110
07D7 00A7 KERNEL32 0097 100 WIN32
1379 2687 WINPSP 241F 110
Это, в частности, говорит о том, что и Win32-nporpaMMbi, вроде Windows 95 Cabinet/Explorer
(САВ32), и ядро Win32 (KERNEL32) должны иметь DOS PSP реального режима. Фирма Microsoft
заявляет: “Если вы запускаете только Windows-приложения, то вы никогда не выполняете какой-
либо код MS DOS”. Хорошо, здесь мы работаем только с Windows-приложениями, но тогда кто же
создает эти PSP? В главе 13 мы увидим, что создает их DOS, a Winl6 KERNEL заставляет DOS
создавать их, вызывая недокументированную функцию DOS — 55h функцию INT 2th (создание
PSP). Эта находка, удивительная на первый взгляд, в действительности логично вытекает из других
хорошо известных аспектов архитектуры Windows 95.
J Фирма Microsoft вполне определенно заявила, что оконная система и система обработки сооб-
щений в Windows 95, даже для Win32-пpилoжeний, использует модуль USER из Winl6:
“Большая часть кода в 32-битовой библиотеке User — это не более чем промежуточный слой,
который принимает обращения к 32-битовому API и передает их для обработки своему 16-
битовому двойнику” (А. Кинг, Windows 95 изнутри, с. 185). Все сообщения WM_XXX, даже
те, которые предназначены Win32-пpилoжeниям, сначала обрабатываются в модуле Win 16
USER.
J Система обработки сообщений модуля Winl6 USER, в свою очередь, зависит от структуры
данных Winl6 KERNEL, называемой базой данных задач (Task Database — TDB). Например,
TDB содержит указатель на очередь сообщений приложения (см. Undocumented Windows, р.
379—385). Даже самое поверхностное исследование Windows 95 показывает, что любой
процесс, в том числе и любой процесс Win32, имеет связанную с ним Winl6 TDB.
J В свою очередь TDB зависит от DOS PSP. В своей книге Windows Internals (р. 254-256)
Мэтт Петрек (Matt Pietrec) показал, что функция создания задачи (Create Task) в Winl6
KERNEL вызывает внутреннюю функцию BuildPDB, которая, в свою очередь, вызывает
функцию 55h прерывания INT 21h (PDB означает Process Data Block и является другим
названием PSP).
Если объединить этих три момента, то становится совершенно ясно, что Windows 95 зависит от
интерфейса DOS INT 21h хотя бы для обслуживания PSP, и совершенно неважно, была ли запуще-
на какая-либо другая DOS-программа.
Однако только то, что Windows 95 вызывает прерывание INT 21h, совершенно не означает, что
Windows 95 вызывает код реального режима DOS. Необходимо учитывать тот факт, что вызов пре-
рывания INT 21h в Windows, даже если он поступает от DOS-программ в реальном режиме, драй-
вера устройства или TSR, не обязательно обрабатывается программой DOS в реальном режиме. То,
что процедура создания задач в KERNEL вызывает функцию 55 прерывания INT 21h, вовсе не озна-
чает, что KERNEL обращается к коду реального режима DOS.
Существуют две причины, почему прерывание INT 21h не обязательно означает использование
реального режима DOS:
J Windows запускает реальный режим работы DOS в режиме Virtual-8086 (V86). Это, конечно,
не очень строго. Как вы увидите в главе 9, режим V86 не похож на реальный режим. В дейст-
Глава!. Добро пожаловать в Windows 95
43
витальности, он имеет больше сходства с защищенным режимом работы с памятью 1 М байт.
Лучший способ разобраться во взаимоотношениях Windows и DOS — считать, что Windows
выполняет код реального режима DOS в защищенном режиме. В главе 10 это странное
явление описывается подробнее.
J Windows избегает пересылки большинства вызовов прерывания INT 21h коду реального режи-
ма DOS (который, как только что упоминалось, Windows эффективно выполняет в защищен-
ном режиме) потому, что эти вызовы, независимо от того, исходят они от DOS-программ,
выполняемых в окне DOS, от программного обеспечения DOS, загруженного до Windows,
или от Windows-приложений, обрабатываются виртуальными драйверами VxD. Как отмеча-
лось раньше, сервис Hook_V86_Int_Chain, предоставляемый VMM и используемый драйве-
рами VxD, является основой для эмуляции DOS-интерфейса в 32-битовом защищенном
режиме. В данной книге уделяется большое внимание глубокому рассмотрению внутреннего
содержания Hook_V86_Int_Chain и другого сервиса VMM, Set_PM_Int_Vector. Эти сервисы
позволяют разнообразным драйверам VxD — среди которых важнее всех IFSMgr — обслу-
живать большинство вызовов прерывания INT 21h без обращения к коду реального режима
DOS.
Повсюду в данной книге вы увидите, что система обслуживания DOS PSP, включающая функ-
ции 50h (Set PSP), 51h и 62h (Get PSP), и 55h (Create PSP), не относится к тем сервисам INT 21h,
которые эмулируют в защищенном режиме драйверы VxD из Windows 95. Независимые производи-
тели могли бы написать VxD с использованием функций VMM, которые были доступны и докумен-
тированы с 1990 года, что привело бы к обработке вызовов в защищенном режиме. Интересно поду-
мать над тем, действительно ли такой воображаемый PSP.VXD, при дальнейшем отходе Windows
95 от базового кода реального режима DOS, смог бы качественно изменит сущность Windows 95.
Во всяком случае, каждый раз при запуске приложения в Windows, будь-то DOS-программа,
Win 16-программа или даже новейшее Win32-пpилoжeниe, Windows запрашивает DOS (в случае с
Win32 сложным образом, обсуждаемым подробно в главе 13) по поводу создания PSP.
Нет ничего плохого в такой опоре на DOS (между прочим, управление PSP является единст-
венным примером такой опоры). Но сама идея, что Windows 95 опирается на DOS, так отличается
от того, что говорят о Windows 95 и фирма Microsoft, и пресса, что вы, наверняка, сомневаетесь в
ее правоте.
Итак, давайте разберемся. Возможно, результаты работы WINPSP, рассмотренные ранее, свиде-
тельствуют о некотором уровне эмуляции DOS. Мы уже видели, что утилита МЕМ, выполняемая
под Windows NT, выдает результаты, достаточно похожие на результаты той же утилиты при работе
под Windows 95. Однако нам известно, что Windows NT не базируется над DOS. Что же случится,
если мы запустим WINPSP в Windows NT? Возможно, каждый процесс в NT характеризуется
некоторым фальшивым PSP, и то же самое справедливо для Windows 95. Ниже приведены резуль-
таты WINPSP, полученные с помощью широко используемого сервера Windows NT 3.1.
DOS PSPs(from MCB chain):
Real Name Paras
05B2 , COMMAND 0095
067E KRNL386 9981
Windows PSPs (from task list):
Real Prot Name Task Size
139F0B57 WINPSP 0B1F 220
1D95 03B7 WOWEXEC 03AF 220
Да, несколько задач Winl6 действительно имеют DOS PSP. Между тем, утилита PSTAT фирмы
Microsoft из Win32 NT SDK, показывает, что выполняются более 30 различных процессов, включая
CSRSS (Client Server Runtime Sybsystem, которая обслуживает окна и графические запросы),
LMSVCS (переадресация), SFMSVC (обработка сообщений), NETDDE и RASMAN (Remote Access
Connection Manager).
44
Неофициальная Windows 95
C:\WINNT>pstat I find “pid"
pid: 0 pri: 0 (null)
pid: 7 pri: 8 (null)
pid: 1c pri: 11 SMSS.EXE
pid: 14 pri:13 CSRSS.EXE
pid: d pri:13 WINLOGON.EXE
pid: 46 pri:13 SCREG.EXE
pid: 41 pri:10 LSASS.EXE
pid: 3f pri: 7 SPOOLSS.EXE
pid: 2f pri: 7 EVENTLOG.EXE
pid: 68 pri: 7 LMSVCS.EXE
pid: 63 pri: 7 NETDDE.EXE
pid: 5b pri: 8 (null)
pid: 71 pri: 7 CLIPSRV.EXE
pid: 6c pri: 7 SFMSVC.EXE
pid: a5 pri: 7 SFMPRINT.EXE
pid: a3 pri: 8 (null)
pid: 97 pri: 7 MSGSVC.EXE
pid: 93 pri: 7 RASMAN.EXE
aid: 90 pri: 7 ATSVC.EXE
pid: 8c pri: 7 UPS.EXE
pid: c8 pri: 7 RASSRV.EXE
pid: b5 pri:13 NDDEAGNT.EXE
pid: ad pri:13 PROGMAN.EXE
pid: ab pri: 7 NTVDM.EXE
pid: e3 pri: 7 PRINTMAN.EXE
pid: b1 pri:24 0S2SRV.EXE
pid: 82 pri: 8 0S2SS.EXE
pid: b7 pri: 7 CMD.EXE
pid: cd pri: 7 NTBACKUP.EXE
pid: 35 pri: 7 WINFILE.EXE
pid:105 pri: 7 CMD.EXE
pid: d4 pri: 7 PSTAT.EXE
Ни один из этих сервисов не появляется в WINPSP. Почему? Зачем сервисам Win32 в опера-
ционной системе NT понадобились DOS PSP в реальном режиме? Очевидно, они не нужны. В рав-
ной степени должно быть ясно, я надеюсь, что процессы Win32 в Windows 95 действительно тре-
буют DOS PSP из реальном режиме.
Итак, у нас остается только то, что Windows 95 может обходиться без COMMAND.COM с его.
противной командной строкой. Это великолепно. Windows 95 может загружаться непосредственно в
Windows без CONFIG.SYS или AUTOEXEC.BAT. Фантастика! Но что же подразумевают, когда
говорят, что Windows 95 “не нужна DOS”? Казалось бы, именно то, что сказано.
Мы скоро увидим, что WINBOOT.SYS содержит старый код DOS реального режима и что
Windows 95 обращается к ней довольно часто (хотя и в режиме V86, котором, как вы помните,
можно считать практически защищенным режимом). Теперь, уровень VxD в Windows 95
действительно обрабатывает большинство вызовов INT 21h полностью в 32-битовом защищенном
режиме, без обращения к DOS. Это заслуживает двух, а может быть и трех “ура”. Однако 32-
битовый доступ к файлам в WfW 3.11 обеспечивает выполнение тех же операций, но сопро-
вождается значительно меньшими фанфарами, чем Windows 95. На самом деле, как мы увидим в
главе 8, пресса обычно жаловалась на то, что WfW 3.11 “обходит DOS” по 32BFA, воспринимая
это как некую технической ошибку, приводящую ко всевозможным проблемам DOS-совместимости.
Интересно, что могло бы означать заявление: “Windows 95 полностью обходит DOS”?
Возможно, оно просто связано с комплектацией Windows 95. Например, в обзорном руководст-
ве фирмы Microsoft по Chicago {Microsoft Windows “Chicago” Revier’s Guide, p. 275) говорится,
что “Chicago будет законченной, интегрированной операционной системой с защищенным режимом
работы, которая не требует или не использует отдельную версию MS DOS”. Обратите внимание,
Microsoft не говорит, что Chicago не будет требовать или использовать DOS. Фирма Microsoft лишь
Глава!. Добро пожаловать в Windows 95
45
-ч.
утверждает, что Chicago не будет требовать или использовать отдельную версию MS DOS. Воз-
можно, Microsoft как раз и говорит нам о том, что все, раньше функционально связанное с MS DOS,
теперь будет распространяться под впечатляющим именем Windows. Если же все, что раньше счита-
лось частью MS DOS, теперь, согласно административному указанию, является частью Windows, тог-
да, я полагаю, заявление о том, что “вы никогда не будете выполнять код MS DOS” имеет смысл.
Зачем пытаться опровергать
заявления Microsoft?
I
Я не думал тратить так много времени на рассмотрение вопроса об использовании DOS PSP в
Windows 95. Это только часть архитектуры Windows. Но контраст между действительными взаимо-
отношениями Windows 95 и DOS и заявлениями фирмы Microsoft о том, что Windows 95 обходит
DOS, показателен с точки зрения более общих заявлений фирмы о Windows 95.
Например, Microsoft также утверждает, что ядро Win32, KERNEL32.DLL “абсолютно незави-
симо от своего 16-битового двойника. При этом возможны отдельные обращения к 32-битовому коду
со стороны 16-битового, однако 32-битовый модуль Kernel никогда не обращается к 16-битовому
коду”. (А. Кинг, Windows 95 изнутри, с. 185). Как и утверждение о независимости Windows 95 от
DOS, сообщение о независимости KERNEL32 от ядра Winl6 (KRNL386.EXE) далеко от истины.
Дело здесь не в том, чтобы опровергнуть заявления фирмы Microsoft о Windows 95. Немногие
взрослые люди ожидают увидеть “правду в рекламе”, даже тогда, когда эта реклама представлена в
виде технической документации, описаний архитектуры или официальных технических изданий.
Было бы бессмысленно ожидать от читателей, что они преодолеют 500 страниц опровержений тор-
говой пропаганды фирмы Microsoft и ее отражение в компьютерной прессе.
Вместо этого, хорошенько разобравшись с некоторыми подобными заявлениями, которым и по-
священа настоящая книга, мы получим четкое представление о работе Windows 95 и ее отличиях и
сходствах с предыдущими версиями Windows и MS DOS.
При углубленном исследовании нескольких избранных аспектов Windows 95, в частности вза-
имоотношений Windows и DOS, ядер Win32 и Winl6, будут выяснены многие аспекты данного
продукта, например: сложный для понимания слой Windows VMM/VxD, Win32 API, thunking, 32-
битовый доступ к файлам, загрузка исполняемых файлов и файлы отображения памяти.
Если мы зададимся несколькими специфичными (может быть, даже чересчур специфичными)
сложными вопросами о Windows 95, то, возможно, в конце концов у нас появится некоторое пред-
ставление об ее архитектуре. Оно может не соответствовать представлению об архитектуре фирмы
Microsoft, но окажется более правдоподобным.
Эти вездесущие диаграммы фирмы Microsoft
Наше сосредоточенное исследование — хорошая альтернатива простому изучению архитек-
турных диаграмм фирмы Microsoft, имеющих несчастливую тенденцию либо навевать сон, либо вы-
зывать состояние, известное под названием MEGO (Мои глаза становятся стеклянными).
В то же время, я полагаю, что все-таки стоит уделить некоторое внимание двум таким
диаграммам. Я уверен, что вы уже видели их или им подобные, поскольку они появляются почти в
каждой компьютерной статье о Windows 95. Большинство людей (в том числе и я), наталкиваясь на
такие диаграммы в журнале, беспечно позволяют убаюкать себя волнам технологического блажен-
ства, вызванным всеми этими стрелками и рамками. Иначе говоря, такие диаграммы оказывают на
простых смертных нечто вроде гипнотического влияния, вызывая ощущение, что программное обес-
печение разрабатывалось, — хотя оно, скорее всего, просто “разрослось”.
На рис. 1.6 приведена первая диаграмма фирмы Microsoft. Кажется, что эта диаграмма появляет-
ся буквально везде. (Точно такая же диаграмма приводится в журнале Windows Magazine, July 1994,
р. 187; лучшего качества, но того же содержания — в журнале PC Magazine, April 12, 1994, р. 193;
с превосходным качеством, того же содержания — в журнале PC World, August 1994, р. 134).
46
Неофициальная Windows 95
Круг 3. (системная VM)
Win32*
арр
Win32*
арр
Win32*
арр
Системные
сервйсы:
ядро
графика
окна
Win 16
арр
Win 16
арр
Круг 3
(MS DOS VM)
Круг 3
(MS DOS VM)
Круг О
Файловая система защищенного
режима:
VFAT.CDFS, SCSI, Network
Менеджер виртуальной машины:
Обработчики страниц и событий,
сервер DPMI
Рис. 26. Интегрированная архитектура Chicago для выполнения приложений
MS DOS, Winl6 и Win32.
Рис. 1.в. Типичная диаграмма фирмы Microsoft, представляющая архитектуру Windows 95
Название данной диаграммы, предлагаемое самой фирмой Microsoft, немного отличается от
приведенного мной. Та, что появилась в Windows “Chicago” Revier’s Guide (May 1994) — отлично
написанном 300-страничном документе, обнародованном Microsoft (и, вполне обдуманно, без каких-
либо упоминаний об авторских правах, что привело к появлению в течение следующих шести меся-
цев десятков книг, содержащих авторизованный пересказ Windows “Chicago Revier’s Guide), —
была озаглавлена “Интегрированная архитектура Chicago для выполнения приложений MS DOS,
Win 16 и Win32”.
Мы должны вернуться к выражению “интегрированная” потому, что это чрезвычайно важная
часть рекламной шумихи — простите, я имею в виду рассматриваемую техническую документацию —
вокруг Windows 95.
Между тем данная диаграмма имеет весьма примечательную особенность: MS DOS опускается.
Да, на диаграмме действительно показаны два DOS-блока, но я подразумеваю саму MS DOS или,
если вам больше нравится, WINBOOT.SYS. Где же она? Надеюсь, фирма Microsoft (и те, кто
перерабатывали ее художества) и не предполагает, что WINBOOT предназначен только для
загрузки Windows (200 Кбайт — чересчур много для загрузочного кода) и что он просто исчезает
(или, возможно, удаляется) при появлении самой Windows. Надеюсь, выходные данные МЕМ,
представленные раньше, показали, что код реального режима WINBOOT.SYS (который Собственная
программа МЕМ фирмы Microsoft упорно называет IO и MS DOS) остается в памяти при работе
Windows. Так где же WINBOOT на диаграмме? Аналогично, где же IFSHLP.SYS (который, как
отмечалось ранее, является DOS-драйвером реального режима и необходим для IFSMgr)? Эти
важные компоненты режима V86 ради удобства удалены из архитектурной диаграммы фирмы
Microsoft.
Глава 1. Добро пожаловать в Windows 95
47
Конечно, диаграмма — это абстракция. Она не может и не должна отражать все детали. Много
других деталей также опущено из этой диаграммы: в Круге 0 нет упоминания о важных аппаратно-
ориентированных драйверах VxD, таких как VPICD, VDMAD, VKD, VDD VMD и VTD (т.е. Vir-
tual Programmable Interrupt Control Device, Virtual Direct Memory Access Device, Virtual Keyboard
Device, Virtual Display Device, Virtual Mouse Device и Virtual Timer Device). Нет упоминания о
VxD-драйверах DOSMGR, или SHELL, или VWIN32; в частности VWIN32 играет жизненно важ-
ную роль в Windows 95. Диаграмма даже не отображает то, что системная виртуальная машина
(VM) находится над 16-битовыми драйверами устройств Windows (совершенно отличными от драй-
веров VxD), таких как KEYBOARD, MOUSE, DISPLAY или SYSTEM. Но, очевидно, этих компо-
нентов так много, что их нельзя втиснуть в одну диаграмму, не перешагнув за допустимый уровень
MEGO.
Тем не менее я считаю исключение WINBOOT, т.е. MS DOS, из этой диаграммы весьма сущест-
венным моментом. Фирма Microsoft намерена убедить не только конечных пользователей, но и раз-
работчиков в том, что DOS исчезла и что программное обеспечение для Windows 95 PC слилось
воедино, подобно интегральной схеме. Программисты — тоже люди, и они хотят достаточно хорошо
прочувствовать операционную систему, с которой они работают. Поэтому Microsoft и подсовывает
разработчикам подобные диаграммы, из которых MS DOS просто удалена.
Вторая диаграмма приведена на рис. 1.7. Она впервые появилась в руководстве Chicago
Reviewer's Guide и, видимо, тоже должна стать промышленным стандартом в компьютерной лите-
ратуре (см. например, точную копию в журнале Windows Magazine, Jule 1994, р. 186).
◄----- --------►
Рис. 25. Система распределения кода в Chicago.
Рис. 1.7. Стандартная диаграмма фирмы Microsoft, показывающая взаимодействие 32- и
16-битовой частей в Windows 95
Здесь ключевым является самый нижний слой, демонстрирующий предполагаемый однона-
правленный поток от Kernel 16 к Кегпе132 (действительные названия KRNL386.EXE и
KERNE32.DLL). В главе 13 показано, что на самом деле связь между ядром Winl6 и Win32 явля-
ется двусторонней, точно так же, как и связь между модулями USER и GDI из Winl6 и Win32. И,
как отмечалось ранее, (обращение KERNEL32 к KRNL386, в любом случае, логически исходит из
того, что USER32 опирается на Winl6 USER.
48
Неофициальная Windows 95
Версия данной диаграммы в InfoWorld (July 4 1994, р. 46) переплюнула даже саму Microsoft,
утверждая, что “все основные функции находятся на 32-битовой стороне, поэтому все 16-битовые
вызовы преобразуются1 в 32-битовые”. Фирма Microsoft никогда не делала подобных заявлений, и
даже быстрый анализ кода Winl6 KERNEL (с использованием отладчика для быстрого просмотра
нескольких функций Winl6 KERNEL, таких как GlobalAllok, LoadModule или LoadResource)
показывает, что это не так.
Декларируемая интеграция
Тогда почему же такое большое желание не только со стороны фирмы Microsoft, но и умных и '
знающих компьютерную технику журналистов, поверить в то, что Windows 95 представляет
качественно новую операционную систему, не использующую MS DOS, за исключением, возможно,
недолго работающей загрузочной программы?
По сравнению с реальным положением дел здесь нет ничего категорически неправильного. В
последующих главах будет показано, что, поскольку режим V86 является действительно разновид-
ностью защищенного режима, использование DOS не подрывает статус Windows 95 как подлинной
операционной системы. Аналогично, нет ничего неправильного в том, что KERNEL32 по некоторым
функциональным возможностям опирается на Winl6 KERNEL. Адриан Кинг замечает (Windows 95
изнутри, с. 185), что использование USER32 кода Winl6 USER API представляется “разумным
способом использования испытанного временем кода”. Эта же общая точка зрения справедлива и по
отношению к KERNEL32 и Win 16 KERNEL.
Итак, если с архитектурой Windows 95 все в порядке и если фирма Microsoft заложила в нее
правильные компромиссы, хогда зачем это ложное представление о Chicago как о некоторой “вол-
шебной земле”, из которой изгнана DOS и где код 32-битового ядра никогда не использует код 16-
битового ядра?
Ответ на этот вопрос, я думаю, заключается в постоянном использовании неясного термина “ин-
тегрированная” для описания Windows 95. Microsoft продолжает повторять каждому, что Chicago
является “интегрированной”. Например, собственная подпись к рис. 1.6 фирмы Microsoft — “Интег-
рированная архитектура Chicago”. В другом месте руководства Reviewer's Guide говорится:
“Chicago будет законченной интегрированной операционной системой защищенного режима, которая
не будет требовать или использовать отдельной версии MS DOS”; далее там говорится:
“Первое, что увидят пользователи Windows 3.1 и MS DOS при включении компьютера (вернее, не
увидят), это отсутствие командной строки DOS, которая прежде была необходима им для вызова
Windows. Chicago — интегрированная операционная система, обладающая многозадачным ядром,
которое загружается непосредственно в графический интерфейс пользователя, да еще обеспечивает
полную совместимость с операционной системой MS DOS.”
Слово “интегрированная” так часто используется для описания Windows 95, что вы быстро пе-
рестаете думать о значении этого термина, просто принимаете его как должное и бездумно повто-
ряете. Интеграция является одним из тех великолепных слов, которое приятно повторять.
Что же скрывается за этим термином?
Я думаю, все очень просто. Тема об “интегрированности” — это часть ответа фирмы Microsoft
фирме Мас. Как отмечалось в начале данной главы, главная цель Chicago — изменить восприятие
Windows и PC. Заявление, что Windows 95 является интегрированной, как я полагаю, означает, что
Windows 95 должна содержать все, а значит, не требовать MS DOS. Таким образом фирма Micro-
soft может создать впечатление, что PC превращается в цельную “бесшовную” Windows-машину.
Как мы видели, при включении PC Windows 95 выполняет превосходную работу, вводя вас
прямо в Windows. Мы также видели, что это не основано на независимости от DOS-кода реального
1 В оригинале используется слово “thunking”. Вообще, словцо thunk накрепко вжилось в лексикон Windows 95.
Если верить Адриану Кингу, то “...это слово в ходу в качестве существительного, глагола, прилагательного, а подчас и
оскорбления", но исходное определение: “фрагмент кода, который позволяет вам что-то сделать” (Windows 95 изнутри,
с. 181). — Прим. ред. ।
Глава 1. Добро пожаловать в Windows 95 49
режима. Microsoft взяла код, прежде известный как MS DOS, изменив его для запуска HIMEM,
IFSHLP и WIN (во многом потому, что он раньше был изменен для запуска DBLSPACE),
переименовала его в WINBOOT и поместила его в тот же самый пакет, что и саму Windows. Это
совершенно приемлемая реализация, но это не то, что я назвал бы целостным или интегрированным,
и это, конечно, не убирает DOS с дороги.
Так почему же происходит обратное? Потому что, если Microsoft признает, что Windows 95
основана на MS DOS 83, хоть даже и совершенно допустимым образом, то ей будет значительно
сложнее утверждать, что PC — цельная Windows-машина или что с выходом Windows 95 “не будет
никакого смысла покупать Apple Macintosh”, как заявил в своей заметке Билл Гейтс (Computer
Reseller News, March 21, 1994).
Существуют дополнительные причины, по которым Microsoft заявляет, что Windows 95 пред-
ставляет собой интегрированную, целостную, завершенную операционную систему. OS/2, я думаю,
гораздо менее важна в глазах Microsoft, чем Apple Macintosh. OS/2 не представляет большой
угрозы для Microsoft, но иногда осмеяние поклонниками OS/2 связи Windows-DOS как “одного-
на-другом” задевает за живое.
Возможно, тема интеграции для фирмы Microsoft олицетворяет стратегию, направленную все
более тотальное использование Microsoft Office. Кроме обычных рассуждений о том, что любой про-
изводитель прикладных пакетов мог бы эффективно использовать все преимущества интеграции, тут
кроется и собственная точка зрения Microsoft: желательно, чтобы все программное обеспечение вы
получали в одном месте — в Microsoft.
Самое смешное,, что действительное отсутствие интеграции программного обеспечения (в зави-
симости, конечно, от смысла, реально вкладываемого в этот термин) раньше считалось достоин-
ством. Не так давно отсутствие интеграции программного обеспечения называлось модульностью. В
этом смысле Windows 95, подобно предыдущим версиям расширенного режима Windows, не осо-
бенно интегрирована и оказывается довольно модульной.
Как мы увидим в главе 13, существует два уровня в Windows, практически ничего не знающие
друг о друге: уровень VMM/VxD внизу и уровень KERNEL/USER/GDI вверху. Преимущество
такой модульности заключается в возможности выполнения одного уровня без другого.
Отрицательная сторона этой модульности, или недостатка интеграции, в том, что KERNEL
Winl6, в частности, должен зачастую обманывать уровень VxD. Например, в то время как IFSMgr
поддерживает текущий каталог на уровне виртуальной машины, для многих приложений Winl6 и
Win32 в системной VM (каждое из которых требует собственного текущего каталога) это ни к чему
хорошему не приводит. Windows 95 практически ничего не делает для связывания этих двух частей
Windows.
С другой стороны, как показывают главы 3 и 4, задолго до Windows 95 существовала удиви-
тельная степень интеграции между DOS и Windows. В файле README.WRI, включенном в
Windows 3.1 и 3.11, констатируется, что: “Microsoft Windows и MS DOS работают вместе как инте-
грированная система”. Вот вам опять “интегрированность”, но в этот раз она относится к комби-
нации DOS и Windows. Если DOS и Windows были интегрированы и Windows 95 интегрирована,
то в чем же различие?
В письме журналу Dr. Dobbs Journal (January 1994, p. 10), отвечая на более раннюю статью за
сентябрь 1993 г., вице-президент Microsoft Брэд Сильверберг (Brad Silverberg) писал:
I “Windows тесно связана с основной операционной системой MS DOS. Эта связь базируется на большом
количестве очень схожих поведенческих характеристик MS DOS...”
В частном разговоре Брэд несколько раз использовал слово “бесшовный” (или “бесслойный”, в
оригинале seamless. — Прим, ред.) для описания того, как Windows и DOS работают вместе. Главы
3 и 4 показывают, что беспристрастный термин “интегрированный” из Windows README.WRI
довольно точно описывает связи между расширенным режимом Windows 3.1 и MS DOS 5 и 6.
Windows не была обычной прикладной программой, которая выполнялась “над” MS DOS.
Но если Windows 3.1 и DOS 6 были “интегрированы”, “тесно связаны” и являлись частями
почти единого “бесшовного” продукта, тогда что же_привносит в интеграцию DOS-Windows
Windows 95? Просто два компонента теперь собраны в одной коробке?
50 Неофициальная Windows 95
Windows 95 и DOS
Итак, довольно язвительных замечаний. Как мы видели, Windows 95 не обходит DOS, не ос-
тавляет DOS в стороне и не может полностью избавиться от DOS. Как же тогда Windows 95
связана с MS DOS? Каково разделение труда в Windows 95 между Windows и DOS?
Это, конечно, зависит от того, что в вашем понимании означает “DOS”. Если вы имеете в виду
исключительно интерфейс INT 21h, то Windows 95, расширяет роль DOS, поскольку длинные име-
на файлов в Windows 95 поддерживаются на совершенно новом наборе вызовов INT 21h, использу-
ющих функцию 71h.
Однако расширенная роль INT 21h в Windows 95 практически ничего не говорит нам о связи
Windows 95 с MS DOS, поскольку INT 21h — всего-навсего интерфейс: например, как говорилось
ранее, любой VxD может использовать Hook_V86_Int_Chain или Set_PM_Int_Vect сервисы VMM
для обеспечения собственной 32-битовой эмуляции INT 21h или любого другого, основанного на
прерывании, интерфейса в защищенном режиме. Именно так IFSMgr VxD обеспечивает 32-битовый
доступ к файлам. На самом деле, таким же образом Windows 95 и обеспечивает новую 71h функ-
цию прерывания INT 21h: обработчики INT 21h из IFSMgr, установленные с помощью
Hook_V86_Int_Chain и Set_PM_Int_Vect, отслеживают вызовы функции 71h и обрабатывают их
полностью в защищенном режиме.
Вместо того чтобы сосредотачиваться на интерфейсе INT 21h, намного полезнее задать вопрос,
каким же образом Windows 95 связана с базовым кодом реального режима MS DOS?
Но что означает фраза “базовый код реального режима MS DOS”? Любой код реального режи-
ма, который использует Windows 95, никогда не выполняется в реальном режиме; Windows 95 всег-
да использует — ну, хором! — одномегабайтовую форму защищенного режима, называемую вир-
туальным 8086-режимом. Под “кодом реального режима MS DOS” в действительности подразуме-
вается код, который в течение ряда лет был частью MS DOS. Например, если программа, выполня-
ющаяся под DOS в реальном режиме, вызывает функцию 62h прерывания INT 21h, чтобы получить
текущий PSP, то где-нибудь в MSDOS.SYS имеется блок кода, который и обрабатывает этот вызов.
Что случается с этим блоком кода в Windows 95? Используется ли он когда-либо после инициа-
лизации Windows 95, когда выполняется графический интерфейс пользователя? Используется ли он
на “чистой” системе Windows 95, которая загружалась без CONFIG.SYS, AUTOEXEC.BAT или
COMMAND.COM, в которой пользователь выполняет только приложения Windows? Будет ли код
DOS использоваться в “суперчистой” системе Windows 95, в которой пользователь выполняет
только новые Win32-пpилoжeния? И если так, то почему? Из-за какой конкретной функции INT
21h приложения Winl6 и Win32 зависят от кода DOS реального режима?
Чтобы компетентно ответить на эти вопросы, мы для начала должны хорошенько разобраться с
самим кодом DOS реального режима. Во втором издании Undocumented DOS целая глава посвяще-
на этому коду, в частности функции обслуживания INT 21h (см. Disassembling DOS, р. 265-341).
Программа INTCHAIN, включенная в Undocumented DOS, весьма полезна для изучения функции
обслуживания DOS INT 21h, даже когда находится в конце длинной цепочки прочих обработчиков
INT 21h. Когда вы даете команду INTCHAIN сгенерировать обращение к INT 21h, программа трас-
сирует выполнение INT 21h и сообщает обо всех обнаруженных обработчиках INT 21h. Например,
следующий результат INTCHAIN выдала под MS DOS 6.2 при вызове 62h функции INT 21h
(получение PSP):
C:\UNDOCDOS>intchain 21/6200
1DD3:16B4 SMARTDRV
105D:0498 IFS$HLP$
0019:40F8 DOS
Последняя строка INTCHAIN представляет наиболее вероятного хозяина обработчика INT 21Н.
В этой конкретной конфигурации функция обработчика находится по адресу 0019:40F8. Мы можем
использовать DEBUG или любой подобный отладчик для исследования кода, размещенного по
этому адресу, чтобы выяснить, действительно ли это обработчик INT 21h из MS DOS. Я добавил
несколько комментариев к коду, дизассемблированному DEBUG:
Глава]. Добро пожаловать в Windows 95
C:\UNDOCDOS>debug
-u 19:40f8
0019:40F8 FA CLI
0019:40F9 80FC6C CMP AH.6C ;;; 6Ch - максимальный номер функции в DOS 6
0019:40FC 77D2 JA 40D0 ;;; если больше, чем 6Ch - ошибка
0019:40FE 80FC33 CMP AH, 33
0019:4101 7218 JB 411B
0019:4103 74A2 JZ 40A7
0019:4105 80FC64 CMP AH, 64
0019:4108 7711 JA 411B
0019:410A 74B5 JZ 40C1
0019:410C 80FC51 CMP AH, 51 ;;; Получить PSP
0019:410F 74A4 JZ 40B5
0019:4111 80FC62 CMP AH, 62 ;;; Получить PSP
0019:4114 749F JZ 40B5
0019:4116 80FC50 CMP AH, 50 ;;; Установить PSP
0019:4^19 / 748E JZ 40A9
Прекрасно видно, что две функции получения PSP DOS (51h и 62h) в этой конфигурации
обрабатываются по адресу 0019:40В5, а функция установки PSP (50h) — 0019:40А9. Давайте рас-
смотрим код DOS реального режима для этих функций:
-и 19:40а9 0019:40А9 1Е 0019:40АА 2Е 0019:40АВ 8Е1ЕЕ730 ' 0019:40AF 891E3003 0019:40ВЗ 1F 0019:40В4 CF . ;;; ищем функцию 21/50 (Установить PSP) PUSH DS CS: MOV DS,[3DE7] ;;; настроить DOS DS за DOS CS MOV [0330],BX ;;; поместить новый PSP в DOS:[330h] POP DS IRET
-и 19:4065 0019:40В5 1Е 0019140В6 2Е 0019:40В7 8E1EE73D 0019:40В7 8В1Е3003 0019:40BF 1F 0019:40С0 CF ;;; Ищем функции 21/51,62 (Получить PSP) PUSH DS CS: MOV DS,[3DE7] ;;; настроить DOS DS MOV BX,[0330] ;;; вернуть DOS:[330h] в BX POP DS IRET
Этот код выполняется всякий раз, когда программа, работающая в реальном режиме DOS, уста-
навливает или получает текущий PSP. Мы видим, что код достаточно прост: установка PSP всего-
навсего пересылает содержимое ВХ по смещению 330h в сегменте данных DOS, а получение PSP
возвращает значения этой той же ячейки памяти. Смещение 330 в сегменте данных DOS оказы-
вается смещением 10h в области обмениваемых данных (SDA — Swappable Data Area), структуре
данных DOS, которую все версии Windows расширенного режима, включая Windows 95, объявляют
как данные реализации (см. главу 4 ).
Теперь вопрос в том, является ли тот же самый код также и частью Windows 95 и, что еще важ-
нее, играет ли он какую-то роль в Windows 95, кроме обеспечения совместимости с программами и
драйверами реального режима DOS.
Давайте сначала проверим, присутствует ли он в Windows 95 вообще. Самый простой способ со-
стоит в том, чтобы выполнить INTCHAIN и DEBUG теперь ужё в окне DOS в Windows 95 (вскоре
мы постараемся избавиться от окна DOS):
С: \UNDOCDOS>intchain 21/6200
00A6:0FAC DOS
0748:41Е4 DOS
52
Неофициальная Windows 95
C:\UNDOCDOS>debug
-u 748:41e4
0748:41Е4 FA CLI
0748:41Е5 80FC73 СМР АН, 73 ;;; 73h - максимальный номер функции в Windows 95
0748:41Е8 77D2 JA 41ВС ;;; если больше 73h - ошибка
0748:41ЕА 80FC33 СМР АН, 33
0748:41ED 7218 •JB 4207
0748:41 ЕЕ 74А2 ’ JZ 4193
0748:41F1 80FC64 СМР АН, 64
0748:41F4 7711 JA 4207
0748:41F6 74В5 JZ 41AD
0748:41F8 80FC51 СМР АН, 51 ;;; Получить PSP
0748:41FB 74А4 JZ 41А1
0748:41FD 80FC50 СМР АН, 50 ;;; Установить PSP
0748:4200 7493 JZ 4195
0748:4202 80FC62 СМР АН, 62 ;;; Получить PSP
0748:4205 74F4 JZ 41FB ;;; JZ JZ 41А1
Это почти идентично коду MS DOS 6.20, за исключением того, что Windows 95 поддерживает
функции INT 21h вплоть до 73h. Теперь давайте убедимся, что функции получения/установки PSP
выглядят в Windows 95 так же, как и в DOS:
-и 748:4195 ;; ищем функцию 21/50 (Установить PSP)
0748:4195 1Е PUSH DS
0748:4196 2Е CS:
0748:4197 8E1E273F MOV DS,[3F27] ; ;; настроить DOS DS за CS
0748:419В 891Е3003 MOV [0330],BX ; ;; поместить новый PSP в DOS:[330h]
0748:419F 1F POP DS
0748:41АО CF IRET
-и 748:41А1 ;; ищем функции 21/51,62 Получить PSP)
0748:41А1 1Е PUSH DS
0748:41А2 2Е CS:
0748:41АЗ 8E1EE73D MOV DS,[3F27] ; ;; настроить DOS DS
0748:41А7 8В1Е3003 MOV BX,[0330] ; ;; вернуть D0S:[330h] в ВХ
0748:41АВ 1F POP DS
0748:41АС CF IRET
Неудивительно, что это почти идентично реализации MSDOS.SYS из DOS 6.2. Немного пово-
зившись с DEBUG, можно выяснить, что в Windows 95 этот код DOS является частью файла
WINBOOT.SYS:
С:\WIND0WS>debug \winboot.sys
-s 0100 ffff fa 80 fc 73 : i i поиск в файле CLI / CMP AH, 73
1E73:E8C1 ё : DEBUG нашел
-u 1e73:e8c1
1E73:E8C1 FA CLI
1E73:E8C2 80FC73 CMP AH,73
1E73:E8C5 77D2 JA E899
1E73:E8DA 80FC50 CMP AH,50
1E73:E8DD 7493 JZ E872
-u 1e73:e872 обработчик 21/50 (Получить PSP)
1E73:E872 1E PUSH DS
1E73-.E873 2E CS:
1E73.E874 8E1E273F MOV DS,[3F27]
1E73-.E878 891E3003 MOV [0330],BX
1E73:E87C 1F POP DS
1E73:E87D CF IRET
Глава! . Добро пожаловать в Windows 95
Мы могли бы отыскать код и для большего числа функций INT 2111, но и полученных резуль-
татов вполне достаточно, чтобы установить, что тот же самый код реального режима DOS, который
в предыдущих версиях операционной системы содержался в MSDOS.SYS, в Windows 95 находится
в WINBOOT.SYS.
Итак, код реального режима DOS является частью Windows 95. Мы выполнили вышеупо-
мянутые тесты из окна DOS внутри Windows, следовательно, код WINBOOT используется не толь-
ко для загрузки Windows и не “выбрасывается” Windows. Имя WINBOOT несколько вводит в за-
блуждение, поскольку создается впечатление, что этот код служит только для загрузки Windows.
На самом же деле, WINBOOT.SYS играет в Windows 95 значительно большую роль. Например,
документация Microsoft на Plug-and-Play ссылается на “ядро реального режима операционной
системы”, и это хороший способ описания WINBOOT.SYS: это действительно часть ядра Windows
95, предназначенная для реального режима. Странно, что все упоминания об этом были так явно
выброшены из документации и диаграмм Microsoft, предназначенных для широкого круга пользо-
вателей. Почему WINBOOT.SYS отсутствует на рис. 1.6? У вас может сложиться впечатление, что
Microsoft этого стесняется. Не очень-то любезно она обошлась с фрагментом кода, принесшим ей
миллиарды долларов.
Теперь мы знаем, что Windows не отбрасывает WINBOOT; он присутствует и в дальнейшем.
Но, даже если WINBOOT и присутствует при обработке определенных вызовов DOS в опреде-
ленных обстоятельствах, следует помнить, что эти тесты проводились в окне DOS, в котором мы
выполняли программу в реальном режиме DOS DEBUG. Хотя то, что мы видели, уже затрагивает
вопросы отсутствия WINBOOT.SYS в диаграммах Microsoft, это, возможно, еще не противоречит
одной теории о том, что Windows 95 использует код реального режима DOS только для совме-
стимости с программами DOS.
Как подчеркивалось раньше, нет никакой обязательной связи между MS DOS и способностью
выполнять программы DOS. MS DOS могла требоваться для обеспечения потребностей не-DOS
(например, Winl6 и Win32) программ. С другой стороны, можно выполнять программы реального
режима DOS в операционной системе, отличной от DOS, если эта операционная система эмулирует
INT 2111-интерфейс. В следующих главах мы увидим, что для многих функций DOS, Windows 95 и
в WfW 3.11 оказываются прекрасными примерами такой операционной системы: при 32-битовом
доступе к файлам даже вызовы INT 21h, исходящие из программного обеспечения реального режима
DOS, во многих случаях обслуживаются в 32-битовом защищенном режиме. Таким образом, идея о
необходимости DOS для выполнения программ DOS, оказывается несостоятельной. Однако предпо-
лагаемая связь MS DOS с DOS-совместимостью, хоть и ошибочна, но столь широко используется,
что мы, ради нашего спора, можем с ней согласиться.
Тогда перефразируем вопрос: до какой степени код реального режима DOS представлен и ис-
пользуется при выполнении только Windows-приложений (Winl6 и Win32) под Windows 95, кото-
рая загружена без CONFIG.SYS и AUTOEXEC.BAT? Нет никаких окон DOS, нет никаких специ-
альных драйверов или резидентных программ для реального режима: является ли в этом случае код
DOS частью Windows 95? И если это так, то какую роль играет DOS в “чистой” Win32-CHcreMe?
Возьмите показанный раньше код, где WINBOOT (читай MS DOS) обрабатывает 50h функцию INT
21h: если бы этот код когда-либо выполнялся на чистой системе Windows 95 (если бы контрольная
точка отладчика в этом коде когда-либо сработала), она использовала бы код реального режима DOS.
Оказывается, даже при выполнении в Windows 95 только Win32-пpилoжeний, этот код реаль-
ного режима DOS действительно выполняется. Используя роскошный отладчик фирмы Nu-Mega
Soft-ICE /Windows (который Microsoft поставляет с комплектом драйверов устройств Windows 95),
я поместил контрольную точку на код установки PSP в WINBOOT:
:bpx 8,748:4195
Даже при выполнении только Win32-пpилoжeний, в частности Cabinet/Explorer, WinBezMT и
Clock, эта контрольная точка вызывалась так часто, что я не мог фактически ничего делать в
Windows 95 до тех пор, пока не изменил контрольную точку так, чтобы обращаться к ней только
каждые 50 раз:
: Ьрх 8,748:4195 с=50
54
Неофициальная Windows
Даже при этом Soft-ICE /Windows активизировался столь часто, что в течение некоторого
времени я был вынужден заниматься только этим тестом. И это было на “чистой” системе Windows
95, выполняющей только Win32-пpилoжeния. Я не открывал окно DOS и даже не запускал никаких
Win 16-приложений. Не было также установлено никаких драйверов устройств или TSR-программ
реального режима.
Впрочем, запускался отладчик Soft-ICE/Windows. Он загружается перед Windows и, таким об-
разом, выступает в Windows неким подобием TSR-программы. Поскольку WINBOOT.SYS непо-
средственно загружает WIN.COM, я использовал <F8> для активизации интерактивной начальной
загрузки, отменил загрузку графического интерфейса пользователя, а из командной строки С:\
загрузил WINICE, который, в свою очередь, загружает WIN.COM и Windows GUI.
Откровенно говоря, это не выглядит чистой Win32 системой. Как и с гипотетическим “микро-
скопом на гамма-лучах” Вернера Гейзенберга, простое использование которого изменяет то, что
находится под ним, вполне возможно, что само присутствие Soft-ICE/Windows так или иначе
заставляет Windows 95 передавать вызовы INT 21h коду реального режима DOS внутри
WINBOOT.SYS, но Windows 95 не будет вызывать этот код при отсутствии отладчика.
Это, конечно, правдоподобно. Давайте используем еще одну программу, которая продемонст-
рирует взаимоотношение Windows-DOS. Вот обзор того, что мы выполняли до сих пор:
S Мы использовали окно DOS, чтобы увидеть, как Windows 95 работает над DOS, но затем
решили, что окно DOS может влиять на результат;
J Мы использовали Win 16-приложение, чтобы выяснить только то, что Windows 95 может
эмулировать DOS тем же самым способом, что и NT;
J Мы использовали отладчик и решили, что отладчик действует как резидентная программа
DOS, само присутствие которой может заставлять Windows обращаться к прерываниям
реального режима DOS, чего в других случаях Windows не делает.
Выполнив все это, давайте обратимся к одной, последней (по крайней мере, в этой первой
главе) демонстрации связи Windows-DOS в Windows 95: WLOG212F является программой Winl6,
которая позволяет выяснить, какие вызовы INT 21h и 2Fh генерируются со стороны Windows, а
какие — со стороны V86. Подсчитывая количество обращений к каждой функции INT 21h и INT
2Fh, сгенерированных в Windows, и сравнивая его с числом обращений, выявленных в режиме V86,
WLOG212F может определить, какие обращения Windows-оболочки типа Windows 95 или
WfW 3.11 передаются в режим V86, а какие эмулируются в защищенном режиме. На рис. 1.8 про-
демонстрирована WLOG212F, выполняемая в Windows 95.
Со стороны Windows, WLOG212F устанавливает свои перехватчики с помощью функции Set
Protected Mode Interrupt Vector DPMI (INT 31h, функция 0205h) и недокументированной функции
Windows API, называемой GetSetKernelDosProc (см. главу 13). Хотя WLOG212F и позволяет от-
слеживать системные вызовы в У86-режиме, она не содержит никакого кода реального режима и не
использует окно DOS. WLOG212F создает собственный перехватчик У86-режима с помощью другой
функции DPMI — Allocate Real Mode Callback Adress (INT 31h, функция ОЗОЗЬ) и устанавливает
его с помощью Set Real Mode Interrupt Vector (INT31h, функция 0201h ).
Например, в следующем фрагменте вывода WLOG212F обработчик INT 21h, установленный с
GetSetKernelDosProc, обнаружил 15 обращений к функции О INT 21h (выход). Однако перехватчик
режима V86 не увидел никаких обращений к этой функции (WLOG212F выдает пустое значение
вместо числа нуль), так что WLOG212F решает, что обращение эмулируется в защищенном режиме,
а не передается режиму V86.
INT 21h:
Func Prot mode KernelDOSProc V36 mode
OOh 15 Emulated
Достаточно просто. Можно даже выяснить, что Winl6 KERNEL содержит обработчик INT 21h,
который, кроме всего прочего, заменяет обращения к устаревшей функции 0 обращением к более
новой функции 4Ch. Это также отражается в выводе WLOG212F:
Глава!. Добро пожаловать в Windows 95
55
INT 21h:
Func Prot mode KernelDOSProc V36 mode
4Ch 15 Passed down
Почему WLOG212F решила, что функция 4Ch передана? Потому что обработчик INT 21h ре-
жима V86 видел столько же обращений к этой функции, сколько сгенерировал обработчик защи-
щенного режима (который сам таких обращений не видел). Возможно, вывод WLOG212F для DOS
функции Get Version поможет разобраться:
INT 21h:
Func Prot mode KernelDOSProc
V36 mode
30h ' 10 33
33 Passed down
Рис. 1.8. Программа WLOG212F (выполняемая здесь бок о бок с некоторыми \У1п32-приложениями под Windows 95)
подсчитывает обращения к INT 21h и INT 2Fh, сгенерированные в защищенном режиме Windows, и сравнивает их
количество с количеством системных вызовов, переданных в режим V86. Если вызов наблюдается в защищенном
режиме, но не в режиме V86, то WLOG212F делает вывод, что он эмулируется. С другой стороны, если в режиме V86
наблюдалось столько же; вызовов, сколько было сгенерированно в защищенном режиме, WLOG212F сигнализирует,
что обращение передано
Здесь обработчик KernelDosProc (см. главу 13 для полного объяснения работы GetSet
KernelDosProc) отследил 33 вызова DOS Get Version и столько же отследил обработчик INT 21h
для режима V86. Следовательно, по крайней мере в этой ситуации, Windows передавала все
обращения к этой функции в режим V86. Просто, не так ли?
56
Неофициальная Windows 95
Мы обсудим реализацию WLOG212F немного позже в этой главе, а сначала давайте исследуем
вывод WLOG212F и посмотрим, что он может сообщить нам относительно связи Windows-DOS в
Windows 95. Важно обратить внимание на то, что, согласно Microsoft, поведение, показанное
WLOG212F, не характерно для Windows 95 или может проявляется только в присутствии специ-
альных драйверов реального режима, для которых в настоящее время не существует доступной
замены для защищенного режима. Как указано в Windows 95 изнутри Кинга, (с. 104):
|“При работе под Windows 95 приложения Windows никогда не используют виртуальный 8086-й режим.
Они вплоть до взаимодействия с аппаратными средствами, от начала до конца, выполняются в
защищенном режиме.”
Мы выполняли некоторые приложения Windows в Windows 95 и WLOG212F, что наглядно
демонстрирует, как они периодически используют режим V86. Правда, столь категорическое утверж-
дение, сопровождается сноской (Windows 95 изнутри, сноска на с. 79):
I “Строго говоря, это не совсем так, поскольку Windows 95 при отсутствии драйверов, способных
работать в защищенном режиме, по-прежнему запускает драйверы устройств MS DOS в виртуальном
8086-й режиме, однако следует иметь- в виду, что драйверы, работающие в реальном режиме,
чрезвычайно опасны.”
Это дополнение не относится к нашему случаю хотя бы потому, что следующий результат
WLOG212F был получен (в Windows 95 Beta-1, май 1994) без CONFIG.SYS, AUTOEXEC.BAT,
COMMAND.COM или окна DOS. Поскольку Beta-1 поставлялась без VxD поддержки системы
уплотнения диска Microsoft DoubleSpace, — а драйвер MS DOS DBLSPACE.BIN относится, по сло-
вам Кинга, к списку “чрезвычайно вредоносных” — важно добавить, что эта система не исполь-
зовала уплотнение диска. На чистой, как свежевыпавший снег, системе Windows 95 я запустил
Microsoft Office 4.2 и другие части операционной системы Windows, вроде Explorer, приложений
Win32, WinBezMT и Clock и, конечно же, саму WLOG212F. Вот часть вывода WLOG212F:
Elapsed: 897 seconds
Calls: 15723
17 calls/second
INT 21h:
Func Prot mode KernelDOSProc V36 mode
00h 15 Emulated
OEh 189 1147 1182 Passed down
19h 228 1162 3526 Passed down
1Ah 18 13410 Emulated
ICh 1 Emulated
25h 32 20 Emulated
2Ah 7 330 5555 Passed down
2Ch 7 6313 11538 Passed down
2Fh 7 5182 Emulated
30h 10 33 33 Passed down
32h 7 7 Emulated
35h 12 10 Emulated
36h 2 6 Emulated
Чтобы не утомлять вас полным выводом WLOG212F, рассмотрим лишь некоторые эмулиро-
ванные вызовы, а затем сосредоточимся на системных вызовах, помеченных как переданные. Как
вы, наверное, уже обратили внимание, в этом тесте под Windows 95 (где, как вы помните, прило-
жения Windows предположительно никогда не используют режим V86, если только не загружены
какие-то необычные драйверы реального режима) обработчик INT 21h для режима V86 заметил
несколько системных вызовов. В случае функции 2Ch (Get Time), например, он видит огромное ко-
личество обращений.
Глава!. Добро пожаловать в Windows 95
57
Почти за 15 минут, которые выполнялась WLOG212F, ее обработчик INT 21h защищенного
режима зафиксировал около 7.800 вызовов, обработчик KernelDosProc — более 80.000 вызовов, а
обработчик режима V86 — более 26.000 вызовов.
Другими словами, в этом конкретном тесте почти одна треть вызовов INT 21h из приложений
Windows заканчивается в режиме V86. В подобной системе Windows 95 — без CONFIG.SYS, без
AUTOEXEC.BAT, без драйверов реального режима DOS (отличных от тех, которые требуются
Windows 95 и загружаются самостоятельно), без TSR-программ, без окон DOS, — только с
приложениями Windows, этого, казалось бы, быть не должно.
Единственные объяснение этому, я считаю, может быть то, что WINBOOT.SYS самостоятельно
должен рассматриваться, как драйвер реального режима (хотя, как вы помните, он отсутствует на
диаграмме Microsoft), и некоторая часть этого драйвера (например, Get Time) воспринимается как
функциональная возможность, для которой “не существует доступного драйвера защищенного
режима”. Другими словами, никто до сир пор не написал VxD, который бы заместил ту часть
WINBOOT.SYS (в том числе и функцию Get Time), которая все еще используется Windows 95.
Сделать это не так уж сложно — примеры CURRDRIV.386 и INTVECT.386 в главе 8
показывают, как обеспечиваемый VMM сервис Hook_V86_Int_Chain упрощает написание VxD для
обработки функций INT 2th в защищенном 32-битовом режиме, — но я почти уверен, что Microsoft
или компьютерная пресса имеют в виду совершенно другое, когда заявляют, что Windows 95 не
требует DOS. Если это действительно все, что подразумевается под освобождением Windows 95 от
DOS, то это можно (и нужно) было бы сделать еще много лет назад, когда Windows впервые
обзавелась VMM, который обеспечил сервис Hook_V86_Int_Chain. Реализация этой функции, а не
ее менее широкое использование в Windows 95, было реальным водоразделом в отношениях
Windows и DOS. Если Windows 95 — полноценная операционная система, то таковой же была и
Windows 3.0 в расширенном режиме, в которой впервые появился Hook_V86_Int_Chain.
Как отмечалось раньше, если WLOG212F обнаруживает вызовы функции со стороны Windows,
но не фиксирует их в режиме V86, программа решает, что эта функция эмулируется. Например, вот
что показывает WLOG212F для функций Open (3Dh), Close (3Eh), Write (3Fh) и Read (40h)
файлового ввода-вывода в Windows 95:
Func Prot mode KernelDOSProc V36 mode
3Dh 698 Emulated
3Eh 1320 Emulated
3Fh 10816 Emulated
40h 1817 Emulated
WLOG212F показывает, что Windows 95 обрабатывает эти критические функции INT 21h пол-
ностью в защищенном режиме. Это весьма удивительно. Однако не будем умалять значение нашего
исследования из-за того, что для большого количества ключевых функций DOS сама Windows 95
все же к DOS не обращается. Я много внимания уделяю изучению случаев, когда Windows 95 про-
должает обращается к режиму V86, для того, чтобы помочь вам оценить те случаи, в которых это не
делается. Мои восхваления Windows 95, основанные на изучении — функция за функцией — ее
связи с DOS, действительно будут иметь определенный вес, чего не скажешь о голословных ут-
верждениях Microsoft о том, что Windows 95 запускает приложения Windows полностью в
защищенном режиме.
Эмулирует ли Windows 95 функции INT 21h или передает их — не является, конечно, исклю-
чительно делом выбора. Иногда обработчики WLOG212F защищенного режима видят больше обра-
щений к функциям, чем обработчик режима V86. В этом Случае WLOG212F только отображает
статистику без каких-либо комментариев. Хороший пример — функция DOS IOCTL:
Func Prot mode KernelDOSProc V36 mode
44h 338 7112 23
Функция IOCTL 44h действительно объединяет большое количество подфункций. Я так сильно
заинтересовался IOCTL Windows 95, что разбил результаты WLOG212F по функции 44h на
подфункции:
58
Неофициальная Windows 95
Func Prot mode KernelDOSProc V36 mode
OOh 338 347 Emulated
08h 2255 Emulated
09h 2255 23
OE 2255 Emulated
Функция 4409h — проверка блочного устройства на удаленный запрос. Мы видим, что,
Windows 95 редко-редко передает этот запрос в режим V86. Далее, сама DOS, обслуживая вызовы
IOCTL, создает пакеты запросов, которые и передает процедуре стратегии и прерывания соответ-
ствующего драйвера устройства DOS реального режима. Так что вполне возможно, что и 32-
битовый код защищенного режима Windows 95 для функции 44h INT 21h делает то же самое.
WLOG212F следит только за INT 21h и INT 2Fh и не может видеть никаких дальних вызовов,
которые драйверы VxD Windows 95 могли бы адресовать драйверам устройств DOS.
Как отмечено раньше, этот тест WLOG212F выполнялся в конфигурации Windows 95 без
CONFIG.SYS и, следовательно, без каких-либо посторонних драйверов устройств DOS. Но, конеч-
но, остаются драйверы устройств DOS, встроенные в WINBOOT.SYS (СОМ1, COM2, LPT1, LPT2,
CLOCKS, CONFIGS и т.д.); существует также драйвер IFSSHLPS, устанавливаемый IFSHLP.SYS;
драйвер XMSXXXX0, создаваемый HIMEM.SYS, и драйвер SETVERXX от SETVER.EXE. Вы
вправе ожидать, что некоторые обращения к IOCTL будут передаваться в соответствующий драйвер
устройства DOS, в тот же WINBOOT.SYS. И действительно, контрольная точка отладчика, установ-
ленная на общую процедуру стратегии, разделяемую встроенными драйверами устройств DOS,
вызывается под Windows 95.
И, наконец, мы приходим к функциям INT 21h, для которых V86-nepexBaT4HK WLOG212F
фиксирует столько же обращений, сколько и перехватчик защищенного режима. WLOG212F марки-
рует эти функции как переданные (Passed down). Я надеюсь, вы уже убедились, что, вопреки заяв-
лениям Microsoft, приложения Windows, выполняемые под Windows 95, используют-таки режим
V86, так что мы можем перейти к более интересным вопросам о том, когда, почему и при каких
обстоятельствах приложения Windows в Windows 95 продолжают использовать режим V86. Я
отредактировал следующий вывод WLOG212F, заменяя строку “Passed down” именем соответст-
вующей функции: I
INT 21h:
Func Prot mode KernelDOSProc V36 mode
OEh 189 1147 1182 Set Drive
19h 228 1162 3526 6et Drive
2Ah 7 330 5555 Get Date
2Ch 7 6313 11538 Get Time
30h 10 33 33 Get DOS Version
45h 226 226 226 Dup File Handle
4Ch 15 Exit
50h 3018 3787 Set PSP
51h 1 1 1 Get PSP
55h 15 15 15 Create PSP
5Ah 3 3 3 Create Temp File
65h 2 2 2 International
DCh 3 3 Novell NetWare Get Station Num
Возможно, вы предчувствуете, что единственная причина того, что Windows 95 передает эти
обращения в режим V86, состоит в наличии моего обработчика для режима V86. (Снова эффект
Гейзенберга обращается к нам своей уродливой стороной!) Но если это так, то почему Windows 95
столь избирательна? Например, она практически не передает моему обработчику для V86-peжимa
вызовы функций файлового ввода-вывода. Действительно, если передача системных вызовов V86
обработчику является следствием присутствия самой WLOG212F, то тогда все вызовы INT 21h были
бы помечены как переданные (Passed down).
Глава!. Добро пожаловать в Windows 95
59
J Получение текущей даты и времени. В главе 14 показан путь этого вызова в Windows 95 от
вызовов API Win32 GetSystemTime и GetLocalTime к функции INT 21h Get Date/Time, к
драйверу устройства CLOCKS и обратно к эмуляции в 32-битовом защищенном режиме вирту-
альным устройством таймера (VTD) функции INT lAh ROM BIOS.
J Получение, установка и создание PSP. В книге мы увидим многочисленные места, где задачи
как Winl6, так и Win32 в Windows 95 обращаются к DOS PSP. Глава 13 показывает путь от
Win32 CreateProcess API к процедуре BuildPDB в KERNEL Win 16 и далее к коду DOS
создания PSP. Переключение задач между приложениями Windows обычно включает получе-
ние и установку текущего PSP DOS.
J Функции DOS для получения номера версии DOS, возврат в операционную систему, дублиро-
вание дескриптора файла и т.д. Например, когда вы вставляете объект из одного документа в
другой, модуль OLE STORAGE.DLL вызывает функцию DOS 45h для дублирования деск-
риптора файла.
Значит ли это, VxD мог бы обрабатывать каждую из этих функций INT 21h в 32-битовом
защищенном режиме и не передавать обращение к DOS? Да, VxD мог бы делать это; см.
CURRDRIV.386 и INTVECT.386 в главе 8. Windows 95 в настоящее время передает многие вызовы
INT 21h в режим V86 DOS, но, судя по всему, никаких объективных причин для этого нет. Вполне
даже возможно, что, в коммерческой версии Windows 95 это будет не так.
Кто боится MS DOS?
Интересный вопрос вот в чем: если все вызовы INT 21h обрабатывались бы VxD в защищенном
режиме, существенно ли изменился бы от этого характер Windows 95? Другими словами, переме-
щение этих оставшихся функций INT 2lh в защищенный режим превратит ли Windows 95 в подлин-
ную, полную, интегрированную операционную систему?
Я не думаю, что это было бы так. С одной стороны, большая часть кода VxD, который
обрабатывает INT 21h в 32-битовом защищенном режиме, еще использует структуры данных DOS
реального режима. Мой CURRDRIV.386, например, манипулирует переменной текущего дисковода
в сегменте данных DOS. Действительно, CURRDRIV работает с этой переменной полностью из
защищенного режима, но это — все еще переменная, размещенная в сегменте данных DOS реально-
го режима. Заметьте, когда Microsoft утверждает, что “если вы запускаете только приложения
Windows, вы никогда не будете использовать код MS DOS” {Windows 95 изнутри, с. 147), ничего
не сообщается относительно записи или извлечения каких-либо данных MS DOS.
Конечно, с введением длинных имен файлов и каталогов, многие структуры данных DOS, такие
как таблица системных файлов (System File Table — SFT) и структура текущего каталога (Current
Directory Structure — CDS), становятся менее полезными. Область обмена данными (Swappable
Data Area — SDA), которую администратор данных Windows 95 скупо выдает на принципу “каж-
дой VM”, могла бы обслуживаться внутри системной VM по принципу “каждой задаче”. Но в лю-
бом случае, как мы увидим в этой книге, Windows 95, даже при использовании VxD для обработки
вызовов DOS в 32-битовом защищенном режиме, должна еще очень часто записывать и извлекать
данные DOS в реальном режиме.
Однако существует намного более веская причина, почему появление VxD-обработчиков для
оставшейся дюжины (или около того) функций INT 2lh, которые в настоящее время Windows 95
передает DOS, не превратит Windows 95 в подлинную операционную систему.
Причина совершенно проста и заключается в том, что Windows 95 — это уже полноценная
операционная система, и ее базирование на коде MS DOS реального режима не изменит положения
вещей. Windows 95 может быть подлинной операционной системой, но продолжать использовать
DOS для некоторых операций.
С тех пор как мы увидели, что Windows 95 полагается на DOS, кажется довольно странным,
что Microsoft превратила независимость от DOS в своего рода критерий доброкачественности
операционной системы. Я думаю, что Microsoft в своей технической пропаганде относительно
62
Неофициальная Windows 95
архитектуры Windows 95 могла сделать большую ошибку, безоговорочно продвигая идею о том, что
операционная среда не может быть полной, интегрированной операционной системой, если обраща-
ется к DOS по какому-то поводу, отличному от вопросов совместимости с приложениями и драйве-
рами DOS. Использование кода реального режима DOS не подрывает статуса Windows 95, как
подлинно операционной системы.
Эти общие утверждения, вероятно, более неожиданны, чем все мои изыскания относительно
тех или иных частных особенностей Windows 95. Как может независимость от DOS не быть такой
важной? Как можно смотреть сквозь пальцы на то, что Windows 95 использует функцию DOS
Create PSP всякий раз, когда вы запускаете приложение Win32, и что переключение задач, даже
между приложениями Win32, часто использует вызов Set PSP?
Конечно, это важно, и нет ничего хорошего в том, что Microsoft преподносит Windows 95 ко-
мпьютерной коммерческой прессе и программистам как операционную систему, в которой код
реального режима DOS никогда не используется приложениями Windows. Но, оставив в стороне
очевидные недомолвки Microsoft относительно собственного изделия, спросим, как может Windows
95 представлять подлинную операционную систему, когда, как мы видели, она полагается на код
DOS реального режима?
А вот как: потому что Windows 95 выполняет этот код DOS в режиме V86! Вспомните, что
режим V86 — это форма защищенного режима. Когда Windows обращается к DOS в режиме V86,
Windows продолжает управлять, a DOS является для нее подручной. Фраза “Windows обращается
к DOS”, которая звучит столь категорично, едва ли завершает историю. Когда Windows 95 посы-
лает вызов INT 21h коду DOS реального режима, он может многократно возвращаться в 32-битовый
защищенный режим.
Например, если код DOS реального режима загружен до того, как Windows сгенерирует инст-
рукцию INT (например, для вызова функции BIOS), вызов немедленно захватывается VMM,
поскольку именно так INT работает в режиме V86. VMM может передавать INT некоторому VxD,
который обработает его полностью в защищенном режиме. Глава 14 предоставляет хороший пример
этого: функции 2Ah INT 21h (Get Date) и 2Ch (Get Time), которые Windows передает коду DOS
реального режима, преобразуются этим кодом в функцию О INT lAh. Все INT в режиме V86 пере-
хватываются в VMM; VMM передает INT lAh виртуальному устройству таймера (VTD), которое -
обрабатывает вызов в защищенном режиме.
Вот другой пример того, как Windows использует режим V86 для управления выполнением
кода DOS реального режима: обработчики косвенных вызовов V86, упомянутые раньше, могут
накладываться поверх существующего фрагмента кода DOS реального режима, вынуждая, таким
образом, VMM перехватывать любое выполнение этого кода. Такие заплатки из обработчиков
косвенных вызовов создаются с помощью сервиса Install_V86_Break_Point, обеспечиваемого VMM.
Благодаря режиму V86, Windows, включая Windows 95, не выполняются “над DOS”; они
используют DOS как драйвер. Windows требуется нечто для реализации функций 2Ah, 2Ch, 50h,
55h и т.д. В один прекрасный день это станет делать VxD; а пока этим занимается драйвер
реального режима WINBOOT.SYS.
WfW 3.11: заброшенная
операционная система
Важно понимать, что в отношениях Windows 95 и MS DOS нет ничего нового. Windows 95 —
не первая версия Windows, использующая код реального режима DOS в качестве драйвера, выпол-
няющего избранные вызовы INT 21h. Windows 95 — даже не первая версия Windows, заслужи-
вающая того, чтобы называться истинно операционной системой.
Как уже отмечалось несколько раз, фундаментальное перевоплощение Windows произошло не в
1995 году с Windows 95, а в 1990 году — с расширенным режимом Windows 3.0, а может, и еще
раньше — с Windows/386 2.0.
Программисты Windows, потратившие больше всего времени на Windows API верхнего уровня,
предоставляемый KERNEL, USER и GDI, вполне понятно, могут сомневаться в том, заслуживает ли
Глава!. Добро-пожаловать в Windows 95 63
какая-то версия, предшествовавшая Windows 95, права называться операционной системой. Но
Windows 95 можно действительно считать операционной системой только после рассмотрения на
нижнем уровне (особенно VMM), и то же самое применимо к ранним версиям Windows. Windows
3.0 и Windows 3.1 в расширенном режиме и WfW 3.1 имеют практически такую же архитектуру,
как и Windows 95. Если есть какая-то логика в том, чтобы называть Windows 95 операционной си-
стемой (и она существует, несмотря на использование ею кода реального режима DOS), то уровень
VMM/VxD в Windows 3.x в расширенном режиме — та же операционная система. В течение мно-
гих лет только группа создателей VxD осознавала, что это операционная система, и она по сущест-
ву заменит реальный режим MS DOS. Как указано в главе 8, 32-битовая операционная система за-
щищенного режима много лет подряд находилась прямо у нас под носом, но мы едва ли замечали это.
Эту 32-битовую операционную систему защищенного режима стало намного труднее игнори-
ровать, когда вышла WfW 3.11 с 32-битовым доступом к файлам (32 BFA). WfW 3.11 — опре-
деленно один из самых недооцениваемых программных продуктов всех времен. Хотя Windows 95
имеет много возможностей (длинные имена файлов, ветви [или нити — “threads”], файлы отобра-
жения памяти и т.д.), не представленных в WfW 3.11 или представленных только частично в расши-
рениях Windows вроде Win32s, ее архитектура в своей основе та же, что и архитектура WfW 3.11.
Тут нет ничего удивительного, ведь Microsoft сама хвасталась (начало 93 - конец 94 гг.), что
32BFA в WfW 3.11 основывается на “32-битовой файловой системе из проекта Chicago”. WfW 3.11
включает драйверы VxD 32-битовой файловой системой, такие как IFSMgr, VCACHE и VFAT —
это же мы видим и в Windows 95. Другими словами, главная часть Windows 95 вышла в продажу
примерно за полтора года до появления полного продукта. На фоне успешной продажи WfW 3.11
компьютерная пресса практически не обсуждала ее свойства подлинной операционной системы 32-
битового защищенного режима.
Включение в WfW 3.11 драйверов VxD (таких, как IFSMgr) из Windows 95 оказалось палкой о
двух концах. С одной стороны (я имею в виду первый конец), Microsoft сделала большую часть
Windows 95 доступной для общества задолго до того, как полная Windows 95 была готова к
самостоятельному распространению. В то же время, совершенно неясно, действительно ли часть
IFSMgr из Windows 95 была готова. В сущности, WfW 3.11 содержит код пред-бета-версии
Windows 95. Тут нет ничего плохого, кроме того, что единственный способ получить хоть какую-то
программистскую документацию на уровень 32BFA WfW 3.11 — это обратиться к бета-версии
Windows 95: IFSMgr в WfW 3.1 остался полностью недокументированным.
В главе И показано, что с появлением 32BFA WfW 3.11 действительно стала версией MS DOS
для 32-битового защищенного режима. Особенно интересно, что 32BFA был вынут из Windows 95 и
помещен в Windows 3.x без фундаментальных изменений в VMM. Сервисы, необходимые для базо-
вого 32BFA, уже были частью VMM и существовали еще со времен Windows 3.0.
Наверное, это звучит довольно абсурдно, что такой продукт, как WfW 3.11, для которого
Microsoft очевидно не придумала ничего более достойного, кроме увеличения номера версии на .01,
мог иметь такое же отношение к MS DOS, как и Windows 95, для которой Microsoft приняла целую
новую схему наименований версий. Однако, выполняя WLOG212F под WfW 3.11, как с задейство-
ванным 32BFA, так и без него (WIN /D:C отключает 32BFA), действительно легко увидеть, что
WfW 3.11 и Windows 95 привязываются к MS DOS во многом похожим способом. Чем показывать
два файла протокола, лучше объединить результаты работы WLOG1212F из WfW 3.11 и Windows
95 в табл. 1.1.
Раньше я часто использовал WLOG212F, чтобы показать, как Windows 95 передает некоторые
вызовы INT 21h в режим DOS V86. Здесь отмечено, что WfW 3.11 с 32BFA эмулирует большое
количество функций INT 21h в защищенном режиме, без передачи их в режим V86.. Как видно из
табл. 1.1, WfW 3.11 с 32BFA почти идентична Windows 95. Если уж изучать их связь с MS DOS,
то следует обратить внимание и на небольшое различие между двумя продуктами.
Из этой таблице кажется, как будто Windows 95 эмулирует немного больше вызовов INT 21h,
чем WfW 3.11. Однако это отражает только различие в наборах вызовов INT 21h, которые были
замечены WLOG212F в каждом тесте. Например, WfW 3.11 передает функцию ODh в DOS, нс
WLOG212F в Windows 95 не зафиксировала ни одного обращения к функции ODh. Как мы увидим
в главе 8, Windows 95 несколько больше связана с DOS, чем WfW 3.11. х
64 Неофициальная Windows 9.'
Таблица 1.1. Обработка функции INT 2th в WfW 3.11 и Windows 95
Функция WfW 3.11 без 32BFA WfW 3.11 c 32BFA Windows 95 Функция WfW 3.11 без 32BFA WfW 3.11 c 32BFA Windows 95
00h E E E 4Bh E E E
ODh - P - 4Ch P P P
OEh p P p 4Eh P E E
11п p E - 4Fh P E E
19h p P p 50h P P P-
1Ah s E E 51h P P P
1Ch p E E 52h - P -
25h E E E 55h p P p
29h P - - 56h p E E
2Ah P p p 57h p S S
2Ch P p p 59h p P E
. 2Fh E E E 5Ah - P P
30h P P P 5Bh p E E
32h - E E 5Ch - - E
35h E E E 5Dh p p -
36h P E E 60h - p -
3Bh P E E 62h p p -
3Ch ' P E E 65h - - p
3Dh P E E 68h - - E
3Eh P E E 71h - - E
40h P E E DCh p p P
41h P E E
42h P E E E 5 23 27
43h P E E P 34 18 13
44h P S S S 1 3 2
45h P P P - 9 5 7
47h P S E
Р = передается; Е = эмулируется; S = иногда передается; - данные отсутствуют
Мне больше нечего добавить относительно различия между WfW 3.11 с 32BFA и без него. Яс-
но, что с заблокированным 32BFA (WIN /D:C) WfW 3.11 передает почти все вызовы INT 2th в
режим V86. Однако она эмулирует небольшое количество вызовов, вроде функций 4Bh (EXEC) и
функций 25h и 35h (Set/Get Interrupt Vector). Эта очевидная эмуляция выполняется обработчиком
INT 21h в KERNEL Wlnl6; как будет показано в главе 13, KERNEL специально выделяет некото-
рые вызовы INT 21h. Например, вызов функции 4Bh из приложения Windows — это запрос к
WinExec (запуск другого приложения Windows); DOS не знает, как выполнять приложения
Windows, так что KERNEL обрабатывает этот вызов функции INT 21h, преобразуя выполнение в
File Open, Read, Seek и т.д.
По большей части, Windows 95 и WfW 3.11 с 32BFA передают DOS в режим V86 одни и те же
вызовы INT 21h. Это в основном нефайловые вызовы INT 21h типа Get Date/Time, Get/Set PSP и
т.д.. В то время как Microsoft публично заявляет, что Windows 95 никогда не передает вызовы INT
21h в режим V86 в приложениях Windows, конфиденциальный документ Microsoft (.“Chicago File
System Features — Tips&Issue” от 22 апреля 1994 г.), поставляемый с Beta-1, сообщает нечто
отличное:
|Все прерывания INT 21Ь, за исключением файлового API INT 21, передаются по умолчанию любому
перехватчику, присутствующему в системе.
3 Неофициальная Windows 95
Глава!. Добро пожаловать в Windows 95
Эта фраза — “за исключением файлового API”, вполне согласуется с результатами, получен-
ными WLOG212F: Windows 95, подобно WfW 3.11, передает нефайловые вызовы INT 21h в режим
V86. Под колоритным словом “перехватчик” Microsoft подразумевает код, который перехватывает
INT 21h. Поскольку WINBOOT.SYS сам является перехватчиком INT 21h, Microsoft здесь подтвер-
ждает то, что так неестественно отрицается в ее более широких публичных заявлениях; функции
DOS, не связанные с файловым вводом-выводом, передаются коду реального режима DOS и им же
обрабатываются. Господи, в чем же еще труднее признаться?
Если бы я должен был в 25 или меньше словах объяснять, как Windows 95 связана с DOS, я
сказал бы вот что: Windows 95 связана с DOS так же, как и WfW 3.11. Windows 95 обеспечивает
32BFA. Для нефайловых системных вызовов она задействует (в режиме V86) код DOS реального
режима в WINBOOT.SYS. Windows 95 — подлинная операционная система; следовательно, таковы-
ми же были WfW 3.11, и Windows 3.1, и Windows 3.0 в расширенном режиме.
66
Неофи1^иальная Windows 95
Глава 2
Наблюдения за процессом
загрузки Chicago
Если вы видели в главе 1, как в действительности связана Windows 95 с реальным режимом MS
DOS, то теперь самое время посмотреть, как все элементы Windows 95 соединены вместе.
Возможно, слово “все” — немного претенциозно, поскольку при установке Windows 95
“сдала мне на хранение” около тысячи файлов — по крайней мере так много их было на моем дис-
ке. Можно получить хорошее представление о различных отдельных компонентах этой “интегри-
рованной” операционной системы, наблюдая за процессом загрузки Windows 95.
От WINBOOT.SYS к WIN.COM
В главе 1 уже упоминалось, что блок начальной загрузки Windows 95 ищет файл
WINBOOT.SYS так же, как старый блок начальной загрузки DOS разыскивал IO.SYS и
MSDOS.SYS. Как только загрузился WINBOOT.SYS, можно начинать наблюдение за процессом
инициализации Windows 95. Превосходный инструмент для этого — DOS-версия отладчика Soft-
ICE фирмы Nu Mega. Soft-ICE (версия для DOS, а не для Windows) поддерживает команду BOOT,
перезагружающую компьютер, сохраняя Soft-ICE в памяти. При этом можно установить некоторые
контрольные точки и перезагрузить машину. Когда ваш компьютер снова загрузится, можно исполь-
зовать Soft-ICE для изучения ранних стадий инициализации. Как довод в ее пользу вспомним, что
команда BOOT из Soft-ICE использовалась Stac Electronics для восстановления недокумен-
тированного интерфейса предварительной загрузки в MS DOS 6.0. (Microsoft позже утверждала в
суде, под присягой и с честным выражением лица, что такое использование команды BOOT Soft-
ICE составляет “незаконное присвоение торговых секретов”. К сожалению, суд поверил Microsoft,
и, к счастью, обе компании впоследствии уладили это дело вне суда.)
Для наблюдений за ранними этапами инициализации Windows 95 я устанавливаю контрольную
точку на функции DOS File Open (BPINT 21 AH=3D), Extended Open/Create (BPINT 21 AH=6C),
Find First File (BPINT 21 AH=4E) и EXEC (BPINT 21 AH=4B). Как мы уже видели, Windows 95
использует INT 21h при запуске, и не удивительно, что она использует INT 21h в течение всей ини-
циализации. Каждый раз при вызове одной из точек INT 21h я исследовал строку, адресуемую па-
рой регистров DS:DX.
Версия Soft-ICE для DOS загружается как драйвер устройства — для этого потребовалось запи-
сать в CONFIG.SYS одну строку DEVICE=C:\SICE\SICE.EXE. Ни один из других драйверов,
показанных на рис. 2.1, не был упЭмянут в этой строке файла CONFIG.SYS.
Func File
OPEN \LOGO.SYS
RENAME IO.SYS -> 10.DOS
RENAME IBMBIO.COM -> IBMBIO.DOS
RENAME MSDOS.SYS -> MSDOS.DOS
RENAME IBMDOS.COM -> IBMDOS.DOS
Comment
отказ - использовать логотип внутри WINBOOT.SYS
я также просматривал INT 21b АН=56
Глава 2, Наблюдения за процессолл загрузки Chicago
67
FIND \DRVSPACE.BIN отказ - DriveSpace отсутствует
OPEN C:\DBLSPACE.BIN отказ - DblSpase отсутствует
EXEC C:\WINBOOT.SYS
FIND C:\SYSTEM.DAT реестр (registry)
OPEN C:\SYSTEM.DAT
OPEN CONFIGS обращается к CONFIGS часть Plug and Play Configuration Manager для реального режима; после открытия, IOCTL
OPEN CONFIG.SYS только одна линия - для загрузки Soft-ICE
OPEN C:\SICE\S - ICE.EXE
EXEC C:\SICE\S - ICE.EXE
OPEN C:\WINDOWS\HIMEM.SYS этого *нет* в CONFIG.SYS!
EXEC C:\WINDOWS\HIMEM.SYS
OPEN IFSSHLPS отказ
OPEN C:\WINDOWS\IFSHLP.SYS этого *нет* в CONFIG.SYS!
EXEC C:\WINDOWS\IFSHLP.SYS
OPEN SETVERXX отказ
OPEN C:\WINDOWS\COMMAND\SETVER.EXE этого *нет* в CONFIG.SYS!
EXEC C:\WINDOWS\COMMAND\SETVER.EXE
OPEN CON
OPEN AUX
OPEN PRN
OPEN \AUTOEXEC.BAT отказ - AUTOEXEC.BAT отсутствует; поэтому нет необходимости загружать COMMAND.COM
FIND WIN.???
FIND C:\WINDOWS\WIN.???
OPEN C:\WINDOWS\WIN.COM
EXEC t:\WINDOWS\WIN.COM вот наша цель!
OPEN C:\WIND0WS\VMM32.VDX отказ - он находится в \windows\system
OPEN C:\WIND0WS\SYSTEM\VMM32.VDX
EXEC C:\WIND0WS\SYSTEM\VMM32.VDX загрузить VMM и драйверы VxD
Рис. 2.1. За ранней стадией инициализации Windows 95 можно наблюдать с помощью команды BOOT Soft-ICE
На рис. 2.1 показана только первая стадия инициализации Windows 95. За следующей стадией
лучше наблюдать с помощью программы регистрации прерываний INTRSPY (см. раздел “От
WIN.COM, к KERNL386.EXE” в этой главе). Тем не менее интересно, что происходит в течение пер-
вого периода инициализации Windows 95.
Как я уже отмечал несколько раз, WINBOOT.SYS выполняет в Windows 95 ту же функцию,
что IO.SYS и MSDOS.SYS в ранних версиях MS DOS или же IBMBIO.COM и IBMDOS.COM в
PC DOS и DR DOS. На рис. 2.1 показано, что каждый раз при загрузке Windows 95 WINBOOT.
SYS ищет эти старые файлы ядра DOS. А найдя, переименовывает в файлы с расширением DOS.
Это позволяет в Windows 95 по нажатии <F4> загружаться с использованием IO.SYS и
MSDOS.SYS из предыдущей версии DOS.
Далее, WINBOOT.SYS пытается предварительно загрузить драйвер уплотнения диска, либо
DRVSPACE.BIN (из MS DOS 6.22), либо DBLSPACE.BIN (из MS DOS 6.0). Словосочетание
“предварительно загрузить” подразумевает, что загрузка этого драйвера осуществляется до обра-
ботки DOS-файла CONFIG.SYS (а в Windows 95 — перед работой с реестром [registry]).
DBLSPACE.BIN мог бы оказаться или собственным DBLSPACE фирмы Microsoft, или таким драй-
вером уплотнения, как STACKER, маскирующимся под Dblspace для своей предварительной загруз-
ки. Интерфейс предварительной загрузки DOS играет важную роль в деле “Stac против Microsoft”
(по поводу деталей см. Undocumented DOS, 2d ed., р. 40—42, 269—270; Geoff Chapell, DOS
Internals, p. 156-161; “LA Law”, Dr: Dobb's Journal, May 1994, p. 137-139).
После попытки загрузить драйвер уплотнения диска WINBOOT.SYS ищет конфигурационную
информацию, определенную пользователем. В предыдущих версиях DOS это означало работу с
CONFIG.SYS. В Windows 95 предпочтительным местом для информации конфигурации оказыва-
ется реестр. На рис. 2.1 показано, что WINBOOT открывает файл C:\SYSTEM.DAT — это
Неофициальная Windows 95
главный файл реестра. На рис. 2.1 не показано (потому что я наблюдал только несколько функций
INT 21h), что WINBOOT определяет маршрут реестра с помощью функции 1613h INT 2Fh, выпол-
няемой в другой части WINBOOT. Заголовочный файл INT2FAPI.H, включенный в Chicago Device
Driver Kit (DDK) (пакет разработки драйверов устройств), ссылается на функцию 16h INT 2Fh,
как на W386_Int_Multiplex, и имеет следующее определение для подфункции 13h:
«define W386_Get_SYSDAT_Path 0x13
/* IO.SYS service to return path to SYSTEM.DAT */
Интересно, что Windows 95, вероятно, использует код реального режима DOS в WINBOOT для
выполнения1 номинальной функции WIN386 (вскоре мы увидим другие примеры этого), а
заголовочный файл DDK рассматривает это не как сервис WINBOOT.SYS, а как сервис IO.SYS.
INT2FAPI.H определяет несколько других функций 16h INT 2Fh. сервиса IO.SYS, новых для
Windows 95. Например, функция 160Eh — это “IO.SYS service for logo management” (служба
управления логотипом).
Полностью переписана?
Если немного подумать над кратким описанием W386_Gct_SYSDAT_Path из
INT2FAPI.H, то появится несколько предположений.
S Программисты в Microsoft свободно используют имя IO.SYS для обозначения располо-
женного в WINBOOT.SYS кода. Ясно, что они не видят больших различий между
WINBOOT.SYS и старыми комбинациями IO.SYS и MSDOS.SYS.
J Программисты в Microsoft ничего не думают о выполнении функций Windows (помните,
функция 16h INT 2Fh — это W386_Int_Multiplex) в реальном режиме IO.SYS.
Программисты в Microsoft не видят больших различий между Windows 95 и старыми
продуктами Windows/386.
Последний пункт требует некоторых разъяснений. INT2FAPI постоянно ссылается на
Windows/386 — продукт, реализованный в 1987-88 гг. Первая дата в INT2FAPI.H — это
“10-Маг-1989 RAL” (т.е. Ральф Лайп (Ralph Lipe), в настоящее время главный “архитектор”
Windows 95). Файл, оказывается, был создан для существовавшей в то время версии 3.0
Windows/386. Через несколько лет (в мае 1990 г.) эта Windows/386 3.0 стала Windows 3.0 в
расширенном режиме.
INT2FAPI.H — один из многих заголовочных файлов DDK, демонстрирующий непре-
рывный переход от планируемой Microsoft версии 3.0 Windows/386 к Windows 95.
Например, заголовочный файл VPICD, включенный в DDK, имеет дату “13-Арг-1988 RAL”,
VMDA имеет дату “05-Мау-1988 AAR” (Аарон Рейнольдс (Aaron Reynolds)) и наиважнейший
VMM.INC датируется “05-Мау 1988 RAL”. Приличные куски Windows 95 были вначале
написаны для проекта Windows/386 3.x.
Ну и что? Но ведь Microsoft заявила, что Windows 95 полностью перешла на новую
архитектуру и не основана ни на каких предыдущих версиях Windows. Например:
I “Усовершенствования Windows больше не похожи на раскраску старого MS-DOS-овского
автомобиля. Поскольку вся операционная система переписана от самого основания, вы получили
такие поразительные возможности, как ветви, файлы отображения памяти и асинхронный ввод-
вывод”.
— Dave Edson, Seventeen Techniques for Preparing Your 16-bit Applications for Chicago, Microsoft Systems
Journal, February 1994, p. 20.
И, конечно, компьютерная коммерческая пресса сразу же в это поверила!
Глава 2. Наблюдения за процессолл загрузки Chicago
69
Другой момент, на который указал Кинг, состоит в том, что. ..о»и \Х'чкЬн\ > 386 ире.со-
ставляет некоторые основания для DOS-совместимости в Windows 95. ' никакой :<•>?. не повто-
рялся”. Уже беглый взгляд на файл W1N386.386 (July 1, 1988) ». i Windows. 386 2.1, ио-
видимому, подтверждает подобную мысль. И хотя есть много концептуальных аналогий
между Windows/386 2.1 и Windows 95, — потому-то статья Дункана. G.hutcan) в A/tcmw/f
Systems Journal в 1987 г. остается полезной для чтения — Microsoft, тем нс менее, исполь-
зовала Windows/386 2.1 как специфический тест (“то, чего нс нужно делать”) для нио, что
позже стало Windows 3.0 в расширенном режиме, а семью годами позже — Windows 95.
В частности, Microsoft оказалась втянутой в большой кампанию переписке кода в не
риод между выпусками Windows/386 2.1 в 1987—88 гг. и Windows 3.0 i< расшииспном пежиме
в 1990 г. В качестве примера посмотрите некоторые исходные Ф.иНы включенные г- DDK.
Windows.
Файл Описание
VPICD.INC "13 Apr-1988 RAL Rewrite”
Так что, части Windows 95 были действительно полностью переписаны — еще в 1988
году!
От реестра к XMS
Обсудив, как WINBOOT обнаруживает реестр, давайте посвятим несколько слов самому ре-
естру. В отличие от CONFIG.SYS, AUTOEXEC.BAT, SYSTEM.INI и т.д. — все файлы, которые он
заменяет — реестр не является обычным ASCII-файлом. Пользователи могут просматривать и ре-
дактировать реестр с помощью приложения REGEDIT. Программы запрашивают и изменяют реестр
с помощью таких функций API Windows, как RegOpenKey, RegEnumKey, RegQueryValue,
RegCreateKey и RegSetValue. VMM обеспечивает фактическую реализацию функций доступа к ре-
естру; Windows API вызывают API защищенного режима VMM. Программы DOS могут обращаться
к регистрации с помощью API V86 VMM.
Реестр — это иерархическая база данных ключей и значений, перенявшая все установки от
Object Unking and Embedding (OLE) и File Viewer до опций производительности и динамической
информации VxD. Например, следующее дерево (полученное рекурсивным вызовом RegOpenKey,
RegEnumKey, RegQueryValue, и RegCloseKey) предоставляет информацию (сохраненную в
HKEY_LOGICAL_MACHINE\Software\Classes) о документах Microsoft Word 6.0:
Word Document. 6=Microsoft Word 6.0 Document
shell
open
command=C:\MSOFFICE\WINWORD\WINWORD.EXE /w
ddeexec=[FileOpen (“%1”)J
print
command=C:\MSDFFICE\WINWDRD\WINWDRD.EXE /w
ddeexec=[FileDpen (“%T’)J [FilePrintOJ [DocClose(2)]
ifexec=[FileOpen [FilePrint .Background = 0] [FileExit(2)J
CLSID={00020900-0000-0000-C000-00000000046}
Insertable
protocol
StdFileEditing
verb
O=Edit
server=C:\MSOFFIC\WINWORD\WINWORD.EXE
Глава 2. Наблюдения за процессолл загрузки Chicago
Чтобы определить “заклинания” для печати документа в Word 6.0, например, программа могла
бы (RegQueryValue) ввести таких два ключа:
Word.Document.6\shell\print\command
Word.Document.6\shell\print\ddeexec
Значение первого ключа может быть передано WINEXEC:
C:\MSOFFICE\WINWORD\W1NWORD.EXE /w
А значение второго ключа представляет собой строку для DDE EXEC:
[FileOpen][FilePrint () ][DocClose(2)]
Заметьте, что программа не требовала обязательного знания чего-либо иного, кроме имен
ключей.
Сама Windows 95 использует реестр для хранения информации конфигурации, например, отно-
сительно динамических VxD, загружаемых супервизором I/O.
[HKEY_LOCAL_MACHINE\System\Cu г rentCont rolSet\Se rvices\Class\fdc\OOOO]
“DevLoader"=“IOS"
“PortDriver”=“HSFLOP. pdr”
“DriverDesc"=“Standard Floppy Disk Controller”
Int13ToDrvSelMap”=hex:00,1c,01,2d
К счастью (я лично портил SYSTEM.DAT неоднократно), Windows 95 может работать с отсут-
ствующим или разрушенным реестром. Впрочем, она предупреждает, что “функции реестра могут
оказаться бесполезными в этом сеансе”. Можно также использовать интерактивную начальную
загрузку по <F8> для обхода реестра. Без реестра Windows 95 не может поддерживать такие воз-
можности, как OLE или Quick View (удобная возможность Explorer, которую мы будем
использовать позже для исследования некоторых компонентов Windows 95). Каждый раз, при
завершении работы, Windows 95 сохраняет предыдущие файлы реестра под именами SYSTEM.DA0
и USER.DA0. Но я держу пари, что независимые производители ПО быстро предоставят програм-
мные средства восстановления информации в реестре.
Возвращаясь к рис. 2.1, мы видим, что после открытия реестра Windows 95 открывает уст-
ройство CONFIGS, являющееся частью WINBOOT.SYS; CONFIGS — это часть Plug-and-Play
Configuration Manager (менеджер конфигурации “подключи и играй”) в реальном режиме.
Компонент защищенного режима живет в VxD под названием CONFIGMG (вероятно, должно прой-
ти некоторое время, прежде чем Microsoft начнет использовать длинные имена файлов для испол-
няемых файлов ядра). VxD CONFIGMG связывается с устройством CONFIGS реального режима с
помощью функции DOS IOCTL; CONFIGMG передает CONFIGS V86 обработчик косвенного
вызова (см. следующую врезку “Вытеснение драйверов реального режима?”), так что CONFIGS мо-
жет вызывать CONFIGMG. Некоторым образом CONFIGS может рассматриваться как расширение
функциональной возможности мультизагрузки DOS 6.0.
Помимо того, что Plug-and-Play Configuration Manager даже имеет компонент реального режи-
ма, стоит также отметить, что CONFIGS предоставляет новый интерфейс, позволяя драйверам
устройств и TSR-программам DOS запрашивать информацию о конфигурации. Как указано в доку-
менте Plug and. Play Device Driver Interface for Windows 3.1 and MS-DOS (October 5, 1993),
Microsoft и Intel предлагают расширение Plug-and-Play (DWCFGMG.SYS) для не-Windows 95
систем — интерфейс использует функцию 1684h INT 2Fh co значением 34h в BX. Оказывается,
функция 1684h INT 2Fh обычно вызывается для определения точки входа в VxD, чей ID (иденти-
фикатор) находится в регистре BX; 34h — это ID VxD для CONFIGMG. И снова мы видим интер-
фейс Windows, реализованный в части Windows 95, отвечающей за реальный режим DOS.
Далее, если существует файл CONFIG.SYS, то с ним работает WINBOOT. Я действительно ис-
пытывал потребность в CONFIG.SYS для загрузки Soft-ICE, но не HIMEM, IFSHLP или же
72
Неофициальная Windows 95
Windows 95 сохраняет свой текущий контекст, переводит машину в реальный режим (не V86)
и запускает приглашение DOS. Это очень сильно напоминает выход из Windows обратно в
DOS или запуск окна DOS в стандартном режиме Windows 3.x. VxD выгружаются, поэтому в
Single Application Mode исчезает поддержка Длинных имен файлов — IFSMGR-то не
работает. Windows 95 даже вызывает функцию 1606h INT 2Fh (оповещение “Windows Exit”),
так что программное обеспечение, загруженное перед Windows 95, считает (и вполне
корректно), что Windows завершила работу (см. главу 3). При выходе из Single Application
Mode обратно в Windows происходит повторная инициализация Windows 95, перезагрузка
VxD, вызов функции 1605h (оповещение “Windows Startup”) и т.д.
Если Windows 95 действительно может возвращаться обратно в DOS, хотя и с помощью
этого странного Single Application Mode, трудно понять, как же она может выгрузить TSR и
драйверы устройств. Windows 95 должна была бы перезагружать их заново всякий раз, как
только пользователь захотел бы перейти в Single Application Mode.
Короче говоря, Windows 95 не только не вытесняет код реального режима в настоящее
время, но и сомнительно, чтобы она вообще смогла это когда-либо сделать. Гораздо вероятнее,
что для тех драйверов реального режима, чьи возможности обеспечиваются VxD и которые не
требуются в течение инициализации Windows (MSCDEX — хороший пример), слова
“вытесняет код реального режима” означают примерно такое: программа Setup Windows 95
могла бы удалять любые упоминания о них из CONFIG.SYS й AUTOEXEC.BAT.
Как эти скромные возможности превратились в прекрасную мысль, будто Windows 95
буквально выгружает “безопасные” драйверы устройств и TSR и освобождает их память? Я
думаю, это случилось благодаря свойству человеческой природы — которое не чуждо ни
программистам, ни компьютерной печати — слышать именно то, что хочется.
Например, вице-президент Microsoft как-то сообщил мне, что Windows 95 “вышвыривает
DOS из машины”, и из контекста нашего разговора я был в то время уверен, что Windows 95
буквально выгружает DOS из памяти и загружав себя в ячейки, ранее занятые DOS и
драйверами реального режима. Честно говоря, я хотел бы в это верить. Рассуждая со своих
нынешних позиций, держу пари, что он имел в виду нечто весьма незначительное, вроде —
программа инициализации Windows 95 удаляет строки DEVICE= из CONFIG.SYS или
WINBOOT.SYS заменяет IO.SYS и MSDOS.SYS. Может быть, этот вице-президент
действительно полагал, что Windows 95 может “выкорчевывать” код реального режима из
работающей машины, поскольку так ему сказал какой-то программист. Программист, тем
временем, всего лишь имел в виду, что программа инициализации Windows 95 удаляет...
Видимо, это некорректное описание Microsoft архитектуры Windows 95 служит примером
известной игры в “испорченный телефон”.
От IFSHIP.SYS к WIN.COM
После HIMEM.SYS WINBOOT загружает IFSHLP.SYS. Как отмечалось в разделе “...но еще и
обход DOS?” (глава 1), код реального режима в IFSHLP является ключевой частью файловой си-
стемы, инсталируемой Windows 95. IFSMGR не сможет загрузиться, если не сможет обнаружить
драйвер IFS$HLP$. IFSMGR вызывает IFSHLP, используя функцию DOS IOCTL и передавая
IFSHLP указатель на V86-o6pa6oT4HK косвенного вызова (callback), который в дальнейшем исполь-
зуется IFSHLP для связи с IESMGR. (См. “Роль IFSHLP.SYS и V86-o6pa6oT4HKH косвенного
вызова” в главе 8).
Затем WINBOOT.SYS загружает SETVER.EXE (подставляющий для приложений номер версии
DOS — обратите внимание!) и, при определенных обстоятельствах, — менеджер расширенной
памяти Microsoft, EMM386.EXE. Заметим, что V86MMGR VxD обеспечивает функции не только
XMS, но и ЕММ.
. Глава 2. Наблюдения за процессом загрузки Chicago 75
В соответствии с рис. 2.1 WINBOOT.SYS затем ищет файл AUTOEXEC.BAT. WINBOOT.SYS
загружает командную оболочку DOS (COMMAND.COM) для обработки AUTOEXEC.BAT. Но, как
указывалось раньше, если нет AUTOEXEC.BAT, то нет и необходимости загружать
COMMAND.COM, и WINBOOT переходит к непосредственной загрузке WIN.COM. Это, конечно,
и есть тесная интеграция, о которой мы так много слышали. Да, это прекрасно, но это так же
“интегрировано”, как класс общественной школы Бостона перед посадкой в экскурсионный автобус.
Обход COMMAND.COM определенно сохраняет некоторую память, но я не вижу никаких
значительных архитектурных различий между прямой загрузкой WIN.COM из WINBOOT.SYS или
из последней строки AUTOEXEC.BAT. Есть, конечно огромное преимущество в том, что конечным
пользователям нет необходимости влезать в AUTOEXEC.BAT (что действительно огорчительно для
многих людей) и добавлять команду WIN. Но это расширение удобств, хотя и важное, не вносит
существенных изменений в архитектуру Windows. Как бы ни была Загружена Windows, DOS все
же находится под ней. Остается только вопрос, находится ли там также и COMMAND.COM.
Для автозагрузки WIN.COM, между прочим, WINBOOT.SYS должен знать его расположение.
Хотя WIN.COM обычно находится в C:\WINDOWS, это расположение не жестко закреплено
(извините меня, я имел в виду “интегрировано”) с WINBOOT.SYS. Это следует из реестра
(HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\SystemRoot).
Итак, автозагрузка WIN.COM — большое дело, только это ничего не говорит об изменении
архитектуры Windows. Я думаю, некоторые предубеждения не позволяют многим в компьютерной
индустрии поверить, что существенное расширение удобств возможно без существенных изменений в
программном обеспечении. Хорошо, допустим даже, что это возможно. Но для чего автозагрузка
WIN.COM? Как хорошо известно, WIN.COM — это еще не Windows. Это маленькая (менее чем 20
Кбайт) программа для загрузки Windows (см. Matt Pietrek, Windows Internals, p. 3-8). Почему
WINBOOT.SYS не загружает непосредственно Windows? Зачем вообще возиться с WIN.COM?
Через некоторое время мы увидим, что в Windows 95 WIN.COM загружает файл VMM32.VXD.
В расширенном режиме Windows 3.x WIN.COM загружает WIN386.EXE, а в стандартном режиме
— DOSX.EXE. В главах 5-7 показано, что такие исполняемые модули, как DOSX.EXE,
WIN386.EXE и даже VMM32.EXE (если вы переименуете его с расширением .EXE), могут
запускаться прямо из приглашения С:\>. Оказывается, WINBOOT.SYS может прямо загружать
VMM32.VXD и обходить WIN.COM так же, как он обходит COMMAND.COM.
Файл, теперь называемый VMM32.VXD, назывался DOS386.EXE в ранних пред-бета-версиях
Windows 95. Существовал переключатель DOS=ENHANCED в CObi₽IG.SYS (так же как^
DOS=HIGH, UMB, ENHANCED), который интерпретировался IO.SYS как запрос на прямую за-
грузку DOS386.EXE. Заметим, что “ENHANCED” относилось не к Windows, а к DOS! Очевидно,
некогда Microsoft подумывала о стандартном и расширенном режимах MS DOS. Стандартный ре-
жим, предположительно, являлся бы версией MS DOS в реальном режиме, а расширенный режим
— версией У86-режима, работающего под DOS386.EXE. В главе 6 показано, как просто создать
собственную копию этого расширенного режима MS DOS в одиночку у себя дома.
Таким образом, некоторые размышления подводят нас к идее обхода WIN.COM. Но WIN.COM
играет важную роль в Windows 95. А именно, он предохраняет вас от возврата в DOS. Когда вы
завершаете Windows 95, именно WIN.COM выводит на экран сообщение “You can now safely turn
off the computer. If you want to restart your computer, press Ctrl-Alt-Del” (Можете теперь спокойно
выключать компьютер. Если хотите перезапустить ваш компьютер, нажмите <Ctrl+Alt+Del>). Как
мы увидим в главе 7, если вы запустите VMM32 без WIN.COM, то сможете вернуться в DOS.
Ясно, что такая способность входит в противоречие с образом, формируемым Microsoft по поводу
“бесшовного”, “интегрированного” Windows PC.
От WIN.COM к KRNL386.EXE
В нижней части рис. 2.1 показано, что WIN.COM выполняет VMM32.VXD. Он содержит, мы
это увидим, самую суть операционной системы Windows (я имею в виду именно операционную
систему, а не операционную среду).
76 Неофициальная Windows 95
Хотя команда BOOT в Soft-ICE дала нам полный обзор ранних стадий процесса загрузки Win-
dows 95, довольно утомительно взаимодействовать с отладчиком при каждом вызове контрольных
точек. Все, что нам действительно нужно, — это список файлов, которые Windows 95 ищет, откры-
вает или запускает. Такой список лучше создавать с использованием не-интерактивного отладчика.
Для этого идеальна утилита INTRSPY Дэвида Максея (David Maxey) из Undocumented DOS (2
ed., p. 229-263). INTRSPY может быть загружена из AUTOEXEC.BAT. На самом деле, я думаю,
вам не нужен AUTOEXEC.BAT (и, следовательно, COMMAND.COM) для запуска такой TSR, как
INTRSPY, поскольку Windows 95 обрабатывает любые команды INSTALL= в CONFIG.SYS. Однако
можно воспользоваться и AUTOEXEC.BAT, поскольку мы уже знаем, что воздействие его
минимально (помимо создания глобального COMMAND.COM, видимого во всех VM).
С помощью компилятора сценариев CMDSPY можно получить сценарий, который заставляет
INTRSPY протоколировать вызовы функций DOS File Find, Open и EXEC. Например, для наблю-
дения за функцией DOS File Open (INT 21h функция 3Dh), которая ожидает имя файла в ASCII-
кодах в DS:DX и возвращает в АХ либо дескриптор (handle) файла (если флаг CARRY сброшен),
либо код ошибки (CARRY установлен), сценарий INTRSPY мог бы выглядеть следующим образом:
intercept 21b
function 30h
on_entry
output “OPEN “ (dc:dx -> byte, asciiz,64)
on_exit if (cflag ==1)
sameline “[Fail “ a “]’’
На рис. 2.2 показана следующая стадия процесса загрузки Windows 95, слегка перекры-
вающаяся с нижней частью рис. 2.1. Кроме функций File Find, Open и EXEC, сценарий INTRSPY
также протоколирует вызовы Extended Open/Create (функция 6Ch, показанная на рисунке как
XOPEN), File Create и новых аналогов этих функций, поддерживающих длинные имена файлов
(LFN). Однако, поскольку INTRSPY — это резидентная программа DOS, она никогда не увидит эти
LFN-вызовы DOS.
EXEC C:\WINDOWS\WIN.COM
OPEN C:\WIND0WS\vmm32.vxd [FAIL]
OPEN C:\WIND0WS\system\vmm32.vxd
EXEC C:\WIND0WS\system\vmm32.vxd
OPEN QEMM386$ [FAIL 2]
OPEN 386MAX$$ [FAIL 2]
OPEN SMARTAAR [FAIL 2]
OPEN C:\SYSTEM.DAT
OPEN SDebugDD [FAIL 2]
OPEN NDISHLPJ [FAIL 2]
OPEN C:\WINDOWS\SYSTEM.INI
CREAT C:\WINDOWS\WNBOOTNG.STS
FIND C:\WIND0WS\SYSTEM\VMM32\*.VDX [FAIL 18]
OPEN C:\WINDOWS\system\nwlink. 386
OPEN C:\WINDOWS\system\wsipx.386
OPEN C:\WINDOWS\system\vnetsup.386
OPEN IFS$HLP$
OPEN C:\WIND0WS\system\ndis.386
OPEN IFS$HLP$
OPEN C:\WIND0WS\system\ndis2sup.386
OPEN NDISHLPS [FAIL 2]
OPEN C:\WINDOWS\system\msodisup. 386
OPEN C:\WINDOWS\system\vnetbios. 386
OPEN C:\WINDOWS\system\wsock.386
OPEN CONFIGS
OPEN C:\WIND0WS\system\vserver.386
OPEN IFS$HLP$
Глава 2. Наблюдения за процессом загрузки Chicago
77
OPEN C:\WIHBOWS\system\vredir.386
OPEN с: \and rew\vxd .386
OPEN C:\WINDOWS\system\dva.386
OPEN C:\WINOOWS\system\vpmtd.386
OPEN IFS$HLP$
OPEN SMARTAAR [FAIL 2]
OPEN C:\WINDOWS\system\ISAPNP.vxd
OPEN C:\WINDOWS\system\MMDEVLDR.vxd
OPEN C:\WINOOWS\system\MSSB16.vxd
OPEN C:\WINDOWS\system\VJYD.vxd
OPEN SCSIMGRS [FAIL 2]
OPENC.\WINDOWS\IOS.INI
OPEN C:\WINDOWS\IOS.LOG
OPEN C:\WINOOWS\system\IOSUBSYS\*.vxd
OPEN C:\WINDOWS\system\IOSUBSYS\apix.vxd
OPEN C:\WINDOWS\system\IOSUBSYS\cdfs.vxd
OPEN C:\WINDOWS\system\IOSUBSYS\cdtsd.vxd
OPEN C:\WINDOWS\system\IOSUBSYS\cdvsd.vxd
OPEN C:\WINDOWS\system\IOSUBSYS\disktsd.vxd
OPEN C:\WINDOWS\system\IOSUBSYS\scsiIhI. vxd
OPEN C:\WINDOWS\system\IOSUBSYS\voltrack.vxd
OPEN C:\WINDOWS\system\IOSUBSYS\rmm.pdr
OPEN C:\WINOOWS\INOISLOG.TXT
OPEN C:\WIND0WS\system\ee16.386
OPEN C:\WINDOWS\system\netbeui. 386
OPEN C:\WINDOWS\WINSTART.BAT [FAIL 2] ;;; ищет путь к WINSTART.BAT
OPEN С:\WINDOWS\system\LPTENUM.vxd
OPEN C:\WINDOWS\system\UNICODE.BIN
OPEN IFS$HLP$
EXEC C:\WINDOWS\system\krnI386.exe
Рис. 2.2. Используя INTRSPY, мы наблюдаем среднюю стадию инициализации Windows 95
Хотя я в одном месте отредактировал вывод INTRSPY — вместо всех строк, где Windows 95
ищет файл WINSTART.BAT в каждом подкаталоге из моего PATH, я показал единственную такую
строку и добавил комментарий — во всем остальном рис. 2.2 точно отражает все, что зафиксировала
INTRSPY в процессе загрузки Windows 95. Даже если вы весьма смутно осведомлены об архитек-
туре Windows, то все равно почувствуете, что на рис. 2.2 должно быть представлено намного боль-
ше компонентов Windows 95. Где Explorer Windows 95? Где GDI? Как насчет ядра WIN32?
Конечно, огромное число файловых вызовов INT 21h отсутствует в журнале INTRSPY на
рис. 2.2, но это не значит, что INTRSPY работает некорректно. INTRSPY не пропустила ничего из
найденного в реальном режиме DOS.
Ах, да: 32-битовый файловый доступ! Как только 32BFA активизирован и выполняется,
Windows 95 не нужно отправлять вызовы файлового ввода-вывода в DOS. Следовательно,
INTRSPY (которая, опять-таки, является резидентной программой DOS реального режима) не мо-
жет увидеть эти обращения. Такой же “урезанный” результат INTRSPY выдает и при использовании
ее для наблюдений за WfW 3.11 с 32BFA. Существует способ (по крайней мере в Beta-1) заставить
Windows 95 загружаться без 32BFA, и я воспользуюсь этим позже, благодаря чему INTRSPY смо-
жет полностью отследить загрузку Windows 95. Между тем, рис. 2.2 имеет достаточно материала, и
его хватит нам на время.
Верхняя часть рис. 2.2 слегка напоминает нижнюю частью рис. 2.1. WINBOOT запустил WIN.
СОМ, который находит и запускает VMM32.VXD. Как отмечено раньше, VMM32.VXD в Windows
95 эквивалентен WIN386.EXE в расширенном режиме Windows 3.x. Однако вам, вероятно, это мало
о чем говорит. Просто удивительно, сколь мало внимания уделялось, до появления Windows 95,
этой критической нижней половине Windows.
78 Неофициальная Windows 95
Несмотря на расширение имени файла, VMM32.VXD является не единственным Virtual Device
Driver (драйвер виртуального устройства — VxD), а целым собранием VxD- Как и WIN386.EXE,
VMM32.VXD использует формат исполняемого файла W3. Для изучения этих ХУЗ-файлов сущест-
вует программа W3MAP.
W3MAP, я должен сказать, — довольно скучная текстовая программа DOS. Нас больше устро-
ила бы “Quick View” DLL для Windows 95 Explorer, которая позволяла бы просматривать VxD. Но
это уже другой проект, и для другой книги. Между тем, W3MAP делает свое дело вполне достойно,
хотя ее пользовательский интерфейс оставляет желать лучшего. Если вы укажете W3MAP имя
файла W3, она “выплюнет” список VxD, включенных в этот файл. Например:
С:\UNAUTHW>W3map \windows\system\vmm32.vxd
W3 00010000
WMM 00011000
WDO 00058000 WFLATD 00063000
ENABLE 00065000
VSHARE 0006d000
WIN32 00071000
VFBACKUP 0007C000
VCOMM 00080000
COMBUFF VCD 0008C000 00089000
IFSMGR IOS OOObOOOO 00091000
SPOOLER OOObeOOO
VFAT 000c5000
VCACHE 000d1000
Программа установки Windows 95 формирует VMM32.VXD из свободных VxD, поэтому файл
может изменяться от одной машины к другой. Но обычно VMM32.VXD включает около 45 VxD.
Шестнадцатеричные числа, следующие за каждым именем VxD, — это смещение в том файле, где
находится VxD. Однако первый элемент на рисунке — W3, не является VxD, это скорее карта
файлового формата W3 для остатка файла. Все перед этим (т.е. ЮОООЬ байт) является программой
DOS реального режима, — той, что выполняется WIN.COM, — чья работа состоит в переключении
в 32-битовый защищенный режим и инициализации процесса загрузки VxD из остатка файла. Сама
DOS не знает, как загружать VxD, поэтому программа DOS в начале VMM32.VXD включает
программу-загрузчик VxD. *
Все остальное, начиная с Virtual Machine Manager (VMM) по смещению HOOOh в файле, —
действительно VxD. Правда, не совсем корректно называть VMM VxD, так как он, на самом деле,
управляет всеми VxD в системе. Но в терминах формата файла VMM ничуть не отличается от
других VxD. Все драйверы VxD и VMM используют 32-битОвый формат файла Linear Executable
(LE). Если вы получите дамп по одному из смещений, выведенных W3MAP, то увидите заголовок
LE:
C:\UNAUTHW>dump -offset 0x11000 \windows\system\vmm32.vxd
00011000 | 4C 45 00 00 00 00 00 00 02 00 04 00 00 00 00 00 | LE .......
00011010 | 00 80 02 00 40 00 00 00 04 00 00 00 66 05 00 00 | .......f...
Конечно, это не так уж увлекательно. Есть гораздо более интересная идея: используя W3MAP с
опцией -VERBOSE, получить представление о содержимом VMM32.VXD или любого другого
файла, использующего формат W3, вроде WIN386.EXE из расширенного режима Windows 3.x.
Глава 2. Наблюдения за процессом загрузки Chicago
79
Внутри менеджера виртуальной машины
Воспользовавшись W3MAP -VERBOSE, для изучения VMM32.VXD мы получим около 2000
строк информации о VxD, содержащихся в этом файле, и об их функциях. Я не собираюсь надо-
едать вам отображением полной выходной информации (она заняла бы около 50 страниц книги и ее
легко можно создать самому с помощью W3MAP), поэтому давайте пока остановимся на VMM.
Как отмечает Адриан Кинг (.Windows 95 изнутри, с. 67), “Диспетчер виртуальной машины (Virtual
Machine Manager) — это, по сути дела, сердце операционной системы Windows 95. Он включает код,
реализующий все действия базовой системы по управлению задачами, действиями над виртуальной
памятью, загрузкой и завершением программ, а также поддержкой взаимодействия программ”.
Относительно загрузки программ сказано не совсем правильно (VMM ничего не знает ни о Winl6-,
ни о Win32^opMaTax исполняемых файлов), но в остальном полный порядок. VMM — действитель-
но сердце Windows 95. Он был также сердцем Windows 93 (WfW 3.11), Windows 92 (расширенный
режим 3.1) и Windows 90 (расширенный режим 3.0), но этим мы займемся через некоторое время.
Кроме различных предоставляемых сервисов, VMM также содержит первичные системные
обработчики прерываний, ошибок и исключительных ситуаций. Например, все аппаратные прерыва-
ния от таймера, клавиатуры, мыши, COM-портов, и т.д. вначале поступают на VMM, который
обычно передает их на Virtual Programmable Interrupt Controller (PIC) Device (VPICD) VxD. Ана-
логичным образом, общие ошибки защиты (GP) и ошибки виртуальных страниц памяти вначале по-
ступают на VMM, а тот либо обрабатывает их самостоятельно, либо выдает интересующймся VxD.
Итак, на что похож VMM? На рис. 2.3 показана малая часть подробного вывода W3MAP.
Module name: VMM
VMM.DDB @ 0001:0000dc28
Real-mode Init @ 0004:00000566
Device # 0001
Virtual Machine Manager
Version 4.00
Init order: 00000000 (Earliest - same as VMM)
DDB_Control_Proc @ 000013de
D0B_V86_API_Proc @ 00001a11
DDB_PM_API_Proc @ 00001a11
DDB_Service_Table @ 00000644 (179 services)
D10000 00001а08 Get_VMM_Version
010001 @ 00000е46 Get_Cur_VM_Handle
010002 @ 00000e4d Test_Cur_VM_Handle
010003 @ 00000е54 Get_Sys_VM_Handle
010004 @ 00000е5Ь Test_Sys_VM_Handle
010005 @ 00000е64 Validate_VMJ1andle
010006 @ 0000312с Get_VMM_Reenter_Count
010007 @ 00003133 Begin_Reentrant_Execution
010008 @ 0000313с End_Reentrant_Execution
010009 @ 00000704 Install_V86_Break_Point
01000а @ 0000078с Remove_V86_Break_Point
01000b @ 0000098с Allocate_V86_Call_Back
01000с @ 000009de Allocate_PM_Call_Back
01000с @ ОООООаЭО Call_When_VM_Returns
01000с @ 00001а58 Schedule_Global_Event
01000с @ 00001аЬ8 Schedule_VM_Event
Рис. 2.3. Малая часть VMM, как она выведена на экран утилитой W3MAP
Мы видим здесь, что, кроме обеспечения программного интерфейса V86 и защищенного ре-
жимов (доступного приложениям DOS и Windows через функцию 1684h INT 2fh), VMM
обеспечивает 179h (377) сервисов для других VxD. Я же показал только несколько первых.
80 Неофициальная Windows 95
Вы можете исследовать код VMM для этих сервисов, если у вас есть дизассемблер Windows
(вроде моего Windows Source от V Communications) или такой отладчик, как Soft-ICE/Windows от
Nu Mega Technologies (который я в большинстве случаев использовал для этой книги). Например,
код нескольких первых сервисов VMM не особо впечатляет, но обзор этого кода позволяет хорошо
познакомиться с VMM и 32-битовым кодом защищенного режима в целом:
Get_VMM_Version
0028:С0002А08 В800040000
0028:С0002А00 ЗЗС9
0028:C0002A0F F8
0028:С0002А10 СЗ
MOV ЕАХ,00000400
XOR ЕСХ,ЕСХ
CLC
RET
На рис. 2.3 W3MAP сообщает, что GET VMM VERSION расположена в VMM по адресу
lAO8h. Soft-ICE показывает функцию в 28:C0002A08h. Таким образом, VMM загружен по адресу
28:С0001000. Хотя полный адрес вида 0028:C0002A08h требует 48 бит (два байта для сегмента,
называемого в защищенном режиме селектором, и четыре байта для смещения), 32-битовый код
обычно работает только с 32-битовыми смещениями. (См. обсуждение “thunk’’-технологии в главе 14
по поводу интересного примера, в котором Win32-KOfl должен управлять полными 48-битовыми
адресами). Селектор 28h, который является сегментом кода для VMM и всех VxD, и селектор 30h,
являющийся сегментом данных, оба имеют базовый адрес 0 и границу (последнее допустимое
смещение) OFFFFFFFFh, т.е. 4 Гбайт адресного пространства. VxD могут использовать 32-битовое
смещение для управления чем угодно в этом адресном пространстве.
Сервис Get_VMM_Version помогает ознакомиться с набором 32-битовых регистров. Вместо 16-
битовых регистров АХ и СХ, известных большинству программистов PC, этот код манипулирует 32-
битовыми регистрами под названием ЕАХ и ЕСХ. ЕАХ, ЕВХ, ЕСХ и т.д. — “родные” регистры
386-х и поздних микропроцессоров.
Большая часть достоинств 32-битового кода связана с использованием этих 32-битовых
регистров. В частности, для сохранения 32-битового числа 16-битовый код должен был использовать
пару регистров, например DX:AX:
mov dx, 1234h
mov ax, 5678h
32-битовый код, напротив, может сохранить то же самое число в единственном ЕАХ-регистре:
mov eax, 12345678h
Выгода заключается не только в использовании одной инструкции вместо двух, но и том, что
задействуют только один регистр — второй свободен для будущего использования. Поэтому 32-
битовый код позволяет хранить в регистрах чаще всего используемые значения, тогда как 16-
битовый код вынужден держать эти значения в более медленной памяти:
mov [102h], 1234h
mov [100h], 5678h
Конечно, если программа не работает с 32-битовыми величинами, преимущества от работы 32-
битового кода маловероятны. На самом деле, у 32-битового кода есть и другая сторона. Заметим,
что Get_VMM_VERSION возвращает номер версии (здесь 0400h для Windows 4.00; интересно,
почему бы не 05fh для Windows 95?) в ЕАХ, т.е. код использует 32-битовые числа. Вместо трех
байтов В8 00 04, необходимых 16-битовому коду для MOV АХ, 0400h, VMM задействует аж пять
байтов В8 00 04 00 00 для MOV ЕАХ, 0400h.
Оборотная сторона 32-битового кода, следовательно, заключается в его тенденция к “ожи-
рению”. Что и не удивительно. То, что 32-битовый код объемнее 16-битового — главная причина,
почему Windows 95 — для которой Microsoft добивалась “отличной работы” на 4 Мбайт памяти —
не является 100%-ной 32-битовой системой. Согласно Адриану Кингу, “переход к 32-битовому коду
увеличил бы требования к памяти примерно на 40!%” (Windows 95 изнутри, с. 187).
Глава 2. Наблюдения за процессом загрузки Chicago
81
Get_VMM_Version — достаточно скучная функция. А вот код для следующего сервиса VMM,
чье имя W3MAP отобразила на рис. 2.3:
Get_Cur_VM_Handle
0028:C0001F46 831Dt40601C0 MOV ЕВХ, [С00106Е4]
0028:C0001L4 СЗ RET
Я отмечал раньше, что VxD могут управлять чем угодао в адресном пространстве 4 Гбайт, и
бесцеремонное манипулирование VMM [С00106Е4] — хороший пример этого. Из кода становится
очевидно, что в этой конкретной конфигурации четыре байта (DWORD) по адресу C00106E4h со-
держат дескриптор текущей виртуальной машины (VM), который Get_Cur_VM_Handle возвращает
в регистре ЕВХ. В коде VxD ЕВХ обычно содержит дескриптор VM.
Для вызова Get_Cur VM_Handle автор VxD включает заголовочный файл VMM.INC и
использует макрос VMMcall Get_Cur_VM_Handle. Когда транслируется исходный текст VxD, это
превращается в INT 20h, за которым следует DWORD 01000th:
;;; VMMcall Get_Cur_VM_Handle
int20h
dd 010001b '
В выводе W3MAP на рис. 2.3 показано, что VMM ймеет VxD ID #1 и что
Get_Cur_VM_Handle является функцией 1, отсюда и магическое число 10001h. Windows использует
INT 20h для динамической компоновки VxD. VMM содержит обработчик INT 20h; всякий раз,
сталкиваясь с INT 20h, он находит магическое DWORD, расположенное за инструкцией, использует i
его, чтобы определить 32-битовый адрес функции, и заменяет INT 20h DD на 32-битовый вызов
функции:
;;; INT 20h DD OlOOOIh
call [С0001Е46П]
Честно говоря, я соврал. Таким образом, VMM может обрабатывать вызовы
Get_Cur_VM_Handle в Windows 3.x. В Windows 95 это происходит несколько иначе. Для выб-
ранного множества таких часто используемых небольших функций, как Get_Cur_VM_Handle и
Get_Cur_Thread_Handle (обсуждаемой немного попозже в этой главе), динамический компоновщик
VMM заменяет INT 20h DD не на CALL, а на фактический код функции:
;;; INT 20h DD OlOOOIh
mov ebx, [C00106E4h]
Ясно, что DWORD в C00106E4h является дескриптором текущей VM:
:dd с00106е4
0030:C00106E4 C51200E8
Здесь, дескриптор текущей VM — это C51200E8h. Само по себе это число не очень интересно.
Однако дескриптор VM является 32-битовым указателем на блок управления VM (VMCB), поэтому
C51200E8h должен быть указателем VMCB работающей в настоящий момент VM. Давайте
посмотрим, как выглядит VMCB:
:dd с51200е8
0030: С51200Е8 00008802 С5000000 C0EE6F70 00000002 .......ро...... / Г"!
0030: C51200F8 62634В56 000E47V2 00000000 00000008 VMcb.G.............
0030:05120108 C0FBC898 00000005 00000000 С45200Е8 .............R. ;
0030:05120118 С5121А04 00000000 00000000 COFCEABC ....................
Первых несколько полей в VMCB документированы в заголовочных файлах VMM.H и
VMM.INC, поставляемых с Windows DDK:
82 ’ Неофициальная Windows 95
struct cb_s {
ULONG CB_VM_Status;
ULONG CB_High_Linear;
ULONG CB_Client_Pointer;
ULONG CB_VMID;
ULONG CB_Signature;
/* флаги состояния VM »/
/* адрес VM размещенной выше */
Если трактовать эту С-структуру как шаблон и наложить ее на шестнадцатеричный дамп, как
показано на рисунке, то можно убедиться, что эти поля имеют следующие значения для нашей VM:
Поде
Значение
CB_VM_Status
CB_High_Linear
CB_Client_Pointer
cbZvmio
CB_Signature
00008802h
C5000000h
C0EE6F70h
000000002h
62634D56h (‘VMcb’)
CB_VM_Status поддерживает до 32 флагов, указывающих состояние виртуальной машины: ра-
ботает ли машина в фоновом или исключительном режиме, работает ли в VM какая-либо программа
защищенного режима (и если работает, то является ли она 32-битовой), заблокирована ли VM в
текущий момент на семафоре или реализовала свой квант времени и т.д.
CB_High_Linear (например, C5000000h в нашем случае) в частности, полезен всем заинтере-
сованным в передаче сообщений от одной VM к другому. Это база адресного пространства VM,
независимо от того, выполняется ли VM в текущий момент. Например, если бы программа в другой
VM захотела обратиться к чему-либо по адресу реального режима 00А6:0330 в этой VM, она могла
бы это сделать по адресу C5000000h + A60h + 0330h = C5000D90h.
Первых четыре поля в VMCB Windows 95 идентичны полям VMCB Windows 3.x. Новым яв-
ляется поле с сигнатурой ‘VMcb’ по смещению 10h. В Windows 95 это введено как часть проверки
параметров VMM/VxD. Например, сервис, который ожидает получить в регистре ЕВХ дескриптор
VM, может проделать следующее: СМР [ЕВХ+10], 62634D56h.
Поскольку формат VMCB в Windows 95 слегка отличается от формата в Windows 3.1 (см.
Kelly Zytaruk, “The Windows3.1 Virtual Machine Control Block,” Dr. Dobb's Journal, January 1994
and February 1994) и поскольку это только одно из многих изменений в VMM, понятно, что VMM
Windows 95 по сравнению с VMM Windows 3.1 скорее усовершенствован, чем переписан.
Действительно, в первой статье по Chicago, в Microsoft Systems Journal (January 1994, p. 15)
отмечено: “Множество отдельных аспектов Chicago сходны со своими прототипами из Windows 3.1,
особенно это касается виртуальных машин”. Такое заявление — гораздо более точная картина
Windows 95, чем заявление вроде “вся операционная система переписана от самого основания”,
которое Microsoft Systems Journal напечатал месяц спустя. (См. врезку “Полностью переписана?”
раньше в этой главе ).
Беда, однако, в том, что лишь немногие Windows-программисты действительно знают о вирту-
альных машинах в Windows 3.1. Загляните в предметные указатели нескольких книг о програм-
мировании в Windows, и шансы очень невелики, что вы найдете какие-нибудь упоминания о
виртуальных машинах, VMM, VxD или о режиме V86. Даже в книгах из разряда “heavy metal”,
вроде Undocumented Windows, соавтором которой я был, обращение с этими “V’’-объектами весьма
трогательно. Для более близкого знакомства с этими темами вы должны обратиться к книгам, пред-
назначенным специально для разработчиков драйверов устройств. К сожалению, представив
критическую информацию о ядре операционной системы Windows 3.x как информацию о написании
драйверов устройств, Microsoft дезориентировала весьма обширную категорию Windows-
программистов.
В Windows 95 изнутри (с. 165) отмечено, что “Диспетчер виртуальной машины (Virtual
Machine Manager) представляет собой самую главную составную часть операционной системы
Windows 95”. Но ведь то же самое можно было бы сказать и о VMM Windows 3.x в расширенном
Глава .2.Н,абл1одения за процессом загрузки Chicago
83
режиме. Почему к этому предмету до Windows 95 было такое пренебрежительное отношение, совер-
шенно непонятно. 32-битовая операционная система защищенного режима была прямо перед нами, а
мы этого так и не заметили!
В то же время VMM в Windows 95 существенно больше, чем VMM в Windows 3.1. Мы видели
на рис. 2.3, что VMM Windows 95 обеспечивает 377 сервисов; тогда как в Windows 3.1 — только
241. Итак, в очень поверхностном смысле, VMM Windows 95 обновлен почти на одну треть. Вот
список нескольких новых сервисов VMM в Windows 95:
0100F9 @ 00001848
0100fа @ 000073f4
OlOOfb © 00001Ь4с
OlOOfc © 0000Ю1С
0100fd @ 00002650
OlOOfe © 0000265с
0100ff © 00002710
010100 @ 00002733
010101 © 00000020
010102 @ 00006С65
010103 © 00006f50
010104 @ 00007f1a
010105 @ 00008023
.010106 @ 00002754
010107 © 00007f40
010108 @ 00000е85
010109 @ 00000е8с
01010а © ОООООеЭЗ
_GetThreadTimeSlicePriority
_SetThreadTimeSlicePriority
Schedule_Th read_Event
Cancel- Thread_Event
Set_Th read_T ime_0ut
Set_Async_T ime_0ut
_AllocateThreadDataSlot
_FreeThreadDataSlot
_CreatMutex
_0estroyMutex
_GetMutexOwner
Call_When_Thread_Switched
VMMCreateThread
VMMStartThread
VMMTe rminateThread
Get_Cu r_Th read_Handle
Test_Cur_Thread_Handle
Get_Sys_ Thread_Handle
He удивительно, что множество новых функций VMM относятся к ветвям {thread, “нить”,
иногда “поток”. — Прим, ред.) и к таким объектам синхронизации, как переключатели {mutex,
“взаимное исключение”). Ветви синхронизации WIN32 и API в Windows 95 основаны на этой
функциональности VMM. Давайте рассмотрим код для двух крошечных сервисов VMM, связанных
с ветвями:
Get_Cu r_Th read_Handle 0028:C0001E85 8B3D700601C 0028:C0001E8B C3 MOV RET EDI,[00010670]
Get_Sys_Th read_Handle
0028:C0001E93 8B30740601C0 MOV EDI,[00010674]
0028:00001 EAO 03 RET
Так же, как VxD используют ЕВХ для передачи дескриптора VM, EDI в Windows 95 обычно
используется для хранения дескриптора ветви. Можно убедиться, что в нашей конфигурации VMM
хранит дескриптор текущей ветви в C00010670h, а дескриптор системной ветви — в DWORD,
который следует за ним. В данном случае текущая ветвь оказалась и системной:
0030:00010670 С4520298 С4520298
Дескриптор ветви является 32-битовым указателем на блок управления ветви (ТНСВ). Для про-
смотра ТНСВ можно воспользоваться отладчиком:
:dd С4520298
0030:04520298 00000000 C0010C2C 42434854 THCB
0030:045202A8 00000180 C45200E8 012F0001 R. . ./
0030:045202B8 000070EB 00000000 00A70117 P *
0030:04520208 01000000 00000000 00100005
0030:C45202D8 C0FD0D4C C000D18C 80001000 . . . .L
Неофициальная Windows 95
Кроме того, что по смещению ОСЬ находится сигнатура ‘ТНСВ’, ничего полезного мы не уви-
дели. Однако файл VMM.H, поставляемый с DDK, описывает ТНСВ следующей С-структурой:
struct tcb_s {
ULONG TCB_Flags; /* флаги состояния ветви »/
ULONG TCB_Reserved1; /* для внутреннего использования VMM */
ULONG TCB_Reserved2; /* для внутреннего использования VMM */
ULONG TCB_Signature;
ULONG TCB_ClientPrt; /* клиент, зарегистрированный ветвью */
ULONG TCB_VMHandle; /* VM, частью которой является ветвь */
USHORT TCB_ThreadId; /* уникальный идентификатор ветви */
USHORT TCB.PMLockOrigSS; /* оригинальный SS:ESP до блокировки стека */
USHORT TCB_PMLockOrigESP;
ULONG TCB.PMLockOrigEIP; /* оригинальный CS:EIP до блокировки стека */
ULONG TCB_PMLocStackCount;
USHORT TCB_PMLockOrigCS;
USHORT TCB_PMPSPSelector;
ULONG TCB_ThreadType; /* dword, посылаемое VMMCreateThread */
USHORT TCB_pad1; /* для повторного использования: для выравнивания по dword ♦/
UCHAR TCB_pad2; . /* для повторного использования: для выравнивания no dword */
UCHAR TCB_extErrLocus; /* положение расширенной ошибки /
USHORT TCB_extErr; /* код расширенной ошибки */
UCHAR TCB_extErrAction; /* действие “ “ */
UCHAR TCB_extErrClass; /* класс “ “ */
ULONG TCB_extErrPrt; /* указатель “ “ ♦/
};
Здесь обнаруживается несколько интересных вещей — заметим, например, что ТНСВ имеет
обратную связь с VMCB, — но один пункт особенно важен:
USHORT TCB_PMPSPSelector;
Теперь я спрашиваю, как кто-то может заявлять, что приложения WIN32 не используют MS
DOS, когда в документированной структуре ветви имеется поле, содержащее PSP? Мы видели в
главе 1, что задачи WIN32 имеют PSP. Мы видели, что это логически вытекало из зависимости
USER32 от модуля Winl6 USER. Но, после многократных заявлений Microsoft, что “Windows 95
окончательно прерывает все связи с кодом MS DOS реального режима”, обнаружение первичной
структуры данных DOS — PSP, внутри одной из ключевых структур данных WIN32 — ветви,
просто шокирует.
Давайте, однако, посмотрим, действительно ли это DOS PSP реального режима, с которым свя-
зывается каждая ветвь. Наложив С-структуру из VMM.H на шестнадцатеричный дамп, можно убе-
диться, что для этой конкретной ветви (которая, вспомните, является системной ветвью)
TCB_PMPSPSelector — это 00А7Ь. В имени находятся “РМ” и “Selector”, поэтому это должен быть
селектор защищенного режима для PSP. Давайте изучим область 00А7 отладчиком:
:db а7:0
00А7:000р|сР 2о| 00 9F 00 9А F0 FE-10 F0 82 ОС ЗС FD F2 ОС ............<...
00А7:0010 35 FD 74 01 93 12 |Р5 17-107 01 01 00 02 03 FF FF 5.t........
00А7:0020 FF FF FF FF FF FF FF FF-FF FF FF FF | В7 Ор] 24 02 ..........$.
0ОА7:ООЗО СВ 18 80 00 00 00 FC 29-FF FF FF FF 00 00 00 00 .............)....
Первых два байта, CD 20 — это смертный приговор, т.е. PSP. Чтобы окончательно убедиться,
давайте выясним, имеет ли он сегмент среды по смещению 2СЬ. Здесь WORD в 2СЬ — это 00В7Ь:
:d Ь7:0
00В7:0000 4D54 3D50 3A43 575С 4Е49
00В7:0010 4D45 3050 3A43 575С 4Е49
00В7:0020 4F52 504D 3D54 7024 6724
4F44 5357 5400 TMP=C:\WINDOWS.Т
4F44 5357 5000 ЕМР=С:\WINDOWS.Р
5000 5441 3D48 PDMPT=$p$g.РАТН=
Глава 2. Наблюдения за процессолл загрузки Chicago
85
Действительно, похоже на сегмент окружения. Уже теплее. А как насчет PSP предка по смеще-
нию 16h? WORD по смещению 16h — это 17D5h, который выглядит скорее как адрес параграфа в
реальном режиме, чем как селектор в защищенном режиме. В Soft-ICE, помещая знак перед
адресом, определяющим адрес в реальном режиме, мы увидим:
:db &17d5^0____
17D5:0000| CD 20| 80 9F 00 9A F0 FE-1D F0 41 01 5C 17 Al 10 ..........A.\...
17D5:0010 62 14 74 01 93 12 5C 17-01 01 01 00 02 FF FF FF b.t...\...............
17D5:0020 FF FF FF FF FF FF FF FF FF FF FF FF C8 17 F2 01 , ..............
Ясно — другой PSP. Несколько странно, что PSP системной ветви содержит селектор защищен-
ного режима ее окружения и сегмент реального режима ее предка, но, в любом случае, — это PSP.
Да, но находится ли это в стандартной памяти? Если эта структура была бы расположена в
дополнительной памяти, не имело бы никакого значения, что это напоминает PSP — DOS ее не ка-
салась бы, так что это была бы структура защищенного режима, просто в форме PSP. Чтобы быть
истинной структурой данных DOS, она должна быть расположена в стандартной памяти. Чтобы
выяснить, действительно ли этот PSP расположен в стандартной памяти, можно воспользоваться
отладчиком и проверить элемент Local Descriptor Table (таблица локальных дескрипторов — LDT)
для селектора 00A7h:
: Idt а7
00А7 Data16 Base=00018BB0 Lini=OOOOOOFF DPL=3 P RW
Адрес 18BB0h определенно находится в стандартной памяти. Давайте еще раз убедимся, что это
PSP:
:d &18bb:Q_____
18ВВ:0000]cD 20|00 9F 00 9A
18BB:0010 35 FD 74 01 93 12
18BB:0020 FF FF FF FF FF FF
F0 FE-1D FO 82 ОС ЗС FD F2 ОС
D5 17-07 01 01 00 02 03 FF FF
FF FF-FF FF FF FF B7 00 24 02
5.t..............
.................S.
Структура по адресу 18BB:0 реального режима выглядит как адрес структуры, для которой мы
выдали дамп по адресу 00А7:0000 защищенного режима. Она выглядит столь похожей потому, что
это именно та же самая структура! Причина в том, что селектор A7h имеет базовый адрес 18BB0h,
поэтому адрес 00А7:0000 защищенного режима и адрес 18ВВ:0000 реального режима указывают на
тот же самый блок памяти. И этим блоком памяти является DOS PSP. Мы разберемся с этими PSP
ветвей в главе 13 (в разделе “WIN32 и PSP”). Впрочем, обратите внимание, что каждая ветвь не
имеет своего собственного PSP. Взамен этого PSP сопровождает задачу Windows, а задача может
иметь более чем одну ветвь.
Вместо попытки представить себе содержимое ТНСВ из сырого шестнадцатеричного дампа, луч-
ше использовать команду THREAD из Soft-ICE:
:thread С4520298
RingOTCB ID Context Ring3TCB Process TaskDB PDB SZ Owner
C4520298 0001 C0FD387C 8117B314 8117B23C 0097 00A7 32 VM 01
CS:EIP=05FB:000001F8 SS:ESP=OCCB:00000226 08=0000 ES=FFFF FS=OOOO GS=OOOO
EAX=C0FD1607
ESI=00000080
EBX=00010018 ECX=00020004 E0X=C0000004
EDI=00000090 EBP=OOOOOOOO EC0DE=C0000004
TLS Offeset OOCC =0000000 VPICD
TLS Offeset 00D0 = 0000000 SHELL
TLS Offeset 00D4 = C0FD8E48 VMCPD
TLS Offeset 00D8 = C0FD1834 VWIN32
TLS Offeset OODC = COFD1204 PAGESWAP
Вывод Soft-ICE демонстрирует, что C4520298h — это Ring 0 ТНСВ для использования драй-
верами VxD. Приложение WIN32 под названием GetCurrentThreadID возвращает другой деск-
риптор: один из них Soft-ICE идентифицирует как Ring 3 ТНСВ. (По поводу использования
GetCurrentThreadld см. CHGDIR.C — WIN32-nporpaMMy в главе 13).
86
Неофициальная Windows 95
TLS в Soft-ICE означает Thread Local Storage. Теперь можно убедиться, что некоторые VxD,
каковыми являются VPICD, SHELL и т.д., сохраняют переменные по принципу “каждой ветви”.
Как данные реализации существуют для VMS (см. главы 3 и 4), так и TLS — для ветвей.
Мы изучали новые функции, обеспечиваемые VMM в Windows 95. Кроме ветвей и сервисов
синхронизации, существует еще одно интересное множество новых сервисов VMM, обеспечивающих
возможность отмены некоторых старых сервисов VMM. Например, VxD всегда могли перехватить
порты ввода-вывода, (или же, “вызывать эту функцию всякий раз, когда кто-нибудь выполнял
инструкции IN или OUT для этого порта ввода-вывода”), используя сервисы VMM
Install_IO_Handler и Install_Mult_IO_Handler. Аналогично, VxD могут перехватывать цепочку ,
прерываний V86, перехватывать сбои V86 и даже перехватывать сервисы устройств. (Другими
словами, VxD могут перехватывать все обращения к любому VxD или сервису VMM, как,
например, Get_Cur_VM_handle — то ли для отслеживания, то ли модификации.)
Но нигде не, было средств удаления этих обработчиков. В Windows 95 такое средство должно
быть, поскольку динамически VxD могут выгружаться или перезагружаться, хотя бы из-за событий
типа Plug-and-Play. Поэтому VMM в Windows 95 обеспечивает новые функции удаления:
010116 @ 00001161
010117 @> ООООЮсб
010118 @> ОООООЬее
010119 @> 00001520
01011а @ 00001527
01011b @ 0000152е
01011с @> 0000004с
Remove_IO_Handler
Remove_Mult_IO_Handler
Unhook_V86_Int_Chain
Unhook_V86_Fault
Unhook_PM_Fault
Unhook_VMM_Fault
Unhook_Device_Servi.ce
Постойте, это еще не все! Большая часть таких новых возможностей управления памятью
WIN32 в Windows 95, как ввод-вывод для файлов отображения памяти (который, в сущности, мо-
жет создавать файлы данных в файле обмена Windows; см. дискуссию о файлах отображения
памяти в главе 14), основана на множестве новых сервисов, обеспечиваемых VMM:
OlOlld @ 000001f0 _MMReserve
01011е @> 00001890 _MMCommit
OlOllf @> 00001bc7 _MMDecommit
010120 @ 00001b20 _MMRegisterPager
010121 @> 00001 b99 _MMQueryPagerInfo
010122 @> 00001 bee _MMDeregisterPager
010123 @> 00000584 _MMCreateContext
010124 @> 0000034b _MMDest royContext
010125 @> ОООООбсО _MMAttach
010126 @> 00000334 _MMFlush
010127 @ 00000745 _MMCopy
010128 @> 00001770 _MMCommitPhys
Все доступы к реестру идут через множество новых функций VMM; API реестра Windows —
это всего-навсего надстройка над этими вызовами VMM:
010148 @> 00005066 _RegOpenKey
010149 @ 0000506b _RegCloseKey
01014a @ 00005057 _RegCreateKey
01014b @ 00005070 _RegDeleteKey
01014C @ 00005075 _RegEnumKey
01014d @ 00005084 _RegQueryValue
01014e @ ООООбОЬЗ _RegSetValue
Далее, появилось новое обеспечение обработчика событий:
010160 @ 00008bb2 010160 @ 00008ЫС Ti me_S 1 i c e_S у s_ VM_ I d 1 e Time_Slice_Sleep
Глава 2. Наблюдения за процессом загрузки Chicago
010160 @ 00008784
010160 @ 0000864а
010160 @ 000086аЬ
010160 @ 000086е8
Boost_With_Decay
Set_Inversion_Pri
Reset_Inve rsion_P ri
Release_Inversion Pri
• И так далее, и так далее... Но со всеми этими новыми возможностями VMM важно не потерять
из виду, что VMM Windows 95— просто следующая итерация того же VMM, что появился в WfW
3.11, в расширенном режиме Windows 3.1 и в расширенном режиме Windows 3.0. Весьма приме-
чательно, что 32-битовый файловый доступ Microsoft смогла позаимствовать из ранней пред-бета-
версии проекта Windows 95 и включить его в WfW 3.11, вместе с практически неизменным VMM из
Windows 3.1. Функциональность, обеспеченная прежним VMM, дала надежную основу для форми-
рования Windows 95.
VXD: TSR 1990-х годов
Мы обсудили только VMM. Не забудьте, однако, что около 40 других VxD также являются
частью VMM32.VXD. Раздел [386Enh] в SYSTEM.INI использует звездочки для указания VxD,
которые встроены в VMM32.VXD (device=*vpicd, device=*v86mmgr и т.д.). Эти VxD включают
уровень Windows-виртуализации аппаратного обеспечения, который обеспечивает 32-битовую
эмуляцию в защищенном режиме большинства стандартных возможностей PC и BIOS. Приведем
несколько примеров:
VxD Описание
VPICD Виртуальное устройство программируемого контроллера прерываний (PIC)
VDMAD Виртуальное устройство прямого доступа к памяти (DMA)
VKD Виртуальное устройство клавиатуры
VDD Виртуальное устройство дисплея
VMD Виртуальное устройство мыши
VTD Виртуальное устройство таймера
IDS Супервизор ввода-вывода (бывшие BLOCKDEV и виртуальное устройство жесткого диска)
Файловая система Windows также располагается внутри VMM32.VXD. Например:
VxD Описание
IFSMgr Менеджер инсталируемой файловой системы (IFS)
VCACHE Виртуальный кзш
VFAT Виртуальная система таблицы размещения файлов DOS (FAT)
VSHARE VxD, заменяющий SHARE.EXE
Давайте рассмотрим один из этих примеров более подробно. Здесь находится малая часть под-
робного вывода W3MAP, демонстрирующая IFSMgr в VMM32.VXD:
Module name: IFSMgr
IFSMgr_DDB @ 0001 :00002bfc
Real-mode Inite @ 0004 :00000000
Device # 0040
32-bit file access Installable File System (IFS) Manager
Version 3.00
Init order: A0011000
DDB_Control_Proc @ OOOOOOcc
DDB_V86_API_PrOC @ 00001ca7
DDB_Service_Table @ 00002a60 (67 services)
400000 @ 0000031a
400001 @ 00000321
400002 @ 00000367
400003 @ 00000395
400004 @ 000003fa
400005 @ 000D03fa
400006 @ 000019e4
IFSMgr_Get_Version
IFSMgr_RegisterMount
IFSMgr_RegisterNet
IFSMgr_RegisterMailSlot
IFSMgr_Attach
IFSMgr_Detach
IFSMg r_Get_NetT ime
Неофициальная Windows 95
400007 © 0000ef7c IFSMgr_Get_DOSTime
400008 © 0000af30 IFSMgr_SetupConnection
400009 © 00000401 IFSMgr_DerefConnection
40000a @ 0000162 IFSMgr_ServerDOSCall
40000b © 0000de18 IFSMgr_CompleteAsync
...etc...
4005f © 000099b8 IFSMgr_FSDAttachSFT
40060 © ООООЗавс IFSMg r_GetT imeZoneBias
40061 © 000120ес IFSMgr_PNPEvent
40062 @ 000003с4 IFSMgr_RegisterCFSD
40063 @ 000072с8 IFSMgr_Win32MapExtendedHandleToSFT
40064 @ 00000429 IFSMgr_DbgSetHandleLimit
40065 © 00007464 IFSMgr_Win32MapSFTToExtendedHandle
40066 @ 000099Ьс IFSMgr_FSDGetCurrentDrive
Хотя IFSMgr получил широкое распространение со времени появления WfW 3.11 в конце
1993 г., программистская документация для такого жизненно важного интерфейса доступна только
как часть бета-версии Chicago. Почему разработчик должен подписывать NDA на бета-версию
Chicago только для изучения файловой системы уже выпущенного продукта? Более того,
документация в Chicago DDK ограничена двумя, в общем-то, незавершенными заголовочными
файлами IFSMGR.INC и IFS.H. А другие части такого 32-битового файлового доступа, как,
например, VFAT (также являющегося частью давно выпущенной WfW 3.11), оказались не доку-
ментированными вовсе. ».
Несмотря на то, что заголовочные файлы IFSMgr, включенные в Windows 95 DDK, пред-
ставляют собой, скорее, “скелеты”, они все же могут обеспечить нас важной информацией отно-
сительно взаимоотношения Windows 95 и MS DOS. IFSMGR.INC имеет те же самые ссылки на SFT
(System File Table DOS реального режима), которые мы видели в выводе W3MAP. Существует так-
же интересная ссылка на IFSMgr_ServerDOSCall, которая эмулирует странную неописанную
функцию 5D00h INT 2lh (см. Undocumented DOS, 2d ed., p. 295, 723).
На обзор всех VxD в VMM32.VXD можно было бы потратить кучу времени. Помимо упомяну-
тых, довольно важны также следующие VxD:
VWIN32 VCOND VXDLDR CONFIGMG DOSMGR V86MMGR SHELL VxD для поддержки И1п32-приложений в Windows 95 Виртуальное устройство CON; используется для Win32 Console-приложений Динамический загрузчик/выгрузчик VxD; используется в Plug and Play Менеджер конфигурации Plug and Play Расширитель DOS; обеспечивает INT 21h в защищенном режиме V86 менеджер памяти; обеспечивает XMS, EMS, расширитель DOS VxD для доступа к Windows API; включает "Арру Time”
Ну, о VMM32.VXD пока достаточно. Когда-то очень давно, постарайтесь припомнить, мы изу-
чали процесс загрузки Windows и встретили такие две строки на рис. 2.2:
OPEN C:\W0ND0WS\system\vmm32.vxd
EXEC C:\W0ND0WS\system\vmm32.vxd
На рис. 2.2 мы видели, как Windows 95 (которая на этом этапе начальной загрузки пред-
ставляется просто собранием VxD) ищет различные драйверы устройств DOS в реальном режиме,
большую часть которых я не установил:
OPEN QEMM386S [FAIT 2] Quarterdeck QEMM/386
OPEN 386МАХ$$ [FAIT 2] Qualitas 386Max
OPEN SMARTAAR [FAIT 2] SmartDrive
OPEN SDebugDD [FAIT 2] WDEB386.EXE (ядро отладчика Windows)
OPEN NDISHLPS [FAIT 2] NDISHLP.SYS (для NDIS 2.0)
OPEN IFS$HLP$ IFSHLP.SYS
OPEN CONFIGS CONFIGS в WINBOOT.SYS
OPEN SCSIMGR [FAIT 2]
Глава 2. Наблюдения за процессолл загрузки Chicago
Windows 95 пытается отыскать эти драйверы устройств реального режима DOS вовсе не для
того, чтобы затем вытеснить. Напротив, Windows 95 пытается найти эти драйверы реального режи-
ма, чтобы при обнаружении одного из них VxD связывался с ним через вызовы DOS IOCTL (функ-
ция 44h INT 2 lh), чтобы снабдить его У86-обработчиком косвенного вызова и т.д.
На рис. 2.2 видно также, что Windows 95 загружает большое число дополнительных VxD (с
расширениями 386 или VXD), которые не располагаются внутри VMM32.VXD. В нашей конкрет-
ной конфигурации, например, SYSTEM.INI содержит директивы device= для загрузки 32-битового
программндго обеспечения работы с сетью в защищенном режиме:
OPEN C:\WINDOWS\system\vserver.386
OPEN C:\WINDOWS\system\vredir.386
OPEN C:\WINDOWS\system\vnetbios.386
OPEN C:\WINDOWS\system\nwlink.386
OPEN C:\WINDOWS\system\wsipx.386
OPEN C:\WIND0WS\system\wsock.386
OPEN C:\WIND0WS\systern\vnetsup.386
OPEN C:\WIND0WS\system\netbeui.386
OPEN C:\WIND0WS\system\ndis.386
OPEN C:\WIND0WS\system\ndis2sup.386
OPEN C:\WINDOWS\system\msodisup.386
OPEN C:\WIND0WS\system\ee16.386
Сервер для однорангового доступа
Сетевой редиректор
Виртуальная NetBIOS (INT 5Ch, и т.д.)
Windows NetWare-совместимый протокол
Windows Sockets для Novell IPX
Windows Sockets
“Win32 виртуальное обеспечение сети"
Расширенный интерфейс пользователя NetBIOS
NDIS 3.0
Преобразование между NDIS 2.0 and 3.0
“Преобразователь обеспечения 0DI в ODI MLID”
Intel EtherExpress 16 (NDIS 3.0)
ЕЕ16.386 VxD внизу этого списка действительно “работает”, передавая сообщения карте сете-
вого адаптера. Как водится для программного обеспечения сети, пакет данных должен пройти много
уровней до передачи по проводу на другую машину.
Windows Microsoft for Workgroups 3.11 Resource Kit содержит приемлемые описания многих
этих файлов и их совместной работы. Windows 95 изнутри Адриана Кинга содержит целую главу о
работе с сетью в Windows 95 (с. 375-413). Расставшись с Microsoft, Кинг был ответственным за
разработки в фирме Artisoft, изготовителе известной одноранговой сети LANtastic, поэтому он дейст-
вительно сведущ в данной области.
Возвратимся еще раз к рис. 2.2. Все файлы .386 и .VXD используют 32-битовый формат Linear
Executable (LE), (формат LX, использованный 32-битовыми приложениями OS/2 2.x, является
вариантом формата LE). Утилита LEDUMP позволяет отобразить содержимое этих VxD. Напри-
мер, NDIS.386 содержит 32-битовую реализацию версии 3.0 Microsoft Network Device Interface
Standard (NDIS) для защищенного режима:
C:\UNAUTHW>LEDUMP \windows\system\ndis.386
DDB_V86_API_PrOC @ 00004512
DDB_PN_API_PrOC 0 00004512
DDB_Service_Table 0 OOOOebO (57 serveces)
280000 0 00000920 NdisGetVersion
280001 0 00000cc5 NdisAllocateSpinLock
280002 0 00000cf9 NdisFreeSpinLock
280003 0 00000cd9 NdisAcquireSpinLock
280004 0 OOOOOced NdisReleaseSpinLock
28002d 0 00000d50 NdisSend
28002e 0 00000d55 NdisTransferData
28002f 0 00003d5a NdisReset
280030 0 00003d5f NdisRequest
280039 0 00000d5a NdisCompleteSend
28003a 0 00.000d5f NdisCompleteT ransferData
28003b 0 00000d78 NdisCompleteReset
28003c 0 00000d7d NdisCompleteRequest
90
Неофициальная Windows 95
Кроме отображения в процессе загрузки Windows 95 тех VxD, что в явной форме указываются
в директивах device= в SYSTEM.INI, на рис. 2.2 также показана динамическая загрузка супер-
визором ввода-вывода (IOS) Windows 95 VxD из подкаталога IOSUBSYS. Сюда включены VxD
RMM, трассировщик носителей (voltrack) и DiskTSD. RMM — это Real Mode Mapper, который
превращает дисковый драйвер реального режима в драйвер Fastdisk защищенного режима. В
Windows 95 32-битовый файловый доступ требует 32-битового обращения к диску, поэтому, если у
вас есть дисковый драйвер реального режима (DBLSPACE.BIN будет лучшим примером, пока
Microsoft не поставит Dblspace VxD для Windows 95), RMM превратит его в подобие VxD. VxD
трассировщика носителей (voltrack) следит за состоянием диска для дисководов со сменными носи-
телями, вроде дисководов гибких дисков или ленточных накопителей. DiskTSD — это драйвер спе-
цифического типа, ответственный за преобразование логического запроса в запрос к физическому
диску.
Ближе к нижней части рис. 2.2 видно, как Windows 95 ищет командный файл под названием
WINSTART.BAT. Как отмечено раньше, я отредактировал вывод INTRSPY в этом месте из-за того,
что Windows 95 ищет этот файл в каждом подкаталоге PATH. У меня нет файла WINSTART.BAT,
поэтому поиск был неудачным. Если этот командный файл существует, Windows 95 использует его
для запуска программного обеспечения DOS (обычно TSR) в системной VM. В конце главы 6 при-
ведено краткое обсуждение WINSTART.BAT. Обратите внимание, что ищет WINSTART.BAT не
какая-то отвлеченная сущность “Windows 95”, а конкретно DOSMGR VxD из VMM32.VXD.
В самом конце рис. 2.2 Windows 95 запускает KRNL386.EXE. Это ядро Winl6 мы увидим через
некоторое время. И опять этим занимается вполне определенный VxD, а именно, SHELL из
VMM32.VXD. SHELL будет загружать любой файл под названием KRNL386.EXE. И мы увидим в
главе 6, что это весьма важно.
Хотя мы и потратили много времени на обзор уровня VMM и VxD Windows 95, наше исследо-
вание было весьма поверхностным — это был не всеобъемлющий экскурс по Chicago, а торопливый
и утомительный осмотр некоторых его подземных переходов, водопровода и сточных труб. Зато вы,
по крайней мере, ощутили всю прелесть операционной системы Windows 95.
Обратите внимание, что я говорю об операционной системе, даже не взглянув на ядра Win 16 и
WIN32. Это потому, что операционной системой Windows является VMM. Для доказательства это-
го, вполне возможно, потребуется весь остаток книги. Между тем, постарайтесь припомнить две
вещи, о которых вы читали: во-первых, именно VMM позволяет Microsoft называть Windows 95 за-
конченной операционной систем, во-вторых, VMM присутствовал уже в Windows 3.x. Поэтому,
если принять, что Windows 95 является полной операционной системой, то же самое можно сказать
и о WfW 3.11, Windows 3.1 в расширенном режиме и даже о Windows 3.0 в расширенном режиме.
Теперь, если VMM — это операционная система, то что же представляют собой VxD, такие как
IFSMGR, DOSMGR, VPICD и т.д.? Существует общее соглашение о громадной важности VxD в
Windows 95. Например, в “Chicago” Reviewer's Guide Microsoft заявляет:
“Большинство возможностей компьютерной системы в Chicago обеспечиваются VxD, а не кодом реаль-
ного режима или процедурами BIOS” (р. 84).
“Сетевой компонент Chicago построен как Windows VxD” (р. 132).
“Драйверы виртуальных устройств являются составной частью операционной системы Chicago и играют
здесь намного более важную роль, чем в Windows 3.1, так как многие компоненты операционной
системы представлены VxD” (р. 85).
Действительно, VxD должны рассматриваться совместно с VMM как часть операционной систе-
мы Windows. В другом смысле, впрочем, VxD являются расширениями операционной системы
Windows, которые может установить пользователь. Как отмечено раньше, Microsoft внедрила 32-
битовый файловый доступ в WfW 3.11, надстроив некоторые VxD поверх практически не претер-
певшего изменений VMM. Это Означает, что некоторые независимые производители ПО могли бы
делать то же самое, по крайней мере в принципе.
Другими словами, VxD становятся составной (простите, я имел в виду “интегрированной”) час-
тью операционной системы, которая может поставляться любым, кто располагает копией Windows
Device Driver Kit.
Глава 2. Наблюдения за процессолл загрузки Chicago
91
Итак, VxD — доступные пользователю части операционной системы. Этот карт-бланш для вся -
кого любителя разбираться и копаться в операционной системе напоминает “бзик” на резидентные
программы DOS в 80-х годах. И правда, VxD в Windows представляют то же, что и TSR в DOS.
И опять-таки, это правильно не только для Windows 95, но и для Windows 3.x. Рассмотрим две
функции VMM: Cali_Priority_VM_Event и Hook_V86_Int_Chain. Call_Priority_VM_Event —
полный базис для коммуникации внутри VM. Как представляет его Microsoft Knowledgebase,
“Call_Priority_VM_Event — это одна из самых ценных функций виртуальных устройств”.
Hook_V86_Int_Chain представляет полный базис для обхода DOS. Эти две функции сначала появи-
лись в Windows 3.0 и остались в Windows 95 практически без изменений (правда, Windows 95
добавила еще функцию Call_Priority_Thread_Event). Уже только эти две функции обеспечивают '
огромные возможности, и они вызываются непосредственно из VxD.
С такой точки зрения названия Virtual Device Driver (драйвер виртуального устройства) и
Device Driver Kit (пакет для разработки драйверов устройств) неудачны. Они автоматически исклю-
чают большинство Windows-программистов, чувствующих, что написание драйверов устройств
становится далекой от них областью. Более подходящие имена были бы — “TSR для Windows” или
“Пожалуйста, взломайте нашу операционную систему”. Итак, имена VxD и DDK отчуждают многих
программистов, которые, при других условиях, могли бы буквально накинуться на это обеспечение.
Например, Мэтт Петрек (Matt Pietrek) — законченный хакер операционных систем, если так
можно сказать, — сообщил в Microsoft Systems Journal (August 1994, p. 30) о VxD следующее:
В настоящее время у некоторых разработчиков вошло в привычку высказывать мысли вроде “Chicago
это сплошь VxD”. Но, хотя VxD и играют большую роль в Chicago, эта система представлена не только
VxD. Если ваши программы построены на API WIN32, большая часть используемого программами
системного кода будет находиться в третьем круге (Ring 3) системных DLL. Если вы не имеете особых
потребностей, подобных управлению прерываниями, вам нет необходимости писать VxD для работы с
Chicago. Я думаю, что крупномасштабные приложения Chicago, содержащие много кода в виде VxD,
будут скорее исключением из правила.
По общему признанию, достаточно мало Windows-программистов будут писать VxD для обра-
ботки аппаратных прерываний или управления устройствами. Однако, потратив немного время на
DDK, вы обнаружите горы недокументированных возможностей VxD, получить которые под
Windows иначе было бы трудно или просто невозможно. Всякий раз, когда программист сообщает,
что нечто “невозможно” в Windows, я полагаю, правильный ответ — “Это не так. Напишите VxD”.
Так же, как TSR позволял DOS-программистам делать в 80-х годах то, что иначе было невоз-
можным, VxD позволят Windows-программистам забираться куда угодно и делать что угодно в 90-
х. Хорошо ли это, плохо ли (вспомните все свои проблемы с резидентными программами), — это
уже другой вопрос.
От KRNL386.EXE к CAB32.EXE
В хвосте списка на рис. 2.2, видно, что Windows 95 (или, говоря точнее, SHELL VxD) вы-
полняет ядро Winl6, KRNL386.EXE. Ядро Winl6 — это, несомненно, жизненно важная часть
Windows 95, но я не собираюсь много говорить об этом здесь, потому что Мэтт Петрек — да, тот са-
мый, с кем я некоторое время назад не соглашался относительно VxD— приводит великолепное
.обсуждение ядра Winl6 в своей книге Inside Windows. (Мэтт Петрек так основательно описал
ядро, что книгу можно было бы назвать “Ядро изнутри”). Глава 1 книги Мэтта Петрека (The Big
Bang: Starting Up and Shutting Down Windows, p. 1-78) описывает инициализацию KRNL386
очень детально.
KRNL386.EXE обеспечивает Winl6 KERNEL такими функциями API, как (выбираю совер-
шенно наугад) GlobalAlloc, LoadLibrary, LoadResource, FindAtom и SetSelectorBase. KRNL386
Использует 16-битовый формат файла Segmented Executable или New Executable (NE). В отличие от
ситуации с форматами файлов W3 и LE, используемых для VxD, о формате NE знают многие ути-
литы, и потому существует достаточно средств для исследования KRNL386.EXE. Например, Borland
C++ поставляется с TDUMP, различные версии Microsoft С предоставляют в наше распоряжение
92
Неофициальная Windows 95
EXEHDR, а мои собственный продукт Windows Source поставляется c EXEDUMP. (Это очень хо-
рошо, что Windows 95 поддерживает длинные имена файлов: система 8.3 настолько исчерпала себя,
что стало трудно находить приемлемые уникальные имена для утилит, расшифровывающих заголов-
ки исполняемых модулей).
Для просмотра KRNL386.EXE или любого исполняемого модуля Winl6 можно даже исполь-
зовать саму оболочку Windows 95: щелкните правой кнопкой мыши на исполняемом модуле, затем в
появившемся меню выберите Quick View (Быстрый просмотр). (Мы увидим в следующем разделе
“Исследование Explorer”, что это меню по нажатии правой кнопки мыши работает на основе реестра
Windows 95 и подлежит расширению). На рис. 2.4 показано несколько API, экспортированных из
KRNL386.EXE. Не все эти API документированы. Книга Undocumented Windows, при моем соав-
торстве, в большой степени посвящена именно тем функциям, экспортированным из KRNL386.EXE
в Windows 3.x, которые не упомянуты в руководстве программиста Windows или заголовочных
файлах.
в
й
Ж
1 £)*<.«»
M
ТЙМкотр»
Рис. 2.4. Quick View из Explorer показывает в Chicago KRNL386.EXE более 200 API, экспортированных
ядром Winl6
«ж
103
w
213
418
69
1/
2Ш
226
2 V
211
223
16<
168
102
•tof
Mu icwt йм*. RerrH Intel ta'd Vars an 4- 00
NEIBIOteCAll
SV- lirt« ’ACM О
Ю13
Gf- IPRlV Al t-PROFh to!-О К >• J
1 INDAT ом
GLOBALFREE
4210
F-l САП VaI UEE-
• CIOPAI sMAfomvj
K211
FAT Al HIT
Rt GDFLL1EVA1 L’F
JOBALLRt IDLDEC1
_ ЛИОН
DOS2CAIL
ISBADHUGFWRnmR
Ся»1
|№2
И
r
в
Итак, мы достигли конца процесса начальной загрузки Windows 95, отображенного на рис. 2.2.
Как я подчеркивал ранее, рис. 2.2 представляет лишь жалкое подобие полноценной начальной за-
грузки процесса Windows 95, поскольку с подачи 32-битового файлового доступа Windows не
только обходит DOS ift большинству вызовов файлового ввода-вывода, но обходит и саму утилиту
INTRSPY (которая является резидентной программой DOS и загружена перед Windows).
Чтобы увидеть все происходящее после загрузки SHELL VxD ядра Winl6, нам требуется запре-
тить 32-битовый файловый доступ. В WfW 3.11 существует опция командной строки WIN /D:C —
запретить 32BFA, но она не имеет эквивалента в Windows 95. Даже не предполагается, что вы бу-
дете запускать Windows 95 без 32BFA. Тем не менее я нечаянно обнаружил, что (в Beta-1, во вся-
Глава 2. Наблюдения за процессолл загрузки Chicago
93
ком случае), если супервизор ввода-вывода не может найти подкаталог IOSUBSYS, то он запреща-
ет 32-битовый дисковый доступ, который в свою очередь запрещает 32-битовый файловый доступ.
Когда отключен 32BFA, INTRSPY отслеживает полную последовательность начальной загрузки
Windows 95. Например, сравните следующее с нижней частью рис. 2.2:
OPEN C:\WINDOWS\WINSTART.BAT [FAIL 2] ;; ищем WINSTART.BAT
OPEN С:\WINDOWS\system\LPTENUM.vxd
OPEN C:\WINDOWS\system\UNICODE.BIN
OPEN IFS$HLP$
OPEN C:\WINOOWS\system\IOSUBSYS\hsflop.pdr [FAIL 3]
OPEN C:\WIND0WS\system\I0SUBSYS\esdi_506.pdr [FAIL 3]
OPEN C:-\WINDOWS\system\IOSUBSYS\scsiport.pdr [FAIL 3]
OPEN C:\WINDOWS\system\VFD.VXD
OPEN C:\WIND0WS\system\krnl386.exe
OPEN C:\WINDOWS\USER.DAT
OPEN C:\WINDOWS\USER.DAT
XOPEN C:\WINDOWS\IOS.LOG
OPEN C:\WINDOWS\system\KERNEL32.DLL
EXEC C:\WIND0WS\system\krnl386.exe
OPEN C:\WIN00WS\system\KERNEL32.DLL
OPEN C:\WIND0WS\SYSTEM\KRNL386.EXE
В частности, обратим внимание на ссылки к KERNEL32.DLL, ядру WIN32. Эти ссылок не было
на рис. 2.2. Ясно, что запрос на открытие файла KFRNEL32.DLL не передавался DOS и, следова-
тельно, не попадал и к INTRSPY.
Полный вывод INTRSPY слишком длинен (около 500 строк OPEN и FIND), чтобы предлагать
его полностью, поэтому мы рассмотрим только выдержки из него.
Как мы уже видели, KERNEL32.DLL загружен. INTRSPY, впрочем, не сообщает нам, кто за-
грузил KERNEL32.DLL. Это интересный момент, по поводу которого Адриан Кинг заявляет в
Windows 95 изнутри (с. 185): “16-битовый модуль Windows — Kernel загружает драйвер VWIN32
при первом же обращении к любой из функций 32-битового API. VWIN32 загружает три библиотеки
динамической компоновки, о которых шла речь выше и передает управление 16-битовому модулю
Kernel; а тот вызовет функцию инициализации KERNEL32 DLL. Как только эта фушсция отрабо-
тает, подсистема Win32 готова к эксплуатации”.
Раз SHELL VxD загружает ядро WIN16, то вполне логично, чтобы VWIN32 VxD загружал яд-
ро Win32. Так и происходит на самом деле, именно VWIN32.VXD загружает KERNEL32.DLL. А
процитированный мной Кинг дает в этом месте следующую сноску:
I Ввиду того, что оболочка Windows 95 представляет собой 32-битовое приложение, загрузка и инициа-
лизация подсистемы Win32 происходит в процессе старта системы.
Значит, если оболочка Windows 95 была бы 16-битовым приложением, то подсистема WIN32 не
загружалась бы то тех пор, пока вы не запустите Win32-пpилoжeниe.
Очень просто сделать оболочку Windows 95 16-битовой; все, что нужно сделать, — это изме-
нить строку SHELL=CAB32.EXE или SHELL=CAB32.EXE /NE в разделе [boot] SYSTEM.INI.
Например, Windows все еще предлагает игру в Solitaire, и вы можете заставить Windows 95
признаться в этом, временно сделав SOL.EXE (которая является приложением Winl6) оболочкой:
[boot]
shell=sol.EXE
;;; shell=cab32.EXE
» .
Таким образом, создана версия Windows 95, в, которой ничего нельзя делать, кроме как играть в
Solitaire. Но дает ли это чистую Winl6-BepcnK> Windows 95? Нет, конечно. Раз KRNL386.EXE
обращается (в оригинале — “thunks”. Привыкаем! — Прим, ред.) по некоторым операциям к
KERNEL32.DLL, то трудно себе представить, как загруженный KERNEL32 будет ожидать появ-
ления оболочки Win32, которая (после соответствующего изменения SHELL=) не появится вовсе.
94
Неофициальная Windows 95
Во всяком случае, VWIN32 VxD загрузил KERNEL32.DLL. Эта DLL, конечно, обеспечивает
API ядра Win32, функции которого, в основном, определены в заголовочном файле WINBASE.H:
CreateThread, MapViewOfFile, CreateFile и т.д. Как отмечено раньше, во многих случаях эти Win32
API базируются на VMM или VWIN32 VxD.
Исполняемые модули Win32, такие как KERNEL32.DLL, используют файловый формат
Portable Executable (РЕ). Это значительное усовершенствование по сравнению с предыдущим фор-
матом исполняемого файла Microsoft.
К сожалению, оболочка Windows 95 в настоящее время не содержит DLL для Quick View, ко-
торая позволила бы исследовать PE-файлы, зато нам помогут некоторые утилиты в текстовом ре-
жиме. Утилита W32DUMP способна отобразить весь импорт (то, что используется API) и экспорт
(то, что API обеспечивает) РЕ-файла Win32. Продукт TNT от Phar Lap, поддерживающий API
Win32 под 32-битовым защищенным режимом MS DOS, поставляется с утилитой МАРЕХЕ. А
Microsoft C++ поставляется с DUMPBIN, Win32 Console-приложением, не только отображающим
импорт и экспорт исполняемого модуля WIN32, но и обеспечивающим опцию /DISASM.
Вот небольшой фрагмент KERNEL32.DLL, как его выводит на экран DUMPBIN. Обратите вни-
мание, что я выбрал только часть API ядра Win32, имена которых начинаются с Create:
C:\MSVC32S\BIN>dumpbin /exports \windows\system\kerne132.dll
ЗА 39 CreateConsoleScreenBuffer (0002а8Ь7)
ЗВ ЗА CreateDirectoryA (00012afb)
ЗС ЗВ CreateDirectoryExA (00012b2d)
3D ЗС CreateDirectoryExW (000303с7)
ЗЕ 3D CreateDirectoryW (ОООЗОЗЬе)
3F ЗЕ CreateEventA (00012198)
40 3F CreateEventW (000303d0)
41 40 CreateFileA (00012bf0)
42 41 CreateFileMappingA (00012240)
43 42 CreateFileMappingW (000303e2)
44 43 CreateFileW (ОООЗОЗеЬ)
45 44 CreateKernelThread (000116c7)
46 45 CreateMailslotA (00011ebd)
47 46 CreateMailslotW (000303d0)
48 47 CreateMutexA (00012137)
49 48 CreateMutexW (000303c7)
4A 49 CreateNamedPipeA (000303f4)
4B 4A CreateNamedPipeW (000303f4)
4C 4B CreatePipe (00011dc1)
4D 4C CreateProcessA (0001230e)
4E 4D CreateProcessW (000303fd)
4F 4E CreateRemoteThread (ОООЗОЗеЬ)
50 4F CreateSemaphoreA (000121f5)
51 50 CreateSemaphoreW (000303d0)
52 51 CreateTapePartition (000303d0)
53 52 CreateThread (0001169d)
54 53 CreateToolhelp32Snapshot (00032b81)
Последняя строка предоставила мне хороший повод выяснить, что KERNEL32 обеспечивает
множество функций Win32 Toolhelp для перечисления Win32-nponeccoB, задач, куч и модулей
Win32. Эти функции: Toolhelp32ReadProcessMemory, Process32First, Process32Next, Thread32First,
Thread32Next и т.д. — документированы в TLHELP32.H, заголовочном файле Microsoft.
Естественно, далеко не все функции, экспортированные из KERNEL32.DLL, документированы
(по крайней мере в текущем SDK). Следующие я использую или обсуждаю в других частях этой
книги:
127 127 GetProcAddressl6 (000114eb)
160 15F GetpWinl6Lock (0001cd32)
Глава 2. Наблюдения за процессолл загрузки Chicago
95
1В0 1AF LoadLibraryl6 (00021d52)
1F4 1F3 QT Thunk (00001247)
2AS 2A4 VxDCallO (00001fOc)
2B8 2B7 Win32HandleToDosFileHandle (00023d94)
VxDCallO особенно полезен. Как показано в главе 14 (WIN32PSP.C), VxDCallO позволяет
\¥т32-приложениям вызывать множество сервисов Win32, обеспечиваемых такими VxD, как
VWIN32, VMM и VCOND. Два сервиса, обеспечиваемые VWIN32, предоставляют Win32-пpилoжe-
ниям просто осуществлять вызовы DOS и DPMI. Это важно, поскольку Win32-пpилoжeния обычно
не могут непосредственно использовать инструкции INT в Windows 95.
Следующий интересный процесс, показанный INTRSPY, после загрузки KERNEL32.DLL, —
загрузка Windows 95 драйверов устройств Winl6 (эти драйверы Winl6 не надо путать с 32-
битовыми VxD):
XOPEN C:\WlNDOWS\SYSTEM\system. drv
XOPEN C:\WINDOWS\SYSTEM\keyboard. drv
XOPEN C:\WINDOWS\SYSTEM\mouse. d rv
XOPEN C:\WIND0W5\M0USE.INI
XOPEN C:\WlNDDWS\system.INI
XDPEN C:\WlNDDWS\SYSTEM\framebuf.drv
XOPEN C:\WINDOWS\SYSTEM\DIBENG.DLL
XDPEN C:\WlNDDWS\SYSTEM\sound.d rv
XDPEN C:\WlNDOWS\SYSTEM\comm.drv
Для загрузки этих драйверов Win 16 KRNL386.EXE использует динамическую компоновку вре-
мени исполнения (см. InitFwdRef в Windows Internals, р. 47-50).
Любопытно, что эти драйверы Win 16 не появляются в архитектурных диаграммах Microsoft для
Windows 95, хотя играют решающую роль в системе передачи сообщений Windows. Много написано
о десинхронизированных очередях ввода в Windows 95 (Мэтт Петрек достаточно подробно обсудил
это в “Investigating the Hybrid Windowing and Messaging Architecture of Chicago”, Microsoft
Systems Journal, September 1994) и о том, чем это отличается от передачи сообщений в Windows
3.x. При этом важно заметить, что многие аспекты системы обмена сообщениями Windows 3.x оста-
лись практически неизменными в Windows 95. В частности, такие драйверы Winl6, как
MOUSE.DRV, KEYBOARD.DRV и SYSTEM.DRV, все еще участвуют в процессе преобразования
аппаратных прерываний в сообщения WM_XXX.
Сообщения Windows, такие как WM_LBUTTONDOWN и WM_KEYDOWN, начинали свою
жизнь, конечно же, как аппаратные прерывания. Аппаратные прерывания обрабатываются VPICD
VxD, другими VxD, вроде VMD (виртуальное устройство мыши), VKD (виртуальное устройство
клавиатуры) или же VTD (виртуальное устройство таймера), и вызывают сервис
VPICD_SET_INT_Request для имитации аппаратного прерывания в соответствующей VM.
Если имитированное прерывание идет в системную VM, где работают приложения Win 16 и
Win32, оно попадает к таким 16-битовым драйвером устройств Windows, как MOUSE.DRV,
KEYBOARD.DRV или же SYSTEM.DRV (который обслуживает таймер). Если один йз этих 16-
битовых драйверов устройств перехватывает аппаратное прерывание, то он может подумать, что
работает совместно с аппаратным обеспечением, хотя, фактически, получил одно из прерываний,
имитированных VPICD. (С другой стороны, несколько таких драйверов, например, MOUSE.DRV,
действительно напрямую общается с соответствующим VxD).
Обработчик прерываний драйвера устройств отвечает за вызов функции USER, которая, в свою
очередь, помещает сообщение в очередь системных сообщений. Обработчик INT 9 KEYBOARD.DRV
вызывает процедуру Keybd_Event в USER {Undocumented Windows, р. 467-469). Обработчик INT
8 SYSTEM.DRV вызывает любые обработчики косвенных вызовов (callback), установленные
CreateSystemTimer {Undocumented Windows, р. 602—607); обеспечиваемый модулем USER SetTimer
API, базируется на обработчике косвенного вызова, который устанавливается USER с помощью
CreateSystemTimer.
96
Неофициальная Windows 95
Ei® к i_iTg_ndgx Mann
0 MiaosolWn'w ’st* Infe fa i
Eki <<»»<» ••; 75 ••isRecreMRtY'i ® <
6! (STMtNHILM TINT
MMlUfcil < »’< ®1»UO ?6FF»ReetE
bill lIN’Kfcl U UM
*”? UiWn
йм|г i: • »О-' 1 # ЖСЙШЙЙ ' : : :< 7< < <
Sec BFARi'S
Hi OADBtMAl
ль в, «’cc
44 Ю •» IC1NIOI hM4>i ГО UK
МкиООтООО
^|::17Й •1:K:<=1 U ...
’ » U Whrt NL 1 VW-SSAi L
ИмШОттдиЖ
' ОсПГЮГЛ Mi OA
’''-> пестр,-wV.=Mi !
Рис. 2.5. USER.EXE насчитывает более 400 API, экспортированных оконным модулем и мо-
дулем пользовательского интерфейса Winl6
> u
Рис. 2.6. Quick View иэ Chicago Explorer показывает наличие в GDI.EXE около 400 API,
экспортированных модулем Winl6 GDI
Х4таГ?Ж.
3BS
i®e<
so>
w»
®is
«52
<:Ш
Ж-
ft&:
6Л,
«i
ш-
ад»
t>2
Na. To
Mints Л’Мп» чы.1 т h
POl l BE »RT >
ANIMATE l Au 1 U
SETM.Au S' ORA
;Оиажж®
<Шжавй«»ттйт;:
^»1ИЛМ !>uK
15®йАймя?а»с<
:еОйШШйвб>
ЖЕеТСИОЙ:®:: 1
Ж'АЦ.ЛЛМЛЛ'Л <НХ
3ETOIW0WUR f>
PTIMRti ION
2REATECE MWIRLC’
4 Неофициальная Windows 95
Глава 2. Наблюдения за процессолл загрузки Chicago
97
Другими словами, все события аппаратного обеспечения, видимые даже приложениями Win32,
исходят от таких драйверов Winl6, как MOUSE и KEYBOARD.
Как показывает INTRSPY, Windows 95 загружает огромное число DLL. Йюда входят GDI.EXE,
GDI32.DLL, USER.EXE и USER32.DLL. На рис. 2.5 и 2.6 представлены некоторые из огромного
числа API, предоставляемых USER.EXE и GDI.EXE. Мы не собираемся копаться во всех этих
DLL, поскольку это отняло бы чересчур много времени, а узнаем ли мы что-то новое о
Windows 95 — не ясно. Огромное количество возможностей Windows обеспечиваются DLL, а в
Windows 95 еще и библиотекой Win32. В целом больше сказать нечего.
Исследование Explorer
Наконец, мы подошли к самой заметной части Windows 95. Оболочка, иногда называемая
Explorer или Cabinet, показана на рис. 2.7.
Рис. 2.7. Стандартной оболочкой Chicago является CAB32.EXE (\У1п32-приложение)
Оболочкой Windows 95 является Win32-пpилoжeниe, CAB32.EXE:
XOPEN C:\WIND0WS\CAB32.EXE
XOPEN C:\WIND0WS\SYSTEM\SHELL32.DLL
XOPEN C:\WIND0WS\SYSTEM\shell16.dll
XOPEN C:\WINDOWS\icocache.dat
XDPEN C:\WINDOWS\SYSTEM\DIBENG.DLL
XOPEN C:\WINDOWS\fonts\coure.fon
XOPEN C:\WINDOWS\cabinet.ini
FIND C:\*.* i .
98
Неофициальная Windows 95
FIND FIND FIND C:\WINDOWS C:\Windows\Desktop C:\Windows\Desktop\*. * Здесь Explorer загружает свои компоненты
XOPEN XOPEN XOPEN XOPEN FIND XOPEN C:\WINDOWS\SYSTEM\LINKINFO. DLL С:\Windows\Programs\Explorer. Ink C:\Wihdows\Desktop\Windows 95b.Ink C:\Windows\Programs\Ms-dospr. Ink C:\Windows\Recent\*. * C:\Windows\Recent\Relnotes. Ink
Но оболочка Windows 95 не является неотъемлемой частью Windows 95. Очень просто изменить
SHELL=C AB32.EXE в SYSTEM.INI на SHELL=WINFILE.EXE, или на SHELL=PROGMAN.EXE,
или даже (как мы видели раньше) на SHELL=SOL.EXE. В то же время весьма привлекательная
оболочка — это один из важных коммерческих моментов в Windows 95. С другой стороны, важное
усовершенствование (а оболочка Windows 95 — это, безусловно, важное усовершенствование
интерфейса пользователя Windows) далеко не всегда требует существенно новой архитектуры.
САВ32 — всего лишь приложение.
Так ли это? Выполнение
DUMPBIN /IMPDRTS\WIND0WS\CAB32.DLL
демонстрирует использование большого количества неописанных API Win32 из SHELL32.DLL (они,
по крайней мере, не были описаны ко времени написания этой книги). Например, вот несколько
API, которые САВ32 импортирует из SHELL32:
16 RegisterShellHook
1С5 FSNotify_HandleEvents
4C7SHRegisterDragDrop
1C2FSNotifyJtegister
154SHFindComputer ,
153SHFindFiles
Выполнение DUMPBIN /EXPORTS \WINDOWS\SYSTEM\SHELL32.DLL действительно
покажет эти, а плюс к тому, и многие другие функции. Хотя некоторые из них и присутствуют в
SHELL.H (например, SHFileOperation и ShellExecuteEx), большинство из них оказались недоку-
ментированными. Выполнение
DUMPBINZIMP0RTS\WIND0WS\SYSTEM\SHELL32.DLL
показывает, что SHELL32, в свою очередь, использует такие недокументированные возможности из
KERNEL32, как MapLS, QT_Thunk и ThunkConnect32. Конечно, SHELL32 является частью опера-
ционной системы, поэтому данные интерфейсы являются внутренними. Следовательно, САВ32 —
тоже часть операционной системы (вот она, “интегрированность”!). Вместо того чтобы использовать
общедоступный интерфейс, САВ32 основывается на некотором углубленном знании Windows 95.
Быть может, использование недокументированных возможностей и прочих скрытых знаний и озна-
чает “интегрированность”?
В выводах DUMPBIN/IMPORTS для САВ32 и SHELL32 заметно отсутствие одного компо-
нента: связывание и внедрение объектов (OLE)! Возможно, это изменится до коммерческого вы-
пуска Windows 95, но по крайней мере в версии бета-1 оболочка Windows 95 OLE не использует.
Это находится в резком противоречии с некоторыми утверждениями прессы, будто “оболочка
Chicago широко использует OLE 2.0” (Ray Valdes, Dr. Dobb's Developer's Update, March 1994, p. 6).
Microsoft, видимо, не пожелала это сообщить. В весьма полезной статье “Extending the Chicago
Shell” {Microsoft Developer Network News, July 1994, p. 10-11) Кил Марш (Kyle Marsh) просто
заявляет, что оболочка подобна OLE:
Глава 2. Наблюдения за процессолл загрузки Chicago
99
I “Разработка расширений оболочки Chicago основана на Compound Object Model (модель составного
объекта) из OLE версии 2.0. Объекты получают доступ к оболочке благодаря интерфейсам, и прило-
жения реализуют эти интерфейсы для расширения оболочки в виде динамически компонуемых
библиотек (DLL), подобных In-Proc Server DLL в OLE 2.0”.
Я не знаю, насколько это важно, но Microsoft не,только явно избегает OLE (не считая наиболее
абстрактного, концептуального использования этой технологии в оболочке Windows 95), но и,
кажется, несколько разочаровалась в OLE. В Windows 95 изнутри Адриан Кинг говорит, что “в
настоящее время обеспечение приложения полной поддержкой OLE является весьма непростой
задачей” и “OLE — это передовая технология. Возможно, сейчас ее использовать трудоемко, однако
она способна обеспечить вам преимущества на рынке приложений для Windows 95” (с. 256-257). В
интервью, приведенном в конце книги, Пол Мариц (Paul Maritz) сказал: “В настоящее время
разгорелось много жарких дискуссий по поводу объектных архитектур и большинство таких
дискуссий не имеет никакого отношения к среднему конечному пользователю. На самом деле, это
чисто внутреннее дело компьютерной промышленности — таким образом мы просто беседуем друг с
другом” (с. 452).
До тех пор, пока сама OLE 2.0 — не более чем очень трудоемкое средство общения в индустрии
программного обеспечения ПК, OLE-подобные расширения в оболочке Windows 95 — как раз то,
что нужно. При использовании оболочки для быстрого просмотра KRNL386.EXE и других испол-
няемых модулей Win 16 я не говорил о том, что обеспечение этой возможности не является
встроенным. Способность обрабатывать нажатия правой кнопки мыши и добавление новых “гла-
голов” во всплывающее меню оболочки реализуется через реестр. Как только начнет поставляться
Windows 95, большую часть утилит этой книги можно будет переделать в расширения оболочки
Windows.
100
Неофициальная Windows 95
Глава 3
Связь между DOS и Windows
В документе Microsoft “Chicago Questions and Answers” (январь 1994 г.) говорится, что Win-
dows 95 “будет законченной интегрированной операционной системой, работающей в защищен-
ном режиме, которая не будет требовать или использовать отдельную версию MS DOS”. Хотя
это, безусловно, приятная новость, технология, лежащая в основе Windows 95 (“Chicago”), не яв-
ляется абсолютно новой:
J приоритетная (preemptive, преимущественная; в русском издании Windows 95 изнутри —
вытесняющая. — Прим, ред.) многозадачность Windows 95 — это всего-навсего усовер-
шенствование старой приоритетной многозадачности менеджера виртуальных машин (Virtual
Machine Manager — VMM) в Windows 3.x. В то же время появление программного интер-
фейса управления приоритетной многозадачностью — это значительный шаг вперед для
рядовых разработчиков приложений.
S 32-битовый доступ к файлам в Windows 95 был разработан намного раньше и использовался в
Windows for Workgroups (WfW) 3.11. К фундаментальным понятиям WfW и к почтенной
файловой системе DOS — FAT — Windows 95 добавляет поддержку длинных имен файлов и
каталогов.
J Возможность Windows 95 выполнять Win32-пpилoжeния основана на усовершенствованиях
старой Win32s, разработанной для Windows 3.1.
Во многих отношениях, следовательно, Windows 95 просто улучшает Windows и позволяет
пользователям и программистам лучше понять те функциональные особенности и возможности,
которые Windows уже имела. (В случае с приоритетной многозадачностью все это не превышает
возможностей Windows/386 2.0, 1988 г.)
То, что Windows 95 является “законченной” операционной системой, которая “не требует или
не использует отдельную версию MS DOS”, и, кроме того, не так уж радикально отличается от
предыдущих версий Windows, наталкивает на мысль, что, возможно, эти ранние версии Windows
были близки к тому, чтобы стать законченными операционными системами. При этом также можно
допустить, что связь между этими операционными системами и MS DOS представлялась не такой,
какой была на самом деле.
До появления Windows 95 Windows часто представлялась не как операционная система, а как опе-
рационная среда. В конце концов, Windows 3.x выполнялась “поверх” DOS. Вы запускали Windows,
набрав WIN в командной строке или поместив WIN в последней строке файла AUTOEXEC.BAT. В
большинстве случаев считалось, что Windows — это одно из приложений DOS.
Где-то между Windows 3.0 и 3.1 Microsoft изменила название поставки Windows с “графической
среды” на “операционную систему”. Но теперь, продвигая Windows 95, Microsoft вдруг открыла,
что предыдущая версия Windows не была истинной операционной системой, поскольку выполнялась
поверх DOS.
Более глубокое рассмотрение показывает, что высказывание “операционная система” и было
на самом деле вполне точной характеристикой Windows 3.x в расширенном режиме. Следо-
вательно, если Windows 95 считается полноценной операционной системой, то таким же образом
Глава 3. Связь ллежду DOS и Windows
101
нужно охарактеризовать и расширенный режим Windows 3.x, поскольку они несущественно
отличаются.
В действительности, начиная с MS DOS 5.0, Microsoft утверждала, что Windows 3.x была не
просто какой-то случайной программой, выполняющейся поверх DOS, а скорее частью единой
интегрированной системы с MS DOS. Например, в разделе “Запуск Windows с операционной
системой, отличной от MS DOS”, в файле README.WRI, включенном в (локализованную. —
Прим, ped.) Windows 3.1, утверждается:
(Microsoft Windows и MS DOS работают вместе как единое целое. Они разрабатывались вместе и
прошли тщательное совместное тестирование на множестве разных компьютеров и конфигураций
оборудования. Запуск Windows версии 3.1 с операционной системой, отличной от MS DOS, может
привести к неожиданным результатам или плохой производительности.
Файл README.WRI, включенный в WfW 3.11, добавляет вполне ясное замечание, что работа
Windows с операционными системами, отличными от MS DOS, “не поддерживается Microsoft”.
Вероятно, термин “не поддерживается” означает, что фирма Microsoft не предоставляет никаких
гарантий на Windows в случае, если она выполняется под управлением других операционных
систем, таких как NOVELL DOS 7.0 или IBM OS/2.
Чтобы беспристрастно оценить претензии Microsoft в отношении Windows 95, как законченной
операционной системы, в противовес зависимости Windows 3.x от MS DOS, вам вначале необхо-
димо понять связь между Windows 3.x и DOS. Что это была за “интегрированная система”, о
которой сообщала Microsoft?
В этой и последующих главах рассматривается интерфейс INT 2Fh, который как раз и является
одним из многочисленных примеров совместной работы DOS и Windows 3.x как интегрированной
системы. Чтобы раскрыть связь между DOS и Windows, в этой главе представлена программа,
называемая FAKEWIN. Она показывает, как DOS (и базирующееся на DOS программное обеспе-
чение, такое как резидентные программы (TSR) и драйверы устройств) реагирует на запуск
Windows. Используя FAKEWIN, можно установить, что MS DOS 5.0 и 6.0 знают о Windows и что
даже предшествующие версии Windows были большим, чем просто привлекательной интерфейсной
оболочкой. В следующей главе подробнее анализируются выходные данные FAKE WIN. Другие
связи между DOS и Windows, такие как широкое использование Windows недокументированных
функций и структур данных DOS, обсуждались в главе 1 Undocumented DOS, 2d ed. и поэтому нет
необходимости касаться данной темы здесь.
Программа FAKEWIN обнаруживает, что даже до Windows 95, Windows (особенно расширенный
режим) и DOS были достаточно интегрированы, чтобы Windows не являлась обычным приложени-
ем, выполняющимся поверх DOS. Благодаря VMM в WIN386.EXE это была действительно опера-
ционная система, которая использовала реальный режим DOS в качестве вспомогательного средства.
Автор книги DOS Internals, Джеф Чапелл говорит, что “DOS и ее драйверы устройств, TSR и так да-
лее, формируют для Windows подсистему 16-битовых драйверов”. В общем, можно сказать, что в Win-
dows 95 подсистема реального режима играет меньшую роль, чем это было в Windows 3.1 и WfW 3.11.
Система оповещения Windows INT 2fh
FAKEWIN — имя, которое говорит само за себя (“фальшивая Windows”. — Прим. ред.)-. это
DOS программа, которая очень похожа на Windows. Как же она это делает?
Когда Windows стартует и заканчивает работу, она генерирует через INT 2Fh четыре отдельных
сообщения (broadcast), называемых Startup, Startup Comlete, Begin Exit и Exit. Драйверы устройств
DOS и TSR, загруженные перед Windows, могут перехватывать прерывание INT 2Fh для отслежи-
вания этих сообщений Windows. В ответ на сообщение Startup (INT 2Fh функция 1605h) драйвер
устройства или TSR может затребовать у Windows загрузку драйвера виртуального устройства (Vir-
tual Device Driver — VxD) или распределения данных экземпляра (дальше вы узнаете, что это такое).
FAKE WIN подменяет Windows, выдавая эти сообщения путем вызовов прерывания INT 2Fh
вместо Windows. Отдельные VxD Windows также могут выдавать сообщения (INT 2Fh функция
1607h), связываясь таким образом с программами, загруженными перед Windows. FAKEWIN эму-
102
Неофициальная Windows 95
лирует один из этих вызовов, а именно вызов, выдаваемый VxD DOSMGR внутри WIN386.EXE
Кроме того, FAKEWIN эмулирует идентификационное сообщение TSR (TSR Identify, INT 2F1
функция 160Bh), выдаваемое модулем USER. TSR могут использовать это сообщение, чтобы потре
бовать от Windows загрузки исполняемых модулей Windows, библиотек динамической компоновка
(DLL) или драйверов устройств.
Эти функций прерывания INT 2Fh документированы в Device Driver Adaptation Guide фирмы
Microsoft, включенном в Windows Device Driver Kit — DDK. О них можно также прочитать в статье
Дэвида Лонга “TSR Support in Microsoft Windows Version 3.1” на компакт-диске Microsoft Develo-
per Network (MSDN). Но, в связи с этой документацией, существуют две серьезные проблемы.
Во-первых, предполагается, что разработчики программного обеспечения для DOS заглянут в
раздел Windows DDK. Понятно, что эти вызовы прерывания INT 2Fh делаются Windows. Но это не
делает их частью интерфейса Windows API. Сообщения Windows через прерывания INT 2Fh
необходимы только для приложений, загруженных до Windows, т.е. для программного обеспечения
DOS. Позже вы увидите, что среди типичных пользователей прерывания INT 2Fh находятся
утилиты DOSKEY, SMARTDRV и EMM386, поставляемые с MS DOS. Хорошо это или плохо, но
DOS-программисты 90-х годов (даже те, кто категорично заявлял “Не разрабатывайте для
Windows”) должны быть знакомы с Windows DDK. Теперь Windows является операционной систе-
мой. A DOS — это только 16-битовый помощник, работающий в реальном режиме.
Во-вторых, как и в 99% документации по программированию в Windows, в документации
Microsoft по INT 2Fh содержится только описание самого интерфейса, но не то, как он используется
йа самом деле. В данном случае Microsoft не указывает, какие драйверы устройств DOS и TSR
отзываются на сообщения при запуске Windows. Если относительно DOSKEY, SMARTDRV и
EMM386 можно сказать, что это зависит от реализации, то, заметим, что система оповещения
Windows INT 2Fh перехватывается не только драйверами и TSR, но, что более важно, самой MS
DOS (версии 5, 0 и выше).
Позвольте повторить: MS DOS 5.0 и выше перехватывает систему оповещения Windows INT 2Fh.
Другими словами, MS DOS знает о Windows. DOS ведет себя иначе, когда выполняется Windows в
расширенном режиме. Windows 3.x — это не просто какое-то случайное приложение DOS (как
утверждала Microsoft до появления Windows 95), но и часть системы, интегрированной с MS DOS.
Используя программу FAKEWIN, вы увидите, как Windows и MS DOS взаимодействуют.
FAKEWIN печатает всю информацию, возвращаемую программным обеспечением DOS, которое об-
рабатывает сообщение Windows Startup (INT 2Fh функция 1605h). Программное обеспечение DOS,
перехватывающее этот вызов, может взаимодействовать с Windows следующими (совершенно неза-
висимыми) способами:
J Драйвер или TSR может потребовать от Windows не загружаться (например, если про-
граммное обеспечение несовместимо с Windows). Использование этой части интерфейса
функции 1605h не рекомендуется.
Y Драйвер или TSR может потребовать от Windows загрузки VxD, возможно VxD, встроенного
в тот же самый файл, что и драйвер или TSR. (Подобным образом программы DOS могут
использовать функцию 160Bh INT 2Fh, чтобы потребовать от Windows загрузки исполняемых
модулей, DLL и 16-битовых драйверов Windows. Как и в случае с VxD, они могут быть
встроены в тот же файл, что и программа DOS.)
J Драйвер или TSR может объявить некоторую область памяти как данные экземпляра.
Windows обычно считает память, распределенную до ее запуска (например, код или данные
DOS или TSR) глобальной. Единственная копия этой памяти отображается (а не копиру-
ется) в каждую виртуальную машину (VM). Однако это не подходит для тех данных DOS,
которые должны быть уникальными для каждого сеанса (т.е. окна) DOS. Например, буфер
истории команд редактора командной строки, вроде DOSKEY, загруженный перед Windows,
должен поддерживаться независимо на каждой VM. Иначе команды, вводимые в одном сеансе
DOS, будут передаваться в другие сеансы DOS (что может привести к непредусмотренным
формам взаимодействия между процессами). Объявление буфера истории команд в качестве
данных экземпляра (instance data) заставляет Windows создавать отдельную копию буфера
Глава 3. Связь ллежду DOS и Windows
103
для каждой VM, в то время как остаток TSR рассматривается как глобальные данные,
единственная копия которых отображается в каждую VM. Отображение памяти связывает
участок памяти с соответствующим зарезервированным линейным адресом, (см. Клаус
Мюллер (Klaus Mueller), “Think Globally, Act Locally: Inside the Windows Instance Data
Manager”, Dr. Dobb's Journal, April 1994).
S Драйвер или TSR могут передать Windows адрес функции для выключения и включения
режима Virtual 8086 (V86). Менеджеры расширенной памяти, такие как EMM386, QEMM и
386МАХ, переводят машину в режим V86 — разновидность защищенного режима (см. главу
9) — для эмуляции интерфейса EMS и использования свободных адресов в блоках верхней
памяти (Upper Memory Block — UMB). Между тем, Windows может понадобиться переклю
чить машину из реального режима в защищенный. Если же менеджер памяти уже перевел
машину в защищенный режим, то Windows переключиться не сможет. Поэтому система опо-
вещения Windows при запуске позволяет менеджеру памяти сообщить Windows способы
переключения режимов. Если DOS-приложения распределяли память с помощью менеджера
памяти (EMS, XMS или UMB), то менеджер должен передать Windows управление над этой
памятью, используя недокументированный интерфейс, известный как Global EMM Import.
Система оповещения при инициализации Windows показана на рис. 3.1. Это система подобна
функции Task Switcher Identify Instance Data (функция 4B05h прерывания INT 2Fh), описанной в
MS-DOS Programmer’s Reference фирмы Microsoft. Оба сообщения используют одну и ту же струк-
туру данных инициализации. DOSKEY, например, использует единый блок кода для обработки
обоих этих вызовов.
Вызывается с:
АХ = 1605И
СХ = 0, если можно запускать Windows, != О в противном случае
DX = флаги(бит 0 сброшен в расширенном режиме, установлен в стандартном)
DI = старший/младший номер версии Windows
ES:ВХ -> 0:0 или предыдущая структура Win386_Startup_Info_Struc
DS: SI -> 0:0 или (если не 0) говорит о том, что обработчик косвенного вызова
(callback) для выключения/включения V86 уже есть
INT 2Fh
Возвращает:
СХ = 0, если можно запускать Windows, != 0 в противном случае
Win386_Startup_Info_Struc -> SIS_Next_Dev_Ptr = предыдущий ES:BX
ES:BX -> Win386_Startup_Info_Struc (см. ниже)
DS:SI -> 0:0 или обработчик косвенного вызова для выключения/включения V86
typedef struct {
viod far *IIS_Ptr // segment:offset данных экземпляра
WORD ISS_Size; // количество байт
} Instance_Item_Struc;
typedef struct _WINFO {
WORD SIS_Version;
struct _WINFO far *SIS_Next_Dev_Ptr; // следующая структура
DWORD SIS_Virt_Dev_File_Ptr; // имя загружаемого VxD
DWORD SIS_Reference_Data; // данные для VxD
Instance_Item_Struc far *SIS_Instance_Data_Ptr;
} Win386_Startup_InfO_Struc;
Рис. 3.1. Если драйвер устройства DOS или TSR, загруженный до Windows, перехватывает сообщение инициализации
Windows (функция 1605Ь прерывания INT 2Fh), то он может потребовать от Windows загрузки VxD или распределения
экземплярных данных
DOS-приложение, которое перехватывает сообщение начальной загрузки Windows, отвечает за
правильное создание связного списка структур начальной загрузки. Перед установкой в указателе
ES:BX адреса своей структуры Win386_Startup_Info_Struc программа, перехватывающая это сооб-
щение, должна вначале передать его (неизменным) ранее установленному обработчику преры-вания
INT 2Fh (см. TSRLDR.ASM в главе 4).
Как показано на рис. 3.1, программа, использующая это сообщение, чтобы возвратить указа-
тель на Win386_Startup_Info_Struc, должна заполнить поле SIS_Next_Dev_Ptr в своей струк-
104 Неофициальная Windows 95
туре указателем на структуру Win386_Startup_Info_Struct, возвращенную предыдущим обработ-
чиком прерывания.
FAKEWIN обходит этот связный список, распечатывая содержимое каждой структуры. Напри-
мер, FAKEWIN показывает, как реагирует менеджер памяти Qualitas 386МАХ при запуске
Windows на вызов INT 2Fh функции 1605h:
Win386_Startup_InfO_Struc at 0255:01Е2 (386МАХ$$)
VxD name: C:\386MAX\386MAX.VxD (Reference data: 0053DA1E)
Instance_Item_Struc at C001:042E
C001:D183 0001
V86 Enable/Disable function: 036E:03D4 (386MAX$$)
Global EMM Import @ OOBDOEACh (version 1.11)
Здесь 386MAX делает несколько вещей. Во-первых, он сообщает Windows о необходимости за-
грузки драйвера виртуального устройства под названием 386MAX.VXD. Во-вторых, он объявляет
один байт данных экземпляра по адресу С001:0183. И в-третьих, он передает Windows адрес функ-
ции выключения/включения режима V86. 386МАХ также поддерживает спецификацию глобаль-
ного импорта ЕММ, и FAKEWIN печатает адрес и версию структуры импорта ЕММ. (См. Таку Ока-
заки (Taku Okazaki), “The Windows Global EMM Import Interface”, Dr. Dobb's Journal, август 1994).
Данные экземпляров
Как я уже отмечал, редактор командной строки DOSKEY объявляет свой буфер истории ко-
манд как данные экземпляра, и FAKEWIN позволяет увидеть как DOSKEY передает эту инфор-
мацию в Windows:
Win386_Startup_Info_Struc at 2996:024В (DOSKEY)
Instance_Item_Struc at 2996:0250 (DOSKEY)
2996:0000 0288 (DOSKEY)
2996:0F23 0200 (DOSKEY)
FAKEWIN содержит опцию -DUMP, которая печатает текущее содержимое блока данных эк-
земпляра в шестнадцатеричном и ASCII-коде. Использование этой опции подтверждает тот факт,
что буфер истории команд DOSKEY относится к данным экземпляра:
*
2996:0F23 0200 (DOSKEY)
2996:0F23 | 45 00 63 ЗА 5С 65 70 73 5С 65 70 73 69 60 6F 6Е | Е.с:\eps\epsilon
2996:0F33 | 2Е 65 78 65 20 24 2А 00 70 00 66 61 6В 65 77 69 | .exe $*.p.fakewi
2996:0F43 | 6Е 20 2D 64 75 6D 70 20 ЗЕ 20 74 6D 70 2Е 74 6D | n -dump > tmp.tm
2996:0F53 | 70 00 72 60 6Е 6В 00 66 61 6В 65 77 69 6Е 20 ЗЕ | р.rink.fakewin >
Если вы запустите два сеанса DOS под Windows и воспользуетесь утилитой DEBUG или
каким-либо другим отладчиком реального режима в каждом сеансе DOS, чтобы вывести на экран
содержимое буфера DOSKEY, то увидите (как показано здесь), что каждый сеанс DOS имеет свой
собственный буфер истории, несмотря на то, что только единственная копия DOSKEY была
загружена перед Windows.
-d 2996:Of23
2996:0F20 | 45 00 63 ЗА 5С-65 70 73 5С 65 70 73 69 | Е.с:\eps\epsi
2996:0F30 | 6С 6F 6Е 2Е 65 78 65 20-24 2А 00 67 72 65 70 20 | lon.exe $*.grep
2996:0F40 | 44 4F 53 4В 45 59 20 66-61 6В 65 77 69 6Е 2Е 74 | DOSKEY fakewin.t
-d 2996:0f23
2996:0F20 | 45 00 63 ЗА 5C-65 70 73 5C 65 70 73 69 | E-c:\6ps\epsl
2996:0F30 | 6C 6F 6E 2Ё 65 78 65 20-24 2A 00 64 69 72 00 64 | lon.exe $*.dir.d
2996:0F40 | 69 72 00 6D 65 6D 00 6D-65 6D’OO 6D 65 6D 00 67 | ir.mem.mem.mem.g
Глава 3. Связь между DOS и Windows
105
Основная идея использования экземплярных данных заключается в следующем: берется единст-
венный элемент данных и тиражируется среди клиентов. Очень важно понимать различия между
локальными, глобальными и экземплярными данными. Эти различия показаны на рис. 3.2. Более
подробно я буду рассматривать экземплярные данные в разделе “Данные экземпляров DOS и SDA"
в главе 4.
Экземплярные данные
Экземплярные данные загружаются
перед запуском Windows и потом
копируются (или создаются) по
одному и тому же адресу в каждой
VM. Потом, после того, как данные
будут скопированы, изменения,
сделанные в одной VM, никак не
отражаются на данных в другой.
Глобальные данные
Глобальные данные видимы всем.
Каждая VM видит те данные которые
были загружены до Windows. Любое
изменение этих данных в одной VM
сразу же отобразится в других VM.
|dos |
Локальные данные
Локальные данные выделяются
отдельно для каждой VM. Данные в
одной VM не имеют никакого
отношения к данным по тому же
адресу в другой VM.
Рис. 3.2. Различия между глобальными, локальными и экземплярными данными
Встроенные VxD
В следующем примере вывода FAKEWIN утилита кэширования диска SmartDrive и утилита
уплотнения диска DoubleSpace запрашивают у Windows загрузить соответствующие VxD
(DriveSpace в MS DOS 6.22 делает то же самое):
Win386 Startup_lnfo_Struc at 2AF4:12EF (SMARTDRV)
VxD name: C:\DOS\SMARTDRV.EXE (Reference data: 2AF41301)
No instance data
Win386J»tartup Info_Struc at 1551:3F10 (DBLSYSHS)
VxD name: H:\DBLSPACE.BIN (Reference data: 15513F32)
No instance data
Однако SMARTDRV.EXE и DBLSPACE.BIN не похожи на имена Windows VxD, это обычные
DOS файлы, не правда ли?
Как отмечалось раньше, программы DOS могут иметь встроенные исполняемые Windows-
модули. Как SMARTDRV.EXE, так и DBLSPACE.BIN имеют встроенные VxD. DRVSPACE.BIN в
MS DOS 6.22 также имеет встроенный VxD. Вообще-то, не совсем правильно называть их DOS-
программами со встроенными исполняемыми Windows-модулями. На самом деле, это как раз
Windows-приложения, которые, как и все Windows-приложения, имеют встроенную программу
DOS. Обычно такой DOS-программой будет заглушка, которая печатает на экране сообщение “This
program requires Microsoft Windows” (Эта программа требует Microsoft Windows). Однако довольно
просто заставить встроенную DOS-программу делать что-нибудь полезное. В случае со
SMARTDRV.EXE и DBLSPACE.BIN встроенная DOS-программа является частью системного про-
граммного обеспечения DOS.
106
Неофициальная Windows 95
Мы можем воспользоваться утилитой VXDSHOW, чтобы обнаружить VxD в этих утилитах
DOS. Заодно можно просмотреть другую утилиту DOS со встроенным VxD, EMM386.EXE. На рис.
3.3 представлен результат.
C:\UNAUTHW>vxdshow h:\dblspace.bin
Module name: DSVXD
Description: “Win386 DSVXD Device (Version 1.0)"
DSVXD_DDB © DOOODIdO
Start @ 0000:00000000
Device # 003b
Version 3.00
Init order: 80000000 (Undefined)
DDB_Control_PrOC © 00000000
DDB_V86_API_Proc © 00000057
C:\UNAUTHW>vxdshow c:\dos\smartdrv.exe
Module name: SDVXD
Description: “Win386 SDVXD Device (Version 2.0)”
SDVXD_DDB © 00000060
Start © 0000:00000000
Version 3.00 '
Init order: 80000000 (Undefined)
DDB_Control_Proc @ 00000000
C:\UNAUTHW>vxdshow c:\dos\emm386.exe
Module name: LoadHi
Description: “Win386 LoadHi Device (Version 1.0)”
LoadHi_DDB © 000012e4
Start @ 0003:00000000
Device # 001c
Version 1.00
Init order: 00000000 (Earliest — same as VMM)
DDB_Control_Proc @ 000005a0
DDB_Service_Table @ 000012e0
1c0000 © 0000079d LoadHi_Get_Version
Рис. 3.3. Запуск VXDSHOW на DBLSPACE.BIN, SMARTDRV.EXE и EMM386.EXE показывает информацию
о встроенных VxD
Оказывается, что эти VxD не особо важны для Windows 95. Например, SDVXD, встроенный в
SMARTDRV.EXE, делает ненамного больше, чем позволяет Windows выдавать сообщение
SHELL_SYSMODAL_Message в случае серьезной дисковой ошибки. Однако, даже если SmartDrv
загружен в Windows 95, Windows в основном игнорирует его, поскольку его роль берет на себя
VxD VCACHE. SMARTDRV.EXE вносится в список “безопасных драйверов” — IOS.INI, с кото-
рым консультируется супервизор операций ввода-вывода (I/O Supervisor — IOS.386) в Windows 95,
при выяснении, какие драйверы реального режима DOS можно проигнорировать.
Если Windows 95 больше не нужен SmartDrv, зачем нам изучать, каким образом он
взаимодействует с более ранними версиями Windows? Да потому, что важен сам факт того, что
SmartDrv и другие утилиты DOS устанавливают связь с Windows. Это еще один признак того, что
Windows 3.x не просто запускалась как обычное приложение DOS, а участвовала в диалоге с DOS
и с утилитами DOS, таким как SmartDrv. А это, в свою очередь, очень важно потому, что помогает
установить, что объединение Windows 95 и DOS в одном пакете — это не такой уж радикальный
отход от предыдущих способов взаимодействия DOS и Windows. Если Windows 95 и “узаконивает”
брак DOS с Windows, то факт остается фактом — они живут вместе уже долгие годы. Это, конечно,
большой шаг вперед, но не настолько большой, как его преподносят.
Гдава 3. Связь ллежду DOS и Windows
107
Огрехи FAKEWIN
Хотя FAKEWIN помогает вскрыть истинные взаимоотношения между Windows и DOS, она
является в некоторой степени агрессивной, поскольку может оказать влияние на работу других про-
грамм, которые используют оповещение Windows по прерыванию INT 2Fh для отслеживания внут-
реннего флага IN_WINDOWS. MS DOS непосредственно является одной из таких программ. На-
пример, во время работы Windows в расширенном режиме MS DOS отслеживает, какая вирту-
альная машина каким открытым файлом владеет. В главе 4 вы увидите (листинг 4.2), что если DOS
считает, что Windows запущена, она может обращаться к тому, что, по ее мнению, является VxD
DOSMGR. К сожалению, при работе FAKEWIN MS DOS считает, что Windows работает в расши-
ренном режиме. Таким образом, нет возможности узнать, как отреагирует каждый драйвер устрой-
ства и TSR DOS на фальсифицированное сообщение FAKEWIN о запуске Windows. Возникает
сложный вопрос: “Как можно использовать функции 1605h и 1606h прерывания INT 2Fh и быть
уверенным, что при этом не будет нарушена работа DOS?”
Стоит сослаться на пространный ответ Джефа Чапелла на данный вопрос, содержащийся в
разделе “Undocumented Corner” по материалам форума Dr. Dobb’s Journal (DDJFORUM) в
CompuServe:
#:65821 S3/Undocumented Corner
16-May-94 01:39:01
Sb: #65697-#SmartDrive API
Fm: Geoff Chappell 100043,564
To: Ralph E Griffin 70323,1440 (X)
He-Windows-программы вполне могут вызывать функции 1605h и 1606h прерывания INT 2Fh,
сымитировав тем самым начало и завершение работы Windows, если между двумя вызовами не проис-
ходит никаких внешних последствий.
В противном случае ситуация становится менее ясной. Вполне вероятно, что программное обеспечение
может переконфигурироваться для работы под Windows. Данное программное обеспечение не знает, что
вы только имитируете работу Windows. Вы можете возразить, что программное обеспечение могло бы не
производить никаких действий, могущих иметь опасные побочные эффекты, если, в действительности,
сигналы исходили не от Windows. Другими словами, программное обеспечение не должно полагать, что
функция 1605h прерывания INT 2Fh вызывается только Windows.
Возможно, это было бы справедливым, но фирма Microsoft разработала DOS 5.0 таким образом, что
машинный идентификатор ID (слово по адресу DOS:033E) обрабатывается по другому с момента, когда
ядро DOS обнаруживает функцию прерывания INT 2Fh. Я не убежден, что это хорошо со стороны
DOS. Это может иметь нежелательные последствия, когда файл, к которому обращается Windows (до
инициализации DOSMGR), оказывается уже открытым. Это маловероятно в реальных условиях, когда
Windows обычно запускается из командной строки (однако эта вероятность достаточно велика, чтобы
даже разработчики IFSMGR, со множеством небольших ошибок и несовместимостей, которые, казалось
бы, должны были быть обнаружены, не преминули обеспечить защиту от конфликтов, возникающих в
случаях, когда при установке 32-битовой файловой системы 16-биговая файловая система уже имеет
открытые файлы).
С моей точки зрения, нельзя пользоваться функциями файлового доступа в промежутке между
вызовами функций 1605h и 1606h прерывания INT 2Fh, — я это утверждаю по имеющимся у меня
сведениям. Если вы планируете самостоятельно использовать функции прерывания INT 2Fh в общей
среде, то вам придется также бороться с неприятностями, которые мы не можем предсказать.
Сейчас вы, возможно, подумаете, что FAKEWIN не вызывает “никаких внешних последствий”
между вызовами функций 1605h и 1606h. Обратите, однако, внимание на листинг 3.1, где
FAKEWIN после win_init_notify (1605h) и перед вызовом win_term_notify (1606h) вызывает
функцию printf. Если вывод FAKEWIN перенаправлен в файл (что случалось бесчисленное число
раз в-процессе написания данной главы), то это может вызвать потенциальные проблемы.
108
Неофициальная Windows 95
С другой стороны, Microsoft Systems Journal рекомендовал программам, отличным от Windows,
задействовать оповещение Windows по INT 2Fh и для обнаружения Windows 3.0 (May-June 1992), и
для переконфигурации SmartDrive 4.0 (September 1992). Трудно сказать, то ли мы пере-
страховываемся, то ли просто Microsoft никогда не сталкивалась с тем, что фальсифицированное
оповещение Windows может привести к непредсказуемому поведению программ (включая саму
DOS), отслеживающих его.
Кроме сообщения программе, что Windows начала работать, хотя в действительности она не
работает, существует и другая потенциальная опасность, связанная с FAKEWIN: она может сооб-
щить программе, что Windows закончила работу, хотя в действительности она еще работает. Это
может произойти, если вы запустили FAKEWIN в сеансе DOS под Windows. FAKEWIN использует
функцию is_win из ISWIN.C для определения присутствия Windows. FAKEWIN не будет работать
под Windows. Чтобы запустить ее, необходимо сначала выйти в DOS. (В Windows 95, где невоз-
можно выйти из Windows в DOS, можно запустить FAKEWIN на выполнение в режиме отдельного
приложения (single application).)
Наконец, FAKEWIN показывает только некоторые аспекты связи между DOS и Windows. Так,
функция 1605h не является единственным механизмом для объявления экземплярных данных. В
SYSTEM.INI можно воспользоваться оператором LOCALTSRS=, который превратит всю TSR в
элемент экземплярных данных, и LOCAL=, который делает то же самое для драйверов устройств
DOS. (SYSTEM.INI уже имеет установленные значения LOCAL=CON и LOCALTSRS=DOSEDIT.)
К тому же, Windows VMM предоставляет VxD функцию _AddInstanceItem для непосредственного
объявления экземплярных данных. В действительности все прочие методы формирования экземп-
лярных данных в конечном счете сводятся к вызовам _AddInstanceItem, поэтому, если вы пожелали
получить полное представление об экземплярных данных, то перехватите _AddInstanceItem.
FAKEWIN изнутри
Несмотря на проблемы, обсужденные в предыдущем разделе, программа FAKE WIN позволяет
увидеть, каким образом Windows 3.x взаимодействует с программами, загруженными до нее. Важ-
ное значение FAKEWIN состоит в том, что она помогает понять связь между Windows 3.x и DOS, и
пока вы не поймете эту связь, вам будет трудно оценить изменения, которые появились в Windows 95.
Исходный текст FAKEWIN на языке С показан в листинге 3.1. Чтобы получить
FAKEWIN.EXE, этот модуль нужно скомпоновать С несколькими другими модулями (TEST1684.C,
FAKEVXD.C и FAKETSR.C, представленными в главе 4, а также с ISWIN.C, DUMP.С и МАР.С).
Листинг 3.1: FAKEWIN. с
/*
FAKEWIN.С
Шульман, 1994
bcc fakewin.с fakevxd.c faketsr.c iswin.c dump.с map.с
bcc -DTEST_1684 - также требует test1684.c
Используя прерывание INT 2Fh, FAKEWIN притворяется Windows. Она имитирует инициализацию и завершение Windows,
чтобы выяснить, как DOS и многочисленные драйверы устройств и TSR-программы реагируют на запуск Windows. Для
каждой Win386_Startup_Info_Struc FAKEWIN выводит имя загружаемого драйвера виртуального устройства (VxD), а
также адреса и размеры всех элементов экземплярных данных.
Ключи:
-WIN30 - притворяется Windows 3.0
-VERS ххх - притворяется Windows версии ххх (например -VERS 400 для версии 4.0)
-STANDARD - имитирует стандартный режим (расширенный по умолчанию)
-DUMP - выводит шестнадцатеричный дамп области экземплярных данных
*/
«include <stdlib.h>
«include <stdio.h>
«include <string.h>
«include <dos.h>
«include <io.h>
Глава 3. Связь ллежду DOS и Windows
109
«include <fcntl.h>
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef void far «FP;
«pragma pack(1)
typedef struct {
FP IIS Ptr;
WORD HS_Size;
} Instance_Item_Struc;
// функция Task Switcher Identify Instance Data (2F/4B05)
// использует эту же структуру
typedef struct _WINFO {
WORD SlS_Version; // должно содержать 3
struct WINFO far *SIS_Next_Dev_Ptr;
DWORD STS_Virt_Dev_File_Ptr;
DWORD SIS_Reference_Data;
Instance Item_Struc far *SIS Instance Data_Ptr;
} Win386_Startup_Info_Struc, far *FPWININFO;
«pragma pack()
static FPWININFO fpinfo;
static void (far *switch_func)(void);
«define ENHANCED MODE 0
«define STANDARD_MODE 1
«define WIN3D 0x0300
«define WIN31 ОхОЗОА
extern void fake_dosmgr_callouts(void); // в FAKEVXD.C
extern void tsr_identify(void); // в FAKETSR.C
extern void dump(unsigned char far *fp, unsigned bytes,
char *mask, unsigned long addr, int width); // в DUMP.C
extern int is_win(int *pmaj, int *pmin, int *pmode); // в ISWIN.G
extern char *find_owner(DWORD lin_addr); // в MAP.C
«define MK_LIN(fp) ((((DWORD) FP.SEG(fp)) « 4) + FP_OFF(fp))
char *owner(FP fp)
{
static char buf[32];
char *s = find_owner(MK_LIN(fp));
if(s) sprintf(buf, “(%sj", s);
else buf[O] = ’\0';
return buf;
}
WORD win init_notify(WORD vers, WORD mode)
{
WORD retval;
WORD handler_ds;
_asm push si
_asm push di
_asm push ds
_asm xor bx, bx
_asm mov es, bx
_asm mov ex, bx
_asm mov dx, mode
_asm mov di, vers
_asm xor si, si
_asm mov ds, si
_asm mov ax, 1605h
_asm int 2fh
_asm mov retval, ex
_asm mov handler_ds, ds
_asm pop ds
_asm emp ex, 0
_asm jne no_init
110
Неофициальная Windows 95
init;
_aam mov word ptr fpinfo+2, es
_asm mov word ptr fpinto, bx
2asm mov bx, handler_ds
_asm mov word ptr switch_func+2, bx
_asm mov word ptr switch_func; si
done:
_asm pop di
_asm pop si
return retval;
no_init:
goto done;
}
«define V86 TO REAL 0
«define REAL_tS_V86 1
void win term notify(WORD mode)
{
_asm mov dx, mode
_asm mov ax, 1606h
_asm int 2fh
}
void win_init complete_notify(void)
{
_asm mov ax, 1608h
_asm int 2fh
void win_begin_exit_notify(void)
_asm mov ax, 1609h
asm int 2fh
}"
static int do_hex_dump = 0;
void print startup info(FPWININFO winfo)
, {
Instance_Item_Struc far «inst;
if(winfO->SIS_Version >= 3) // для совместимости с последующими версиями
{
printf(“\nWin386_Startup Info Struc at %Fp %s\n”, winfo, owner(winfo));
if(winfo->SIS Virt Dev File_Ptr!= 0)
{
printf(“VxD name: %Fs “, winfo->SIS_Virt_Dev_File_Ptr);
printf(“(Reference data: %081X)\n", winfo->SIS Reference_Data);
}
if((inst = winfo->SIS Instance_Data_Ptr) != 0)
{
printf(“Instance_Item_Struc at %Fp %s\n”, inst, owner(inst));
while(inst->IIS Ptr)
<
printf(“ %Fp %04X *s\n”,
inst->IIS_Ptr,
inst->IlS_Size, owner(inst->IIS_Ptr));
if(do_hex_dump)
dump((unsigned char far *) inst->IIS_Ptr,
(unsigned) inst->IIS_Size, “%Fp”,
(unsigned long) inst->IIS_Ptr, 16);
inst++;
}
}
else
printf(“No instance data\n”);
// рекурсивный обход цепочки структур начальной загрузки
if(winfo->SIS_Next_Dev_Ptr != 0)
print_startup_ihfo((FPWININFO) winfo->SIS_Next_Dev_Ptr);
else
printf(“%Fp not a valid.Win386 startup info structure!\n”, winfo);
Глава 3. Связь ллежду DOS и Windows
111
«pragma pack(1)
typedef struct (
DWORD addr;
BYTE maj, min;
} EMM-IMPORT;
«pragma pack()
void check_emm_import(void)
{
static EMM_IMPORT emm_import;
int emm = openC'EMMXXXXO", O_RDWR | O.BINARY);
if(emm == -1) emm = open(“$MMXXXXO”, O_RDWR | O_BINARY);
if(emm == -1) emm = open(“EMMQXXXO”, O_RDWR | O_BINARY);
if(emm == -1) return; // нет EMM
emm_import.addr = 1; // установить первый байт в 1
emm import.maj = emm_import.min =0;
«define IOCTLREAD 2
if(ioctl(emm, IOCTLREAD, &emm_import), sizeof(emm_import)) != 0)
printf(“Global EMM Import © %081Xh (version %d.%02d)\n",
emm_import.addr, emm_import.maj, emm_import.min);
close(emm);
}
void fail(const char *s, ...) { puts(s); exit(1); }
static char *usage_msg =
“usage: fakewin [-standard] [-win30 | -vers xxxx] [-dump]”;
void usage(void) { fail(usage_msg); }
main(int argc, char *argv[])
WORD mode = ENHANCED_MODE; // no умолчанию
WORD vers = WIN31; /7 no умолчанию
char *s;
int dummy;
int i;
fputs("FAKEWIN 1.0 -- Simulate Windows Initialization\n", stderr);
fputs(“Displays instance data, VxD startup, DOSMGR interface\n", stderr);
fputs(“Copyright (c) Andrew Schulman 1993. All rights reserved\n\n", stderr);
for(i=1; i<argc; i++)
if((argv[i][0] == '-') || (argv[i][0] == /'))
{
s = strupr(argv[i]) + 1;
if(strcmp(s, “STANDARD") == 0) mode = STANDARD_M0DE;
else if(strcmp(s, “WIN30") == 0) vers = WIN30;
else if(strcmp(s, “VERS”) == 0) sscanf(argv[++i], “%04X", &vers);
else if(strcmp(s, “DUMP”) == 0) do_hex_dump++;
else usage();
}
else
usage();
if(is_win(&dummy, &dummy, &dummy))
fail(“Already running under Windows\n”
“Exit back to DOS before running FAKEWIN'');
printf(“FAKEWIN pretending to be Windows %u.%02u %s mode\n”,
(vers » 8), (vers & OxFF),
(mode == ENHANCED_MODE) ? “Enhanced” : “Standard");
// сказать DOS и всем остальным, что запускается Windows
if(win_init_notify(vers, mode) == 0)
// начать рекурсивный обход цепочки структур
if(fpinfo == 0)
printf(“\nNo Windows startup info\n");
else
print_startup_info(fpinfo);
if(switch func)
{
printf(“\nV86 Enable/Disable function: %Fp %s\n",
112 Неофициальная Windows 95
switch_func, owner(switch_func));
check emm import();
}
}
else // кто-то запретил запускать .Windows
fail(“Not allowed to start Windows");
// вызов устройств -- порядок инициализации VxD определен в VMM.INC
fake_dosmgr_callouts();
s
// вызов идентификации TSR (2F/160B)
tsr_identify();
// сообщить DOS и всем прочим, что Windows закончила инициализацию
win_init_complete_notifу();
#ifdef TEST.1684
{
extern void test_1684(void); // в TEST1684.C
test_1684();
}
ffendif
// сообщить DOS и всем прочим, что Windows начинает процесс завершения
win_begin_exit_notify();
// сообщить DOS и всем прочим, что Windows завершается
win_term_notify(mode);
return 0;
}
После прочтения опций командной строки функция main в FAKE WIN. С вызывает win_
init_notify для оповещения посредством вызова функции 1605h прерывания INT 2Fh, которое
обычно генерируется кодом реального режима модуля WIN386.EXE. Функция win_init_notify
сохраняет возвращенные адреса структуры начальной загрузки и функции выключения/включения
режима V86. Проверив, что никто не против загрузки Windows (по значению в регистре СХ),
FAKEWIN вызывает print_startup_info, которая печатает содержимое структуры начальной
загрузки. Если имеется указатель на следующую структуру (winfo->SIS_Next_Dev_Ptr != 0),
print_startup__info рекурсивно вызывает саму себя. Таким образом функция выводит на экран связ-
ный список структур начальной загрузки.
Для отображения содержимого структур используется не только функция printf. Для владель-
цев структур начальной загрузки с именами 386МАХ$$, DOSKEY, SMARTDRV и DBLSYSHS
функция print_startup_info вызывает функцию owner, которая находится в модуле МАР.С.
Если в командной строке была указана опция -DUMP, то print_startup_info вызывает функцию
dump печати шестнадцатеричного дампа из модуля DUMP.С.
После отображения информации начальной загрузки FAKEWIN распечатывает функцию
выключения/включения режима V86, если она была предоставлена каким-то менеджером памяти, и
вызывает функцию check_emm_import, чтобы проверить, выдает ли менеджер памяти структуру им-
порта глобальной ЕММ (см. главу 4).
Затем FAKEWIN вызывает функцию fake_dosmgr_callouts из модуля FAKEVXD.C и функцию
tsr_identify из модуля FAKETSR.C. Эти функции рассматриваются в главе 4.
И наконец, раз FAKEWIN имитировала запуск Windows, то теперь она должна сымитировать и
некоторые вещи: что Windows закончила инициализацию (INT 2fh функция 1608h), что она завер-
шает работу (функция 1609h) и что она, наконец, успешно завершила работу (функция 1606h).
Глава 3. Связь между DOS и Windows
ИЗ
Глава 4
Женитьба в Редмонде
9
В предыдущей главе была представлена программа FAKEWIN и кратко обсуждались такие
аспекты связи между DOS и Windows, как данные экземпляров и встроенные VxD. В этой
главе мы детальнее рассмотрим вывод программы FAKEWIN и обратим внимание на неко-
торые способы сов-местной работы DOS и Windows.
На рис. 4.1 показан пример полного вывода программы FAKEWIN. Этот вывод был получен
при работе компонента системы DOS 7.0 в Chicago с большим количеством загруженных TSR. По-
нятно, что у вас может получиться другой результат: при различных конфигурациях программа
FAKEWIN может выдавать различные результаты. Тем не менее на рис. 4.1 показано, как некото-
рые утилиты DOS и сама DOS реагируют на такую новость, как запуск Windows.
FAKEWIN pretending to be Windows 3.10 Enhanced mode
Win386 Startup Info_Struc at 08F4:125F (SMARTDRV)
VxD name: C:\WINDOWS\SMARTDRV.EXE (Reference data: 08F41271)
No instance data -
Win386_Startup_Info_Struc at 0805:0107 (TSRLDR)
VxD name: C:\UNAUTHW\PIPE\PIPE.386 (Reference data: 00000000)
No instance data
Win386_Startup_Info_Struc at 085E:0D0A (COUNTDOS)
Instance_Item_Struc at 085E:001C (COUNTDOS)
085E:00DD 0404 (COUNTDOS)
Win386_Startup_Info_Struc at E9F7:024B (DOSKEY)
Instance Item_Struc at E9F7:025D (DOSKEY)
E9F7:0000 0288 (DOSKEY)
E9F7:0F23 0200 (DOSKEY)
Win386 Startup Info Struc at 0329:01EE (386MAX$$)
VxD name: C:\386MAX\386MAX.VxD (Reference data: 00BCE3C8)
Instance Item Struc at C001:0440
C001:0183 0001
Win386 Startup Info Struc at C0FD:3F10 (DBLSYSHJ)
VxD name: H:\DBLSPACE.BIN (Reference data: C0FD3F32)
No instance data
Win386_Startup_Info_Struc at FFFF:1DDF (HMA)
Instance_Item Struc at FFFF:1D25 (HMA)
0050:0000 0001
0050:0004 0001
0050:000E 0014
0050:0030 0004
0070:0012 0004 (10)
0070:0266 0001 (10)
E833:0000 0948 (DBLSYSHS)
E808:0010 0002 (DBLSYSHJ)
114
Неофициальная Windows 95
Win386_Startup_Info_Struc at 00A0:0EE1 (DOS)
Instance Item Struc at 00A0:0EF7 (DOS)
00A0:0022 0002 (DOS)
00AO:0032 0004 (DOS)
00A0:01F9 0106 (DOS)
00A0:0300 0001 (DOS)
OOAO:OEBF 0022 (DOS)
00A0:0089 0001 (DOS)
00A0:008C 0002 (DOS)
00A0:0086 0001 (DOS)
00AD:12B8 0001 (DOS)
00A0:12B9 0001 (DOS)
V86 Enable/Disable function: 0329:03D4 (386MAXS)
Global EMM Import phys 00BD9EACh (version 1.11)
DOSMGR instance interface ON
Segment of DOS drivers: 0005
Patch table: 00A0:0F47
DOS version 5.00
05EC (SAVEDS)
05EA (SAVEBX)
0321 (INDOS)
D33E (USER ID)
0315 (CRITPATCH)
D08C (UMB_HEAD)
Current Directory Structure = 88 bytes
No DOS data structures instanced via DOSMGR API
TSR_Info Struc at 085E:0026 (PSP 084Eh) (COUNTDOS)
TSR_WINEXEC SW SHOWNOACTIVATE
“C:\UNAUTHW\FAKEWIN\COUNTDOS.EXE /085E:0DDD”
TSR_ID_Block: “Sample TSR / Windows App”
TSR_Data_B10Ck: D85E:00DD
Рис. 4.1. Запуск программы FAKEWIN с большим числом загруженных TSR демонстрирует связь между DOS и
Windows: некоторые утилиты запрашивают Windows, и мы видим, как сама DOS взаимодействует с Windows
Давайте рассмотрим рис. 4.1. Первая информационная структура начальной загрузки Windows
в цепочке принадлежит программе SMARTDRV.EXE. Как отмечалось в главе 3, часть этой про-
граммы, работающая под DOS, обращается к Windows для загрузки своего VxD-модуля.
Далее, какая-то программа с именем TSRLDR обращается к Windows для загрузки PIPE.386
(программы коммуникационного канала между DOS и Windows, написанной Томасом Олсеном
(Thomas Olsen)). В листинге 4.1 приведена маленькая DOS-программа TSRLDR.ASM, которая
демонстрирует, как программное обеспечение DOS может перехватывать функцию 1605h для
загрузки VxD.
ЛИСТИНГ 4.1. TSRLDR. ASM
; TSRLDR.ASM
; Томас Олсен
CODE segment para public ’CODE’
assume cs:CODE
org 100h
EntryPoint:
jmp Setup
oldlnt2f0ffset
01dInt2fSegment
dw ?
dw ?
Instance Struct label byte
HS_Ptr dd 0
IIS_Size dw 0
done dd 0
Win386_Startup_Info_Begin label byte
SIS_Version db 3,0
Глава Ду Женитьба в Редмонде 115
SIS_Next_Dev_Ptr_Offset dw ?
SIS_Next_Dev_Ptr_Segment dw ?
SIS_Virt_Dev_File_Ptr_Offset dw ?
SIS_Virt_Dev_File_Ptr_Segment dw ?
SIS_Reference_Data dd 0
SIS_Instance_Data_Ptr_Offset dw offset Instance_Struct
SIS_Instance_Data_Ptr_Segment dw seg Instance_Struct
Win386 Startup Info_End label byte
db ‘PATH=’
vxdName db ‘C:\UNAUTHW\PIPE.386’,0
db 128 dup(0)
Int2fHandler proc far
public Int2fHandler
cmp ax,11605h
je 12f
jmp dword ptr cs:oldlnt2f0ffset
12f: ; Перед возвращением своей информации нужно
; вызвать предыдущий обработчик
pushf
call dword ptr cs:oldlnt2f0ffset
; Теперь у нас есть указатель на структуру предыдущего обработчика
mov cs:SIS_Next_Dev_Ptr_Segment, es
mov cs:SIS_Next_Dev_Ptr_Offset, bx
mov cs:SIS_Virt_Dev_File_Ptr_Segment, cs
mov cs:SIS_Virt_Dev_File_Ptr_Offset, offset vxdName
mov bx, cs
mov es, bx
mov bx, offset Win386_Startup_Info_Begin
iret
Int2fHandler endp
Setup proc near
public Setup
mov bx, ds
mov es, bx
mov bx, es:[2Ch]
mov ah, 49h
mov es, bx
int 21h ; Удалить среду
push cs
pop ds
mov ax,352Fh
int 21h
mov cs:old!nt2fSegment,es
mov cs:oldlnt2f0ffset,bx
mov ax,252Fh
lea dx, Int2fHandler
int 21h ; Перехватить мультиплексное прерывание
mov dx,offset Setup
mov cl,4
shr dx,cl
inc dx
mov ah,31h
int 21h
Setup endp
CODE ends
end EntryPoint
Кстати, в случае ошибки в вашем обработчике функции 1605h код реального режима WIN386
использует некоторые довольно неудачно выбранные сообщения об ошибках. Если TSR попросит
Windows загрузить файл, который на самом деле не является VxD (например, текстовый файл,
который вы по каким-то причинам переименовали в PIPE.386), вы получите сообщение: “A device
file that is specified in the SYSTEM.INI file is damaged. It may be needed to run Windows” (Файл
устройства, указанный в SYSTEM.INI, поврежден. Он может требоваться для запуска Windows).
116
Неофициальная Windows95
•i
Основная идея функции 1605h состоит в том, что нет необходимости указывать VxD в файле
SYSTEM.INI. Без программы, подобной FAKEWIN, системному администратору придется поломать
голову над этим сообщением об ошибке! Более того, если TSR указал VxD, который вовсе
невозможно найти, Windows выдаст такое сообщение:
Cannot find a device file that may be needed to run Windows.
Make sure that the PATH line in your AUTOEXEC.BAT file points to
the directory that contains the file and that it exists on your
hard disk. If the file does not exist, try running Setup to
install it or remove any references to it from SYSTEM.INI
file.
C:\MISTAKE\PIPE.386
Press a key to continue
(Невозможно найти файл устройства, который может потребоваться при работе
Windows. Убедитесь, что строка PATH в вашем файле AUTOEXEC.BAT указывает на
каталог, в котором содержится этот файл, и что он существует. Если файл
отсутствует, попытайтесь запустить Setup для его установки или удаления
всех ссылок на этот файл из SYSTEM.INI).
Снова, упоминание о SYSTEM.INI так же бесполезно, как и совет по поводу переменной PATH.
Если PIPE.386 находится в C:\PIPE, и C:\PIPE указан в переменной PATH, Windows все равно
не найдет его.
Данные экземпляра DOS и SDA
Продолжая рассматривать рис. 4.1, вы можете обратить внимание на две информационные
структуры начальной загрузки, созданные не TSR и не драйвером устройства DOS, а самой
MS DOS. Первая структура, размещенная в верхних адресах памяти (НМА), с данными экземп-
ляра по адресах типа 0050:0000 и 0070:0012, принадлежит компоненту Ю модуля WINBOOT.SYS
(аналог IO.SYS в более ранних версиях DOS). Вторая структура, чьи данные экземпляра находятся
в сегменте 00A0h, принадлежит компоненту MSDOS модуля WINBOOT.SYS (аналог MSDOS.SYS в
более ранних версиях).
О переменных, обычно включаемых DOS в список данных экземпляра функции 1605h, Джеф
Чапелл заметил: “Почти все переменные ядра, создаваемые Windows, относятся к консольным
операциям. Особенно следует отметить тот факт, что буфер по DOS:01F9h обслуживает функцию
3Fh как системный эквивалент буфера, который передается программами как параметр при вызове
функции DOS OAh для чтения с консоли”. Во второй структуре Win386_Startup_Info_Struct на рис.
4.1 также видно, что DOS сообщает Windows об этом 106-байтовом буфере по адресу 00A0:01F9.
Большое количество внутренних переменных и структур DOS, которые должны создаваться
Windows, не показано в списке данных экземпляра DOS. (Хорошим примером может служить
структура текущего каталога DOS (Current Directory Structure, CDS), поскольку в различных
сеансах DOS текущие каталоги могут быть разными.) Это происходит потому, что, как отмечалось в
главе 3, функция 1605h — это только один из механизмов распределения данных экземпляров.
Данные экземпляров компонентов Ю и MSDOS, показанные на рис. 4.1, являются единст-
венными, которые Windows не может получить самостоятельно. VxD DOSMGR, широко используя
недокументированные вызовы DOS, находит ключевые внутренние переменные и структуры DOS.
Затем он передает адреса и размеры этих структур сервису _AddInstanceItem VMM. Ключевым
примером является выгружаемая область данных (Swappable Data Area, SDA) DOS. DOSMGR
вызывает недокументированную функцию 5D06h прерывания INT 21h (получить SDA), возвращаю-
щую адрес области DOS, которая должна выгружаться и восстанавливаться переключателем задач.
Функция также возвращает число байтов SDA, которые переключатель задач должен подгрузить,
если установлен флаг InDOS, и число байтов SDA, которые подгружаются независимо от того,
установлен флаг InDOS или нет.
Данные экземпляров во многих случаях являются только расширениями SDA. SDA, к сожа-
лению, не включает полного “состояния” DOS. И сюда не могут входить области, принадлежащие
Глава 4. Женитьба в Редллонде
117
TSR независимых производителей. Таким образом, существует потребность объявлять области вне
SDA с целью их выгрузки. Данные экземпляров в основном и представляют методику дополнения
SDA. Аналогично тому, как функция DOS 5D06h возвращает значения Swap_In_DOS (размер
области подгрузки, когда установлен флаг InDOS) и Swap_Always (размер области безусловной
подгрузки), структура InstDataStruc, используемая Addinstanceitem, имеет поле InstType, которое
может принимать значения INDOS_Field и ALWAYS_Field. В настоящее время VMM игнорирует
эти два типа данных экземпляров, но в любом случае существует явная связь между DOS SDA и
данными экземпляров Windows (что вполне понятно, поскольку ответственным за оба фрагмента
кода, судя по всему, является один и тот же программист фирмы Microsoft, Аарон Рейнольдс
(Aaron Reynolds)). На рис. 4.2 представлен пример кода, а на рис. 4.3 показано, как VxD
DOSMGR создает DOS SDA.
; Сначала вызвать DOS 21/5D06, чтобы получить SDA в DS:SI
05DBB mov [ebp.Client_AX], 5D06h
05DC1 mov eax, 21h
05DC6 VMMCall Exec_Int
; Сформировать линейный адрес SDA
05DCC movzx eax, [ebp.Client_DS]
05DD0 shl eax, 4
05DD3 movzx ebx, [ebp.Client_SI]
05DD7 add eax, ebx ; eax = (DS « 4) + SI
; Сохранить переменные, относящиеся к SDA
05DD9 movzx edx, [ebp.Client_DX] ; SWAP_ALWAYS
05DDD movzx ecx, [ebp.Client_CX] ; SWAP_IN_DOS
05DE1 mov DOS_SDA, eax
05DE6 mov dword ptr SWAP_ALWAYS, edx
05DEC mov dword ptr SWAP_IN_DOS, ecx
; Ниже, часть для SWAP_ALWAYS
; ESI указывает на InstDataStruc
060FB mov edi, dword ptr DOS_SDA
06101 mov ecx, dword ptr SWAP_ALWAYS
06107 mov dword ptr [esi.InstLinAddr], edi ; DOS_SDA
0610A mov dword ptr [esi.InstSize], ecx ; SWAP_ALWAYS
0610D mov dword ptr [esi.InstType], ALWAYS Field
06114 push 0
06116 push esi
06117 VMMCall _AddInstanceItem
; Часть для SWAP_IN_DOS
06133 mov edi, dword ptr DOS SDA
06139 add edi, dword ptr SWAP ALWAYS
0613F mov ecx, dword ptr SWAP_IN DOS
06145 sub ecx, dword ptr SWAP ALWAYS
0614B mov dword ptr [esi.InstLinAddr], edi ; SDA + ALWAYS
0614E mov dword ptr [esi.InstSize], ecx ; IN_DOS - ALWAYS
06151 mov dword ptr [esi.InstType], INDOS_Field
06158 push 0
0615A push esi
0615B VMMCall _AddInstanceItem
Рис. 4.2, Этот пример кода показывает, как VxD DOSMGR создает SDA. Имеется явная связь между DOS SDA
и данными экземпляров Windows
Как только DOSMGR создал SDA, при каждом переключении VMM с одной виртуальной
машины на другую переключаются также и SDA, даже когда MS DOS запустилась только с одним
SDA. Благодаря этому замечательному механизму Windows эффективно передает DOS различные
SDA. Это хороший пример того, как DOS и Windows работают вместе для формирования единой
многозадачной операционной системы. DOS часто описывается как система, не поддерживающая
многозадачность, но SDA показывает, что DOS имеет некоторые средства для создания много-
задачности.
118
Неофициальная Windows 95
Рис. 4.3. DOSMGR создает SDA в каждой VM. Таким образом, каждая VM получает свой собст-
венный набор переменных DOS, таких как текущий PSP. Этот процесс становится более сложным
при выполнении одной из множества задач в System VM, когда ядро Winl6 Kernel просит DOS
создать PSP для этой задачи (см. главы 12 и 13)
Ранее я отметил, что SDA, к сожалению, не запоминает все состояние DOS. Это важное замеча-
ние, поскольку некоторые программисты последовали совету из Undocumented DOS относительно
использования SDA для создания TSR-программ, и тогда обнаружилось, что некоторые переменные
DOS не включаются в SDA. Хорошим примером является UMBHEAD, хранящая адрес первого
блока верхней памяти (Upper Memory Block, UMB). На рис. 4.1 она находится по адресу
00А0:008С. Это значит, что UMBHEAD, не находящаяся в SDA, является одной из переменных,
объявленных DOS как данные экземпляра.
И это не случайно. Как было отмечено раньше, списки данных экземпляров, возвращаемые
функцией 1605h прерывания 2Fh, позволяют расширять SDA. Эта методика вполне подходит тем
DOS-программистам, которые все еще хотят писать TSR-программы, используя SDA. Хотя SDA не
содержит полного состояния DOS, функцию 1605h можно использовать в TSR для получения
списков данных экземпляров для того, чтобы узнать, какие данные за пределами SDA TSR должны
сохранять и восстанавливать. Идея заключается в следующем: что важно для Windows, возможно,
важно и для любого выгружаемого (swappable) TSR.
К сожалению, эта превосходная идея вызывает проблемы, о которых я уже говорил. Во-пер-
вых, не ясно, насколько безопасно вызывать 1605h программам, отличным от Windows. Во-вторых,
Глава 4. Женитьба в Редмонде
119
Это не имеет большого значения в Windows 95 или в WfW 3.11 с 32-разрядным файловым
доступом, поскольку эти версии Windows могут выполнять файловый ввод-вывод без использования
DOS. До суть в том, что даже когда Windows для файлового ввода-вывода использовала DOS,
DOS знала о присутствии — и содержала специальные средства — Windows.
IO.SYS также проверяет флаг IN_WIN3E, и если Windows работает в расширенном режиме,
при определенных обстоятельствах вызовет VxD DOSMGR. VxD могут предоставлять API для при-
кладных программ DOS и Windows. Вызов функций 1684И прерывания 2Fh выдает точку входа
API для VxD, ID которого (например, 15h для DOSMGR) находится в регистре ВХ.
Как обычно, Windows DDK документирует этот механизм прерывания 2Fh, но не конкретные
интерфейсы API, предоставляемые DOSMGR и другими VxD, встроенными в Windows.
Эти интерфейсы VxD доступны даже программному обеспечению, которое, как и сама DOS, было
загружено до Windows. MS DOS действительно содержит вызовы функции 1684И прерывания INT
2Fh. FAKEWIN, скомпилированная с переключателем TEST_1684 позволяет исследовать поведение
DOS, перехватывая функцию 1684 INT 2Fh (листинг 4.2), и запускает командный интерпретатор.
ЛИСТИНГ 4.2. TEST1684.C
/*
TEST1684.C
Фрагмент кода для проверки взаимодействия DOS с FAKEWIN
Эндрю Шульман, 1994
*/
«include <stdlib.h>
«include <stdio.h>
«include <dos.h>
typedef unsigned short WORD;
typedef struct {
«ifdef __TURBOC_
WORD bp, di, si, ds,es, dx, ex, bx,ax;
«else
WORD es,ds, di,si,bp,sp,bx,dx,ex,ax; /* порядок такой же, как в PUSHA */
«endif
WORD ip.es,flags;
} REG.PARAMS;
, static WORD vxd_calls = 0;
static WORD vxd = 0;
static void far *requ = (void far *) 0;
static void (interrupt far *old_2f)();
void interrupt far int2f(REG_PARAMS r)
{
if (r.ax == 0x1684)
{
vxd_calls++;
requ = MK_FP(r.cs, r.ip);
vxd = r.bx;
}
chain_intr(old 2f);
}
void test_1684(void)
{
void interrupt far int2f(REG_PARAMS r);
old_2f = _dos_getvect(0x2F);
_dos_setvect(0x2f, int2f);
// можно такде перехватить INT 24h и т.д.
system(getenv(“COMSPEC”));
_dos_setvect(0x2F, old_2f);
if(vxd calls)
{
printf(“\nReceived %d calls 2F/1684 (Get VxD API)\n", vxd_calls);
printf(“Request VxD #%04Xh из %Fp\n”, vxd, requ);
}
}
Глава 4. Женитьба в Редмонде
121
В командной строке DOS, которая появляется после запуска FAKEWIN, можно наблюдать, как
DOS вызывает DOSMGR. Для этого, как показано на рис. 4.4, необходимо обратиться к дисководу
гибкого диска, что приведет к переключению DOS с одного логического дисковода на другой. После
выхода из DOS обратно в FAKEWIN, программа сообщит, что функция 1684И прерывания INT 2Fh
действительно вызывалась. Это, между прочим, является хорошим подтверждением рискованности
вызова фиктивных прерываний Windows INT 2Fh. Как показано в дизассемблированной части
внизу рис. 4.4, DOS вызывает каждую функцию с ненулевым указателем, который возвращается
функцией 1684И прерывания INT 2Fh.
Microsoft(R) MS-DOS Version 5.00
(C) Copyright Microsoft Corp 1981-1991
C:\UNAUTHW>dir b:\foo.*
Insert diskette for drive B: and press any key when ready
Volume in drive В is FAKEWIN
Volume Serial Number is 3239-1303
Directory of B:\
File not found
328.192 bytes free
C:\UNAUTHW>dir a:foo.*
Insert diskette for drive A: and press any key when ready
Volume in drive В is FAKEWIN
Volume Serial Number is 3239-1303
Directory of A:\
File not found
328.192 bytes free
C:\UNAUTHW>exit
Received 2 calls to 2F/1684 (Get VxD API)
Request VxD #0015h from 0070:08d1
C:\UNAUTHW>debug
-и 70:8d1 8f6
0070:08D1 57 PUSH DI
0070:08D2 06 PUSH ES
0070:0803 53 PUSH BX
0070:08D4 50 PUSH AX
0070:08D5 33FF XOR DI,DI
0070:08D7 8EC7 MOV ES.DI
0070:08D9 BB1500 MOV BX,0015
0070:08DC B88416 MOV AX, 1684
0070:08DF CD2F INT 2F
0070:08E1 8CC0 MOV AX,ES
0070:08E3 0BC7 OR AX,DI
0070:08E5 740B JZ 08F2
0070:08E7 0E PUSH CS
0070:08E8 B8F208 MOV AX.08F2
0070:08EB 50 PUSH AX
0070:08EC 06 PUSH ES
0070:08ED 57 PUSH DI
0070:08EE B80100 MOV AX,0001
0070:08F1 CB RETF
0070:08F2 58 POP AX
0070:08F3 5B POP BX
0070-.08F4 07 POP ES
0070:08F5 5F POP DI,
0070:08F6 CB RETF
; поместить адрес возврата
; поместить адрес DOSMGR API
; вызов SetFocus
; вызов DOSMGR API
; это адрес возврата
Рис. 4.4. Вывод FAKEWIN для TEST_1684 и дизассемблированный утилитой DEBUG код, показывающий, что DOS
вызывает DOSMGR
Перед выводом сообщения “Insert diskette for drive x:” (Вставьте дискету для дисковода х:),
показанного на рис. 4.4, IO.SYS (или компонент Ю модуля WINBOOT.SYS) проверяет внутренний
122
Неофициальная Windows 95
флаг IN_WIN3E. Если Windows работает в расширенном режиме, то DOS (см. код в нижней части
рис. 4.4) вызывает DOSMGR API для активизации VM, чтобы пользователь увидел сообщение даже
тогда, когда сеанс DOS находится в фоновом режиме и невидим.
Поначалу может показаться странным рассматривать запросы DOS к Windows, но это еще
одно подтверждение тому, что старое высказывание “Windows является надстройкой над DOS”
было несколько неточным, даже применительно к конфигурации Windows в расширенном режиме с
DOS 5 или 6. В главах 9 и 10 будет показано, что в этой конфигурации DOS даже не работает в
реальном режиме. А в 8-й главе мы будем использовать точки останова V86, чтобы показать, что
DOS действительно частенько обращается к Windows VxD. В некотором смысле можно даже ска-
зать, что DOS выполняется поверх Windows!
Режим V86
Снова возвращаясь к выводу программы FAKEWIN, приведенному на рис. 4.1, мы видим, что
менеджер памяти 386 (в нашем случае, 386МАХ) передал Windows (а скорее, FAKEWIN. Просто
386МАХ подумал, что это Windows!) указатель на функцию включения/выключения V86:
V86 Enable/Disable function: 0329:03D4 (386МАХ$$)
При запуске менеджера расширенной памяти, например, такого как 386МАХ, QEMM или
EMM386, ваш компьютер переводится в режим V86. Режим V86 — это фактически 1-Мбайтовый
вариант защищенного режима, управляемый монитором V86 (он же VMM), который может выпол-
нять программы для реального режима. Так же как расширенный режим Windows имеет VMM для
управления сеансами DOS, так и 386-е менеджеры расширенной памяти в действительности
являются VMM, выполняющими только одну задачу DOS. (VMM — термин, предложенный фир-
мой Intel для управляющей программы V86.) Из-за широкого использования менеджеров расширен-
ной памяти, реальный режим в конце концов умрет. (Я имею ввиду реальный режим процессора
Intel 80x86. Реальный режим Windows 3.0 умер уже давным-давно.) Если вы„?апустите программу
РЕ, приведенную в главе 9, из командной строки DOS, то вероятность получить сообщение о том,
что бит защищенного режима (РЕ, Protect Enable) установлен, достаточно велика. Вы можете ду-
мать, что находитесь в реальном режиме, но, на самом деле, это режим V86, который, не устану
повторять, является формой защищенного режима.
К сожалению, расширенный режим не будет запускаться, если на компьютере работает другой
VMM в режиме V86. (Такое было бы возможным, если бы фирма Microsoft разрабатывала WIN386
в соответствии со стандартом VCPI [Virtual Control Program Interface]. В стандартном режиме
Windows 3.1 соответствует VCPI, но стандартный режим и расширенный режим — совершенно
разные вещи.) Windows хочет монопольно управлять всей кучей. Так что 386-й менеджер памяти
должен либо сам выключать режим V86 при запуске Windows, либо передавать WIN386 указатель
функции включения/выключения V86. WIN386 (или WMM32 в Windows 95) вызывает функцию
отключения режима V86 перед переходом в защищенный режим.
Импорт глобальной ЕММ
Существует только одна проблема со схемой, согласно которой менеджеры памяти предостав-
ляют возможность их включения и выключения. Если какой-нибудь блок памяти был выделен
менеджером памяти, он становится недоступным при запуске Windows. В частности, если какие-то
драйверы DOS или TSR загружаются в верхние адреса памяти (UMB), они моментально становятся
невидимыми. Это, вероятнее всего, приведет к краху системы. Таким образом, администраторам
памяти необходимо предоставить механизм передачи управления таблицами страниц в Windows. Это
и есть главная цель импорта глобальной ЕММ, о котором упоминается на рис. 4.1.
Global EMM Import @ phys 00BD9EACh (version 1.11)
Глава 4. Женитьба в Редмонде
123
Термин “глобальная” обосновывается тем, что ЕММ присутствует до того, как запустилась
Windows. Импорт глобальной ЕММ (также называемый страничным импортом V86MMGR) — это
недокументированный интерфейс, хотя фирма Microsoft кратко упоминает о нем в документации
DDK по _AddFreePhysPage и V86MMGR_GetPgStatus. Согласно этой документации:
“Windows/386 поддерживает импортирование текущего состояния дескрипторов ЕММ из 386 LIMulator
[т.е. эмулятора спецификации управления расширенной памятью, разработанной фирмами Lotus, Intel и
Microsoft], который работал при загрузке Windows/386. Это дает возможность программам продолжать
использовать распределенные ранее блоки ЕММ. Имеется также возможность импорта информации о
дескрипторах верхних блоков памяти (UMB) и XMS-памяти от текущего драйвера XMS.
ЗАМЕЧАНИЕ: должен быть только один источник импорта. Даже если используется два различных
драйвера управления памятью (XMS и EMS), импорт не должен идти от двух драйверов сразу, так как
страничной организацией памяти управляет только один из них.
Вызов DOS IOCTL, при котором устройство V86MMGR получает указатель на структуру данных
импорта, производится при разрешенных прерываниях. После этого вызова вполне возможно, что
какая-то программа “проснется” и изменит состояние ЕММ или будет манипулировать дескрипторами
XMS или ЕММ. По этой причине структура данных импорта не может быть заполнена во время вызова
IOCTL, потому что она может измениться прежде, чем Windows/386 перейдет в защищенный режим.
Поэтому “сигналом” для заполнения структуры является не вызов функции IOCTL, а вызов Disable
виртуального режима.
При работе WIN386 вся деятельность ЕММ становится ЛОКАЛЬНОЙ по отношению к текущей VM, а
не ГЛОБАЛЬНОЙ. Пользователь глобальной ЕММ должен будет изменить свое поведение, чтобы пра-
вильно работать в такой ситуации. В частности, любые вызовы, связанные с отображением памяти или
сохранением контекста, функционируют относительно текущей VM и не имеют никакого значения для
других VM. ВЫДЕЛЕНИЕ блока пользователем глобальной ЕММ при работе WIN386 небезопасно,
поскольку распределенный блок будет ЛОКАЛЬНЫМ. По этой причине пользователи глобальной
ЕММ должны РАСПРЕДЕЛЯТЬ ВСЕ свои блоки памяти с правильными размерами во время
инициализации. ”
Функция check_emm_import в FAKEWIN (см. листинг 3.1) моделирует только наиболее общую
часть импорта ЕММ. Это делается посредством открытия устройства ЕММХХХХО и выполнения
функции чтения IOCTL. Для получения содержимого структуры необходимо вызвать функцию
switch_func для выключения и включения менеджера памяти, а также функцию 87h прерывания
INT 15 (Block Move) для копирования содержимого структуры в обычную память.
Интерфейс оповещений DOSMGR
Следующим на рис. 4.1 представлен блок, относящийся к интерфейсу, обеспечиваемому VxD
DOSMGR. Так же как Windows генерирует оповещения (broadcasts) через функцию 1605b преры-
вания INT 2Fh, VxD могут посылать свои собственные оповещения посредством вызова функции
1607b прерывания INT 2Fh. Этот интерфейс VxD используют для обращения к программному обес-
печению, загруженному перед Windows, и его не нужно путать с функцией 1684b прерывания INT
2Fh, которую программы DOS и Windows используют для обращения к VxD.
Как обычно, механизм интерфейса описан в DDK, — VxD вызывает INT 2Fh со значением
1607b в регистре АХ, в регистре ВХ содержится VxD ID (например, 15b для DOSMGR), значения
других регистров зависят от конкретного VxD, — но конкретно об интерфейсе оповещений Windows
там ничего не сказано. Это серьезное упущение, поскольку некоторые из этих интерфейсов VxD,
вроде тех, что WSHELL обеспечивает для WinOldAp, очень важны.
На листинге 4.3 показано, как FAKEWIN эмулирует часть интерфейса оповещений DOSMGR
для проверки того, какая программа, загруженная до Windows, перехватывает функцию 1607h
прерывания INT 2Fh с BX=15h для связи с DOSMGR. Вывод FAKEWIN на рис. 4.1 показывает,
что этот вызов перехватывает сама MS DOS. Она использует этот интерфейс для предоставления
DOSMGR адреса нескольких переменных DOS. На этот раз это не экземплярные данные, а
переменные, которые будут изменяться самим DOSMGR. Особенно важно, что среди всего прочего
DOS передает DOSMGR адрес переменной USER_ID (размещенной на рис. 4.1 по адресу
124
Неофициальная Windows 95
00А0:033Е), так что DOSMGR может устанавливать в ней идентификатор VM. Как было отмечено
раньше, эта переменная используется DOS при файловом вводе-выводе.
Интерфейс оповещений DOSMGR был описан в неопубликованной документации “API to
Identify MS-DOS Instance Data” фирмы Microsoft. Даже без этой документации довольно просто
выяснить интерфейс с помощью дизассемблирования обеих частей кода: вызова функции 1607h в
DOSMGR и перехватчика функции 1607h в MSDOS.
ЛИСТИНГ 4.3. FAKEVXD.C
/*
FAKEVXD.C
Шульман, 1994
*/
«include <stdlib.h>
«include <stdio.h>
typedef unsigned short WORD;
«define VXD_CALLOUT(vxd_id) \
{ \
_asm { mov bx, vxd_id } ; \
_asm { mov ax, 1607h; } ; \
_asm { int 2fh; } ; \
}
«define DOSMGR_CALLOUT(func) \
{ \
_asm { mov ex, func } ; \
VXD_CALL0UT(0x15); \
}
«define NUM_PATCH 6
// имена из документации “API to Identify MS-DOS Instance Data" фирмы Microsoft
char *patch_str[] = {
“SAVEDS”, “SAVEBX”, “INDOS”, “USER_ID", “CRITPATCH”, “UMB_HEAD”,
};
void fake_dosmgr_callouts(void)
{
WORD w, w2, w3;
WORD far ‘patchtab;
WORD far ‘patch;
int i;
DOSMGR_CALLOUT(O); // запрос на обработку экземпляра
_asm mov w, ex
if(w != 0)
{
_asm mov w, dx
_asm mov word ptr patchtab+2, es
_asm mov word ptr patchtab, bx
printf(“\nDOSMGR instance interface ON\n“);
if(w != 0)
printf(“Segment of DOS drivers: %04X\n", w); // обычно 0
printf(“Patch table: %Fp\n", patchtab);
printf(“DOS version %u.%02u\n”,
(patchtab[0] & OxFF), (patchtab[0] » 8));
for(i=0, patch = &patchtab[1]; i<NUM_PATCH; i++, patch++)
printf(“%04X (%s)\n”, ‘patch, patch_str[i]);
// вероятно, небезопасно вызывать DOSMGR_CALLOUT(1), если только
// за ним не следует вызов D0SMGR_CALL0UT(2)
_asm mov dx, 1
D0SMGR_CALL0UT(3); // получить размер структуры данных DOS
_asm mov w, ax
_asm mov w2, ex
Глава 4. Женитьба в Редмонде
125
asm mov w3, dx
if((w != 0xB97C) && (w3 != 0xa2ab))
printf(“DOSMGR callout 3 failed: signature wrong!\n”); // AX:DX
else
printf(“ Current Directory Structure = %u байт\п”, w2); // CX
DOSMGR_CALLOUT(4); // определить созданные экземпляры структур данных
_asm mov w, ax
_asm mov w2, bx
_asm mov w3, dx
if((w3 == 0) || (w2 == 0))
printf(“No DOS data structures instanced via DOSMGR API\n");
else if((w != 0xB97C) && (w3 != 0xa2ab))
printf(“DOSMGR callout 4 failed: signature wrong!\n’’); // AX:DX
else
{
printf(“DOS data structures instanced: %04X\n”, w2);
if(w2 & 1) printf (“CDS\n");
if(w2 & 2) printf (“SFT\n");
if(w2 & 4) printf (“Device chain\n");
if(w2 & 8) printf (“SDA\n”);
}
}
else
printf(“\nNo DOSMGR instance interface\n”);
Интерфейс DOSMGR был разработан таким образом, чтобы не использовать знания о внутрен-
ней организации DOS. Его API оповещений может запросить DOS предоставить всю необходимую
информацию. Однако интерфейс не полон и в действительности не разграничивает DOS и Windows.
Раз этот интерфейс остался скрытым, то он представляет еще один аспект скрытых взаимодействий
между DOS и Windows. В любом случае, интерфейс является еще одной иллюстрацией того, как
MS DOS и Windows формируют единое целое.
Интересно, что, в то время как DR DOS 6.0 не поддерживает DOSMGR API (FAKEWIN выво-
дит “No DOSMGR instance interface”), Novell DOS 7.0 предоставляет DOSMGR по крайней мере
таблицу изменяемых переменных.
Функция идентификации TSR
В конце рис. 4.1 показано, что FAKEWIN обнаружила нечто, называемое TSR_Info_Struc, при-
надлежащее COUNTDOS. FAKEWIN обнаружила эту структуру с помощью перехвата INT 2Fh
AX=160Bh, функции Windows идентификации TSR. На листинге 4.4 показан модуль FAKETSR.C.
ЛИСТИНГ 4.4. FAKETSR.C
/*
FAKETSR.C — для FAKEWIN
Шульман, 1994
См. статью Дэвида Лонга на MSDN^CD no 2F/160B.
*/
«include <stdlib.h>
«include <stdio.h>
«include <dos.h>
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef void far *FP;
«pragma pack(1)
typedef struct {
WORD size;
BYTE str[1];
} TSR_ID_Block_Struc;
126
Неофициальная Windows 95
typedef struct .TSRINFO {
struct _TSRINFO far *TSR_Next;
WORD TSR_PSP Segment;
WORD TSR_APlZVer_ID; /* 0x100 */
WORD TSR_Exec_Flags;
WORD TSR_Exec_Cmd_Show;
char far *TSR_Exec_Cmd;
DWORD TSR_Reserved;
TSR_ID_Block_Struc far *TSR_ID_Block;
FP TSR_Data_Block;
} TSR_Info_Struc;
«pragma pack()
// Значения TSR Exec_Flag
«define TSR WINEXEC 1
«define TSR LOADLIBRARY 2
«define TSR_OPENDRIVER 4
// Значения TSR_Exec_Cmd Show
«define SW HIDE “ 0
«define SW SHOWNORMAL 1
«define SW^SHOWMINIMIZED 2
«define SW SHOWMAXIMIZED 3
«define SW_SHOWNOACTIVATE 4
«define SW SHOW 5
«define SW_MINIMIZE 6
«define SW SHOWMINNOACTIVE 7
«define SW SHOWNA 8
«define SW.RESTORE 9
char *sw str[SW_REST0RE+1] = {
“SW HIDE", “SW_SHOWNORMAL", “SW SHOWMINIMIZED”, “SW.SHOWMAXIMIZED",
“SW-SHOWNOACTIVATE", “SW SHOW",-“SW_MINIMIZE”, “SW.SHOWMINNOACTIVE",
“SW_SHOWNA", “SW RESTORE"
};
// в FAKEWIN.C
extern char «owner(FP fp);
void print tsr_info(TSR Info_Struc far *tsr_info)
{
if(tsr_info->TSR_API_Ver_ID < 0x100)
printf(“%Fp not a valid TSR structure!\n”, tsr_info);
printf(“\nTSR_Info_Struc no %Fp (PSP %04Xh) %S\n",
tsr_info, tsr_info->TSR_PSP_Segment, owner(tsr_info));
if(tsr_info->TSR_Exec_Cmd)
{
switch(tsr_info->TSR Exec_Flags)
{
case TSR.WINEXEC:
printf(“TSR_WINEXEC“);
// Exec_Cmd_Show используется только для TSR_WINEXEC
if(tsr_info->TSR_Exec_Cmd_Show <= SW_RESTORE)
printf(“%s“, sw_str[tsr_info->TSR_Exec_Cmd_Show]);
else
printf(“TSR_Exec_Cmd_Show: %04Xh“,
, tsr_info->TSR_Exec_Cmd_Show);
reek'
case TSR_LOAd’lIBRARY: printf(“TSR_LOADLIBRARY“); break;
case TSR_OPENDRIVER: printf(“TSR_OPENDRIVER“); break;
default: printf(“TSR_Exec_Flags %04Xh“,
tsr info->TSR Exec Flags);
} -
printf(“\n\”%Fs\"\n”, tsr info->TSR Exec_Cmd);
} " " .
if(tsr_info->TSR_ID_Block)
printf(“TSR_ID_Block: \”%Fs\"\n", tsr_info->TSR_ID_Block->str);
if(tsr_info->TSR_Data_Block)
printf(“TSR_Data_Block: %Fp\n”, tsr_info->TSR_Data_Block);
void tsr_identify(void)
Глава 4. Женитьба в Редмонде
127
{
TSR_Info_Struc far *tsr_info;
_asiii push di
_asm xor ex, ex
_asm mov es, ex
_asm mov di, ex
_asm mov ax, 160bh
_asm int 2Fh
_asm mov word ptr tsr_info+2, es
_asm mov word ptr tsr_info, di
_asm pop di
if(tsr_info != 0)
{
do {
print_tsr_info(tsr_info);
tsr_info = tsr_info->TSR_Next;
} while(tsr_info != 0);
}
}
Равно как DOS-программы могут перехватывать функцию 1605h для запроса у Windows загру-
зить VxD, они могут перехватывать 160ВИ для запроса у модуля USER загрузить исполняемый
файл Windows, динамически компонуемую библиотеку (DLL) или драйвер Winl6. Модуль USER
обслуживает вызов функции 160Bh при выполнении InitApp (см. Matt Pietrek, Windows Internal,
p. 279-281) и, в зависимости от запроса, вызывает функцию API WinExec, LoadLibrary или
OpenDriver.
Функция идентификации TSR описана в статье Дэвида Лонга “TSR Support in Microsoft
Windows Version 3.1” на компакт-диске Microsoft Developer Network. Эта документация сопро-
вождается примером программы, называемой COUNTDOS. Результаты работы перехватчика функ-
ции 160ВЬ программы COUNTDOS можно наблюдать в выводе FAKEWIN на рис. 4.1. Согласно
документации Microsoft, функция 160Bh также используется программным обеспечением работы с
факсом и утилитой Windows поддержки сети.
COUNTDOS представляет собой интересный пример взаимодействия DOS и Windows.
COUNTDOS.EXE — это исполняемый файл Windows со встроенной программой DOS, которая
загружается перед Windows. DOS-программа перехватывает INT 21h и хранит протокол всех
вызовов DOS. Она также может требовать от пользователя подтверждения на удаление файла. Как
показано в конце рис. 4.1, DOS-часть программы COUNTDOS.EXE перехватывает функцию 160ВЬ
так, что при запуске Windows модуль USER выполняет следующее:
WinExec(“C:\UNAUTHW\FAKEW1N\COUNTDOS.EXE /085Е:OODD", SW_SHOWNOACTIVATE):
Windows-часть программы COUNTDOS использует вызовы DPMI для общения с ее DOS-
частью, которая загружается до Windows. Подобно программе V86TEST в главе 10, COUNTDOS
отображает количество вызовов INT 21h. Эта информация очень полезна для выяснения, в какой
мере Windows отграничивает себя от реального режима DOS или не отграничивает.
Было бы интересно узнать, насколько интенсивно используется DOS в Windows с 32-битовым
доступом к файлам и без него. На рис. 4.5 показана работа COUNTDOS в WfW 3.11 без 32-бито-
вого доступа к файлам (WIN /D:C). С загруженной программой COUNTDOS я запускал Microsoft
Office, Microsoft Word, производил различные другие действия. Обратите внимание на вызовы
функции 3Dh DOS (открытие файла), ЗЕЬ (закрытие файла), 3Fh (чтение файла) и 40b (запись
файла) — Windows использует DOS для операций файлового ввода-вывода.
Затем я перезапустил WfW 3.11 с разрешенным 32-рязрядным файловым доступом и выполнил
точно тот же самый набор операций. На рис. 4.6 показаны результаты работы COUNTDOS.
Обратите внимание, что среди множества вызовов DOS нет ни одного обращения к стандартным
функциям открытия, закрытия, чтения и записи файлов, а есть всего лишь несколько вызовов
IOCTL вроде получить/установить дату/время файла.
128
Неофициальная Windows 95
Misi.lfliei
РигДммО)-
Рис. 4.5. Выполнение программы COUNTDOS без 32-битового файлового доступа
обнаруживает, что Windows опирается па DOS
С*яи<11>09 TSfA»W.A,.p
Rpfreshl Re ptl Settings H«ip
| Last Update: 1324:59
Ot Z3Z IX Select
19 464 2X Get Current Disk
IA 21 OX Set DTA Address
2A 62 ox Get date
2C IB77O 85X Get time
38 В OX Change Current Directory
3C 11 OX Create Fie wtth Handte
3D Bl OX Open FMe wtth Handte
3E 120 ox Close Fie wtth Handte
3F 520 2X Read Fie or Device
40 65 OX Write Fie or Device
41 15 OX Delete Fie
42 387 IX Move He pointer
43 IB OX GeVSet Fie Attrtbutes
44 425 IX IOCTL
45 5 ox Duplicate Fie Handte
47 25 ox Get Current Directory
4C 1 ox Terminate wtth Ret Code
41 20 ox Find First Fie
41 14 ox Find Next Fie
50 380 IX Set PSP Segment 1
57 76 ox GeVSet DateTDme Fie
58 6 ox Create New Fie
5C 142 ox LocWUnlock Fie Region
62 150 ox Get PSP Address
Рис. 4.6. Выполнение программы COUNTDOS с 32-битовым файловым доступом показывает,
что WfW 3.11 может выполнять мши нс операции, минуя DOS
Глава 4. Женитьба в Редмонде
129
5 Неофициальная Windows 95
И наконец, я попробовал запустить COUNTDOS в Windows 95. Я поместил строку TNSTAT.T -
COUNTDOS.EXE в CONFIG.SYS, что избавило от необходимости создавать файл AUTOEXEC.
ВАТ. После того как запустилась Windows 95, я сбросил статистику COUNTDOS, чтобы не
показывались вызовы DOS, прошедшие при запуске Windows. (То же самое я делал при тести-
ровании WfW 3.11). Если бы Windows 95 не использовала DOS, программа COUNTDOS не зареги-
стрировала бы никаких вызовов DOS. Результаты, приведенные на рис. 4.7, впечатляют (литпъ
очень немногие вызовы INT 21h доходят до самой DOS), но не так, чтобы очень. Особенно потому,
что кроме самого COUNTDOS выполнялись только прикладные программы Win32: оболочка
Windows 95, версия Win32 программы Clock и WinBezMT. COUNTDOS показывает, что даже при-
кладные программы Win32 в Windows 95 по-прежнему используют реальный режим DOS для вы-
полнения некоторых операций типа получения даты и времени, а также создания и установки PSP.
Рис. 4.7 —иллюстрация фактически для всей этой книги. Здесь мы видим большое количество
прикладных программ Win32 и поверх них — небольшую программу от самой фирмы Microsoft,
напоминающую нам, что DOS еще не умерла.
Рис. 4.7. Выполняемая в Windows 95 программа COUNTDOS показывает, что даже прикладные программы
Win32 все еще нуждаются в DOS для выполнения некоторых операций вроде получения даты и времени, а
также создания и установки PSP
Имеется еще одно интересное замечание по COUNTDOS. Эта программа имеет хитрую опцию
Confirm Delete (Подтверждение удаления). Когда эта опция установлена, обработчик прерывания
INT 21h программы COUNTDOS контролирует вызов функции 41h (удаление файла) и сообщает
Windows-части программы о необходимости вывода диалогового окна для подтверждения удаления
файла. Это прекрасно работает в Windows 3.1 и является хорошим примером совместной работы
программ DOS и Windows (встроенных в один исполняемый файл).
130
Неофициальная Windows 95
Но когда разрешен 32-битовый файловый доступ в WfW 3.11 или Windows 95, опция Confirm
Deletes программы COUNTDOS не дает никакого эффекта. И понятна почему: если разрешить 32-
битовый файловый доступ, COUNTDOS не увидит ни одного вызова функции 41h (удаление
файла), потому что все они будут обрабатываться VxD в 32-битовом защищенном режиме.
Это хороший пример того, что обход DOS, может иметь серьезные отрицательные побочные
эффекты. Совершенно легальная программа DOS-Windows, предназначенная для обучения про-
граммистов использованию интерфейса операционной системы Microsoft, была испорчена системой
32-битового файлового доступа.
Итак, нет ничего лучше бесплатного обеда. Но было бы еще лучше, если бы фирма Microsoft
время от времени все-таки сообщала нам цены на еду.
Глава 4. Женитьба в Редмонде
131
Глава 5
Два лика WINDOWS
Что же будет с программами под MS DOS? Этот вопрос был поставлен в “Chicago Questions
and Answers” — официальной статье Microsoft (январь, 1994 г.). Ответ, кажется, предпо-
лагает радикальную перестройку этой древней операционной системы реального режима:
I Microsoft будет продолжать улучшать MS DOS до тех пор, пока на нее не пропадет спрос. Будущие
версии будут сделаны по технологии защищенного режима, разработанной в проекте Chicago.
Что это означает? Как из программного обеспечения защищенного режима можно создать новые
версии DOS? Можно ли DOS защищенного режима считать DOS? Возможна ли вообще MS DOS
защищенного режима (т.е. сделанная по технологии защищенного режима)?
В этой главе вы убедитесь, что DOS защищенного режима уже существует (и реально существо-
вала в течение ряда лет) как часть Windows.
Внутреннее ядро WINDOWS
Если вы попросите типичного Windows-программиста перечислить основные ее компоненты,
скорее всего он отбарабанит: KERNEL, USER и GDI. Это названия библиотек динамической компо-
новки Windows (DLL), которые содержат необходимый набор функциональных возможностей
Windows, используемых при написании Windows-приложений. Внутри этих библиотек находятся
такие функции, как GlobalAlloc, CreateWindow и TextOut.
Эти библиотеки, конечно, важны, но не они формируют ядро Windows. Читая эту главу, вы пой-
мете, что видимая часть GUI — то, что многие собственно и воспринимают как Windows — явля-
ется не более чем приложением, которое работает на самом верху операционной системы Windows.
Файлы KERNEL, USER и GDI не содержат операционной системы Windows. Она в основном
находится в файле с именем WIN386.EXE или, для Windows 95, в VMM32.VXD. Когда-то Micro-
soft называла этот файл DOS386.EXE, что показывает, как трудно понять, является ли эта програм-
ма частью Windows или частью DOS. К концу этой главы вы придете к выводу, что WIN386.EXE —
это часть будущей версии DOS или по крайней мере часть расширенного режима MS DOS, которая,
то ли случайно, то ли нет, уже несколько лет распространяется вместе с Windows.
Для запуска Windows вы выполняете программу WIN.COM, которая, как указано в главе 2,
в свою очередь запускает Другую программу.
> В стандартном режиме (WIN /S) WIN.COM запускает WSWAP.EXE, выполняющую
DOSX.EXE, которая, в свою очередь, выполняет либо KRNL286.EXE (на 286-х процес-
сорах), либо KRNL386.EXE (на процессорах 386 и выше).
> В расширенном режиме (WIN /3) WIN.COM запускает WIN386.EXE, который представ-
ляет набор VxD. VxD WSHELL внутри WIN386 запускает KRNL386.EXE.
> В Windows 95 'VIN.COM запускает VMM32.VXD (набор VxD), VxD WSHELL которой
запускает ядро Winl6, KRNL386.EXE.
132
Неофициальная Windows 95
Так что же это за программы DOSX, WIN386 и VMM32? Каким образом они соотносятся с
внешней (видимой) частью Windows? В этой и двух последующих главах мы будем запускать
DOSX, WIN386 и VMM32 нестандартными способами, чтобы выяснить, что же эти программы
делают для Windows.
Расширители DOS и будущее DOS
Хотя в этой книге основное внимание уделено WIN386.EXE и VMM32.VXD, простейший способ
понять, как устроены эти две программы — заглянуть сначала в DOSX.EXE, на котором базируется
стандартный режим Windows 3.x для 286-х процессоров (стандартный режим исчез в Windows for
Workgroups 3.11 так же, как реальный режим исчез в Windows 3.1).
WIN /S ищет DOSX.EXE и WSWAP.EXE и затем запускает WSWAP.EXE. WSWAP и другой
исполняемый модуль, DSWAP — это всего лишь переключатели задач, заимствованные из
DOSSHELL.EXE, оболочки DOS 5. В свою очередь, DOSSHELL.EXE была переключателем задач
стандартного режима Windows 3.0; ее просто “выдрали и бултыхнули в DOS 5.0” (Ray Duncan,
“Programming consideration MS-DOS”, PC Magazine, November 12, 1991). WSWAP запускает
DOSX, a DSWAP запускает другие программы DOS.
Само название DOSX.EXE говорит, что это расширитель (extender) DOS. Это означает, что
DOSX обеспечивает интерфейс DOS защищенного режима. Благодаря DOSX, приложения защи-
щенного режима в стандартном режиме Windows могут вызывать DOS прерывания INT 21h для до-
ступа к файлам, распределения памяти, настройки векторов прерываний и т.д., несмотря на то, что
мы представляем DOS операционной системой реального режима. Механизм обработки таких вызо-
вов показан на рис. 5.1. В расширенном режиме Windows расширитель DOS находится в DOSMGR
VxD, размещенном внутри WIN386.EXE. Когда в WfW 3.11 и Windows 95 разрешен 32-битовый
доступ к файлам, VxD IFSMGR.386 можно также рассматривать как часть расширителя DOS.
Препытп iw омочсг * чч» pc. ahwtwih DOS
•жремй'-р’’»''* ’ INI ?1h »ч:
в р.-ольныи о*'»» <, с чоа орме
Рис. 5.1. Расширитель DOS позволяет приложениям защищенного режима (таким, как программы Windows) вызы-
вать INT 21Ь, несмотря на то, что MS DOS не является операционной системой защищенного режима. Расширитель
DOS обычно делает это путем перевода (или отражения) вызова INT 21Ь защищенного режима в реальный или V86
Глава 5. Два лика Windows
133
Что же .делает расширитель DOS? В качестве примера рассмотрим программу, которая
обращается к функции DOS с номером 3Dh, чтобы открыть файл с именем по дальнему указателю,
содержащемуся в регистрах DS:DX. В программе реального режима DS:DX — это указатель
реального режима, который DOS без труда понимает. В случае же с программой защищенного ре-
жима, такой как Windows-приложение, при вызове функции 3Dh прерывания INT 21h (непосредст-
венно или через функцию Windows API вроде OpenFile или DOS3CALL) дальний указатель в
регистрах DS:DX будет указателем защищенного режима, т.е. DS будет содержать селектор, а не
адрес параграфа. Более того, селектор, вероятнее всего, будет иметь базовый адрес в дополнитель-
ной памяти, за пределами 1 Мбайта.
Код реального режима DOS для открытия файла не может правильно интерпретировать указа-
тели защищенного режима, так же как не может обращаться к памяти за пределами 1 Мбайта. Если
Windows-приложение или другая программа защищенного режима передаст DOS указатель защи-
щенного режима, DOS воспримет его как неправильный.
Вот тут-то на помощь и приходит расширитель DOS вроде DOSX.EXE. Расширитель DOS
перехватывает прерывание INT 21h в защищенном режиме и обеспечивает правильную обработку
всех известных функций DOS. Реализация функции 3Dh в расширителе DOS, естественно, должна
ожидать при вызове в DS:DX указатель защищенного режима и делать все необходимое для откры-
тия файла и возвращения дескриптора файла приложению защищенного режима. По существу,
расширитель DOS превращает DOS в операционную систему защищенного режима.
Расширитель DOS может выполнять функции прерывания INT 21h защищенного режима лю-
бым доступным способом. Он может транслировать вызов в реальный режим и передать его DOS
рли же (это решающий момент, на который часто не обращают внимания) он может обслужить
вызов полностью в защищенном режиме, без обращения к DOS.
Примером второго варианта выполнения функций прерывания INT 21h может служить функция
распределения памяти DOS (функция 48h прерывания INT 21h). Программа защищенного режима,
вызывающая эту функцию (или вызывающая функцию malloc в языке С или C++, которая, в свою
очередь, обращается к функции 48h), естественно захочет, чтобы DOS (или что-то еще, обеспечива-
ющее интерфейс прерывания INT 21h в защищенном режиме) возвратила селектор блока памяти
защищенного режима. Чтобы обеспечить настоящий интерфейс защищенного режима DOS, функция
должна сохранить старую семантику обычной DOS: т.е. чтобы полученный селектор можно было бы
сразу использовать в указателе без необходимости вызывать какую-то еще функцию:
unsigned short para, sei;
char far *fp;
//... _asm mov ah, 48h _asm mov bx, [para] _asm int 21h _asm jc error _asm mov [sei], ax fp = MK_FP(sel, 0); »fp = ' x’; ; Функция выделения памяти ; количество параграфов ; вызов DOS ; запомнить селектор // создать по селектору дальний указатель // использовать его
Расширитель DOS мог бы выполнить функцию 48h, обратившись к DOS реального режима, и
затем перевести адрес параграфа реального режима, возвращенный DOS, в селектор защищенного
режима. Но приложению защищенного режима практически никогда не нужно, чтобы селектор ука-
зывал на стандартную память ниже 1 Мбайта. Программа защищенного режима хочет выбраться на
просторы дополнительной памяти выше предела 1 Мбайта. В конце концов, это и есть причина
перехода к защищенному режиму. Таким образом, расширитель DOS, который предоставляет
приложениям защищенного режима функцию распределения памяти, не должен передавать этот
запрос DOS. Он должен сам обработать такой запрос в защищенном режиме, чтобы эта функция
стала DOS-интерфейсом доступа к расширенной памяти.
Теперь, если расширитель DOS будет обрабатывать каждую функцию прерывания INT 21h, у
нас получится полностью новая операционная система защищенного режима с интерфейсом, анало-
гичным INT ^ih. Фактически, расширителю DOS нет никакой нужды в подгружаемой копии DOS,
за исключением, быть может, использования ее как удобной программы начальной загрузки.
134
Неофициальная Windows 95
Расширитель DOS мог бы даже расширить интерфейс INT 21h до 32-битового (например, функ-
ция 48h ожидала бы число параграфов в 32-битовом регистре ЕВХ вместо 16-битового регистра ВХ
или функция 3Dh получала бы указатель на имя файла в регистре EDX). Он мог бы также
предоставить новые возможности INT 21h (например, поддержка длинных имен файлов), чего DOS
реального режима не поддерживает. Вот это, как раз таки, Microsoft и делает с DOS в Windows 95.
Заметьте, что даже если расширитель DOS не проводит политику “все в защищенном режиме”
и продолжает перенаправлять некоторые вызовы в DOS реального режима, это не меняет его сущ-
ности как операционной системы защищенного режима. Решение относительно того, перенаправлять
ли обработку прерывания INT 21h в реальный режим DOS или обрабатывать его в защищенном
режиме полностью, принимает расширитель DOS. Сама DOS находится в состоянии подчинения,
делая только то, что расширитель не желает сделать самостоятельно. Вместо того, чтобы рассмат-
ривать расширитель DOS как какую-то надстройку над DOS, будет точнее рассматривать DOS как
не более чем 16-битовый драйвер реального режима, используемый расширителем DOS.
В частности, это справедливо в том случае, когда расширители DOS выполняют DOS в V86,
поскольку, как вы увидите в главах 9 и 10, тогда расширитель DOS может контролировать все, что
делается внутри DOS, ее драйверов и TSR. Это означает, что У86-расширитель DOS может предо-
ставить интерфейс прерывания INT 21h защищенного режима также и для программ реального ре-
жима. Функции DOS, вызываемые программами реального режима, будут нормально выполняться
и в защищенном режиме. Однако я немного забегаю вперед.
Теперь, со свежими впечатлениями о возможностях технологии использования расширителей
DOS и предположениями о будущем интерфейса DOS, давайте вернемся к DOSX.EXE.
Расширитель DOS для стандартного режима Windows основывался на расширителе, который
Microsoft использовала раньше, в отладчике CodeView. В свою очередь, этот расширитель DOS
создавался на основе отладчика SST, написанного Мюрреем Саржентом (Murray Sargent). В книге
Gates, превосходной биографии Билла Гейтса, написанной Стефаном Мэйнсом (Stephen Manes) и
Полом Эндрюсом (Paul Andrews), хорошо освещено, как Мюррей Саржент и Дэвид Вейс исполь-
зовали расширитель DOS, написанный Мюрреем, для перевода Windows в защищенный режим.
Большая часть этой работы до сих пор жива в DOSX.EXE:
В 1988 году, как только создался костяк группы Windows, “сумасшедшая собака” Стив Баллмер (Stive
Ballmer) начал тормозить ее работу, выдергивая людей для работы над OS/2. Windows версии 2 была
мертворожденной, но группа Windows все же продолжала работу. Медленно, но уверенно росла кол-
лекция приложений Windows. Однако, как точно будет выглядеть следующая версия Windows, и будет
ли она вообще, оставалось неясным.
Тогда же в июне Дэвид Вейс (David Weise), один из “смышленых малых” из группы Исследований
динамических систем, заглянул к своему старому другу..., Мюррею Сарженту, “всемирно знаменитому
специалисту по лазерам и любителю ковыряться в компьютерах”. Профессор физики из Аризонского
университета, Саржент тем летом занимался адаптацией отладчика Microsoft CodeView к программе1,
известной тогда как “расширение DOS”, которая позволяла бы специально написанным программам
использовать расширенную память на 286-х и 386-х компьютерах. Недавно он добавил поддержку Win-
dows и расширитель DOS к своему собственному отладчику SST, который пришелся по душе Вейсу.
С точки зрения Вейса, перед Windows всегда стояли три большие проблемы: “память, память, память”.
Рано или поздно, но проблема нехватки памяти всегда возникнет, и поэтому нужно искать любые комп-
ромиссы ...
Итак, Вейс привел Саржента к себе в офис, запустил новый отладчик и начал строка за строкой
просматривать Windows, чтобы заставить ее работать в защищенном режиме, т.е. работая со всей до-
полнительной памятью. “Мы не собираемся спрашивать ни у кого разрешения, а если у нас что-то
получится и его зарубят, то так и будет”. Вейс продолжил работу над этим дома, потом три месяца
возился по ночам и в выходные дни в офисе. И вот хорошая новость: “Стив Вуд (Stive Wood), у ко-
торого я позаимствовал ядро, проанализировав всю программу, сказал, что она будет работать в защи-
щенном режиме...”.
1 В оригинале использовано слово “kludge”, которое означает программу или часть программы, которые теоретически
не должны работать, ио почему-то работают. — Прим. ред.
Глава 5. Два лика Windows
135
“Всюду [в процессе перевода Windows в защищенный режим] какие-то ошметки, но в основном вы так
и работаете: закрываете глаза, и вперед! Вы не думаете о предстоящих проблемах, либо ны так ничего и
не сделаете... Так, шаг за шагом, дело движется. Вот, есть уже драйверы клавиатуры, есть драйверы
дисплея, на подходе графический интерфейс — ух, ты, да это же USER!”
“Без этого отладчика, работающего в защищенном режиме с помощью расширителя DOS, я бы ничего
этого не создал”.
За несколько недель до всеобщего обсуждения проекта (с привлечением Билла Гейтса) Вейс намекнул
Баллмеру, как близко он подобрался к решению проблемы с работой Windows в защищенном режиме.
“Это интересно”, — сказал Баллмер.
Вейс поднял этот вопрос на встрече в мотеле La Quinta в Киркланде. Проект Windows 3 был поручен
менеджеру Рассу Вернеру (Russ Werner) самим Гейтсом: “Сделайте это великим!” “Что может сделать
это великим?”, — вопрошал Вернер свою команду. Некоторые ответили, что это интерфейс, который
мог бы конфигурировать сам пользователь. Другие сказали — внешний вид. “Работа в защищенном
режиме”, — сказал Дэвид Вейс.
“Ну и что нам это даст?” — спросил Вернер.
Не показывая, насколько он уже продвинулся, Вейс привел десяток причин. И Вернер предложил ему
же и попробовать.
Накануне обсуждения проекта, Вейс предложил Баллмеру встретиться в его офисе в восемь утра. Сам
он просидел до трех часов ночи, переделывая программы Windows для запуска в защищенном режиме,
и вышел, не выключив компьютер. Когда через несколько минут он вернулся, машина вышла из строя.
Он знал, кто мог это сделать, пришел к Баллмеру в офис и спросил: “Ты это видел?”
“Да, — сказал Баллмер. — Куда мы отсюда двинем?”
“Стив, это полностью в твоих .руках” .
“Давай займемся”, — ответил Баллмер.
Идя на встречу, Вейс был взволнован. “Это как у [Джорджа] Гамова,” — думал он, вспоминая физика
русского происхождения, который разработал теорию звездного развития. Его подружка спросила: “О
чем ты думаешь?” — А он ответил: “Я единственный в мире, кто знает как работает солнце”.
То, к чему пришел Вейс, было как луч света в темном царстве [OS/2] Presentation Manager. В
некотором смысле, он был готов выполнить обещание Стива Баллмера, данное в 1985 году: “Напишите
приложение для Windows и вы сможете без проблем запускать его в защищенном режиме.”
Остальные высказывали свои мнения, и тогда Вейс бросил свою “бомбу” . Все, что обсуждалось до сих
пор, внезапно отошло на второй план... Однако, игра шла против IBM. “Хорошо, давайте займемся
этим”, — сказал Гейтс.
Баллмер повернулся к Биллу: “А что мы скажем IBM?”
Улыбка осветила лицо Билла Гейтса: “А это ваша проблема, Стив”.
И проблема была. IBM видела в Windows промежуточное звено между DOS и РМ, но не более того.
Теперь, преодолев внутренние ограничения DOS, Windows сама противопоставлялась OS/2 и
Presentation Manager. Единственное, чего ей не доставало, — даты выхода.
— Stephen Manes and Paul Andrews, Gates, p. 380-382.
DOSX: многоцелевой расширитель DOS
и DPMI-сервер
To, что DOSX.EXE в стандартном режиме Windows базируется на технологии ранее созданного
отладчика, указывает, что либо Windows является замаскированным отладчиком, либо DOSX не
является характерной частью Windows.
Действительно, DOSX — вполне универсальный программный продукт. Эта маленькая
(32 Кбайта) программа не только выступает в качестве расширителя DOS, но является также и
DPMI-сервером. DPMI расшифровывается как интерфейс защищенного режима DOS (DOS Protec-
136
Неофициальная Windows 95
ted Mode Interface). DOSX поддерживает DPMI версии 0.9. Несмотря на свое название, DPMI —
это не то же самое, что интерфейс прерывания INT 21h для защищенного режима. Это значит, что
DPMI не является просто расширителем DOS. Вместо этого, DPMI обслуживает прерывания INT
2Fh и INT 31h, которые можно использовать для написания своего собственного расширителя DOS,
совместимого с Windows или менеджерами памяти QEMM и 386МАХ.
Например, одной из функций, предоставляемых DPMI-сервером типа DOSX.EXE, является
функция 1687h прерывания INT 2Fh, которая в документации по DPMI (полученной от Intel)
описывается как “Получение точки входа функции переключения из реального режима в
защищенный”. Когда программы реального режима вызывают эту функцию, DPMI возвращает
дальний указатель на функцию, которую необходимо вызвать для перехода в защищенный режим.
Листинг 5.1 (USEDPMI.C) содержит самый простой пример того, как программа DOS
реального режима может использовать DPMI для перехода непосредственно в защищенный режим.
Если DPMI недоступен, USEDPMI выдает сообщение “Эта программа требует DPMI”. Но при
наличии доступа к DPMI (например, когда загружен 386МАХ или QEMM) программа выдает
“Привет из защищенного режима!” и завершается. Ключевым моментом в этой программе является
функция dpmi_init, которая сначала обращается к функции 1687h INT 2Fh для получения функции
перехода в защищенный режим, а затем вызывает эту функцию.
Документация по DPMI отмечает, что DPMI-клиент должен осуществлять выход через
функцию 4Ch прерывания INT 21h. Вместо того, чтобы полагаться на стандартную процедуру
выхода из С-программы, USEDPMI использует функцию _dos_exit. А поскольку USEDPMI
обходит стандартную процедуру выхода из С-программы, она обращается к функции flushall для
сброса буферов ввода-вывода. В противном случае выход будет некорректен.
Листинг 5.1. USEDPMI.C
/*
USEDPMI.C -- очень простой пример программы, использующей DPMI
bcc usedpmi.c
Шульман, Апрель 1994
Эта программу ‘НЕОБХОДИМО* компилировать в малой (small) модели, потому что,
как только программа перейдет в защищенный режим, она вызовет printf из
библиотеки С реального режима. В большой (large) модели библиотека использует
дальние указатели, что может привести к краху программы, так как при
переключении в защищенный режим сегментные регистры используются по-другому
и содержат другие значения.
*/
#include <stdlib.h>
^include <stdio.h>
#include <dos.h>
void _dos exit(int retval)
{
_asm mov ah, 04Ch
_asm mov al, byte ptг retval
asm int 21h
}
void fail(const char *s) { puts(s); exit(1); }
// Вызывает функцию DPMI “Obtain Real to Protected Mode Switch Entry Point"
// (“Определить точку входа функции переключения из реального режима в защищенный")
// (INT 2Fh AX=1687h)
int dpmi_init(void)
{
void (far *switch_proc)();
unsigned hostdata_seg, hostdata_para, dpmi_flags;
_asm push si
_asm push di
_asm mov ax, 1687h /* проверить наличие DPMI ♦/
_asm int 2Fh
_asm and ax, ax
Zasm jz got_dpmi /* если AX == 0, to DPMI присутствует */
Zasm jmp noZdpmi
Глава 5. Два лика Windows
137
got.dpmi:
_asm mov dpmi.flags, bx
_asm mov hostdata_para, si /* размер памяти для частных данных DPMI */
_asm mov word ptr switch_proc, di
_asm mov word ptr switch_proc+2, es
_asm pop di
_asm pop si
if(_dos_allocmem(hostdata_para, &hostdata_seg) != 0) ,
fail(“невозможно выделить память’’);
dpmi_flags &= "1; /* это 16-битовая программа защищенного режима */
/* войти в защищенный режим */
_asm mov ах, hostdata_seg
_asm mov es, ax
_asm mov ax, dpmi_flags
(*switch_proc)();
/* Я не думаю, что мы еще в Канзасе, Ту-ту! */
return 1;
no_dpmi:
_asm pop di
_asm pop si
return 0;
}
void print regs(void)
{
unsigned short ds_reg, cs_reg;
_asm mov cs_reg, cs
_asm mov ds_reg, ds
printf(“CS=%04Xh DS“404Xh\n”, cs_reg, ds_reg);
•'ain()
print_regs();
if(!dpmi_init()) // переключиться в защищенный режим через DPMI
fail(”3Ta программа требует DPMI’’);
else
{
print_regs();
printf(“Привет из защищенного режима!\п”);
// чтобы выйти из защищенного режима, необходимо использовать 21/4С!
// и, если мы обходим С-код очистки при выходе, то должны
// сделать flushall
flushalK);
_dos exit(0);
}
Обратите внимание, что в защищенном режиме USEDPMI обращается к функции printf библио-
теки С для вывода на экран сообщения “Привет из защищенного режима”. Но USEDPMI запус-
кается как программа реального режима, она скомпонована с библиотекой С реального режима.
Использование этой библиотеки в защищенном режиме требует, чтобы этот код реального режима
был “чистым для защищенного режима”. Это, в свою очередь, требует, чтобы USEDPMI компили-
ровался в малой (small) модели памяти. Причина этого становится очевидной, если обратиться к
выводу программы, запускающейся под DPMI-сервером:
CS=2l5Ah DS=22C9h
CS=02E7h DS=027Fh
Привет из защищенного режима!
DPMI-сервер изменил сегментные регистры USEDPMI! Он получил в защищенном режиме
селекторы USEDPMI, которые имеют тот же самый линейный базовый адрес, что и сегментные
регистры USEDPMI в реальном режиме.
При использовании в USEDPMI функции printf библиотеки реального режима, кроме требова-
ния использования малой модели памяти, есть еще одна особенность: USEDPMI предполагает при-
138
Неофициальная Windows 95
сутствие не только DPMI-сервера, но и расширителя DOS, обрабатывающего запросы прерывания
INT 21h в защищенном режиме. Функция printf в реальном режиме, которую USEDPMI вызывает
из защищенного, использует прерывание DOS INT 21h, поэтому лучше предоставить сервис INT 21h
прямо в защищенном режиме.
Часто DPMI-серверы одновременно являются и расширителями DOS, но документация по
DPMI ничего об этом не сообщает. Поэтому не следует думать, что у вас есть сервис INT 21h в
защищенном режиме только потому, что у вас есть DPMI. DPMI-сервер в OS/2, драйвер
виртуального устройства под названием VDPX.SYS, имеет установку DPMI_DOS_API, которая
определяет, должен ли обеспечиваться сервис INT 21h в защищенном режиме. Не совсем понятно,
имеет ли смысл отключать DPMI_DOS_API, но если он будет отключен, то USEDPMI (вместе с
большинством других программ, которые считают, что DPMI-сервер всегда обеспечивает функции
расширителя DOS) будет работать неправильно.
Во всяком случае, DPMI и INT 2lh защищенного режима — логически различные сервисы. Как
я упоминал перед листингом 5.1, с помощью DPMI можно написать свой собственный расширитель
DOS. Вы сами видите, как полезна функция переключения в защищенный режим для того, кто
пишет расширитель DOS. Расширитель DOS запускается в реальном (или V86) режиме и переклю-
чает компьютер в защищенный режим для запуска программ защищенного режима под DOS. DPMI-
сервер берет на себя большую часть работы, которую должен выполнять расширитель DOS, и
облегчает написание компактных расширителей DOS, которые могут работать в таких средах, как
Windows, OS/2, и под менеджерами памяти QEMM и 386МАХ.
Как только DPMI-клиент запустился в защищенном режиме, он может вызывать функции
DPMI прерывания INT 31h, например, функцию 0 для распределения селекторов защищенного
режима, функцию 050 lh для распределения памяти, функцию 0600h для блокировки области стра-
ничной памяти или же функцию 0205h для установки обработчика прерывания защищенного режи-
ма. Эта последняя функция очень важна для расширителей DOS, которые для обеспечения работы
их клиентов должны перехватывать в защищенном режиме прерывание INT 2lh.
Таким образом есть три уровня программного обеспечения:
• DPMI-сервер предоставляет функции DPMI INT 2Fh и INT 31h.
• DPMI-клиент вызывает DPMI сервисы INT 2Fh и INT 31h; во многих случаях этим DPMI-
клиентом является расширитель DOS, который предоставляет функции прерывания INT
21h в защищенном режиме. Некоторые клиенты DPMI (типа USEDPMI) как раз и
предполагают наличие расширителя DOS.
• Приложение для расширенной DOS вызывает прерывание INT 21h защищенного режи-
ма, которое обеспечивается расширителем DOS. Оно также может обращаться к функ-
циям DPMI.
В случае с DOSX.EXE расширитель DOS (DPMI-клиент) и DPMI-сервер находятся в одной
программе. Как известно, DOSX.EXE запускает KRNL286.EXE или KRNL386.EXE. Другими сло-
вами, KRNL286 и KRNL386 — это программы, работающие под расширителем DOS, которые также
являются библиотеками динамической компоновки Windows, содержащими Windows KERNEL API.
В то же время, эти два файла содержат код инициализации, который обращается к функции 1687h
прерываний INT 2Fh для’ переключения в защищенный режим (см. описание программы начальной
загрузки в главе 1 книги Мэтта Петрека Windows Internals).
Первоначально DPMI предназначался для облегчения написания компактных, совместимых рас-
ширителей DOS. По мнению Рея Дункана, “весьма маловероятно, что вам когда-либо понадобится
вызывать функции DPMI прямо в вашей собственной программе” (Extending DOS, 2d ed.). Однако
DPMI оказался полезным не только горсточке поставщиков расширителей DOS, но также и
разработчикам более типичных программ, вроде Windows-приложений, для которых обычный API
не удовлетворяет все их потребности. Например, многие приложения Windows обращаются к
функции DPMI 0300h, которая имитирует прерывание в реальном режиме (см., например,
WV86TEST.C в главе 12).
Из главы, посвященной DPMI в книге Extending DOS (2d ed.), мы узнаем, что первоначально
Microsoft имела намерение включить в спецификацию DPMI обработку прерывания INT 21h в
Глава 5. Два лика Windows
139
защищенном режиме. Как и говорится в названии, DPMI — это интерфейс защищенного режима
DOS, поэтому прерывание INT 21h защищенного режима, естественно, рассматривалось как клю-
чевая часть DPMI:
Я никогда не забуду, как поразился, когда впервые столкнулся с интерфейсом защищенного режима
DOS (DPMI) в его изначальной форме. Осенью 1989 года я был на семинаре Microsoft по OS/2 2.0
для независимых производителей. Я вполуха слушал скучный доклад по кратным виртуальным
машинам DOS для OS/2 (MVDM). Выступающий вскользь упомянул, что OS/2 будет поддерживать
новый интерфейс для выполнения приложений расширителя DOS. Это случайное замечание привлекло
мое внимание....
После того как выступление закончилось, я подошел к нему и попросил более подробно пояснить, что
это за таинственный интерфейс, который может оказать довольно серьезное влияние на ближайший и
столь близкий моему сердцу проект книги. Через пару часов представитель Microsoft вернулся с еще
теплым от ксерокса толстым томом, помеченным грифом “Конфиденциально” и озаглавленным “Спе-
цификация интерфейса защищенного режима DOS, предварительный выпуск версии 0.04”. Я думаю,
что выглядел весьма забавно, бегая глазами по строчкам, с челюстью, постепенно отвисавшей до пола.
Документ, который мне показали, был ни чем иным, как описанием версии DOS для защищенного
режима!
Оглядываясь назад, было практически очевидно, что Microsoft должна создать что-то вроде DPMI. Лю-
бой американский компьютерный журналист, не говоря уже о тысячах участников бета-тестирования,
был в курсе, что еще не анонсированная Windows 3.0 должна иметь возможность использовать преи-
мущества расширенной памяти, выполняя приложения в защищенном режиме, несмотря на то, что сама
она работает как надстройка над DOS и использует ее файловую систему....
Но я никогда еще не видел ни единого печатного слова об этом нововведении и должен признать, что
никогда и не задумывался над этим...
Microsoft первоначально разделила DPMI на две части: набор низкоуровневых функций для управле-
ния прерываниями, переключения режимов и управления расширенной памятью; и более высокоуров-
невый интерфейс, предоставляющий доступ к MS DOS, ROM BIOS и драйверу мыши с помощью вызо-
ва прерываний INT 21h, INT 10h, INT 33h и т.д. в защищенном режиме. Высокоуровневые функции
' DPMI были реализованы через функции DPMI низкого уровня и сохранившиеся функции DOS и ROM
BIOS реального режима...
Когда детали DPMI начали просачиваться в среду разработчиков программного обеспечения для MS
DOS, они стали вызывать немало неодобрений и резких слов по двум очень важным причинам. Во-
первых, поставщики других расширителей DOS предполагали, что Microsoft, понимая, что OS/2 не
вытеснит DOS сразу, решила вторгнуться в ту нишу рынка, которую они с таким трудом развивали, и в
конце концов вытеснить их из нее, пользуясь явным преимуществом в ресурсах и маркетинге. Во-вто-
рых, Microsoft разработала DPMI с полным пренебрежением к совместимости с существующим про-
мышленным стандартом для программного обеспечения, базирующегося на DOS защищенного режима —
виртуальным интерфейсом управления программами (VCPI)....
Через несколько месяцев стало ясно, что “едва оперившийся” рынок расширителей DOS разделился на
два взаимоисключающих направления, вызывая* дополнительную головную боль у разработчиков про-
граммного обеспечения, докучая конечным пользователям и хорошо нагружая работой юристов. К
счастью, хладнокровие возобладало. Microsoft передала контроль за документацией по DPMI промыш-
ленному комитету, и предыдущие защитники XVCPI (расширенный VCPI) решили объединить свои
усилия вокруг DPMI. Фирма Intel, с ее понятным энтузиазмом по поводу всего, что повысило бы спрос
на 80386 процессоры, была инициатором этого соглашения, а также взяла на себя ответственность за
издание и распространение документации по DPMI.
При этом Microsoft согласилась на уничтожение тех частей DPMI, которые вторгались на территорию
расширителей DOS, — особенно непосредственную поддержку прерываний DOS и ROM BIOS в защи-
щенном режиме. И вот, первая обнародованная версия спецификации DPMI версии 0.9 была выпущена
комитетом по DPMI в мае 1990 года и определяла только “низкоуровневые” или “фундаментальные”
функции.... Естественно, верхний уровень или интерфейс расширителя DOS для Windows 3.0 все же
существовал, но он находился в тени. Единственной документацией Microsoft на расширитель DOS для
Windows 3.0 были пять страниц технических примечаний, озаглавленных “Поддержка Windows DPMI
прерываний INT 21h и NetBIOS”, которые были примечательны в первую очередь тем, что не содержали
никакой полезной информации.
— Ray Dunkan, Extending DOS, 2d ed., 1992, p. 433-438.
140
Неофициальная Windows 95
Действительно, интерфейс INT 21h, обеспечиваемый DOSX в стандартном режиме и DOSMGR
и IFSMGR в расширенном режиме, практически не описан. Впрочем, по оценке Дункана, это — по
крайней мере отчасти — является уступкой Microsoft таким поставщикам расширителей DOS, как
PharLap, Rational systems и Ergo.
У Microsoft имеется служебный документ (“MS-DOS API Extentions for DPMI Hosts”, October
31, 1990), в котором 30 страниц посвящено расширителям DOS для Windows 3.0. Упомянутые
Дунканом пять страниц — не что иное, как вытяжка из этого более обширного документа. Напри-
мер, в документации 1990 г. обсуждается 32-битовый расширитель DOS, предоставляемый
DOSMGR. Файловые запросы DOS на чтение и запись (функции 3Fh и 40h прерывания INT 21h)
принимают размер блока в 32-битовом регистре ЕСХ, что позволяет 32-битовым программам
производить за один раз считывание или запись блока размером больше 64 Кбайт.
Хотя сам по себе DPMI едва ли назовешь недокументированным (документация по DPMI бес-
платно распространяется Intel и присутствует в огромном количестве книг), Microsoft практически
ничего не говорит о присутствии DPMI (версия 0.9) в Windows. В Windows 3.1 SDK содержится
всего около четырех страниц о расширителе DOS и DPMI, вместе взятым (“Windows Applications
with MS-DOS Functions”, Programmer's Reference, Vol. 1: Overview, Chapter 20). В этой главе
содержится скудный перечень семи простых функций DPMI, которые Microsoft предлагает для
использования в приложениях Windows. С другой стороны, в этом же самом документе объявлено,
что Windows поддерживает DPMI версии 1.0. В действительности же, стандартный и расширенный
режимы Windows 3.x, равно как и Windows 95, поддерживают DPMI версии 0.9.
RUNDOSX
Я установил, что часть DOSX.EXE для стандартного режима Windows является обычным
DPMI-сервером и расширителем DOS. Но насколько он обычен? В конце концов, DOSX, который
содержит код отладчика, теперь является как бы неотъемлемой частью Windows и не может быть
использован для других целей.
Действительно, DOSX может запускать только KRNL286.EXE или KRNL386.EXE, независимо
от того, что вы указываете в его командной строке. Если DOSX не может найти какой-то из этих
файлов, он прерывается с сообщением: “Cannot find files needed to run Windows in standard mode”
(He могу обнаружить файлы для запуска Windows в стандартном режиме). Что еще хуже, DOSX
захватывает наибольший доступный блок XMS-памяти и (прерывая выполнение из-за того, что не
может найти KRNL286 или KRNL386) и не утруждает себя освободить этот блок. Поэтому, когда вы
пробуете снова запустить DOSX с KRNL286.EXE или KRNL386.EXE, DOSX опять прерывается с
сообщением: “Cannot start Windows in standard mode” (He могу запустить Windows в стандартном
режиме). Чтобы DOSX смог нормально запускаться, нужно перезагрузиться для освобождения
XMS-памяти. Создается впечатление, что DOSX безнадежно привязан к файлам KRNL286.EXE и
KRNL386.EXE.
Хотя DOSX может запускать только KRNL286.EXE или KRNL386.EXE, с другой стороны, он
запустит любой файл с одним из этих имен! Если некоторый файл под именем KRNL286 обращается
к функциям DPMI для переключения в защищенный режим и вызывает прерывание INT 21h в за-
щищенном режиме, DOSX сможет запустить его так же, как запускает файлы KRNL286.EXE и
KRNL386.EXE, входящие в Windows. На рис. 5.2 показано, как запускается программа USEDPMI
под DOSX.
Видите? Значит DOSX.EXE действительно является (если на время забыть об авторском праве)
чем-то, что можно взять из Windows и использовать в других целях.
Пример, представленный на рис. 5.2, основан на трех предположениях.
• Во-первых, этот пример работает только на 386-х или более поздних машинах. На 286-х
машинах вы должны изменить имя USEDPMI.EXE на KRNL286.EXE, потому что тогда
DOSX будет запускать KRNL286.EXE вместо KRNL386.EXE.
Глава 5. Два лика Windows
141
• Во-вторых, сообщение “Эта программа требует DPMI” не появится, если вы работаете с
менеджером памяти, который уже обеспечивает DPMI, — например, QEMM (с QDPMI)
или 386МАХ. Если это так, вам не нужен DOSX для запуска USEDPMI. Несмотря ни на
что, это все-таки хорошая иллюстрация того, что маленький файл DOSX.EXE является
обычным DPMI-сервером и расширителем DOS. Способность DOSX запускаться под другим
менеджером памяти имеет тоже свою интересную историю, но, к сожалению, у нас нет
времени в нее углубляться. Относительно деталей загляните в книгу Джефа Чапелла (Geoff
Chappells) DOS Internals (p. 559-569).
• В-третьих, этот пример показывает, что вы не находитесь в Windows. DOSX можно
запустить только вне Windows. Если вы попробуете выполнить DOSX из расширенного
режима Windows, то получите интересное сообщение: “Cannot start Windows in Standard
mode. You are using an expanded memory manager which is hot compatible with Microsoft
Windows 3.1, or which is configured incorrectly. Try removing or reconfiguring your memory
manager, or using the copy of EMM386.EXE supplied with Microsoft Windows 3.1” (He могу
запустить Windows в стандартном режиме. Вы используете менеджер расширенной памяти,
который не совместим с Microsoft Windows 3.1 или же некорректно сконфигурирован.
Попробуйте отключить или переконфигурировать ваш менеджер памяти или же использовать
EMM386.EXE, который поставляется с Microsoft Windows 3.1). DOSX думает, что
расширенный режим Windows — это такой же менеджер памяти, как и EMM386. Ну что ж,
во многом это справедливо.
С:\WINDOWS\SYSTEM>copy \unauthw\usedpmi.ехе
С:\WINDOWS\SYSTEM>usedpmi
Эта программа требует DPMI
C:\WINDOWS\SYSTEM>ren krnl386.exe krnl386.sav
C:\WINDOWS\SYSTEM>ren usedpmi.exe krnl386.exe
C:\WIND0WS\SYSTEM>dOSx
CS=215Ah DS=22C9h
CS=02E7h DS=027Fh
Привет из защищенного режима!
C:\WINDOWS\SYSTEM>ren krn!386.sav krnl386.exe
Рис. 5.2. Переименование USEDPMI.EXE в KRNL386.EXE и выполнение его под DOSX доказывает, что DOSX —
универсальный DPMI-сервер и расширитель DOS
После всех операций с переименованием файлов, как показано на рис. 5.2, я позаботился о том,
чтобы написать маленький bat-файл. Этот bat-файл под названием RUNDOSX представлен в
листинге 5.2. Он запускает любую программу, указанную в его командной строке под DOSX,
временно переименовывая ее в KRNL286.EXE (если нет файла KRNL386.EXE, DOSX запустит
файл KRNL286.EXE как на компьютерах с 286-м, так и с 386-м и старшим процессором).
ЛИСТИНГ 5.2. RUNDOSX. ВАТ
eecho off
rem RUNDOSX.BAT
if (%!)==() goto usage
if exist krnl286.exe ren krnl286.exe krnl286.zzz
if exist krnl386.exe ren krnl386.exe krnl386.zzz
if not exist %1 goto no_exe
if exist dosx.exe goto havejdosx.
copy \windows\system\dosx.exe >nul
if not exist dosx.exe goto no_dosx
:have_dosx
142 Неофициальная Windows 95
сору %1 krnl286.exe >nul
dosx krnl286 %2 *3 *4 %5 »6 %7 M %9
del krnl286.exe
if exist krnl386.zzz ren krnl386.zzz krnl386.exe
if exist krnl286.zzz ren krn!286.zzz krnl286.exe
goto end
:usage
echo RUNDOSX запускает клиента DPMI (DOS программу реального режима, которая
echo использует DPMI для переключения в защищенный режим) под DOS-расширителем
echo стандартного режима Windows (DOSX.EXE) путем временного переименования
echo клиента DPMI в файл с именем KRNL286.EXE.
echo Клиенты DPMI могут быть созданы с помощью библиотеки DPMISH.
echo
echo Использование: rundosx [имя клиента DPMI]
goto end
: no_exe
echo Невозможно найти %1
goto end
:no.dosx
echo Невозможно найти DOSX.EXE
:end
echo.
Хотя RUNDOSX можно использовать для запуска любой программы (даже COMMAND.COM),
целесообразнее запускать DPMI-клиентов, вроде USEDPMI, которые используют расширитель DOS
для Windows:
С:\UNAUTHW>rundosx usedpmi.exe
CS=215Ah DS=22C9h
CS=02E7h DS=027Fh
Привет из защищенного режима!
К слову сказать, команда RUNDOSX \COMMAND.COM будет работать не так, как вам этого
хотелось бы. Конечно, вы попадете в командную строку DOS с загруженным в памяти DOSX. И,
конечно же, отсюда можно запустить DPMI-клиента:
С:\UNAUTHW>rundosx \command.com
C:\UNAUTHW>usedpmi
CS=215Ah DS=22C9h
GS=02E7h DS=D27Fh
Привет из защищенного режима!
C:\UNAUTHW>exit
Но, к сожалению, запустить DPMI-клиента таким образом можно только один раз:
С:\UNAUTHW> rundosx \command.com
C:\UNAUTHWXisSdpmi
CS=215Ah DS=22C9h
CS=02E7h DS=027Fh
Привет из защищенного режима!
C:\UNAUTHW>usedpmi
Эта программа требует DPMI
Вот так?!. Пожалуй, это единственное место, где DOSX ведет себя необычно. DOSX предпо-
лагает, что на весь период работы имеется единственный DPMI-клиент. После перехода программы
в защищенный режим, DOSX восстанавливает старый обработчик прерывания INT 2Fh реального
режима. Следующая программа, которая вызывает функцию 1687h INT 2Fh, будет вести себя так,
как будто DPMI отсутствует, если только не запущен другой DPMI-сервер, вроде QEMM или
386МАХ. В этом случае DPMI-клиент буд^у работать с ним, вместо DOSX.
Глава 5. Два лика Windows
143
Между прочим, именно поэтому программы DOS, которые запускаются в стандартном режиме
Windows, не могут пользоваться DPMI-сервером Windows или расширителем DOS. Они доступны
только для Windows-программ. После того, как DOSX обнаруживает, что KRNL286.EXE или
KRNL386.EXE переключаются в защищенный режим, он восстанавливает предыдущий обработчик
прерывания INT 2Fh, и поэтому никакая другая программа не может вызвать DPMI, чтобы перейти
в защищенный режим. Приложения под Windows могут использовать функции DPMI-прерываний
INT 31h и INT 2Fh, которые в защищенном режиме поддерживает DPMI, а благодаря тому, что эти
программы уже непосредственно запускаются в защищенном режиме, им не надо обращаться к
DPMI-процедуре переключения в защищенный режим.
Для многократного запуска программ под DOSX надо каждый раз использовать RUNDOSX.
Использование библиотеки DPMI SHELL
USEDPMI продемонстрировал принципы написания DPMI-клиента. Библиотека DPMI Shell
(DPMISH) — еще большее усовершенствование такого подхода. Например, в листинге 5.3
(USEDPMI2.C) представлена программа USEDPMI, переписанная с использованием DPMISH.
Листинг 5.3. USEDPMI2.C
/*
USEDPMI2.C -- очень простой клиент DPMI
bcc usedpmi2.c dpmish.c ctrl_c.asm
Шульман,Апрель 1994
«include <stdlib.h>
«include <stdio.h>
«include <dos.h>
«include “dpmish.h"
, void fail(const char *s, ...) { puts(s); _dos_exit(1); }
void print regs(void)
{
unsigned short ds_reg, cs_reg;
_asm mov ax, cs
_asm mov cs_reg, cs
_asm mov ax, ds
_asm mov ds_reg, ax
printf(“CS=%04Xh DS=%04Xh\n", cs_reg, ds_reg);
int real_main(int argc, char *argv[])
print_regs();
return 0;
1
int pmode main(int argc, char *argv[J)
{
print_regs();
printf(“Привет из защищенного режима!\п”);
1
Как видите, программа включает DPMISH.H и содержит две функции: real_main и pmode_
main. В реальном режиме (или V86) DPMISH обращается к функции real_main. Если real_main
возвращает О, DPMISH переключится в защищенный режим и вызывает функцию pmode_main. Обо
всем остальном DPMISH позаботится самостоятельно.
144
Неофициальная Windows 95
MEMLOOP
Программы USEDPMI и USEDPMI2 не показательны. Более впечатляющим примером того,
для чего нужен расширитель DOS, является программа MEMLOOP.C из листинга 5.4. MEMLOOP
обращается к функции распределения памяти DOS (INT 21h функция 48h) в цикле, через функцию
_dos_allocmem, которая содержится в стандартной библиотеке компилятора С для DOS, рас-
пределяя память до тех пор, пока она (память, а не программа) не истощится. MEMLOOP ис-
пользует функцию _fmemset для обращения к каждому байту каждого распределенного блока.
Это нужно как для того, чтобы показать, что выделенная память не заблокирована и готова к
работе, так и1 для того, чтобы получить реальную картину в системах, использующих виртуальную
память. Каждый раз, когда функция _dos_allocmem не может выделить память, MEMLOOP
уменьшает размер запрашиваемой памяти. Когда памяти не остается вообще, MEMLOOP печатает
отчет по ее распределению и прекращает работу, предоставляя DOS самой освободить
распределенную память.
При компиляции для DPMI (#ifdef DPMI_APP), MEMLOOP.C содержит real_mam и
pmode_main. В противном случае программа будет содержать обычную функцию main и работать в
реальном режиме.
Листинг 5.4. MEMLOOP.C
/*
MEMLOOP.C
Сколько памяти можно выделить через DOS (INT 21h AH=48h)?
Можно скомпилировать как для реального, так и для защищенного режима (DPMI)
реальный режим: bcc memloop.с
защищенный режим: bcc -2 -DDPMI_APP memloop.с dpmish.c ctrl_c.asm
На машине с 12 Мбайт ОЗУ:
Реальный режим: 536 Кбайт
DPMI, под 386МАХ 10764 Кбайт (18916 Кбайт для DPMI с файлом подкачки)
DPMI, под DDSX 10894 Кбайт (18916 Кбайт для 386МАХ DPMI с файлом подкачки)
DPMI, под WIN386 38096 Кбайт
*/
Sinclude <stdlib.h>
sinclude <stdio.h>
Sinclude <string.h>
sinclude <dos.h>
sifdef DPMI_APP
Sinclude “dpmish.h" // содержит переопределение _dos_allocmem
sendif
void mem_loop(void)
{
unsigned long kb = 0;
unsigned blocks = 0;
unsigned blocksize = 2048;
unsigned kbytes = 32;
unsigned segsel;
while(kbytes)
{
while(_dos_allocmem(blocksize, &segsel) == 0) // INT 21h AH=48h
{
// проверить каждый байт!
_fmemset(MK_FP(segsel, 0), ’x', blocksize * 16);
kb += kbytes;
blocks++;
printf(“%04Xh\t%d\t%lu\t\t\r’’. segsel, blocks, kb);
#ifdef DPMI_APP
• if(ctrl_c_hit)
ТаЩ“\0бнаружена Ctrl-C”);
sendif
}
Глава 5. Два лика Windows
145
blocksize »= 1;
kbytes »= 1;
}
рг1п1Г(“\пВыделено %lu Кбайт в %u блоках\п", kb, blocks);
#ifdef DPMI_APP
void fail(const char »s, ...) { puts(s); _dos_exit(1); }
int real main(int argc,.char *argv[])
{
return 0; // Ладно, переключаемся в защищенный режим
}
int pmode_main(int argc, char *argv[])
#else
int main()
#endif
{
mem_loop();
return 0;
}
Если мы скомпилируем MEMLOOP для реального режима, не удивительно, что она не может
выделить память объемом более 640 Кбайт, даже на компьютере с 12 Мбайт памяти:
С: \UNAUTH>bcc memloop, с
C:\UNAUTH>memloop
Выделено 536 Кбайт в 17 блоках
Вот что происходит, если мы компилируем MEMLOOP как DPMI-программу и запускаем ее
под DOSX.EXE на том же самом компьютере с 12 Мбайт памяти:
C:\UNAUTH>bcc -DDPMI_APP -I..\dpmish memloop.с ..\dpmish.c
..\dpmish\ct rl_c.asm
C:\UNAUTH>rundosx memloop.exe
Выделено 10894 Кбайт в 341 блоках
Удивительно! Если мы используем просто DOSX.EXE, версия MEMLOOP для DPMI показы-
вает доступной память объемом 10 Мбайт, несмотря на то, что практически идентичная версия не
для DPMI распределяет на том же самом компьютере менее 640Кбайт. А ведь все, что мы сделали —
это набрали в командной строке: RUNDOSX MEMLOOP.EXE, не переходя к новой операционной
системе. Круто для простого расширителя DOS, не правда ли?
Итак, технология расширителя DOS является основой для Windows 3.x и для Windows 95.
Именно поэтому Windows 1.x и 2.x, не имея подобной технологии (всего лишь крохотной програм-
мы типа DOSX.EXE), использовали так мало памяти и, следовательно, были почти бесполезны и
пользовались довольно низким спросом. Эта небольшая программа DOSX.EXE действительно чер-
товски многого стоит! Я надеюсь, Мюррей Саржент и Дэвид Вейс хорошо на ней заработали.
Пример с RUNDOSX MEMLOOP.EXE отлично иллюстрирует, что же представляют собой
DPMI и расширитель DOS. Он показывает не только то, что DPMI позволяет программе реального
режима простым вызовом функции перескочить в защищенный режим, а расширитель DOS —
пользоваться обычными обращениями к DOS в защищенном режиме. Такие программы могут делать
вещи, о которых нельзя и мечтать под обычной DOS (например, распределение 10 Мбайт опера-
тивной памяти).
Пример RUNDOSX MEMLOOP.EXE также показывает, что в действительности Windows де-
лится на две части, и что эти части почти независимы друг от друга. Часть нижнего уровня включа-
ет DPMI-сервер и расширитель DOS, верхний уровень — динамические библиотеки DLL вроде
KRNL286 и KRNL386, USER и GDI. На месте MEMLOOP точно так же могли быть файлы
KRNL286.EXE или KRNL386.EXE из Windows. И тот, и другой, предположительно, могли бы
запускаться бы под каким-либо DPMI-сервером и расширителем DOS, отличным от DOSX. Вы
познакомитесь с одним из таких расширителей DOS в следующей главе, когда мы будем рассмат-
ривать расширенный режим Windows.
14V
Неофициальная Windows 95
Две части Windows только наполовину независимы, поскольку хорошо известно, что верхняя
часть'Windows не является чисто DPMI-клиентом. Мэтт Петрек так говорит об этом в своей книге
Windows. Internals'.
Процедуры нижнего уровня модуля KERNEL не слишком “скромничают”, напрямую используя LDT
[Таблицу локальных дескрипторов]... Почему KERNEL обходит DPMI? Не создан ли DPMI именно
для предотвращения такого загрязнения чувствительных системных ресурсов? Ответ — да. Однако
довольно часто приличный код и производительность “сталкиваются лбами”. Разработчики KERNEL
создали и протестировали версию KERNEL, которая распределяла каждый селектор с помощью DPMI.
Они достаточно насмотрелись на то, как терялась производительность, чтобы понять необходимость
прямого обращения к LDT. Модуль KERNEL совместим с DOSX и WIN386, т.е. он позволяет другим
программам распределять селекторы с помощью DPMI-сервера. К сожалению, эта версия KERNEL
предполагает, что DPMI-сервер управляет LDT строго определенным образом. Если другой DPMI-сер-
вер заменит WIN386 или DOSX, он должен будет организовать работу с LDT таким же образом.
Однако спецификация по DPMI умалчивает о том, что DPMI-сервер может управлять LDT каким-то
специфичным образом, что порождает неоднозначность между напечатанной и реальной специфи-
кациями DPMI.
— Matt Pietrek, Windows Internals, 1993, p. 90.
Как установила IBM в процессе создания OS/2 for Windows, KRNL386.EXE требует некото-
рых серьезных изменений для запуска под DPMI-сервером для OS/2. С другой стороны, инженер
из Qualitas сообщает, что его компания использует KRNL386.EXE как внутреннюю тестовую
программу для DPMI-сервера в своей программе управления памятью 386МАХ, и добавляет, что
Qualitas имела больше проблем с DPMI-программами фирмы Borland, чем с KRNL386I Хотя при
этом полностью отсутствуют VxD, запуска KRNL386.EXE под 386МАХ без какого-либо расши-
рителя DOS для Windows вполне достаточно, чтобы сыграть в Solitaire. А для чего еще, черт возь-
ми, всем нам нужна Windows?!
Главе 5?Д$сглика Winddws
147
Глава 6
DOS защищенного режима:
WIN386 и MS DPMI
Теперь, когда эксперименты с DOSX в предыдущей главе дали хорошее обоснование технологии
DPMI и расширителя DOS, мы можем ближе познакомиться с WIN386.EXE. Как отмечалось в
предыдущей главе, WIN386 — это набор VxD. Основным компонентом WIN386 является Virtual
Machine Manager (VMM), VxD ID #1 — подлинное ядро операционной системы Windows. Файл
VMM32.VXD в Windows 95 служит тем же целям, что и WIN386 в Windows 3.x.
Обычно мы сталкиваемся с утверждением, что WIN386.EXE — это DPMI-сервер. Это анало-
гично тому, что “KERNEL является функцией GlobalAlloc”. Хотя VMM действительно содержит
DPMI-сервер, он составляет всего лишь малую часть VMM. A VMM, в свою очередь, всего лишь
одна (хотя и самая фундаментальная) часть WIN386 (в расширенном режиме 3.x) и VMM32.VXD
(в Windows 95). VMM ответствен за выполнение множества других задач, в частности за управле-
ние приоритетной (вытесняющей) многозадачностью виртуальных машин и устройств, обработку
прерываний, управление памятью. DPMI — лишь внешний уровень всего этого, и его задача заклю-
чается, фактически, в предоставлении “нормальным” (т.е. не VxD) приложениям возможности ис-
пользовать сервисы VMM.
В расширенном режиме Windows расширитель DOS находится в VxD DOSMGR, который
также содержится внутри WIN386.EXE. Когда включается 32-битовый доступ к файлам в WfW 3.11
и Windows 95, VxD IFSMGR.386 может также считаться частью расширителя DOS.
В Windows Tech Journal (March, 1992) Дэвид Тилен (David Thielen), служащий Microsoft,
пишет:
IB действительности, WIN386 — даже не часть Windows. Это многозадачное ядро, управляющее вирту-
альными машинами. Как только WIN386 инициализируется, она загружает Windows в системную VM
(главная виртуальная машина, которая всегда существует). Однако с таким же успехом можно прово-
дить загрузку COMMAND.COM, тем самым делая DOS многозадачной (но я не буду вам рассказывать,
как это делается).
Итак, после обсуждения в главе 5, как заставить DOSX.EXE загружать что-либо иное, кроме
ядра Windows, становится совершенно очевидным, как сделать то же самое с WIN386.EXE. Точно
так же, как DOSX.EXE запускает KRNL286.EXE или KRNL386.EXE, WIN386.EXE (фактически
VxD SHELL внутри WIN386) запускает KRNL386.EXE. Но так же, как и DOSX, WIN386 запустит
любой файл с именем KRNL386.EXE (даже COMMAND.COM, если вы его переименуете). После
проверки работы DPMI-сервера и расширителя DOS в WIN386 мы можем создать bat-файл, анало-
гичный тому, который использовали для DOSX.
Однако есть одно различие: в дополнение к WIN386.EXE и файлу KRNL386.EXE нам также
необходим файл SYSTEM.INI и (в некоторых случаях) виртуальный драйвер дисплея (Virtual
Display Driver). Обычно SYSTEM.INI требует наличия только раздела [Enh386], в котором пере-
числяются необходимые VxD. Наша первая версия перечисляет только те VxD, которые находятся в
WIN386.EXE. В листинге 6.1 приведен GOWIN386.INI, который будет скопирован нашим bat-
файлом на место SYSTEM.INI.
148 Неофициальная Windows 95
ЛИСТИНГ 6.1. G0WIN386.INI
; gowin386.ini -- минимальный system.ini для gowin386.bat
[386Enh]
display=*vddvga
device=*vpicd
device=*int13
device=*wdctrl
mouse=*vmd
network=*dosnet, *vnetbios
ebios=*ebios
keyboard=*vkd
device=*vtd
device»*reboot
device=*vdmad
device=*vsd
device=*v86mmgr
device=*pageswap
device=*dosmgr
device=*vmpoll
device=*wshell
device=*blockdev
device=*pagefile
device»*vfd
device=*parity '
device=*biosxlat
device=*vcd
device=»vmcpd )
device=*combuff >
device=*cdpscsi
Изучая листинг 6.1, вы обнаружите, что расширенный режим отличается от стандартного
режима: он не только предоставляет расширитель DOS и DPMI-сервер, но также является
загрузчиком VxD. В зависимости от конфигурации вам может понадобиться добавить еще и другие
строки к SYSTEM.INI, такие как SystemROMBreakPoint=false, если вы работаете с QEMM. Или же
может понадобиться включение дополнительных строк device= для других VxD, с которыми вы
будете работать или экспериментировать. В листинге 6.2 приведен простой командный файл DOS,
названный GOWIN386.BAT, который запускает любую программу под WIN386.EXE.
Листинг 6.2. G0WIN386.BAT
©echo off
rem G0WIN386.BAT
if (%1)==() goto usage
if not exist %1 goto no_exe
if exist win386.exe goto have_win386
copy \wlndows\system\win386.exe >nul
if not exist win386.exe goto no_win386
:have_win386
if exist system.ini goto have_sysini
if not exist gowin386.ini goto no_sysini
copy gowin386.ini system.ini >nul
:have_sysini
cis
echo Loading win386 %1...
copy %1 krnl386.exe >nul
win386 %2 «3 «4 «5 «6 «7 «8 %9
del krnl386.exe
goto end
Глава 6. DOS защищенного режима: WIN386 и MS DPMI
149
:usage
echo Q0WIN386 запускает клиента DPMI (DOS-программу реального режима, которая
echo использует DPMI для переключения в защищенный режим) под WIN386;EXE из расширенного echo режима Windows
путем переименования клиента в KRNL386.EXE.
echo Клиентов DPMI можно создавать с помощью библиотеки DPMISH.
echo.
echo Использование: gowin386 [имя клиента DPMI]
goto end
:no_exe
echo Невозможно найти %1
goto end ,
:no_sysini
echo Эта программа требует G0WIN386.INI
goto end
:no_win386
echo Невозможно найти WIN386.EXE
:end
echo.
В первую очередь, попробуем запустить USEDPMI (из главы 5), чтобы проверить, работает ли
GOWIN386. Результат ничего впечатляющего не преподносит, он просто подтверждает, что
GOWIN386 работает приблизительно так же, как RUNDOSX:
C:\UNAUTHW>gowin386 usedpmi.exe
CS=246Eh DS=25CBh
CS=0097h DS=OO8Fh
Привет из защищенного режима!
Теперь давайте попробуем запустить MEMLOOP (опять из главы 5). Если вы вспомните, под
DOSX на моей машине с 12 Мбайт памяти программа MEMLOOP смогла распределить 10 Мбайт
памяти, используя функцию 48h прерывания INT 21h DOS. Естественно, это вполне ожидаемый ре-
зультат для машины с 12 Мбайт памяти, но ведь обычно под DOS мы ограничены 640 Кбайт.
Однако результаты запуска MEMLOOP на той же машине под WIN386 весьма отличаются от
тех, какие мы видели в DOSX. Во-первых, создается впечатление, что программа не работает, по-
скольку несколько секунд просто мерцает индикатор жесткого диска. Затем выводится сообщение:
C:\UNAUTHW>gowin386 memloop.exe
Выделено 38096 Кбайт в 1191 блоках
Предоставляемый вам объем памяти зависит от того, как много свободного места на жестком
диске, как много у вас физической памяти (учетверенный объем физической памяти — это макси-
мум виртуальной памяти, которую может предоставить WIN386), а также доступен ли для
GOWIN386 постоянный файл обмена (например, вы можете скопировать файл-указатель
SPART.PAR в каталог, из которого запускается GOWIN386).
Итак, в этой конфигурации мы распределили 37 Мбайт памяти. Добро пожаловать в мир вир-
туальной памяти!
Помните, что это — та же самая MEMLOOP.EXE, которая выделяет память не через специ-
альные функции API Windows, а с помощью обычной DOS-функции распределения памяти. Следо-
' вательно, это не просто виртуальная память, а виртуальная память с интерфейсом DOS.
В мире, где довольно незначительные улучшения обычно рекламируются как революционные
достижения, виртуальная память с интерфейсом DOS — это совершенное новшество. Это нечто но-
вое и захватывающее, замаскированное под старое и надоевшее. Приходит на ум фраза “Меньше
обещай, больше делай”. Конечно же, это не обычный подход Microsoft к созданию программного
150 л Неофициальная Windows 95
обеспечения. Для Microsoft типичнее представлять сервисы DOS режима V86 как Windows
(WIN386.EXE), затем, осознав, что эти сервисы должны быть частью DOS (о чем свидетельствует
имя DOS386.EXE в пред-бета-версиях Chicago), в итоге переутвердить выпуск программы и присво-
ить ей совершенно блеклое имя VMM32.VXD.
Как бы там ни было, WIN386, DOS386, VMM32 или как-нибудь еще (далее в этой главе мы
увидим, что Microsoft однажды назвала ее также MSDPMI) действительно являются версией
MS DOS в режиме V86, которая поддерживает виртуальную память, виртуализацию устройств и
кучу других возможностей, некоторые из которых" (вроде 32-битового доступа к диску и файлам)
обеспечивают даже лучшую производительность. На рис. 6.1 показано, что нижний уровень Win-
dows действительно является расширенным режимом MS DOS. Как только мы набрали GOWIN386
\COMMAND.COM в командной строке, мы попадаем в мир, который выглядит абсолютно так же,
C:\UNAUTHW>gowin386 /command.com
С:\UNAUTHW>usedpmi
CS=714Eh DS=72ABh
CS=0097h DS=D08Fh
Привет из защищенного режима!
C:\UNAUTHW>uBedpmi2
CS=714Eh DS=72ABh
CS=0D97h DS=008Fh
Привет из защищенного режима!
С:\UNAUTHW>memloop
Выделено 38096 Кбайт в 1191 блоках
C:\UNAUTHW>..\vxdlist\vxdlist
Name Vers ID DDB Control V86 API PM API #Srvc
VMM 3.11 0001 h 80011A88 8000AE14 242
VPICD 3.10 0003h 8001CB78 8001BAE4 8001C2A0 8D01C2A0 21
VTD 3.10 0005h 800225C0 80021B52 80021AE7 80021AE7 8
PageFile 2.00 0021h 800366DC 80036080 800365A7 7
PageSwap 2.10 0007h 8002D1EC 8002C894 7
PARITY 1.00 0008h 80036AF4 80036A4C 0
Reboot 2.00 0009h 80023274 800226B8 800229E1 0
EBIOS 1.00 0012h 8001F7E0 8001F770 2
VDD 2.00 OOOAh 8001A75C 80014058 80017FDB 14
VSD 2.00 OOOBh 80025238 800250C4 2
VCD 3.10 OOOEh 80037D80 8003722D 80037275 9
VMD 3.00 OOOCh 8001E514 8001E2D0 8001E45D 8001E45D 3
VKD 2.00 OOODh 800216B4 8001FD90 800202C6 15
BLOCKDEV 3.10 001 Oh 80036038 80035D80 7
INT13 3.11 0020h 8001D700 8001D458 5
VFD 2.00 001 Bh 800369E0 80036890 0
VMCPD 1.02 0011h 80038258 800380BC 3
BIOSXLAT 1.00 0013h 80036F48 80036B88 0
VNETBIOS 3.00 0014h 8001F62C 8001E748 4
DOSMGR 1.00 0015h 80031034 8002ECF7 8002EB3B* 12
VMPOLL 3.10 0018h 80031848 800315FB 3
DSVXD 3.00 003 Bh 80013FF8 80013E28 80013E7F 0
COMBUFF 1.00 80038650 800382A8 0
VDMAD 2.00 0004h 80024D48 800233F8 24
V86MMGR 1.00 0006h 8002C840 8002AED7 21
SHELL 3.00 0017h 80035B50 8D034466 80032E50 6
Ряс. в.1. Запуск COMMAND.COM под WIN386.EXE переключает вас в У86-режим с виртуальной памятью,
DPMI-сервисом, VxD-драйверами и т.д.
Глава 6. DOS защищенного режима: WIN386 и MS DPMI
151
как версия MS DOS режима V86 с полным сервисом DPMI и полным набором VxD. Фактически,
это многозадачная DOS, которая упоминается в процитированной выше статье из Windows Tech
Journal. Правда, мы имеем здесь только одну виртуальную машину, но можем написать VxD, кото-
рый позволит пользователю запускать дополнительные VM и переключаться между ними. Обратите
внимание, что, в отличие от ситуации с DOSX, в WIN386 мы можем запускать более чем одно
DMPI-приложение из командной строки.
Что же хорошего в этой версии DOS режима V86?
Даже если создается впечатление, что вы находитесь в реальном режиме MS DOS, на самом де-
ле вы теперь в режиме V86: DPMI загружен, у вас есть расширитель DOS, который поддерживает
16- и 32-битовые программы, и у вас есть также виртуальная память и полный ассортимент VxD. У
вас нет только каких-либо признаков пользовательского интерфейса Windows. Исходя из этого мы
можем констатировать факт, что Windows GUI (KERNEL, USER, GUI и т.д.) — это просто отдель-
ные программы, подобные MEMLOOP или USEDPMI.
Работа COMMAND.COM под GOWIN386 це особенно отличается от сеанса DOS в Windows. В
конце концов, вы также можете запускать DPMI-клиентов и приложения расширителя DOS в окне
DOS в Windows. Но, в нашем случае, сеанс DOS никакого особого отношения к Windows не имеет.
WIN386 совершенно независима от всего прочего в Windows и, как мы убедились, она кажется го-
раздо более похожей на часть DOS. Как-то, в одной из бета-версий Chicago WIN386 была пере-
именована в DOS386; это более точное название, чем WIN386.
| В каком режиме загружается KRNL386?
: ill.p.i ’A:VP n : ... i’ • * l. ..• 'is-.; ;
I | ,\l v. . I .’!'*• i E Г-i 1Ч'М:2 И Ml UOI'.
j >n pcit'if I.KM.:'’; ‘ S. " . ...
I DOS. пиши. DP'-I! .... . ... -. .. . ..=• I! .. ••v-r.!-,' M>! :
ЫШ' lit.tiM. : *' 'IVtMi i- ' ; ... , ' .. ; I 6|:-i|'..?i
MliiilUU'i! Ц11Я J-l.Jl'i 1'.'. M..;. .- . - I . .1 ' 4 ' ’ ’ > !i ...
\VIN3NG ..... i; ! ... . . - •-Г--I v •'...::<i!..i
KRXli:X}' '«-iiv’ р.’Гл.'ч ч । i: । ; .
Oiimi in m-ii\. i )iui H Г..Ы.Н- >: \ '•’<> Г.г; .--ч.:ч re.-- • '>> п<.
пмигиция jh..•.''•a1 чь.-ч.n.. . ч... i* i 'i.i ttit-. i<-- я 4 MM
WJN3S6 .IHHII'.IIK 14 ,. I? Ap...p- >>tp fl- .ti.,-,-,
eiapiycr iii-pn.i.- ниц: :•.-bii.ri 'A'.;’.-.: • - ।-• ..c- ‘M =-. '.
режим Vb’i rm kj kRNi J '*• t ,\l. '•»> .:. ...• K’lAl I '• : » . -.-t.\'I
КИ\ЧЛ“.«>.1.л)- <.. I. c’ .................... .. . •
библии:> ки I Л’М iM: > »n 11 i; .. i.n- ;»i.- .. • - i.,-. : < -< • '
виним INI . n: i!-p< k h-.-.i ’.ь r и!•-•..-t i •.. >-. t’r
j .liter VM?-I, « i-nip; м < .iij.riii Dl’Mi - чц. . - ’•-•«t .
; .'lhl6l-4 < I lLii< I)1»’’. >J.|. ............... : 4 . .-.11 ... Г
I If. n.lll.i'tl! VM •! Ml-. .' 14 .•!.. .1 4 • , )' I _
I Klin-H’-IB -I hPH :-i *-i :ll! i-.><••• 1 :. I.i i1! > '• I i'l u • .= ' । : • M ' ‘
I VM KRXl.tMi 1 XI . = - lh"i; .. -J-;.— -•
I DOS i«i Ki |.1!ч 14.. •' -i J ' IB :-.4’ -1.- • ‘
DPMISU 1'1-И..1 >11--.. Л.- : i . i. • • .-• r ..X I
.’KU ISl'l . «110 \Vl\3St) | \f. . 4< l-I IX •: :.s i-.K il ' 1 ... :• ... ,'“-f. I
ЯП !lp< .'Hto.'l.tl .U 1 41 OUXX 1 \|- Ilin, Illi I in 3 Il • ‘ j :: ।
ВЫ №U||4|-|I II|W4 ;•-( .Uli.И--I.. T.l-.M.’I U’MM'.Xi!1 i >' '•. I I • :. ki<\-.
! EXE и lain 11 и и и I \\ i\. m pt it -A >r.,t>ы . « IX ''•«• . t ». - =.
itilitii:ip Hiin.M p< JKe.Mt
152
Неофициальная Windows 95
DPMIINFO
RUNDOSX — всего лишь любопытный пример, но GOWIN386 \ COMMAND.СОМ — это уже
серьезно: расширенный режим MS DOS. Но в чем же состоит отличие между DOSX и WIN386?
Одна разница очевидна — размер файлов:
DOSX.EXE 32682 11-01-93 3:11а
WIN386.EXE 577557 11-01-93 3:11а
Что ж₽ такое делает WIN386, чего не делает DOSX, при том, что потребовало этих дополни-
тельных 540000 байт кода? Мы уже знаем, что WIN386, в отличие от DOSX, поддерживает более
одного DPMI-клиента, обеспечивает виртуальную память и загружает VxD. VxD, перечисленные на
рис. 6.1, занимают большую часть объема WIN386.EXE.
Чтобы получить более систематический взгляд на различия между WIN386 и DOSX, мы можем
использовать простую программу DPMIINFO, приведенную в листинге 6.3. Эта маленькая про-
грамма (которая снова использует библиотеку DPMISH) вызывает функцию 0400h прерывания ШТ
31h для получения такой информации об установленном DPMI-сервере, как поддерживает ли он 32-
битовые программы и отражает ли прерывания в реальный режим или режим V86.
ЛИСТИНГ 6.3. DPMIINFO. С
/*
DPMIINFO.С -- выводит информацию о DPMI-сервере I
Шульман, 1994 |
*/
«include <stdlib.h>
«include <stdio.h>
«include <dos.h>
«include “dpmish.h"
void fail(const char *s, ...) { puts(s); _exit(1); }
unsigned _dpmi_flags(void)
{
_asm mov ax, 0400h
_asm int 31h
_asm mov ax, bx
// результат в AX
}
unsigned _dpmi_version(void)
{
_asm mov ax, 0400h
_asm int 31h
// результат в AX
}
unsigned long _dpmi_mem(void)
{
unsigned long buf[12], far *fp = buf;
_asm push di
_asm mov ax, 0500h
_asm les di, fp
_asm int 31h _asm pop di return buf[0];
}
Глава 6. DOS защищенного режима: WIN386 и MS DPMI
153
static unsigned dpmi_32_flag * -1;
int real main(int argc, char *argv[])
// Этот флаг можно получить только из реального режима или режима V86, поэтому
// перед использованием этого флага нужно выяснить у DPMISH, действительно ли
// присутствует DPMI.
_asm puah si
_asm puah di
_asm mov ax, 1687h
_asm int 2Fh
_asm pop di
_asm pop si
_asm mov dpmi_32_flag, bx
return 0;
}
int pmode main(int argc, char *argv[J)
{
unsigned flags = _dpmi_flags();
unsigned vers = _dpmi_version();
unsigned char maj = vers » 8;
unsigned char min = vers & OxFF;
if((maj == 0) && (min == 0x90)) min = 90; // вот такая вот ошибочка
| printf("DPMI версии %d.%02d\n", maj, min);
' printf(“Поддерживаются %s программы\п",
(flags & 1) ? "32-битовые": "только 16-битовые");
printf("DPMI на базе %d-ro процессора \n", (flags & 1) ? 386 : 286);
printf(“npepHBaHHS отображаются в %s\n",
(flags & 2) ? “реальный режим": “режим V86");
printf(“%s виртуальная память\п”,
(flags & 4) ? “Поддерживается”: “Не поддерживается");
printf(“Доступно %1и байт в наибольшем блоке памяти\п", dpmi mem());
}
На рис. 6.2 приведен результат работы DPMIINFO как для DOSX, так и для WIN386. Как вы
видите, оба DPMI-сервера поддерживают DPMI версии 0.90. Однако WIN386 гораздо мощнее
DOSX: она поддерживает 32-битовыё программы, перенаправляет обработку прерываний в V86, а
не в реальный режим (см. главы 9 и 10, чтобы окончательно убедиться в этом) и поддерживает
виртуальную память.
C:\UNAUTHW>rundosx dpmiinfo.exe
DPMI версии 0.90
Поддерживаются только 16-битовые программы
DPMI на базе 286-го процессора
Прерывания отображаются в реальный режим
Не поддерживается виртуальная память
Доступно 11104192 байт в наибольшем блоке памяти
C:\UNAUTHW>gowin386 dpmiinfo.exe
DPMI версии 0.90
Поддерживаются 32-битовые программы
DPMI на базе 386-го процессора
Прерывания отображаются в режим V86
Поддерживается виртуальная память
Доступно 43909120 байт в наибольшем блоке памяти
154
Неофициальная Windows95
C:\UNAUTtfliOdpmiinfo.exe
DPMI версии D.90
Поддерживаются 32-битовые программы
DPMI на базе 386-го процессора
Прерывания отображаются в режим V86
Поддерживается виртуальная память
Доступно 21508096 байт непрерывного участка памяти
Рис. 6.2. Запуск DPMIINFO под DOSX, WIN386 и QEMM показывает различия между этими средами
Обратите внимание, что в дополнение к RUNDOSX й GOWIN386 на рис. 6.2 также представлен
результат запуска DPMIINFO непосредственно из командной строки DOS. Для этого теста был за-
гружен QEMM 7.03. Как видно, DPMI-сервер в QEMM очень похож на аналогичный в WIN386.
Он даже поддерживает виртуальную память (через файл DPMI.SWP), размер которой был установ-
лен равным 10 Мбайт:
DEVICE=C:\QEMM\QDPMI SWAPFILE=DPMI.SWP SWAPSIZE=10240
QEMM также включает расширитель DOS фирмы Ergo Computing. 386MAX имеет подобные
характеристики (но поддерживает DPMI версии 1.0).
i WIN3S6 — действительно менеджер памяти
' Ч1Ш1 .1 ; 10 ! ‘ . 1 : М ?•!;>!. ;
• ч* lit,1.1л р !•...•!.•:> ! ! ‘5 Ч и •= , > ч-.ч .-- ’ 1 '•> . i •• :
I запуска WIN386 у вас уже был запущен драйвер EMS. и даже несмотря на то. что для за-
пуска WIN386 необходим драйвер XMS.
||В1ИЯ||||ЙМ1И1ИВ1яИИвИИИ||!|вМ
; н.-р. д WIS.J4G BiHipi! W IS.W.
Без EMM386;
|ИИ|1|М|||1||^^
1ИИ1И||М
11|1|1ИИИв1!И1ЯИ111вв111И11И1111^^
> :е*.р.!-.лас! . 410 .,(1Ц< КИНГ Ч ”1,. ... Ц...О -| • •:• >•. • • .. - -.
||!||^^И^^^^^^^И11ИВИ111И1И1В1ИИИ11 illiliilllieiilliilililillilie
Глава 6. DOS защищенного режима: WIN386 и MS DPMI 155
! U-ЧКИ 1;к1(1!л ’r>ll.i:'P)|1i.: щч:," ''!?.-> • '. •.'!’' !•' Ч'-.-Л, .' •:. «!: ./-^'. Р”4*
памяти. H.tiipi!v< р. .ia<v <<--»: щи-.»;- '< :'- ' ':.•. ..»> ,.• WithUr,.,
рЗбо'ГЛЮЩГЙ 4Vl !’..;>« х Г.Ы Гч,.*,'. .’.t Ч> >.!:;:.> >. !-'..!>:=: •.»i;,’,. .X.'-'is ” !>
'Гот 'jiUKi. •.!> \ *••«' !?•*( .1- ...-'••:•; !’ i ч"' I, . Г. ,-,<-ИИ» Л
мсигджг|Я|Ц намят, h>;>.ki наюн .ы рч .•> -л.^. . » -j’ \ .т 'л :-д.<.л> *. i‘ .:
! >и><*уж.(алась <'11саи||>иьа>1Н» нмпо:'? ;> • !'/-i. Г' *М ;«>!« >т :.•,:< г'ч ни <
| мпкажсрпм VSfiMMGR всех футат1 ••-., lan-- ! * >S и \'t‘< ; •.
• It<X'teariния ,List p.16.4а.-.чччч !<R, i-.p--: .:< ; ул ' .. '. s .>
i р.|6»»гы UJS3.46 ' VSGMMGR = <: .•.•„.: ry- .: .-:у>
I Н».1ЬК«» ДЛЯ При’ р.гЧМ. n.i/H-iahUhli'i ;-. . .- ,. .< i??;
! '>(•! ГНЛЪЕП 11ДИ1» И I >•!•«' a,. r:s <-j • 1 ’ •’: ?•.::' v
I загруженных до нее. Как будет показано н главах 9 и !0, утверждение, будто Winona .> - эго
H,'VlLT|kHiKa !',!.! ’> Я ц H-lsi- •••!• ч.,...-. : .. _ >, = ,. -ч.-дц.
Матом<*: > л':,; к -..'.isy, <..!;> от на ' ; от I : Н •.,;;•.:?;•... ; :.,;;. л
< Davin Lonj’J "TSR Si;ppe»i >e 'Гд-ютпн д д.д,,. • ...........: t ; .. гт, чрн • : . *ъ
: G1H41 EMS X.MS ( Ч.-;,;" .о, от>. •: i I'r
за! ру ;кс:н!ым \\ пл'.-ч» ; .ч.: -ч н. • н, с,-п, <-hi-:;;*:: 1 '1.'- ХМ4
И11МИМ1||И||М^1ИМ1|||[И|И11|ИИ
I Обратно пии.-ааш 1 .. .tr •• '., •.- '•> \’. р--.: •'• \ ’ Г ч« а .> . •’!) t ,
работающий ХМS-драйвер во время запуска WIN38S имеет версию 3.0, то она будет меняться в
ароцслт* pa’’«>!i>. !'< i”i X'.!;. . ч-,у,- , п. . - . 1»,.’О
Ник.1К,в| hnк::а." Х';4 3 ч, < .. .«... . ’.-'J
И1ЯИ1^^и^^^^ЙЯ1И1М111И1!1ИИ1ИИ1ИЯИИ1И1И111111ВЯ11ИЯИ11Ив1^
Раз менеджеры памяти обеспечивают среду, подобную WIN386 (и наоборот), непонятно, что за
суета возникает вокруг запуска GOWIN386 \COMMAND.COM? Можно ли говорить о какой-то
заготовке для будущей DOS, если она ничем принципиально не отличается от того, что обеспечивает
менеджеры памяти независимых фирм? Как вы видели, при загруженном 386МАХ или QEMM
DPMI-сервере, USEDPMI2 и MEMLOOP можно запускать так же, как из GOWIN386
\COMMAND.COM. Действительно, эти менеджеры памяти изменяют операционную систему
серьезнее, чем это предполагают многие программисты.
Однако существует один решающий пункт, который менеджеры памяти не обеспечивают —
VxD. Пока это еще не очевидно, но, как вы убедитесь по ходу чтения этой книги, VxD, представ-
ленные на рис. 6.1, в особенности VMM, образуют полностью новую операционную систему. Менед-
жеры памяти не имеют VxD, хотя интерфейс Cloaking API фирмы Helix Software обеспечивает
нечто аналогичное. Cloaking — это метод переноса системного обеспечения BIOS и DOS в защи-
щенный режим. Например, Helix предоставляет версии защищенного режима системной BIOS и
видео-BIOS, Microsoft CD-ROM Extention (MSCDEX), драйвера мыши Logitech, кэша диска/CD-
ROM и RAM-диска. Это программное обеспечение защищенного режима может работать под
менеджером памяти NetRoom фирмы Helix или, используя Cloaking-драйвер, под любым другим
менеджером памяти. Другим API для переноса системного программного обеспечения DOS в защи-
щенный режим является DOS Protected Mode Servises (DPMS) фирмы Novell, используемый в
Novell DOS 7 для Stacker, NWCACHE, DELWATCH и сервера Personal NetWare.
Однако такие API, как Cloaking и DPMS, используются (пока что) только для переноса
отдельных системных средств в защищенный режим, a VxD фирмы Microsoft могут переместить в
защищенный режим весь фундамент PC/BIOS/DOS. Тем не менее нужно понимать, что даже в
Windows 95 Microsoft должна проделать долгий путь, прежде чем реально сможет утверждать, что
полностью все ядро системы PC перенесено в защищенный режим. К тому же до конца непонятно,
действительно ли необходимо на все 100% создавать новый код защищенного режима. MS DOS
156
Неофициальная Windows 95
имеет набор известных проблем, но, по крайней мере, нам эти проблемы известны. Тем более, что в
главе 9 мы увидим, как Windows в любом случае способна выполнять старый код реального режима
DOS в абсолютно контролируемой среде V86.
Если вам кажется, что я вешаю лапшу — утверждая, что VxD замечательны, потому что они
способны дать нам полностью работающую в защищенном режиме архитектуру PC, но Microsoft все
еще далека от этой цели, и что вовсе не очевидно, будто к этому нужно стремиться, и что Windows,
даже как промежуточный этап, способна фундаментально изменить архитектуру PC — то это пото-
му, что подобные вещи стремительно меняются прямо у нас на глазах. Сейчас самое главное оп-
ределить направление, в котором VxD изменяют архитектуру PC.
MSDPMI
Вычленение WIN386 из Windows и использование ее для запуска COMMAND.COM дает
прекрасную картину будущего DOS и помогает объяснить, что имеет в виду Microsoft, утверждая,
что будущие версии DOS будут сделаны по технологии защищенного режима. Фактически, трюк с
GOWIN386 \COMMAND.COM очень близок к тому, что сделала сама Microsoft несколько лет
назад, назвав его MSDPMI.
Вы никогда не слышали о MSDPMI? Хорошо, попробуйте запустить Windows из GOWIN386
\COMMAND.COM:
C:\UNAUTHW>gowin386 \command.com
C:\UNAUTHW>win \
The MS-DOS Protected Mode Interface (MSDPMI) is running on this |
computer. You cannot start Windows when it is running. To quit i
the MSDPMI, type exit and then press Enter. :
MSDPMI недолгое время был DPMI-сервером и расширителем DOS фирмы Microsoft для бета-
версии Microsoft C/C++ 7.0 (MSC7). Практически это была WIN386.EXE, переименованная в
DPMI.EXE и переделанная для запуска COMMAND.COM вместо KRNL386.EXE. Microsoft пред-
назначала его для MSC7, которому были необходимы DPMI-сервер и расширитель DOS. Требо-
валось загрузить более 500К системного обеспечения только для запуска компилятора. Поэтому
Microsoft сочинила MSDPMI, вместо того, чтобы сопровождать каждый пакет MSC7 копией
386МАХ фирмы Qualitas.
Весьма странно, что WIN.COM (как в Windows 3.1, так и в Windows 95) вспоминает об этом
программном обеспечении, которое использовалось только в бета-версии и никогда не продавалось.
Однако в документации к библиотеке времени исполнения MSC7 имеются частые ссылки на
“расширитель DOS фирмы Microsoft”, так что, вероятно, Microsoft действительно планировала вы-
пускать MSDPMI как продукт.
По сути, Microsoft выпустила MSDPMI: это как раз то, что мы называем Windows.
Действительно, зачем бы Microsoft использовать для реализации этой технологии невыразительное
название MS DOS, когда она может дать (тем более, что прецедент есть) более яркое имя Windows.
В обоих случаях это одна и та же продукция, но, очевидно, Microsoft может заработать больше
денег, преподнося эту технологию под маркой Windows, чем описывая ее как новую версию DOS.
Почему WIN решает, что GOWIN386 \COMMAND.СОМ является MSDPMI? Дизассемблиро-
вание файла WIN. CNF, который используется для создания WIN.COM, дает нам следующий код:
4534:2002 db 'The MS-DOS Protected Mode Interface (MSDPMI) is
running on this computer. You cannot start Windows
when it is running.’, 0
4534:341C B8 1683 mov ax,1683h
4534:341F 33 DB xbr bx,bx
4534:3421 CD 2F int 2Fh ; Получить ID текущей виртуальной машины
4534:3423 8D 16 1FDF lea dx,cs:[IFDFh] ; ‘You are already running Win...’
Глава 6. DOS защищенного режима: WIN386 и MS DPMI
157
4534:3427 83 FB 01 cmp bx,1 ; Системная VM
4534:342A 75 20 jne short loc_ret_365
4534:342C 80 16 2002 lea dx,cs:[2002h] ; ‘The MS-DOS Protected Mode...’
4534:3430 EB 27 jmp short loc_ret_365
Windows вызывает функцию 1683h прерывания INT 2Fh (описанную в Windows DDK), предо-
ставляемую как сервис VMM и возвращающую в регистре ВХ идентификатор текущей VM. (За-
метьте, как близок номер этой He-DPMI-функции к функции 1687h, которая используется DPMI.
VMM не делает различия между DPMI и He-DPMI-функциями прерывания INT 2Fh, т.е. DPMI не
должен быть просто основанной на спецификации перекодировкой существующего VMM, подобно
тому, как XMS-спецификация была получена из реализации HIMEM.SYS.) Если функция 1683h
возвращает в ВХ число, неравное нулю, WIN знает, что Windows уже запущена, и то, что ВХ=1,
означает, что WIN запускается из системной VM. Таким образом WIN решает, что MSDPMI уже
работает.
Как показано на рис. 6.3, я включил такую же проверку в ISWIN.C.
«define SYSTEM.VM 1
#//....
unsigned short vm;
/* вызвать 2F/1683, чтобы проверить, работает ли DOS-приложение
в системной VM. Если это так, то либо используется какая-то взломанная версия
Windows типа MSDPMI, либо программа запускается из WINSTART.ВАТ */
...asm mov ах, 1683h
, _asm int 2Fh
_asm mov vm, bx
if(vm == SYSTEM.VM)
рг!п1Т(“Приложение DOS запущено в системной VM:
Это либо WINSTART.BAT, либо взломанная Windows!\n");
else
printf(“VM #%u\n", vm);
Рис. в.З. ISWIN.C, программа DOS, проверяющая присутствие Windows, также включает проверку на MSDPMI
Запуск ISWIN под GOWIN386 \COMMAND.COM выводит следующее сообщение:
C:\UNAUTHW>gowin386 /command.com
C:\UNAUTHW>iswin
Работает Windows 3.10 (или выше) в расширенном режиме
Приложение DOS запущено в системной VM: это либо WINSTART.BAT, либо взломанная Windows!
Обратите внимание на выражение “взломанная Windows”: это именно то, чем мы с вами зани-
маемся! Но заметьте также, что ISWIN, в отличие от WIN, упоминает о WINSTART.BAT. После
запуска системной VM, но перед запуском ядра Windows в расширенном режиме (а точнее, VxD
DOSMGR) ищет WINSTART.BAT. Это обычный командный файл DOS, который вы можете соз-
дать для запуска DOS-программ в системной VM. WINSTART.BAT обычно используется для
загрузки TSR, необходимых для приложений Windows, что предпочтительнее глобальной загрузки
TSR до Windows, занимающей память в каждой VM. WINSTART.BAT позволяет загрузить TSR
исключительно в системной VM.
Если запустить WIN из WINSTART.BAT под GOWIN386 \COMMAND.COM, то WIN будет
считать, что запущен MSDPMI:
C:\UNAUTHW>type winstart.bat
echo %0
win
iswin
():\UNAUTHW>gowin386 /command.com
159 Неофициальная Windows 95
C:\UNAUTHW>echO C:\UNAUTHW\EXPERM\WINSTART
C:\UNAUTHW\EXPERM\WINSTART
C:\UNAUTHW>win
The MS-DOS Protected Mode Interface (MSDPMI) is running on this
computer. You cannot start Windows when it is running. To quit
the MSDPMI, type exit and then press Enter.
Type Exit and press Enter to quit this MS-DOS prompt and
return to Windows.
Press ALT+TAB to switch to Windows or another application
C:\UNAUTHW>iswin
Работает Windows 3.10 (или выше) в расширенном режиме
Приложение DOS запущено в системной VM: это либо WINSTART.BAT, либо взломанная Windows!
Запуск WIN из WINSTART.BAT приводит к выдаче сообщения о MSDPMI только в том слу-
чае, если вы запустили WIN386.EXE, не запустив перед эти WIN. Если вы запускаете Windows
обычным образом из WIN.COM, вторая копия (запущенная из WINSTART.BAT), обращаясь к INT
2Fh функции 160Ah, обнаруживает первую и выдает более веское сообщение: “You are already
running Enhanced mode Windows” (Вы уже работаете в расширенном режиме Windows).
Впрочем, WIN корректна даже тогда, когда путает WINSTART.BAT и MSDPMI. В конце кон-
цов, если вы поместите COMMAND.COM в WINSTART и не будете набирать команду exit, то, по
существу, получаете MSDPMI, как будто переименовали COMMAND.COM в KRNL386.EXE. Все
это лишь различные пути получения расширенного режима Windows (низкоуровневой операционной
системы WIN386), но без Windows. Нам самом деле, мы получаем V86 DOS. Название Windows
просто лучше звучит.
Использование WIN386 как GOWIN386 или MSDPMI либо запуск COMMAND.COM из
WINSTART.BAT — не более чем эксперимент. Это вовсе не способ предоставления защищенного ре-
жима DOS потребителям. Но использование WIN386 в качестве MSDPMI действительно подчер-
кивает, насколько WIN386 независима от прочих системных компонентов Windows. Вся функцио-
нальность, которую вы видели в MSDPMI: режим V86, механизм виртуализации устройств, вирту-
альная память, сервис DPMI, 16- и 32-битовые расширители DOS и так далее — принадлежит
DOS. WIN386 на самом деле является частью MS DOS, если не брать во внимание того, что она
поставляется с Windows, и Microsoft в целях громкой рекламы решила назвать ее Windows 95.
Глава 6: DOS защищенного режима: WIN386 и MS DPMI
159
Глава 7
Откуда взялись
эти 32BFA и LFN?
Взяв WIN386.EXE из расширенного режима Windows и используя его для запуска
COMMAND.COM (маскирующегося под именем KRNL386.EXE), мы получим новый расши-
ренный режим MS DOS. Это, быть может, еще не совсем ясно из экспериментов, проводимых
в предыдущей главе. В конце концов, большая часть возможностей, доступных под GOWIN386,
доступна также и при использовании менеджера памяти. Это утверждение, вероятно, имеет смысл
еще раз повторить: менеджеры памяти сильнее отходят от стандартной DOS, чем представляется
большинству программистов. И все же, обеспечение DPMI, прерывания INT 21h в защищенном
режиме и даже интерфейс INT 21h в DOS для работы с виртуальной памятью в настоящее время
настолько очевидны и известны, что, видимо, уже не надо оправдывать существование окна DOS в
Windows как будущее DOS. Кроме обеспечения собственной версии (возможно, более примитивной)
XMS, они больше ничего не дают “обычным” DOS-программам.
В этой главе мы будем экспериментировать с конфигурацией WIN386, незначительно отличаю-
щейся от той, которую мы использовали в предыдущей главе, но чей отход от старой DOS гораздо
более очевиден.
На этом этапе мы возьмем 32-битовый (32BFA) доступ к файлам из WfW 3.11 и будем
использовать его для дальнейшего расширения DOS. Мы снова воспользуемся GOWIN386.BAT, но
теперь скопируем WIN386.EXE из WfW 3.11, а не из Windows 3.1, изменим слегка файл
SYSTEM.INI, чтобы заменить BLOCKDEV на IOS.386, и добавим несколько дополнительных VxD:
;;; device=*BLOCKDEV
device=ios. 386
device=ifsmgr. 386
device=vfat. 386
device=vcache. 386
device=vxdldr.386
device=vshare. 386
Вот эти новые VxD:
• IOS.386: супервизор ввода-вывода, расширенная версия BLOCKDEV из Windows 3.1, ко-
торая в свою очередь была расширенной версией VxD-драйвера виртуального жесткого диска
(VHD) из Windows 3.0.
• IFSMGR.386: менеджер инсталируемой файловой системы (IFS).
• VFAT.386: виртуальная файловая система FAT (странно, но VFAT.386 содержит строку
описания “Win386 HPFS (Prototype)”, аббревиатура HPFS (High Performance File System —
высокопроизводительная’файловая система) обычно относится к OS/2).
• V С ACHE.386: виртуальный файловый кэш (заменяет SMARTDRV.EXE).
• VXDLDR.386: загрузчик VxD (для динамической загрузки и выгрузки VxD, вроде драй-
вера отображения реального режима, RMM.D32).
• VSHARE.386: виртуальное разделение ресурсов (заменяет SHERE.EXE).
160
Неофициальная Windows 95
Эти VxD должны быть скопированы из того же самого каталога WfW 3.11, что и WIN386.EXE.
Они не будут загружаться WTN386.EXE из версии 3.1; если вы попытаетесь это сделать, то получи-
те сообщение “A device file specified in the SYSTEM.INI file is corrupted” (Файл устройства,
указанный в SYSTEM.INI, испорчен).
Нужно также скопировать драйвер отображения реального режима, RMM.D32, а также файл
SPART.PAR, который содержит указатель на постоянный файл обмена.
Наконец, 32BFA требует DOS-драйвера устройства, IFSHLP.SYS, который поставляется с
WfW 3.11. В CONFIG.SYS нужно поместить строку:
device=c:\wfw311\ifshlp.sys
Заметим, что если VxD IFSMGR (менеджер инсталируемой файловой системы) не может найти
IFSHLP (имя устройства IFS$HLP$), он просто не поддерживает 32BFA, не выдавая при этом
какого-либо сообщения. Почему же нет сообщения об ошибке? IFSMGR.386 в действительности
содержит код для выдачи сообщения “The Microsoft Installable File System Manager (IFSMGR)
cannot find the helper driver. Please ensure that IFSHLP.SYS has been installed” (IFSMGR не может
найти вспомогательный драйвер IFSHLP.SYS. Убедитесь, что он установлен) обработчику фаталь-
ных ошибок VMM, Fatal_Error_Hendler. Но, к сожалению, IFSMGR содержит ошибку при обраще-
нии к сервису VMM, и, вместо того, чтобы выйти по фатальной ошибке, IFSMGR завершает свою
работу с нулевым указателем на функцию IFSHLP. К счастью, VFAT VxD также проверяет наличие
IFSHLP.SYS и делает все правильно, сообщая WIN386, что не может ее загрузить (хотя и не выдает
никакого сообщения).
Установив IFSHLP.SYS, взяв WIN386.EXE из WfW 3.11, драйверы VxD и RMM.D32 и изме-
нив SYSTEM.INI, запустим GOWIN386 \COMMAND.COM. Среда, в которую мы попадаем при
выполнении этой команды, производит такое же впечатление, что и GOWIN386 на основе Windows
3.1, ив большей части аналогична. Но те дополнительные VxD, которые мы внесли в SYSTEM.INI,
дают потрясающий эффект. До тех пор, пока мы не выйдем из GOWIN386, ключевые части MS
DOS реального режима на самом деле будут заменяться 32-битовым кодом защищенного режима.
Что из того? Теперь, к примеру, ваш жесткий диск должен работать значительно быстрее, чем
прежде. Но это еще ничего не доказывает: возможно, VCACHE.386 — просто достаточно активный
кэш диска, подобный SMARTDRV.EXE, или SMARTDRV.EXE уже был загружен.
Нет, лучший способ понять, что такое 32-битовый доступ к файлам, — посмотреть, каким обра-
зом доступ к файлам под WIN386 переводится в вызовы файлового ввода-вывода, которые можно
отследить в реальном режиме MS DOS.
Давайте вернемся немного назад и посмотрим, каким образом взаимодействуют Windows и
DOS, когда 32BFA отключен. Выйдем из GOWIN386 в реальный режим DOS. (Можно заметить
разницу, потому что WIN386 вызывает неприятное мерцание экрана при выходе.) Из командной
строки DOS загрузим утилиту, регистрирующую вызовы файлового ввода-вывода INT 21h. Напри-
мер, я использовал утилиту INTRSPY из Undocumented DOS, вместе со сценарием FOPEN.SCR:
С:\UNOOCOOS>intrspy
C:\UNDOCDOS>cmpspy compile fopen
При этом регистрируются вызовы следующих функций прерывания INT 2 lh:
• 0FH (Открыть файл с помощью FCB);
• ЗСН (Создать файл);
• 3DH (Открыть файл);
• 4ВН (ЕХЕС);
• 4ЕН (Найти первый файл);
• 6СН (Расширенная функция открыть/создать).
Теперь запустим GOWIN386 с отключенным 32BFA. Это проще всего сделать, запустив конфи-
гурацию GOWIN386, базирующуюся на Windows 3.1, или временно отключив 32BFA в конфигу-
Глава 7. Откуда взялись эти 32BFA и LFN?161
6 Неофициальная Windows 95
рации WfW 3.11. Вы можете отключить 32BFA, поместив в SYSTEM.INI строку 32BitFileAccess=
false или запустив WIN /D.C. (Все равно, как вы будете отключать 32-битовьгй доступ к диску — с
помощью строки 32BitDiskAccess=false или запуском WIN /D:F.) Мы здесь не используем
WIN.COM, поэтому ключ /D:C нужно передать прямо WIN386.EXE. Сделаем это с помощью
GOWIN386, поместив /D:C после имени программы для запуска:
С:\UNAUTHW>gowin386 \coinniand. com /D:C
( Да, я знаю, что это глупо, потому что WIN386.EXE, а не COMMAND.COM, использует ключ
/D:C, но это всего лишь игрушечный командный файл и не более).
Дальше, из GOWIN386 сбрасываем буфер INTRSPY (тогда вы не увидите файлы, которые
открывала WlN386.EXE во время начального запуска):
C:\UNAUTHW>cmdspy flush
Наконец, сделаем что-нибудь, чтобы Вызвать множество действий по открытию файлов, например:
C:\UNAUTHW>copy con foo.bar
This is foo.bar
~Z
C:\UNAUTHW>copy con foo.bat
type foo.bar
foo
~Z
C:\UNAUTHW>foo
Позволим этому рекурсивному командному файлу выполняться несколько секунд и нажмем
<Ctrl+C>. Затем получим отчет INTRSPY:
C:\UNAUTHW>cmdspy report
XOPEN con >
FINO con
XOPEN CON
XOPEN foo.bar
XOPEN foo.bar
XOPEN con
FIND con
XOPEN CON
XOPEN foo.bat
XOPEN foo.bat
FIND foo.???
FIND C:\UNAUTHW\FOO.BAT
OPEN C:\UNAUTHW\FOO.BAT
XOPEN foo.bar
OPEN C:\UNAUTHW\FOO.BAT
FIND foo.???
FIND C:\UNAUTHW\FOO.BAT
OPEN C:\UNAUTHW\FOO.BAT
XOPEN foo.bar
OPEN C:\UNAUTHW\FOO.BAT
Вспомним, что мы загрузили INTRSPY перед запуском Windows, поэтому INTRSPY регистри-
ровала те обращения, которые Windows передавала DOS. Доступ к файлам по INT 21h в окне DOS
обслуживается DOS реального режима. Ну, а чего вы ожидали — чтобы Windows как-то сама
управляла доступом к файлам без обращения к DOS?
Теперь может показаться безумием то, что происходит при активизации 32BFA. Выйдем из
GOWIN386 в реальный режим DOS и снова запустим GOWIN386, но теперь активизируем 32BFA.
Например, перезапустим GOWIN386 \COMMAND.COM, но теперь без параметра /D:C. Затем
сбросим буфер INTRSPY, повторим тест FOO.BAR/FOO.BAT и получим отчет INTRSPY:
С.: \UNAUTHW>exit
C:\UNAUTHW>gowin386 \command.com
C:\UNAUTHW>cmdspy flush
C:\UNAUTHW>copy con foo.bar
162
Неофициальная Windows 95
C: \UNAUTHW>foo
; ... дадим командному файлу поработать несколько секунд ...
C:\UNAUTHW>cmd report
XOPEN con
FIND con
XOPEN CON
XOPEN con
FIND con
XOPEN CON
EXEC C:\UNAUTHW\CMDSPY.EXE report
Боже правый! Все, что INTRSPY смогла обнаружить, это открытия файлов при выполнении ко-
манд COPY CON и CMDSPY REPORT. Если уж INTRSPY чего-то не видела, то DOS (которая,
естественно, загружена до INTRSPY) и подавно не могла этого заметить. Операции над
FOO.BAR/FOO.BAT не дошли до DOS! Все они были обработаны в 32-битовом защищенном ре-
жиме на уровне VxD.
Правильно, хоть наш эксперимент не так уж сильно отличается от версии GOWIN386 из
Windows 3.1, мы получили что-то, очень близкое к 32-битовой операционной системе защищенного
режима.
Было бы неплохо иметь менее эмпирический тест для 32BFA, чем простое отслеживание обра-
щений к INT 21h, которые обрабатываются DOS реального режима. Проверка наличия IFSMGR,
VFAT и VCACHE (возможно, с помощью их V86 API, доступных через функцию 1684h прерывания
INT 2Fh) выглядит неплохо, но выключение 32BFA не предотвращает загрузку этих VxD. Таким
образом, хотя отсутствие этих VxD могло бы указать вам, что 32BFA выключен, их присутствие
еще не гарантирует, что 32BFA включен.
Каким же еще образом можно узнать, что VxD 32BFA подменяют MS DOS, кроме использо-
вания программы вроде INTRSPY? Можно использовать программу TEST21 из главы 8. Из резуль-
татов ее работы видно, что функции File Open (3Dh), File Close (3Eh) и File Read (3Fh) обрабаты-
вались без обращения к DOS. Знак минус (-) в конце трех соответствующих строк указывает, что
было зарегистрировано меньше обращений, чем сгенерировано. Это в свою очередь означает (как
TEST21 указывает в последней строке вывода), что “Некоторые-INT 21h обрабатываются без вызова
DOS!” Этот тест подробно объясняется в главе 8.
C:\UNAUTHW>test21
Сгенерировано 301 вызовов Перехвачено 1 вызовов
21/25 1 вызвано 1 перехвачено
21/3D 100 вызвано 0 перехвачено -
21/ЗЕ 100 вызвано 0 перехвачено -
21/3F 100 вызвано 0 перехвачено -
Некоторые INT 21h обрабатываются без вызова DOS!
Добавив несколько VxD к нашему GOWIN386, мы получим новый тип DOS 32-битового защи-
щенного режима. И нам не нужны никакие компоненты Windows GUI: этот трюк с GOWIN386 по-
лучился целиком за счет VxD. (Ну, не учитывая DOS-драйвер IFSHLP.SYS).
Просто удивительно, что Microsoft выпустила 32BFA как незначительную модернизацию
(WfW3.ll), ведь он представляет собой гораздо более существенные изменения, чем те возмож-
ности, для которых Microsoft сочла допустимым увеличить старший номер версии DOS (как,
например, DOS=HIGH в DOS 5.0 и DoubleSpace в DOS 6.0). Это первый верный признак превра-
щения DOS в 32-битовую операционную систему защищенного режима. С другой стороны, это тот
редкий случай, когда Microsoft пообещала мало, а сделала много.
Глава 7. Откуда взялись эти 32BFA и LFN?
163
Вперед к WINDOWS 95: VMM32
Мы уже видели постепенное развитие возможностей от DOSX в главе 5 через WIN386 в главе 6
к WIN386 с 32-битовым доступом к файлам в этой главе. Наша конечная остановка — VMM32.
VXD, который является операционной системой в Windows 95 (так же, как WIN386.EXE является
операционной системой в Windows 3.x). Давайте вначале заглянем с помощью утилиты W3MAP
внутрь VMM32.VXD. Результаты показаны на рис. 7.1.
С:\UNAUTHW>w3map \windows\system\vmm32.vxd
W3 00010000
VMM 00011000
VDD 00058000
VFLATD 00063000
ENABLE 00065000
VSHARE D006d000
VWIN32 00071000
VFBACKUP 0007C000
VCOMM 00080000
COMBUFF 00089000
VCD 0008C000
IFSMGR 00091000
IOS OOObOOOO
SPOOLER OOObeOOO
VFAT 000c5000
VCACHE 000d1000
VCONO 000d4000
VCDFSD OOOddOOO
INT13 000e2000
VXDLDR 000e5000
VDEF OOOecOOO
DYNAPAGE OOOefOOO
CONFIGMG 000F4000
EBIOS 00104000
VMD 00109000
DOSNET 0010d000
VPICD 00111000
VID 0011c000
REBOOT 00124000
VOMAO 00129000
VSD 00132000
V86MMGR 00134000
PAGESWAP 0014b000
DOSMGR 0014f000
VMPOLL 00163000
SHELL 0016b000
PARITY 0017a000
BIOSXLAT 0017d000
VMCPD 00182000
VTDAPI 00185000
PERF 0018a000
VMOUSE 0018f000
VPO 00197000
VKD 0019c000
VPOWERD 001a8000
Рис. 7.1. Утилита W3MAP ьыгодит на экран информацию о драйверах виртуальных устройств, содержащихся в файле фор-
мата W3. Использование этой утилиты для проверки VMM32.VXD показывает, что VMM32 содержит в себе больше 40 VxD
164
Неофициальная Windows 95
Некоторые из этих VxD появились в Windows 95 впервые. Например, VWIN32 обеспечивает
сервис Win32-приложениям, VCOND представляет виртуальную консоль (Virtual CON Device).
W3MAP имеет несколько опций командной строки, которые позволяют получить дополнитель-
ную информацию об отдельных VxD, встроенных в исполняемые модули формата W3, каковыми
являются, например, VMM32.VXD или WIN386.EXE. На рис. 7.2 приведен один такой пример,
взятый более или менее наугад.
C:\UNAUTHW\W3MAP>w3map -vxd vxdldr \windows\system\vmm32.vxd
Module name: VXDLDR
VXDLDR_DDB @ 00000068
Real-mode Init @'0000:00000000
Device # 0027
Dynamic VxD Loader
Version 3.00
Init order: 16000000
DDB_Control_Proc @ 00000000
DDB_V86_API_Proc @ 000021C9
DDD_PM_API_Proc @ 000021C9
DDB_Service_Table @ 0000002c (of services)
270000 @ OOOOOOId VXDLDR_Get_Version
270001 @ 00000000 VXDLDR_LoadDevice
270002 @ 000000Ы VXDLDRJJnloadDevice
270003 @ 00000007 VXDLDR_DevInitSucceeded
270004 @ 00001137 VXDLDR_DevInitFailed
270005 @ 00000024 VXDLDR_GetDeviceList
270006 @ 0000219с VXDLDR_UnloadMe
270007 @ 00002220 PELDR_LoadModule
270008 @ 00002860 PELDR_GetModuleHandle
270009 @ 000028Ь0 PELDR_GetModuleUsage
27000a @ 000028е0 PELDR_GetEnt ryPoint
27000b @ 00002910 PELDR_GetProcAddress
27000с @ 000029f0 PELDR_AddExportTable
27000d @ 00002ad0 PELDR_RemoveExpo rtT able
27000е @ 00002Ь90 PELDR_FreeModule
Рис. 7.2. Чтобы получить дополнительную информацию по конкретному VxD в файле W3, используйте опцию -VXD,
как показано здесь. Чтобы получить подробную информацию по всем VxD, используйте опцию -VERBOSE
Сервисы VXDLDR_LoadDevice и VXDLDR_UnloadDevice используются как часть 32-битового
доступа к диску в Windows. Кроме того, согласно документации Microsoft, архитектура “Plug and
Play” основана на динамически загружаемых VxD. Когда система запускается, она определяет, ка-
кие устройства присутствуют, и использует загрузчик VxD для подгрузки драйверов для этих
устройств. Из вывода программы W3MAP видно, что драйвер VXDLDR также предоставляет набор
средств PELDR, позволяющих VxD динамически компоноваться с портативными исполняемыми
(Portable Executable — РЕ) модулями Win32.
Так же как и в предыдущих случаях, мы можем взять VMM32.VXD и запустить его без Win-
dows GUI. Фактически, это в точности та конфигурация, которую я месяцами использовал на ран-
них стадиях тестирования пред-бета-версии Chicago. Предварительная бета-версия Chicago конца
1993 г, не могла работать на моей машине, но выяснилось это только по ходу инициализации. Это;
возможно, означало, что нижний уровень VMM/VxD мог бы работать, несмотря на то, что верх-
ний, графический уровень не запускался. Иметь возможность тестировать компоненты VMM и VxD
из Chicago лучше, чем ничего не иметь, поэтому я переименовал KRNL386.EXE в KRNL386.SAV и
скопировал COMMAND.COM в KRNL386.EXE. Таким образом, набирая WIN, я с помощью DOS
386 (как VMM/VxD-части Chicago) запускал COMMAND.COM. Получилась V86-eepcHH DOS 7,
которая работала так же, как и в наших экспериментах с GOWIN386.
Глава 7. Откуда взялись эти 32BFA и LFN?
165
Ho Windows 95t выглядит такой монолитной, такой интегрированной! Каким образом VMM/
VxD-часть Windows 95 смогла прорваться наружу и использоваться для запуска чего-то другого, а
не части GUI Windows 95?
Случайному пользователю Windows 95 кажется, что Windows загружается мгновенно, как толь-
ко он включает машину. Вместо сообщения “Starting MS-DOS...”, которое MS DOS 5 и б выводят
на экран при загрузке, в той же точке начальной загрузки Windows 95 выведет на экран “Starting
Windows...”. Эта незначительная мелочь производит небольшой, но важный эффект. Она заставляет
среднего пользователя почувствовать, будто Windows заменила собой MS DOS и уже ничто больше
не стоит между Windows и непосредственно машиной.
Да, когда вы включаете компьютер под Windows 95, он сообщает “Starting Windows...”, но, как
мы видели в главе 1, это почти тот же самый старый код реального режима DOS, который работал
на этом этапе загрузки. На самом деле, эта часть Windows 95 является всего лишь MS DOS 90: MS
DOS никуда не делась. А непосредственная загрузка Chicago монолитной (“бесшовной”) Windows
оказывается шитой белыми нитками.
То ли автоматически загружаемый из WINBOOT.SYS, то ли из COMMAND.COM, или же за-
пущенный традиционным способом из командной строки WIN.COM запускает VMM32.VXD, так
же, как WIN.COM в Windows 3.x запускал DOSX или WIN386. Никакой магии здесь нет. Ника-
кой, за исключением магии упаковки. Взяв то, что было всегда частью MS DOS — код реального
режима, содержащийся в файлах IO.SYS и MSDOS.SYS, и снабдив его более привлекательной,
более молодой, менее заезженной маркой Windows (и Windows 95, в том числе), Microsoft мудро
извлекла выгоду из того, что в течение ряда лет было предметом непонимания: разделение труда
между DOS и Windows. Где точно заканчивалась DOS и начиналась Windows? Теперь Microsoft бе-
рет все свое системное программное обеспечение для PC и называет его Windows. Новая упаковка!
Вот в чем весь фокус!
Хотя Windows 95, по крайней мере поверхностно, кажется монолитной (“бесшовной”) и хотя
Microsoft теперь, кажется, рассматривает реальный режим DOS как часть Windows (что, опять
таки, подтверждается сообщением “Starting Windows...”, которое Windows 95 выводит на экран,
когда вы запускаете то, что до сих пор называлось MS DOS), факт остается фактом — Windows 95
состоит из нескольких отдельных полу-независимых кусков, которые для формирования Windows
объединяются вместе таким же образом, как и раньше.
Это означает, что, даже в Windows 95, можно взять слой VMM/VxD и использовать его для
создания маленькой, замкнутой версии DOS режима V86. Если Microsoft запросто объявляет части
реального режима DOS частью Windows, то мы, с тем же успехом, можем заявить, что определен-
’ные элементы Windows на самом деле являются частью DOS. Вспомним утверждение бывшего слу-
жащего Microsoft, приведенное в предыдущей главе: “WIN386, в действительности, даже не часть
Windows”. То же самое справедливо и для VMM32.VXD. Итак, давайте используем VMM32.VXD
для создания версии DOS, которая поддерживает 32-битовый доступ и длинные имена файлов.
На моем компьютере инициализация Chicago создала дерево каталогов, содержащее больше
1000 файлов. В этой Земле Тысячи Файлов я хотел найти минимальное множество файлов Chicago
для создания конфигурации DOS386/VMM32 без GUI. Для DOS386 мне требовалось около 50
файлов для получения чего-нибудь работающего. Для VMM32 требуется даже меньшее количество.
Хотя полная установка Windows 95 гораздо более полезна, чем та минимальная, которую я
здесь описываю, поиск минимального количества файлов, необходимых для обеспечения 32-битовбго
доступа к файлам, длинных имен файлов или некоторых других функциональных возможностей
Windows 95 вполне поучителен, поскольку дает реальное представление о том, что же составляет
ядро Windows 95.
Вот, что нужно взять, чтобы получить небольшую замкнутую конфигурацию VMM32.
• VMM32 требует DOS 7 или выше. При запуске VMM32 в DOS 5 или 6 выдается сооб-
щение "Cannot run Windows with the installed version of MS-DOS. Upgrade MS-DOS to a
version that is at least 7.0" (Невозможно запустить Windows с установленной версией MS
DOS. Обновите MS DOS до версии не ниже 7.0). Попытка обмануть VMM32.VXD с
помощью SETVER не проходит.
166
Неофициальная Windows 95
• Так же как в случае с WfW 3.11, нужно, чтобы CONFIG.SYS содержал строку DEVICE=
. IFSHLP.SYS. Но при запуске Windows 95 убедитесь, что вы используете ту версию
IFSHLP.SYS, которая поставляется вместе с Windows 95. Версия для WfW 3.11 не знает о
новых функциях INT 21h поддержки длинных имен файлов (LFN).
• Файл реестра SYSTEM.DAT должен находиться в корневом каталоге. Если VMM32 не мо-
жет найти SYSTEM.DAT, она все же запускается, но выводит на экран предупреждение
"Registry File was not found. Registry services may be inoperative for this session" (Файл
реестра не найден. Сервисы реестра могут быть недоступны в этом сеансе).
• Естественно, вам потребуется сам VMM32.VXD. Программа установки Windows 95 фор-
мирует этот файл, упаковывая вместе все VxD (включая тот, который назывался
VMM32.VXD), необходимые для вашей конкретной конфигурации, в один единственный
файл (также называемый VMM32.VXD). Он может занимать около 1700 Кбайт —
значительно больше, чем WIN386.EXE, и содержит среди всего прочего VMM, который
предоставляет гораздо больше сервиса, чем VMM в WIN386 (см. главу 2).
• Вам нужна программа, играющая роль KRNL386.EXE, например, COMMAND.COM.
Вы столкнетесь с некоторыми дополнительными требованиями, когда проработаете остаток гла-
вы, но пока этого достаточно.
Я создал подкаталог VMM32 и скопировал в него два файла:
C:\md vmm32
C:\cd vmm32
C:\VMM32>copy \windows\system\vmm32.vxd vmm32.exe
C:\VMM32>copy \command.com krnl386.exe
C:\VMM32>vmm32
Уже при наличии только этих двух файлов VMM32 заработал, по крайней мере в том смысле,
что я попал в приглашение DOS, из которого можно было запускать программы DPMI. Совершенно
це требовалось дополнительных VxD (программа установки поместила все необходимые VxD в
VMM32.VXD). Не было даже файла SYSTEM.INI.
Постойте! Ведь “родной” каталог Chicago никуда не делся, следовательно, VMM32 взял
SYSTEM.INI оттуда. Это могло привести к чему угодно. Оказывается, VMM32 все-таки нужен файл
SYSTEM.INI.
Однако способ создания минимальной рабочей конфигурации все же существует: запустим
VMM32 в отказобезопасном режиме (fail-safe mode). Это полезная возможность, которая позволяет
загрузить Windows 95, даже если ваши файлы конфигурации безнадежно испорчены. Отказо-
безопасный режим можно вызвать нажатием <F5> во время загрузки машины. Можно также выйти
в отказобезопасный режим, запустив WIN /D:M. WIN.COM передает параметр /D:M VMM32,
поэтому можно сразу запустить VMM32 /D:M.
Это работает! Теперь с VMM32.VXD (переименованным в VMM32.EXE, чтобы я мог запустить
его из командной строки DOS) и COMMAND.COM (замаскированным под KRNL386.EXE) я загру-
зил самый нижний уровень Windows 95. Никакого GUI, никаких INI- файлов, абсолютно ничего, за
исключением VMM32 и COMMAND.COM. В этой взломанной среде Windows 95 я мог бы
запускать обычные тестовые DPMI-программы:
C:\VMM32>vmm32 /0: М
С:\VMM32>dpmitest
CS=21AAh DS=237Bh
CS=0097h DS=008Fh
CS base=00021AA0 limit=OOOOFFFF
DS base=000237B0 limit=OOOOFFFF
C:\VMM32>dpmiinfo
DPMI версии 0.90
Поддерживаются 32-битовые программы
DPMI на базе 386-го процессора
Глава 7. Откуда взялись эти 32BFA и LFN?
167
Прерывания отображаются в режим V86
Поддерживается виртуальная память
Доступно 4014080 байт непрерывного участка памяти
Опять же, в этой конкретной конфигурации нет ничего полезного, сравнимого с полноценной
Windows 95. Зато, экспериментируя с этой и другими урезанными версиями, можно многое узнать о
Windows 95.
Например, я был совершенно удивлен, обнаружив возможность выхода в реальный режим DOS.
Способность к выходу из Windows в DOS кажется очевидной и неинтересной. Однако, когда вы
запускаете Windows 95 обычным способом, набрав WIN вместо VMM32, выход из Windows не воз-
вращает вас обратно в DOS. Вместо этого у вас появляются три альтернативы: перезагрузить Win-
dows, выключить машину или же нажать <Ctrl+Alt+Del>. Запуск VMM32 позволяет возвращаться
в DOS реального режима (компонент Windows 95), и для предотвращения этого нужен WIN.COM.
Раз некоторые VxD из Windows 95 могут основываться на невозможности выхода в DOS при
завершении (возможно, какие-то VxD исправляют код DOS и, стартуя в Windows 95, не беспоко-
ятся о последующем восстановлении исправленного кода при выходе из Windows), вы могли бы
думать, что такое предотвращение выхода должно быть более интегрировано в Windows. С другой
стороны, существование в Windows 95 режима единичного приложения (Single Application Mode),
быть может, означает, что все VxD должны, в любом случае, быть готовы правильно восстанав-
ливать затертые данные. Однако в этом случае нет технически разумного обоснования запрету
выхода в DOS со стороны WIN.COM. Это только попытка Microsoft представить Windows 95 как
единое целое с PC.
Чтобы немного упростить тестирование этой минимальной среды VMM32, я создал другой ко-
мандный файл, RUNVMM32, показанный в листинге 7.1.
ЛИСТИНГ 7.1. RUNVMM32. ВАТ
@echo off
rem renvmm32.bat
set path=\dos;\bin;\eps;\borlandc\bin
if (%1)==() goto usage
if exist vmm32.exe goto have_vmm32
copy \windows\system\vmm32.vxd vmm32.exe >nul
if not exist vmm32.exe goto cant_get
:have_vmm32
copy % krnl386.exe >nul
vmm32 %2 %3 %4 %5 ‘
cis
echo Back in DOS
goto done
:cant_get
echo Can’t find \WIN00WS\SYSTEM\VMM32.VXD
goto done
:usage
echo usage: RUNVMM32 [program] [args to VMM32]
:done
echo.
Например, чтобы создать минимальную VMM32-cpe,oy, описанную раньше, необходимо набрать:
C:\VMM32>runvmm32 \command.com /D:M
При этом ваш жесткий диск несколько секунд поработает, после чего вы получаете приглашение
С:\>, из которого можете запускать множество программ, иначе DOS не поддерживаемых. Мы
уже видели, что DPMITEST и DPMIINFO правильно работают в этой среде. Другой хороший тест —
программа MEMLOOP:
168
Неофициальная Windows 95
С: \VMM32>nfemloop
Выделено 8178 Кбайт в 257 блоках
Хм, выглядит в общем не очень правильно! Вспомним (глава 6), что MEMLOOP, запускаясь
под WIN386.EXE из WfW 3.11 на этой же машине, могла распределить 37 Мбайт. Почему же толь-
ко 8 Мбайт удается распределить с помощью более усовершенствованного VMM32 из Windows 95?
[386Enh]
PagingFile=H:\386SPART.PAR
MinPaginFileSize=4075
32BitDiskAccess=on
32BitFileAccess=on
OverlappedIO=on
VirtualHDIRQ=true
FileSysChange=off
maxbps=512
mouse=*vmouse
ebios=*ebios
display=*vdd
keyboard=*vkd
device=vfd.vxd
device=c:\unauthw\generic\vxd.386
device=*int13
device=*vpicd
device=*reboot
device=*vdmad »
device=*vsd
device=*v86mmgr
device=*pageswap
device=*dosmgr
device~*vmpoll
device=*parity
device=*biosxlat
device=*vcd
device=*vmcpd
device=*combuff
device=«enable
device=*vshare
device=*vwin32
device=*vfbackup
device=*vcomm
device=*ifsmgr
device=*ios
device=*spooler
device=*vfat
device=*vcache
device=*vcond
device=*vcdfsd
device=*vxdldr
device=*vdef
device=*dynapage
device=*vtd
device=*shell
device=*vtdapi
device=*perf
device=*vpd
device=«vpowerd
Рис. 7.3. Файл SYSTEM.INI, настроенный для тестирования минимальной среды VMM32
Глава 7. Откуда взялись эти 32BFA и LFN?
169
Вообще-то, это похоже на некоторую особенность (и, притом, весьма разумную) отказобезо-
пасного режима, при которой VMM32 будет использовать находящийся на первом жестком диске
файл подкачки (386SPART.PAR) и не увеличит его размеры сверх текущих.
Поэтому давайте больше не будем использовать этот отказобезопасный режим. Для этого нам
будет нужен файл SYSTEM.INI. Созданный мной SYSTEM.INI показан на рис. 7.3.
Кроме VxD, встроенных в VMM32.VXD, я здесь также загружаю два дополнительных VxD:
VFD.VXD, поскольку его требует VBACKUP, и VXD.386 — мой собственный VxD, который нам
пригодится для экспериментов с VMM32. Обратите внимание, что в SYSTEM.INI также явно указы-
вается файл обмена.
С таким SYSTEM.INI объем памяти, выделяемый MEMLOOP, определяется размером доступ-
ного пространства на диске, где находится файл обмена. К сожалению, у меня уже не было столько
же свободного места, как во время тестирования WfW 3.11, но увидеть, что MEMLOOP на моей
машине с 12 Мбайт памяти использует виртуальную память из файла обмена, все же можно:
С:\VMM32>memloop
Выделено 16866 Кбайт в 528 блоках
Как показано на рис. 7.4, запуск программы VXDLIST выявляет, что минимальную конфигу-
рацию образуют 43 VxD. Между прочим, под VMM32 VXDLIST использует новый сервис VMM
(VMM_GetDDBList) для поиска начала цепочки VxD. Она также мог бы использовать сервис
VXDLDR_GetDeviceList, показанный на рис. 7.2 при запуске W3MAP -VXD VXDLDR.
VXDLIST version 1.20
Displays Windows Enhanced mode Virtual Device Driver (VxD) Chain
Copyright © 1994 Andrew Schulman. All rights reserved.
Using VMM function 81013F
Name Vers ID DDB Control V86 API ' PM API #Srvc
VMM 4.00 0001h C000EC28 C00023DE C0002A11 C0002A11 377 ! 39
VCACHE 3.01 048Bh C0032F6C C0032A8D C0032ED9 C0032ED9 15
VPOWERD 4.00 0026h C0035CAC C0035984 C0265B6C C0265B6C 14
VPICD 3.10 0003h C001A9F8 C0019480 C001A064 C001A064 22
VTD 4.00 0005h C0033FE4 C0033DC6 C0262114 C0262114 9
VXDLDR 3.00 0027h C00336C0 C0033658 C0261289 C0261298 15
CONFIGMG 4.00 0033h C0036038 C0035D48 C02663CC C02663CC 81
VCDFSD 3.00 0041h C0033534 C0033428 5
IOS 3.10 001 Oh C00298F0 C0027E74 С0257ЕЛ4 C0256710 17
PAGEFILE 4.00 0021h C0033C6C C0033BE8 C0262068 1 0
PAGESWAP 2.10 0007h C001E958 C001E854 10
PARITY 1.00 0008h CD01EF0C C001EE4C 0
REBOOT 2.00 0009h C001B3AC C001B27C C0233B50 0 ! 2
EBIOS 1.00 0012h C0011B50 C0011B28 2
VDD 2.00 OOOAh C0016658 C0011BA0 C0014944 C0014944 20
VSD 2.00 OOOBh C001D4F4 C001D32C 4
VCD 3.10 OOOEh C001F360 C001F014 C023D3E9 11
VMOUSE 4.00 OOOCh C00118C8 C00112F8 C022F204 C022F204 10
VKD 2.10 OOOOh C0018350 C00172D8 C02334B0 21
ENABLE 0.128 0037h C001F0E8 C001FACF C023E298 C023E241 10
VPD 3.00 OOOFh C00357C0 C0034FA0 C003521A 0
INT13 3.10 0020h C0018F9C C00188F6 5
VFD 2.00 001 Bh C001883C C00186D4 0
VMCPO 1.02 0011h C001F7C8 C001F428 8
BIOSXLAT 1.D0 0013h C001EFAC C001EF64 0
DOSMGR 4.00 0015h C001EB60 C001E9B4 C023C5A8 16
VSHARE 1.00 04B3h C0020244 C002011F C00200F4 C00200F4 1
VMPOLL 4.00 0018h C001E0E0 C001ECD4 4
170•Неофициальная Windowses
0SVX0 3.00 003Bh C0010F1C C0010D4C C0010DA3 0
VXD 2.00 28C0h C0011280 C0010F7C C0010FA6 C0010FA6* 0
COMBOFF 1.00 C001F9A8 C001F830 0
WIN32 1.02 002Ah C0022670 C00216C8 C023FD78 21 ! 65
VCOMM 1.00 002Bh C0023180 C0022ED0 C0243350 C0243350 35 ! 27
VCOND 1.00 0038h C003315C C0033118 C025B4D8 C025B5E2 2 ! 52
VTDAPI 4.00 0442h C0034EAC C0034E83 C0265330 0
VDMAD 2.00 0004h C001D08C C001B5CC 28
V86MMGR 1.00 0006h C001E6CC C001DAAF 25
SPOOLER 1.00 002Ch C0029CF8 C0029C10 18
VFAT 3.00 0486h C003285C C0030CDC 0
VDEF 3.00 C0033B98 C0033964 0
IFSMGR 3.00 0040h C0025EB0 C0023380 C0246B33 103
VFBACKUP 4.DO 0036h C0022E60 C002281A C0022847 C0022848 5
SHELL 4.00 0017h C0D34A79 C003476E C02624E5 C02624E5 26
Рис. 7.4. Программа VXDLIST выводит все VxD, находящиеся на данный момент в памяти. Здесь VXDr 1ST вывела
43 VxD, которые и формируют минимальную конфигурацию VMM32
Если вы посмотрите на вывод VXDLIST из предыдущей главы (см. рис. 6.1), то увидите, что
Windows 3.1 (которая не обеспечивает 32-битового доступа к файлам) использовала в два раза
меньше VxD, чем Chicago. Мы уже видели в начале этой главы, что 32-битовый доступ к файлам
WfW3.ll требует около пяти дополнительных VxD. Хотя это довольно грубые прикидки (многое
зависит от вашей конкретной конфигурации, в частности от того, используете ли вы сеть и какая у
вас карта графического ускорителя), совершенно ясно, что Windows 95 гораздо интенсивнее исполь-
зует VxD, чем более ранние версии Windows.
В выводе VXDLIST на рис. 7.4 номера справа (следующие за восклицательными знаками) ука-
зывают число специальных функций, которые обеспечивает данный VxD для Win32-np^oxeHHft.
Много функций расширенного Win32 API, доступных в Windows 95, но не доступных в Win32s,
реализованы посредством этих сервисов VxD Win32. Например, функция CreateThread из
KERNEL32 базируется на функции VMMCreateThread, KERNEL32 вызывает эту функцию VMM
через одну из Win32^yHKinrii, обеспечиваемую VWIN32. Недокументированная функция VXDCallO
в KERNEL32, которая будет использоваться позже в этой книге, позволяет Win32-пpилoжeниям
вызывать Win32-cepBHCbi, предоставляемые VxD.
Теперь мы видим, что Windows 95 содержит массу VxD, и это имеет смысл, так как они помо-
гают реализовать многие из новых возможностей Windows 95. Однако до сих пор мы не видели в
Windows 95 чего-либо, действительно отличающегося от WfW 3.11 с 32BFA. В следующем разделе
мы рассмотрим аспект Windows 95, который показывает реальный отход от предыдущих версий
Windows: длинные имена файлов.
Поддержка длинных имен файлов
в Windows 95
С самого начала MS DOS волочила за собой так называемое соглашение 8.3 на формат имени
файла. Вместо имен файлов, имеющих какой-то смысл, например: “3rd quarter 1994 report from
Cleveland” или “Piano Concerto No.20 in D minor”, — пользователи должны были создавать и
помнить имена такого типа, как CLEV3Q94.WKS и PI20DMIN.SCR. В Windows 95 допустимы
длинные имена файлов и каталогов (вплоть до 255 символов), при этом максимальная длина
пути — 260 символов. Ниже приведен вывод команды DIR в Windows 95:
3RDQUART 36.517 09-13-94 8:43р 3rd quarter 1994 report from Cleveland
PIANOCON 201 126.455 09-13-94 8.44p Piano Concerto No.20 in D minor
Глава 7. Откуда взялись эти 32BFA и LFN?
171
Каждое длинное имя файла в Windows 95 имеет уникальный короткий псевдоним, автомати-
чески создаваемый операционной системой. В выводе команды DIR 3RDQUART является псевдо-
нимом для “3rd quarter 1994 report from Cleveland”. Windows 95 сохраняет регистр (хотя и не
чувствительна к нему), поэтому, в сущности, все файлы и каталоги, созданные под Windows 95,
имеют две формы. Например,
copy foo.bar foobarsk.doc
создает как стандартный элемент каталога каталога (FOOBARSK.DOC), так и (несмотря на то, что
FOOBARSK.DOC удовлетворяет соглашению 8.3) второй элемент, сохраняющий точное написа-
ние — foobarsk.doc.
Конечно, Microsoft вынуждена была реализовать зти длинные имена файлов таким образом,
чтобы не повредить существующим приложениям. Также и носитель, на который записывалась
информация в Windows 95, должен оставаться пригодным для использования в старых версиях
DOS и Windows.
Пусть, скажем, DOS-программа вызывает функцию 47h прерывания INT 21b для получения
текущего каталога. Согласно DOS 6.0 Programmer's Reference фирмы Microsoft, программе, вызы-
вающей эту функцию, нужен буфер размером, по крайней мере, 64 байт, достаточный, чтобы
вместить самый длинный возможный путь. Теперь программа запущена под Windows 95, где не то
что полный путь, а само имя может быть длиннее 64 байт. Что же происходит?
Программа, конечно же, получает короткую форму. В противном случае под Windows 95 не
могла бы работать никакая DOS-программа, соблюдающая правила и выделяющая не более 64 байт
для получения строки текущего каталога. Это означает, что разработчики не должны беспокоиться о
том, что длинные имена файлов отрицательно скажутся на старых программах.
Однако многим новым программам потребуется работать с полными именами файлов. Для полу-
чения длинных имен файлов вместо коротких псевдонимов необходимо пользоваться новым набором
функций. Windows-программы будут использовать функции Win32 API, такие как GetCurrent
Directory и CreateFile. А DOS-программы могут использовать новый набор подфункций функции
7111 прерывания INT 21h, где номер подфункции в AL соответствует номеру функции в АН для
старых функций DOS. Например, поскольку старая функция DOS получения текущего каталога
имеет номер 47h, то новая функция, которая использует длинные имена файлов, будет иметь номер
7147h. Причем, использование других регистров идентично старому. Буферы, на которые указы-
вают DS:SI, должны быть достаточными по размеру для размещения максимального маршрута. Как
вы увидите в примере LFN.C, позже в этой главе, программы могут вызывать новую функцию
получения информации о томе (Get Volume Information — функция 71A0h прерывания INT 21h)
для получения максимальной разрешенной длины пути.
Длинные имена файлов не поддерживаются в WINBOOT.SYS, представляющей части Windows 95
реального режима, которая выполняется перед VxD VMM32. Это означает, что программы
реального режима, вроде TSR и драйверов устройств, не могут вызывать новые функции работы с
длинными именами файлов, если они запущены во время начальной загрузки системы. Аналогично,
программы, выполняющиеся в режиме единичного приложения (Single Application Mode), также не
могут использовать эти функции. Чтобы определить, доступны ли новые функции, программа может
воспользоваться функцией 71A0h прерывания INT 21h.
Если эти новые функции INT 21h не доступны в реальном режиме DOS 7, кто же их обеспечи-
вает? Конечно же, VxD IFSMGR. Из-за того, что длинные имена файлов не поддерживаются в DOS 7
реального режима и не требуют ни Winl6, ни WIN32, мы можем посмотреть на них в нашей среде
VMM32.
Однако есть одна проблемка. Попытка создать файл с длинным именем в нашей крошечной сре-
де VMM32 не дает ожидаемых результатов:
C:\VMM32>copy data.1 “3rd quater 1994 report from Cleveland"
1 file(s) copied
C:\VMM32>copy data.2 “Piano Concerto No.20 in D minor"
1 file(s) copied
172
Неофициальная Windows 95
C:\VMM32Xjir
3RD QUAR 2.481 09-13-94 9:01p
PIANO C020 2.526 09-13-94 9:01p
И это еще не все. Нет признаков проявления 32-битового доступа к файлам: диск работает
слишком медленно. Если поддержка LFN точно отсутствует, вполне вероятно, что отсутствует также
и 32-битовый доступ к файлам, так как оба они обеспечиваются IFSMGR. Однако, как было пока-
зано раньше в VXDLIST, IFSMGR благополучно загружается вместе с другими основными компо-
нентами 32-битового доступа к файлам и диску:
Name Vers ID DDB Control V86 API PM API #Srvc
IFSMGR 3.00 0040h C0025EB0 C0023380 C0246B33 103
VCACHE 3.01 048Bh C0032F6C C0032A8D C0032ED9 C0032ED9 15
VFAT 3.00 0486h C003285C C0030CDC 0
VSHARE 1.00 0483h C0020244 C002011F C00200F4 C00200F4 1
IOS 3.10 0010h C00298F0 C0027E74 C0257EA4 C0256710 17
INT13 3.10 0020h C0018F9C C00188F6 5
Как же так? Должно быть, мы пропустили один или несколько файлов, — вероятно, VxD —
которые необходимы VMM32 для поддержки 32BFA и LFN. Чтобы выяснить, что же требуется
VMM32 для обеспечения 32BFA и LFN, мы можем взять утилиту INTRSPY Дэвида Максея из кни-
ги Undocumented DOS, запустить ее до RUNVMM32 и получить список всех зарегистрированных
вызовов функций File Open (Открыть файл), Find First (Найти первый) и ЕХЕС (Выполнить).
После запуска и завершения VMM32 INTRSPY создает отчет (который я немного урезал), показан-
ный на рис. 7.5.
ЕХЕС C:\VNN32.EXE
OPEN QEMM386S [FAIL 2]
OPEN 386МАХ$$ [FAIL 2]
XOPEN C:\WIND0WS\EMM386.EXE
OPEN SMARTAAR [FAIL 2]
OPEN C:\SYSTEM.DAT
OPEN C:\VMM32\VMM32.EXE
OPEN SDebugDD [FAIL 2]
OPEN NDISHLPS [FAIL 2]
OPEN C:\VMM32\SYSTEM.INI
FIND [FAIL 3]
OPEN H:\DBLSPACE.BIN
OPEN c:\vmm32\vfd.vxd
OPEN EMMXXXXO
OPEN SMARTAAR [FAIL 2]
OPEN IFS$HLP$
OPEN CONFIGS
OPEN SCSIMGRS [FAIL 2]
OPEN C:\VMM32\I0S.INI [FAIL 2]
OPEN C:\VMM32\I0S.INI [FAIL 2]
OPEN C:\VMM32\I0S.INI [FAIL 2]
FIND C:\VMM32\I0SUBSYS\*.vxd [FAIL 3]
OPEN C:\VMM32\rmm.pdr [FAIL 3]
OPEN C:\VMM32\WINSTART.BAT [FAIL 2]
OPEN \dOS\WINSTART.BAT [FAIL 2]
OPEN \bin\WINSTART.BAT [FAIL 2]
OPEN C:\VMM32\UNIC0DE.BIN [FAIL 2]
OPEN IFS$HLP$
OPEN C:\VMM32\VFD.VXD
OPEN C:\VMM32\KRNL386.EXE
OPEN C:\VMM32\USER.DAT
Глава 7. Откуда взялись эти 32BFA и LFN?
173
OPEN C:\VMM32\USER.DAT
OPEN C:\VMM32\KERNEL32.DLL [FAIL 2]
OPEN C:\VMM32\KERNEL32.DLL [FAIL 2]
OPEN \dos\KERNEL32.DLL [FAIL 2]
OPEN \bin\KERNEL32.DLL
EXEC C:\VMM32\KRNL386.EXE
OPEN C:\VMM32\KRNL386.EXE
OPEN C:\VMM32\KRNL386.EXE
OPEN C:\CDMMAND.COM
OPEN C:\SYSTEM.DAT
XOPEN C:\SYSTEM,—
OPEN C:\VMM32\RUNVMM32.BAT
Рис. 7.5. INTRSPY перечисляет файлы, необходимые для минимальной среды VMM32
Здесь мы видим, что Windows ищет различные драйверы устройств DOS, такие как
QEMM386S, 386MAXSS, SMARTDRV, SDebugDD, NDISHLPS, ЕММХХХХО, IFS$HLP$,
CONFIGS и SCSIMGRS. Она также пытается найти файл WINSTART.BAT и т.д. Но ключевые
строки, которые приковывают внимание, следующие:
OPEN C:\VMM32\I0S.INI [FAIL 2]
FIND C:\VMM32\I0SUBSYS\*.vxd [FAIL 3]
OPEN C:\VMM32\rmm.pdr [FAIL 3]
Для поддержки 32BFA и LFN VMM32 должен требовать IOS.INI (список безопасных драй-
веров, используемых супервизором вйода-вывода, IOS.VXD), и/или подкаталог IOSUBSYS, и/или
драйвер отображения реального режима (real mode mapper), RMM.PDR.
Наш следующий шаг очевиден: создаем подкаталог \VMM32\IOSUBSYS и копируем в него
содержимое подкаталога \WINDOWS\SYSTEM\IOSUBSYS из Windows 95. (Примечательно,
что IOSUBSYS может содержать VxD DBLSPACE, но мы доберемся до этого попозже). Скорее все-
го, не вредно будет переписать также и IOS.INI.
Создав каталог IOSUBSYS, перезапустим VMM32. Если вы снова запустите VXDLIST, то уви-
дите в результате три новых VxD. IOS динамически загружает эти VxD с помощью сервиса
VXDLDR_LoadDevice:
Name Vers ID DDB Control V86 API PM API #Srvc
DiskTSD 3.10 C0FD23CC C0FD21A8 0
volt rack 3.10 0090h C0FD54D8 C0FD5000 0
RMM 3.10 C0FD6224 C0FD5850 0
DISKTSD *- это драйвер диска специального типа, VOLTRACK.VXD — драйвер отслеживания
тома и RMM.PDR — драйвер отображения реального режима.
Имеется почти дюжина уровней файловой/дисковой архитектуры Windows. У меня в голове
они просто не укладываются. Если вам так же тяжело запомнить все эти чертовы драйверы портов и
мини-портов, читайте врезку “Одиннадцать видов дисковых/файловых драйверов Windows”.
Загрузив DISKTSD, VOLTRACK и RMM, мы, наконец-то, получили 32BFA и LFN. В чем же
было дело? Все эти драйверы являются частью 32-битового доступа к диску (32BDA), а мы говорим
о 32-битовом доступе к файлам. Однако 32BFA требует 32BDA или чего-то подобного. Эту роль и
играет драйвер отображения реального режима.
174
Неофициальная Windows 95
Одиннадцать видов дисковых/файловых
драйверов Windows
Следующий словарик дисковых/файловых драйверов Windows является удобной шпаргал-
кой, которую я почти дословно содрал из документации Microsoft “Layered Block Device
Drivers” (Многослойные блочные драйверы устройств). Как вы увидите, “многослойный” —
это мягко сказано для этой целой башни драйверов.
V Драйвер файловой системы (File System Driver — FSD): управляет высокоуровне-
выми запросами ввода-вывода приложений. FSD обрабатывает запросы от приложений
и инициализирует низкоуровневые запросы через супервизор ввода-вывода. VFAT —
это FSD. FSD являются драйверами инсталируемой файловой системы и управляются
IFSMGR. Имеются также расширения FSD типа VxD DBLSPACE.
V Супервизор ввода-вывода (I/O supervisor — IOS): обеспечивает различные сервисы
для FSD и других драйверов системы. IOS регистрирует драйверы, обрабатывает и ста-
вит в очередь запросы ввода-вывода, по мере надобности посылает асинхронные сооб-
щения драйверам и обеспечивает сервисы, которые драйверы могут использовать для
распределения памяти и завершения обработки запросов. IOS также обеспечивает серви-
сы BlockDev (блочных устройств) для поддержки совместимости с BlockDev-клиентами.
V Драйвер отслеживания тома (Volume Tracker): работает с группой устройств, которые
разделяют права съемного тома. Этот драйвер определяет, правильный ли носитель
установлен в устройстве, обнаруживает и сообщает о неправильном извлечении и уста-
новке носителя.
V Драйвер специального типа (Type-Specific driver — TSD): работает со всеми устройст-
вами специального типа, такими как CD-ROM. TSD регистрирует поток запросов ввода-
вывода, преобразует логические запросы в физические и выдает уведомляющие сообще-
ния. TSD может также инициировать восстановление при возникновении логической
ошибки, что требуется для некоторых типов устройств, преимущественно дисков.
V Обработчик SCSI: работает со всеми SCSI-устройствами данного типа, такими как
SCSI-диски. Обработчик SCSI конструирует блоки дескрипторов команд SCSI (CDBS)
для определенного класса и выполняет восстановление и регистрацию ошибок на уровне
устройств.
V Драйвер производителя (Vendor-supplied driver — VSD): перехватывает и обрабатывает
запросы ввода-вывода для данного блочного устройства. VSD позволяет производителю
либо эффективно дополнить существующий драйвер блочных устройств, либо расширить
возможности применения драйвера к новому, но подобному аппаратному обеспечению.
V Драйвер порта SCSI, менеджер SCSI (SCSI port driver, SCSI manager): перехватыва-
ет и управляет взаимодействием между обработчиком SCSI и драйвером мини-порта
SCSI Windows NT. Драйвер порта SCSI инициализирует драйвер мини-порта, преобра-
зует формат запроса ввода-вывода и выполняет все взаимодействия с драйвером мини-порта.
V NT драйвер мини-порта SCSI (NT SCSI miniport driver — MPD): работает co специаль-
ным набором SCSI-адаптеров. Драйвер мини-порта обнаруживает и инициализирует
адаптер, обрабатывает прерывания, посылает запросы устройству и выполняет восста-
новление и регистрацию ошибок адаптера.
V Драйвер порта (Port Driver — PDR): работает со специальным адаптером, обычно
своим собственным. Например, есть драйверы портов для IDE/ESDI и дисководов
гибких дисков NEC. Драйвер порта обеспечивает то же самое множество функций, что и
комбинация SCSI-менеджера и драйвера мини-порта. Драйвер порта обнаруживает и
инициализирует адаптер, обрабатывает прерывания, посылает запросы к устройству и
выполняет восстановление после ошибок на уровне адаптера и регистрацию.
Глава 7. Откуда взялись эти 32BFA и LFN?
175
J Драйвер отображения реального режима (Real-mode mapper — RMM)'. обеспечивает
интерфейс между файловой системой и драйвером DOS реального режима, вроде
DBLSPACE.BIN, переводя драйвер реального режима на верхние уровни, как будто он
драйвер защищенного режима.
✓ Драйвер реального режима (Real-mode driver)-, это драйвер устройства DOS, вроде
DBLSPACE.BIN, который выполняется в Windows в режиме V86.
Лично моя голова не способна все это вместить. Считаю, что одной из основ проекти-
рования пользовательского интерфейса должно быть правило психолога Джорджа Миллера
(George Miller) “семь плюс-минус два” (т.е. средний человек может удерживать от пяти до
девяти сущностей в своей кратковременной памяти). Уверен, это правило применимо и к
программным интерфейсам.
Мой жесткий диск уплотнен с помощью DblSpace. (Это, в сочетании с использованием 32-бито-
вого доступа к файлам в бета-версии, показывает, что мне нравится жизнь, по крайней мере в мире
компьютеров, на лезвии бритвы.) Без VxD DblSpace (это обсуждалось в свое время) весь доступ к
диску должен проходить через 16-битовый драйвер реального режима DblSpace. Этот драйвер, в свою
очередь, обращается к файлу упакованного тома (Compressed Volume File — CVF) на главном диско-
воде, а это — 32-битовый доступ. Но в отсутствие VxD DblSpace обращение к драйверу реального
режима Dblspace неизбежно. Однако, как только что было отмечено, 32BFA требует 32BDA. Драй-
вер отображения реального режима сделает 16-битовый драйвер уплотнения реального режима, то есть
DblSpace, таким, что для 32BFA он будет казаться 32-битовым драйвером защищенного режима.
Действительно, это делает именно RMM. Имея в каталоге IOSUBSYS лишь один-единственный
файл RMM.PDR, мы уже получаем поддержку 32BFA и LFN.
Чтобы продемонстрировать 32BFA, мы можем воспользоваться программой V86TEST из гла-
вы 10. Загружая V86TEST до VMM32, мы видим, сколько обращений к INT 21h VMM32 передает
DOS. Запустив V86TEST -QUERY до и после выполнения операции, получив соответственно две ста-
тистики и затем вычтя из второй первую, мы можем измерить DOS-’’стоимость” каждой операции.
До создания каталога IOSUBSYS один тест (поиск строки “foobish” в файле размером 3210
Кбайт) под VMM32 занял 19 с и сгенерировал 367 вызовов INT 21b (73 из них — функции Read 3Fh).
Сразу после него повторный запуск (чтобы попробовать оценить какое-либо кэширование) занял 14 с.
Для сравнения, тот же самый тест в реальном режиме DOS использовал то же количество вы-
зовов INT 21h, но занял всего Ис. Ясно, что без чего-то похожего на 32-битовый доступ к файлам
V86-peжим DOS, вроде VMM32, может причинить вред вместо пользы.
После создания каталога IOSUBSYS и перезапуска VMM32 под V86TEST тот же самый тест
занял 13 с и сгенерировал только 218 вызовов INT 21b и ни одного вызова функции Read. Повтор-
ный запуск теста занял всего 4 с. Да, первый тест пока еще занимает больше времени, чем в старой
доброй DOS, но мы ощутили выгоды 32BFA во втором тесте. Заметим, что суммарное время для
двух тестов под 32BFA (13 + 4 = 17 с) меньше, чем в случае с DOS (И + И = 22 с).
Поскольку 32BFA потребовал двух проходов, чтобы стала заметна какая-либо польза, может
показаться, что 32BFA является не больше, чем кэшированием. Конечно, VCACHE является решаю-
щей частью 32BFA. Могли ли мы добиться того же, используя SmartDrv? Это рассматривается
более подробно в главе 8, во врезке, озаглавленной “32BFA против системы кэширования диска”.
Теперь я только замечу, что, вопреки расхожему мнению, — важен размер (т.е. размер кэша).
Много зависит от размера кэша, который вы определяете для SmartDrv. В то время как VCACHE
определяет размер кэша динамически, по мере необходимости, максимальный размер кэша Smart
Drv после запуска остается фиксированным. Если размер кэша не достаточен для ваших данных, то
от SmartDrv пользы будет немного. В этом примере кзш SmartDrv размером 2048 Кбайт обеспечил
небольшое повышение эффективности при чтении 3210 Кбайт данных: суммарное время для двух
проходов стало 12 + И = 23 с. Для сравнения, 4096 Кбайтный кэш уменьшает время до И + 6 = 17 с.
Обратите внимание, что это похоже на время, затрачиваемое 32BFA.
176
Неофициальная Windows 95
Во всяком случае, сейчас мы определенно имеем 32BFA. Как отмечалось раньше, 32BFA и
поддержка LFN идут в Windows 95 рука об руку. И то, и другое обеспечивается IFSMGR. Чтобы
убедиться теперь в присутствии поддержки LFN, достаточно повторить эксперимент, потерпевший
ранее неудачу:
C:\VMM32>copy data.1 “3rd quarter 1994 report from Cleveland”
1 file(s) copied
C:\VMM32>copy data.2 “Piano Concerto No.20 in D minor”
1 file(s) copied
C:\VMM32>dir
3RDQUART 36.517 09-13-94 8:43p 3rd quarter 1994 report from Cleveland
PIANOCON 201 126.455 09-13-94 6:33p Piano Concerto No.20 in D minor
Мы можем использовать также длинные имена каталогов. На рис. 7.6 приведены некоторые
примеры. Я удалил неинтересные части вывода DIR.
C:\VMM32>md “this is a long directory name"
C:\VMM32>dir /a:d
IOSUBSYS <DIR> 06-13-94 4:25p IOSUBSYS
THISIS'1 <DIR> 06-13-94 10:49p this is a long directory name
C:\VMM32>cd “this is a long directory name”
C:\VMM32\THIS IS A LONG DIRECTORY NAME>ver > “a long filename with a.big extension"
C:\VMM32\THIS IS A LONG DIRECTORY NAME>dir
Directory Of C:\VMM32\THIS IS A LONG DIRECTORY NAME
ALONGFIL BIG 30 06-13-94 10:50p “a long filename with a.big extension"
It
C:\VMM32\THIS IS A LONG DIRECTORY NAME>dir *."big extension”
ALONGFIL BIG 30 06-13-94 10:50p “a long filename with a.big extension”
Рис. 7.6. Распечатки каталогов выглядят совершенно по-другому, когда пользователи начинают создавать файлы
и каталоги с длинными именами
Заметим, что расширения файла больше не ограничены тремя символами. Расширение имени фай-
ла состоит из любой последовательности символов после последней точки в имени файла. Заметим
также, что можно задать поиск по шаблону по этим большим расширениям (DIR *.”big extension”).
Это большой шаг вперед по сравнению с WIN386 и даже с WIN386 вместе с 32BFA. 32BFA в
WfW 3.11 обеспечивает реализацию интерфейса файлового ввода-вывода прерывания INT 21h в 32-
битовом защищенном режиме, но сам интерфейс при этом не меняется — по крайней мере, этого не
видно. С другой стороны, можно было бы возразить, что при обходе DOS (как мы видели в
TEST21) WfW 3.11 действительно изменяет DOS API. В любом случае, Windows 95 очевидно
обеспечивает новый DOS API, но он пока не доступен в DOS 7 реального режима. Еще раз уточ-
няю: получить новый DOS API можно, только загрузив VxD. Это превосходная иллюстрация того,
что именно VxD представляют будущее DOS (даже если теперь это называется Windows).
Проверить наличие этого нового DOS API можно, например, следующим образом. Рассмотрим
приведенную в листинге 7.2 программу LFN.C, которая работает в реальном режиме DOS, вызывает
функцию 71A0h INT 21h (Получить информацию о томе) и выводит на экран некоторую
информацию о файловой системе.
Глава 7. Откуда взялись эти 32BFA и LFN?
177
ЛИСТИНГ 7.2. LFN. С
/* LFN.С -- проверяет доступность функций для работы с длинными именами */
«include <stdlib.h>
«include <stdio.h>
«include <dos.h>
«define GET_VOLUME_INFORMATION 0x71A0
«define OLD_GET_VOLUME_INFORMATION 0x4302 ।
«define FS_CASE_SENSITIVE 1
«define FS_CASE_IS_PRESERVED 2
«define FS_UNICODE_ON_DISK 4
«define FS_LFN_APIS 0x4000
«define FS_VOLUME_COMPRESSED 0x8000
int GetVolumeInformation(
char far ‘RootName,
char far ‘Buffer, unsigned BufSize,
unsigned »pFlags, unsigned ‘pMaxFilename, unsigned *pMaxPath)
{
unsigned rbx, rex, rdx;
_asm push ds
_asm push di
_asm les di, dword ptr Buffer
_asm Ids dx, dword ptr RootName
_asm mov ex, BufSize
_asm mov &x, GET_VOLUME_INFORMATION
_asm int 21h
_asm pop di
_asm pop ds
_asm jc no_71A0
_asm mov rbx, bx
_asm mov rex, ex
_asm mov rdx, dx
}
‘pFlags = rbx;
•pMaxFilename = rex;
•pMaxPath = rdx;
return 0;
no_71A0:
_asm xor ah, ah
// код ошибки в AL
}
main(int argc, char *argv[])
{
char ‘RootName = (argc < 2) ? “C:\\” : argv[1];
char Buffer[128];
unsigned Flags, MaxFilename, MaxPath;
int ret;
if((ret = GetVolumeInformation(
RootName,
Buffer, sizeof(Buffer),
&Flags, &MaxFilename, &MaxPath)) != 0)
{
printf(“%s -- Длинные имена файлов не поддерживаются\п", RootName);
printf(“KOfl ошибки: %d (%02Xh)\n”, ret, ret);
return 1;
}
17Д
Неофициальная/Windows 95
else
{
printf(“%s -- Поддерживаются длинные имена файлов\п”, RootName);
printf(“Имя файловой системы: \''%s\’’\n", Buffer);
printf(“Максимальная длина имени файла: %d\n", MaxFilename);
printf(“Максимальная длина пути: %d\n”, MaxPath);
«define PRINT_FLAG(fl, s1, s2) \
printf(“%s\n", (Flags & (fl)) ? (s1) : (s2))
PRINT_FLAG(FS_CASE_SENSITIVE,
“Поиск чувствителен к регистру",
“Поиск НЕ чувствителен к регистру”);
PRINT_FLAG(FS_CASE_IS_PRESERVED,
“Сохраняет регистр в элементах каталога”,
“НЕ сохраняет регистр в элементах каталога");
PRINT_FLAG(FS_LFN_APIS,
“Поддерживает функции DOS для длинных имен файлов",
“НЕ поддерживает функции DOS для длинных имен файлов’’);
PRINT_FLAG(FS_VOLUME_COMPRESSED,
“Том сжат",
“Том НЕ сжат”);
// следующий вывод может показаться ошибочным!!!
PRINT_FLAG(FS_UNICODE_ON_DISK,
“Использует кодировку Unicode в именах файлов и каталогов",
“НЕ использует Unicode для имен файлов/каталогов’’);
return 0;
)
На рис. 7.7 представлен результат работы программы LFN, запущенной в реальном режиме
DOS 7, а затем в нашей минимальной конфигурации VMM32. Сообщение “Использует кодировку
Unicode” правильное: Windows 95 сохраняет имена файлов и каталогов, используя двухбайтовые
символы.
Возможно, самое интересное на рис. 7.7 — это использование длинного имени файла для
исполняемого модуля. Мне показалось вполне логичным дать файлу LFN.EXE другое длинное имя
файла, поэтому я переименовал его в “Long Filename Test Program.exe”. Такой файл также успешно
должен запускаться из командной строки VMM32 DOS. Однако выявилась одна проблемка (это все
же пока бета-версия). Как видно из рис. 7.7, когда программа выполнялась, используя свое длинное
имя файла, она получила в качестве аргумента командной строки завершающий символ имени
исполняемого модуля.
Длинные имена файлов — это серьезное добавление к DOS, или Windows, или как бы вы это
ни называли. Они обеспечиваются не какой-то бесформенной массой под названием Windows 95, а
именно VxD IFSMGR. Версия IFSMGR без поддержки LFN существовала еще в WfW 3.11.
Совершенно ясно, что с чисто технической точки зрения WfW 3.11 также могла бы обеспечить
поддержку LFN. Просто в одном случае поддержка LFN — это огромные изменения, а в другом —
всего лишь логичное использование возможностей VxD, которые уже были в Windows на
протяжении нескольких лет.
Глав0(7рФукуда взялись эти 32BFA и LFN?
179
C:\UNAUTHW>lfn
0: — Длинные имена файлов не поддерживаются
C:\VMM32>runvmm32 \command.com
c:\VMM32>lfn
С: -- Поддерживаются длинные имена файлов
Имя файловой системы: “FAT”
Максимальная длина имени файла: 255
Максимальная длина пути: 260
Поиск НЕ чувствителен к регистру
Сохраняет регистр в элементах каталога
Поддерживает функции DOS для длинных имен файлов
Том сжат
Использует кодировку Unicode в именах файлов и каталогов
C:\VMM32>ren lfn.exe “Long Filename Test Program.exe"
C:\VMM32>“Long Filename Test Program”
m -- Поддерживаются длинные имена файлов
Имя файловой системы: “FAT"
Максимальная длина имени файла: 255
Максимальная длина пути: 260
Поиск НЕ чувствителен к регистру
Сохраняет регистр в элементах каталога
Поддерживает функции DOS для длинных имен файлов
Том сжат
Использует кодировку Unicode в именах файлов и каталогов
Рис. 7.7. LFN сообщает, что максимальная длина имени файла — 255 символов, а максимальная общая длина пути —
260 символов
Сага о VXD DBLSPACE
Как я заметил несколько раньше, на машинах, использующих DblSpace или другие системы
уплотнения типа Stacker, 32BFA требует использования RMM, который из 16-битового програм-
много обеспечения уплотнения в реальном режиме сделал бы 32-битовый дисковый драйвер защи-
щенного режима.
Другое решение состоит в том, чтобы поместить программу дискового сжатия в настоящий 32-
битовый драйвер защищенного режима.
Предварительная бета-версия Chicago конца 1993 г., распространявшаяся на Конференции
профессиональных разработчиков, проводимой Microsoft в Анахейме (Anaheim), Калифорния, была
снабжена одновременно VxD и для DblSpace (DBLSPACE.386), и для интерфейса уплотнения в
реальном времени фирмы Microsoft (MRCI32.386), являющегося инструментом сжатия/распа-
ковки, используемым DblSpace и другими утилитами Microsoft, такими как BACKUP.
Ни DBLSPACE.386, ни MRCI32.386 не было в следующем основном релизе Chicago (май
1994); несомненно потому, что в феврале 1994 г. федеральный суд Лос-Анджелеса вынес вердикт по
делу Stac против Microsoft, обнаружив, что Microsoft DblSpace нарушает два патента на программы
дискового сжатия, принадлежащие Stac (US Patent № 5,016,009, Doug Whiting et al., “Data
Copmpression Apparatus and Method” (Механизм и метод сжатия данных), Мау 14, 1991; US Patent
№ 4,701,745, John Waterworth (Ferranti pic), “Data Compression system” (Система сжатия данных),
October 20, 1987). Суд определил ущерб Stac в 120 млн. долларов.
Microsoft выиграла вторую часть процесса с иском на сумму 13 млн. долларов, по которому ут-
верждалось, что фирма Stac использовала недокументированный интерфейс MS DOS 6.0, нарушив
торговые секреты (см. “LA Law”, Dr. Dobb's Journal, May 1994).
180
Неофициальная Windows 95
8 июня 1994 года судья федерального округа Эдвард Рэфиди (Edward Rafeedie) огласил по-
становление, которым обязал Microsoft (и Vertisoft, на чей DoubleDisk фирма Microsoft приобрела
лицензию для создания DblSpace) “возвратить, стереть или разрушить” во всем мире все копии MS
DOS 6.0, MS DOS 6.2, Microsoft Flash File System, Microsoft NT Windows Remote Access, Vertisoft
DoubleDisk Gold и “любые другие продукты Microsoft, которые используют технологию сжатия
DoubleSpace, содержавшуюся в коммерческой версии MS DOS 6.0”. Хотя это постановление не бы-
ло опубликовано до июня, устный приговор был вынесен на несколько недель раньше. Итак, к тому
времени, когда Microsoft была готова поставлять бета-версию Chicago образца мая 1994 г., было
совершенно ясно, что судья, вероятно, вынесет приговор против фирмы Microsoft.
Через несколько недель после Приговора произошло нечто интересное. 21 июня Microsoft и Stac
соглашаются забыть обо всех убытках, платежах и исках. Microsoft согласилась платить Stac 1 млн.
долларов в месяц в течение 43 месяцев и купить акции Stac на сумму около 39.9 млн. долларов
(около 15% капитала компании). Две компании подписали соглашение о взаимном лицензировании
патентов, поэтому Microsoft теперь мсжет свободно использовать DblSpace, в котором суд обна-
ружил запатентованную Stac технологию. Stac, наоборот, теперь может свободно использовать недо-
кументированный интерфейс. Хотя Microsoft получила лицензию на патенты Stac, а не на ее
продукты, существует возможность, что будущие операционные системы Microsoft могут заимст-
вовать что-либо из последующих технологий дискового уплотнения фирмы Stac.
Для предварительного обзора программного обеспечения уплотнения диска в 32-битовом защи-
щенном режиме, я думаю, было бы полезно взять VxD DBLSPACE.386 и MRCI32.386 из
декабрьской предварительной бета-версии и поместить их в конфигурацию VMM32:
сору \oldchic\dblspace. 386 iosubsys
сору \oldchic\mrci32.386
После добавления в SYSTEM.INI строк:
DEVICE=C:\VMM32\I0SUBSYS\DBLSPACE. 386
DEVICE=C:\VMM32\MRCI3 2.386
и перезапуска VMM32 программа VXDLIST показывает, что эти VxD действительно загружаются:
Name Vers ID DDB Control V86 API PM API #Srvc
MRCI32 3.10 0042h C0FD16D4 C0FD167C 5
DBLSPACE 3.10 C001AC30 C00186D0 0
Каков же от этого эффект? К сожалению, эти VxD не замещают DblSpace и MRCI реального
режима. С загрузкой этих VxD производительность повышается несущественно, а код реального ре-
жима, по-видимому, не обходится. Например, запустив утилиту INTCHAIN, мы увидим, что функ-
ции получения версии DblSpace и MRCI обрабатываются кодом реального режима:
C:\VMM32>intchaln 2f/4a11/0
Tracing INT 2F AX=4A11
109 instructions
1077:0F27 WINICE
1892:0EF2 DOSKEY
12EA:01C8 COMMAND
12E9:0154 COMMAND
FFFF:F94F HMA
0385:0017 DBLSYSS
0313:006C XMSXXXXO
0215:0808 IFS$HLP$
01F6:0020 0:
Глава 7. Откуда взялись эти 32BFA и LFN?
181
ОЗВС:41АЗ
01F6:0028
ОЗВС:1478
DBLSYSHS
D:
DBLSYSH$
С:\VMM32>intchain 2f/4а12/0/4052/4349
Tracing INT 2F AX=4A12
74 instructions
1077:0F27, WINICE
1892:0EF2 OOSKEY
12ЕА:01С8 COMMAND
12Е9:0154 COMMAND
FFFF:F94F HMA
0385:0017 DBLSYSH$
Среди данных, возвращаемых функцией получения версии MRCI, есть указатель на функцию
сервера MRCI. MRCI-клиенты вызывают эту функцию с запросами на упаковку и распаковку
блоков данных, находящихся в памяти. (DblSpace заботится о перемещении данных на и с диска, а
MRCI производит упаковку/распаковку данных).
Обычно точка входа сервера имеет адрес вроде 0385:001Е. Однако, когда загружается MRCI32,
он изменяет его на что-то вроде FBA1:2632. Более того, дизассемблирование кода по этому адресу
показывает, что он начинается с запрещенной инструкции, ARPL, которая в режиме V86 приводит к
возникновению ошибки общей защиты (General Protection — GP). Эта инструкция представляет со-
бой обработчик косвенного вызова (callback) V86. Когда программа DOS выполняет запрещенную
команду, VMM отлавливает возникшую ошибку и смотрит, где она возникла (в нашем случае по
FBA1:2632), а дальше использует этот адрес для определения, какой VxD должен обработать эту
ошибку. VxD типа MRCI32 создают эти V86-o6pa6oT4HKH косвенного вызова с помощью процедуры
Allocate_V86_Call_Back из VMM. Таким образом MRCI32 перехватывает все обращения к MRCI-
серверу.
VxD DBLSPACE тесно взаимодействует с VCACHE для кэширования сжатых данных, что
практически удваивает размер кэша.
Это все значит, что мы определенно увидим VxD дискового сжатия в Windows 95, несмотря на
то, что бета-версия мая 1994 г. не содержала этих средств из-за предстоящего тогда приговора
против DblSpace и что драйверы в предварительной бета-версии декабря 1993 г. преследовали
довольно ограниченные цели.
DOS: четыре шага к нирване
Самая замечательная возможность, которую мы увидели пока в Windows 95 — поддержка
длинных имен файлов и каталогов. Этот новый интерфейс DOS, обеспечиваемый VxD и реализо-
ванный в 32-битовом защищенном режиме, демонстрирует следующий шаг в развитии DOS. Этот
новый интерфейс DOS доступен только при работе с VxD. Реальный режим DOS не поддерживает
длинных имен файлов.
Каким образом мы достигли понимания того, что новые интерфейсы DOS становятся доступ-
ными только благодаря 32-битовому защищенному режиму? Довольно постепенно. В последних не-
скольких главах мы уже видели медленное, но уверенное движение, и все под личиной Windows, по
направлению к защищенному режиму DOS.
• DOSX (подобно Любому DPMI-серверу или расширителю DOS) показал, что сервис
защищенного режима может модифицироваться в DOS.
• WIN386 обеспечивала все основные функции для V86 DOS, включая сервисы VMM и VxD,
благодаря которым могут быть получены более интересные возможности.
1В2
Нег6фиЦиальнЬ^^Гп^6уу5 95
• 32BFA использовал преимущества сервиса VMM и VxD для реорганизации доступа к
файлам, который является наиболее важной частью DOS в 32-битовом защищенном
режиме (мы не особенно в это вникали, но в Windows 3.1 32-битовый доступ к диску
делал то же самое для ключевой части ROM BIOS),
о
• VMM32 добавляет новые сервисы VMM и VxD и создает полностью новый интерфейс
DOS, не доступный в реальном режиме DOS.
По-видимому, все будущие расширения основ DOS и Windows будут делаться с помощью VxD.
Трудно вообразить какие-либо существенные добавления к базовому коду реального режима DOS.
Означает ли это, что “DOS мертва”, как это объявили многие рекламные публикации? Не совсем.
Хотя код реального режима DOS становится все менее (если вообще) заметным, интерфейс DOS
INT 2 th, насколько можно судить, пока что пребывает в полном здравии. Мы увидим в следующих
главах, что даже самое новое Win32-npraw^eHHe украдкой, но интенсивно использует интерфейс
INT 21h .
Другими словами, DOS не только не мертва, но и не собирается умирать. Как показали наши
эксперименты с WIN386 и VMM32, DOS превратилась в 32-битовую операционную систему защи-
щенного режима, организованную вокруг VMM и состоящую из VxD. В следующих нескольких
главах мы увидим, что эти VxD продолжают обращаться к коду DOS реального режима. Однако мы
также увидим, что VxD главенствуют, а код реального режима DOS играет лишь подчиненную роль.
Глава 7. Откуда взялисьэти 32BFA и LFN?
183
Глава 8
Постепенное
исчезновение DOS
Microsoft утверждает, а компьютерная пресса послушно повторяет, что Windows 95 — это со-
вершенно новая операционная система, не требующая MS DOS. Истина же состоит в том,
что Windows 95 продолжает использовать DOS реального режима. Откровенно говоря, нет
ничего плохого в том, что Windows 95 использует DOS. Проблема только в нежелании Microsoft
признать это.
Более важно, однако, то, что не только Windows 95 обходит DOS при выполнении большинства
операций — это же верно и для 32-битового доступа к файлам (32BFA) в Windows for Workgroups
(WfW) 3.11. На самом деле, WfW обходит DOS в большей степени, чем Windows 95. Как мы уви-
дим, это — один из недостатков WfW.
А самое, быть может, важное то, что способность Windows 95 обходить код реального режима
была частью расширенного режима Windows 3.x уже при первом ее появлении в 1990 г. Честно го-
воря, эта способность восходит еще к Windows/386 2.x в 1988 г.
Поэтому в Windows 95 Microsoft не делает ничего большего (или меньшего), чем просто расши-
ряет использование достаточно древних особенностей Windows: сервисы, ранее обрабатываемые в
16-битовом реальном режиме DOS драйверами устройств, TSR или BIOS, теперь обрабатываются в
32-битовом защищенном режиме. Фактически, даже Windows 3.x в расширенном режиме уже пред-
ставляла собой подлинную 32-битовую операционную систему защищенного режима.
• При работе Windows в расширенном режиме MS DOS не была больше операционной систе-
мой. Она становилась только помощником Windows. Это верно для Windows 3.x и не измени-
лось в Windows 95.
• Операционной системой является менеджер виртуальной машины Windows (VMM) при под-
держке виртуальных драйверов устройств Windows (VxD). Это справедливо с момента появ-
ления Windows 3.0 в 1990 г.
• При работе Windows INT 21h — и, фактически, все прерывания — обслуживаются в первую
очередь (а иногда и только) в 32-битовом защищенном режиме. Например, нет смысла вы-
зывать _dos_^getvect(0x21), чтобы узнать, кто обрабатывает INT 21h. Перед (а иногда и
вместо) передачей обработки прерываний через таблицу векторов прерываний (Interrupt
Vector Table — IVT) в нижней памяти Windows сначала (а иногда и только) использует
совершенно другую структуру данных 80x86 — таблицу дескрипторов прерываний (Interrupt
Descriptor Table — IDT).
• Прерывания INT 21h не обязательно обслуживаются DOS. DOS обрабатывает INT 21h только
с разрешения Windows. A Windows, как мы это увидим, все больше и больше ей этого не
позволяет. С 1990 г. DOS реального режима медленно, но уверенно исчезает.
Программы TEST21 и TEST2F16 в этой главе убедительно доказывают эти жестоко звучащие
заявления. Программы показывают, что вызовы DOS не обязательно попадают в DOS, но иногда
обрабатываются в Windows. Они также показывают, что постепенное замещение DOS реального ре-
184
Неофициальная Windows 95
жима на Windows продолжается у нас на глазах уже по крайней мере пять лет с тех пор, как в
Windows 3.0 введен расширенный режим. И наконец, эти программы показывают, что процесс не.
закончен даже в Windows 95 и, на самом деле, он является малым, но необходимым шагом назад от
WfW 3.11.
Другими словами, Windows 95 — это “новая” операционная система защищенного режима, с
которой мы все время имели дело.
Обход DOS
TEST21, показанная в листинге 8.1, это DOS-программа реального режима. Она устанавливает
обработчик INT 21h и затем генерирует некоторые обращения к INT 21h. Обработчик INT 21h
должен видеть собственные вызовы INT 21h. Даже если некоторые резидентные программы DOS
перехватывают INT 21h, собственный обработчик TEST21 прерывания INT 21h будет установлен
позже, поэтому он должен первым отловить свои собственные вызовы INT 21h. Но, как вы сейчас
увидите, поведение TEST21 не всегда можно предугадать. И хотя TEST21 выглядит скучной DOS-
программой, она может предоставить достаточно много информации о функционировании новых
версий Windows.
Листингв.1. TEST21.C
/*
TEST21.C -- Проверяет, отображаются ли вызовы INT 21h в режим V86
Шульман, 1994
Ьсс -2 -Р- test21.c
Ьсс -2 -Р- -DTESTFILE=“test" test21.c
test21
test21 -mysetvect
test21 > tmp.tmp
*/
«include <stdlib.h>
«include <stdio.h>
«include <string.h>
«include <time.h>
«include <dos.h>
«include <fcntl.h>
typedef unsigned short WORD;
typedef unsigned long DWORD;
«pragma pack(1)
typedef struct {
«ifdef __TURBOC__
WORD bp, di, si, ds.es, dx, ex, bx, ax;
«else
WORD es, ds, di, si, bp, sp, bx, dx, ex, ax; /* порядок такой же, как в PUSHA */
«endif
WOR.D ip, cs, flags;
} REG_PARAMS;
static void interrupt far int21(REG_PARAMS r);
static void interrupt far ctrl_c(REG_PARAMS r);
static void interrupt far crit_err(REGJ3ARAMS r);
static int failed = 0;
Глава 8. Постепенное исчезновение DOS
185
typedef void (interrupt far *INTPR0C)():
static INTPROC old_2l = (INTPROC) 0;
static void (*setv)(unsigned intno, INTPROC proc) = _dos_setvect;
static INTPROC (*getv)(unsigned intno) = _dos_getvect;
static DWORD total_called = 0;
static DWORD called[0x100] = {0};
static DWORD total_received = 0;
static DWORD received[0x100] = {0};
void fail(const char *s) { puts(s); exit(1); }
void my setvect(unsigned intno, INTPROC proc)
{
INTPROC far *ivt = (INTPROC far *) OL;
ivt[intno] = proc;
}
INTPROC my_getvect(unsigned intno)
{
INTPROC far *ivt = (INTPROC far *). OL;
return ivt[intno];
)
main(int argc, char »argv[])
{
char «filename = argv[0];
time_t t1, t2;
WORD num_iter = 100;
WORD i;
int use_my_setvect = 0;
int received_less = 0;
int receivedjnore = 0;
for(i=1; i<argc; i++)
if(argv[i][0] == || argv[i][0] == '/)
{
if((strncmp(strupr(&argv[i][1]), “MYSETVECT", 2)) == 0)
{
use_my_setvect++;
setv = my_setvect;
getv = my_getvect;
printf(“Используется my_setvect\n");
.}
else
1а11(“Использование: test21 [-mysetvect] [num_iter]”);
)
else if(atoi(argv[i]))
num_iter = atoi(argv[i]);
else
filename = argv[i];
time(&t1);
/* перехватить INT 21h */
old_2-1 = (*getv)(0x21);
(*setv)(0x23, ctrl_c);
(*setv)(0x24, crit_err);
(*setv)(0x21, int21);
186
Неофициальная Windows 95
/* выдать вызовы INT 21h */
for(i=0; i<num_iter; i++)
{
«ifdef TESTFILE
«include TESTFILE
«else
unsigned n;
char buf;
int f;
// нам все равно, успешны ли будут вызовы или нет
_dos_open(filename, O_RDWR, &f); called[0x3d]++; // или 0x6c
_dos_read(f, &buf, 1, &n); called[0x3f]++;
_dos_close(f); called[0x3e]++; '
_dos_open(filename, O_RDWR, &f); called[0x3d]++; // или 0x6c
_dos_write(f, &buf, 1, &n); called[0x40]++;
_dos_close(f); called[0x3e]++;
buf = ’.';
«define STDOUT 1 ч
_dos_write(STDOUT, &buf, 1, &n); called[0x40]++;
total_called += 7;
«endif
if(failed)
{
(*setv)(0x21, old_21);
Та11(“\пКритичная ошибка!”);
}
if(my_getvect(0x21) != int21)
fail(“\nKTO-TO еще захватил INT 21h!");
}
/* восстановить вектор INT 21h: не забудьте включить этот вызов! */
(*setv)(0x21, old_21);
if(!use_my_setvect) { called[0x25]++; total_called++; }
// INT 23h и 24h автоматически восстанавливаются при выходе
time(&t2);
/* вывести результаты */
printf(“\пПрошло %lu секунд\п\п”, t2 - t1);
printf(“Сгенерировано %lu вызовов\1Перехвачено %lu вызовов\п",
total_called, total_received);
for(i=0; i<0x100; i++)
if(called[i] || received[i])
{
printf(“21/%O2X\t%lu Bbi3eaHO\t%lu перехвачено\1\1”,
i, called[i], receivedti]);'
if(received[i] < called[i])
{
received_less++;
printf(“-”);
}
else if(received[i] > called[i])
{
received_more++;
printf(“+”);
}
printf(“\n”);
}
printf(“\n");
Глава 8. Постепенное исчезновение DOS
187
if(received_less)
printf(“Некоторые INT 21h обрабатываются без вызова DOS!\n”);
if(receivedjnore)
printf(“bnnn сгенерированы дополнительные вызовы INT 21h!\n");
if(!(received_less || receivedjnore))
printf(“INT 21h обрабатывается обычным образом\п”);
return 0;
void interrupt far int21(REG_PARAMS r)
{
total_received++;
received[r.ax » 8]++;
_chain_intr(old_21);
void interrupt far ctrl_c(REG_PARAMS r)
{
(*setv)(0x21, old_21);
fail(“Ctrl-C detected!”);
void interrupt far crit_err(REG_PARAMS r)
{
failed++;
r.ax = 3;
Текст TEST21 довольно прост. Главная функция вызывает _dos_setvect из библиотеки С (или,
если параметр MYSETVECT определен в командной строке, функцию my_setvect, обсуждаемую
дальше в этой главе) для установки обработчика INT 21h. Этот обработчик (см. самый конец лис-
тинга TEST21.C) имеет весьма оригинальное имя — int21. При каждом вызове int21 использует
входящий номер функции DOS в АН как индекс в массиве счетчиков. После увеличения соответ-
ствующего счетчика int21 использует функцию _chain_intr для вызова следующего в цепочке обра-
ботчика прерывания, адрес которого был получен предварительно при вызове _dos_^getvect или
my^getvect.
После установки обработчика int21 (и перехвата обработчиков <Ctrl+C> и критической ошибки
для восстановления INT 21h при неожиданном завершении программы) TEST21 выполняет цикл
вызовов DOS. Обратим внимание на #ifdef TESTFILE. Позднее мы определим TESTFILE в команд-
ной строке компилятора и сможем использовать TEST21 как генератор тестов. По умолчанию, одна-
ко, TEST21 в цикле открывает файл, читает байт, закрывает файл, открывает файл опять, пишет
байт, пишет в stdout (который может быть перенаправлен в файл на диске командой TEST21
>FOO.BAR) и закрывает файл. При каждом обращении к DOS .TEST21 увеличивает счетчик.
Если в командной строке не определено иное имя файла, то файл, который TEST21 читает в
цикле, является самой программой TEST21.EXE. (TEST21 получает свое собственное имя файла из
argv[0]). По крайней мере при начальном тестировании, мы не заботимся о том, что читает TEST21
и сколь это успешно — мы всего лишь хотим инициировать некоторые обращения к INT 21h и
отловить их обработчиком INT 21h в TEST21.
После того как цикл вызовов DOS закончится, TEST21 восстанавливает старый обработчик INT
21h и выводит на экран результаты тестирования. Для каждого возможного номера функции INT
2lh от 0 до FFH TEST21 проверяет количество выданных обращений к этой функции и количество
вызовов INT 2 th, зафиксированных ее обработчиком. Очевидно, этих два числа должны совпадать;
Когда TEST21 работает под обычной DOS, числа действительно совпадают. Зная структуру
программы, удивляться нечему. Результаты показаны на рис. 8.1.
188
Неофициальная Windows 95
C:\UNAUTHW>test21
Прошло 21 секунд
Сгенерировано 701 вызовов Перехвачено 701 вызовов
21/25 1 вызвано 1 перехвачено
21/30 200 вызвано 200 перехвачено
21/ЗЕ 200 вызвано 200 перехвачено
21/3F 100 вызвано 100 перехвачено
21/40 200 вызвано 200 перехвачено
INT 21h обрабатывается обычным образом
Рис. 8.1. Когда TEST21 выполняется из обычной DOS, DOS получает все вызовы INT 21Ь, сгенерированные программой
Помимо сообщения “INT 21h обрабатывается обычным образом”, указывающего, что вызовы
INT 2th получает DOS-обработчик INT 21h, единственный интересный пункт на рис. 8.1 — это
строка “Прошло 21 секунд”.
Время будет отличаться при переходе от одной конфигурации к другой. Если переназначить
вывод TEST21 в файл, время немного увеличится (в конфигурации, которую я использовал, — до
29 с), поскольку DOS должна все записать в файл на диске, а не вывести на экран.
Если вы запустите TEST21 под дисковым кэшем, например Microsoft SmartDrv, время работы
значительно уменьшится (что не удивительно, раз TEST21 выполняет небуферизованное чтение и
запись по одному байту за раз). В этой же самой конфигурации, но со SmartDrv, время выполнения
TEST21 уменьшается приблизительно до 4 с (или 5 с при переназначении вывода TEST21 в файл).
Тест на рис. 8.1 работал на диске, уплотненном DblSpace. Если же запустить TEST21 на
несжатом диске без кэширования, то время работы снова значительно уменьшается, как показано в
табл. 8.1.
Таблица 8.1. Время работы (в секундах) TEST21
Диск DblSpace Несжатый диск
TEST21
Без кэша 21 7
С кэшем 4 < 1
TEST21 > FOO.BAR
Без кэша 29 10
С кэшем 5 1
Теперь давайте запустим TEST21 в окне DOS под WfW 3.11, с 32-битовым доступом к файлам
(32BFA). Для разрешения 32BFA Microsoft заставляет вас пробраЛся сквозь туннель из последова-
тельности диалоговых окон: запустите Control Panel (Панель управления), выберите пиктограмму
386 Enhanced (386-й расширенный), затем нажмите Virtual Memory ’(виртуальная память. Да,
виртуальная память, хотя и не ясно, какое отношение она имеет к 32-битовому доступу к файлам —
не путать с доступом к диску); выберите Change (Изменить), пометьте ячейку Use 32-bit File Access
(Использование 32-битового доступа к файлам). Теперь для вступления в силу 32BFA достаточно
перезапустить Windows.
На рис. 8.2 показаны результаты работы TEST21 под 32BFA. Они довольно удивительны, осо-
бенно если учесть, что мы отметили всего одну какую-то опцию в глубине Control Panel (Панели
управления).
Глава 8. Постепенное исчезновение DOS
189
C:\UNAUTHW>test21
Прошло 0 секунд
Сгенерировано 701 вызовов Перехвачено 101 вызовов
21/25 1 вызвано 1 перехвачено
21/30 200 вызвано 0 перехвачено -
21/ЗЕ 200 вызвано 0 перехвачено -
21/3F 100 вызвано 0 перехвачено -
21/40 200 вызвано 100 перехвачено -
Некоторые INT 21h обрабатываются без вызова DOS!
Рис. 8.2. При выполнении TEST21 под WfW 3.11 с разрешенным 32BFA большая часть сгенерированных программой
вызовов INT 21Ъ обходит DOS
Во-первых, заметим, что TEST21 работала менее одной секунды. Я не загружал SmartDrv (или
какой-нибудь другой кэш диска). Если бы он у меня был, в любом случае результаты были бы теми
же, поскольку 32BFA имеет свой собственный встроенный динамически настраиваемый файловый
кэш (VxD VCACHE). На уплотненном диске TEST21 выполняется быстрее с 32BFA, но без
SmartDrv, чем с SmartDrv, но без 32BFA. (Да, я знаю, что это трудноперевариваемый кусок текста.
См. “32BFA против кэширования диска” дальше в этой главе).
Во-вторых, обратите внимание на сообщение “Некоторые INT 21h обрабатываются без вызова
DOS!”. Даже если предположить, что с 32BFA Windows как-то “обходит” DOS (что бы это ни
значило!), результаты все равно удивляют. Обработчик INT 21h из TEST21 не видит большую часть
собственных программных вызовов INT 2lh файлового ввода-вывода!
Одно важное замечание. При запуске TEST21 с гибкого диска ваши результаты выглядели бы
скорее как на рис. 8.1, чем как на рис. 8-2. Это свидетельствует о том, что WfW 3.11 не обеспе-
чивает 32BFA для дискет. Как мы увидим позднее, в Windows 95 это уже исправлено.
Знак минус (-) указывает, что TEST21 “видела” меньше вызовов данной функции DOS, чем
ожидалось. TEST21 ставит знак плюс (+), если вызовов INT 2lh было больше, чем ожидалось.
TEST21 видит свой собственный вызов функции 25h INT 21h (Set Interrupt Vector), которая
восстанавливала обработчик INT 21h. Но при том, что TEST21 выдала 200 обращений к функции
3Dh INT 21h (File Open), ее обработчик INT 21h не зафиксировал ни одного. То же самое касается
функции 3Eh (File Close) и функции 3Fh (File Read).
TEST21 выдала 200 вызовов функции 40h (File Write), но обнаружила только 100 вызовов этой
функции. Если вы переназначите вывод TEST21 в файл (например, TEST21 >FOO.BAR), то про-
грамма также не зафиксирует обращений к функции 40h. Эмпирически 32BFA должен передавать
обработку функции 40h при записи на экран дальше по цепочке обработчиков INT 21h, а запись в
файлы на диске перехватывать и блокировать.
Однако, принимая во внимание, что функция int21 в'листинге 8.1 была установлена последней,
этот перехват вызова INT 21h кажется невозможным. В листинге 8.1 практически ничего нет между
вызовом _dos_setvect (или my_setvect) и вызовом dos_open. Как мог проскользнуть другой обра-
ботчик INT 21h перед TEST21?! Более того, если бы TEST21 не перехватывала этот вызов INT 21h,
как DOS (которая установлена намного раньше в цепочке обработчиков INT 21h) собиралась
отловить эти вызовы? ,
Ответ на второй вопрос заключается в том, что DOS и не собирается их отлавливать. Исполь-
зуя программу V86TEST из главы 10, я убедился, что вызовы INT 21h, выданные TEST21 при вы-
полнении под WfW 3.11 с 32BFA, действительно не передаются никакому программному обеспе-
чению, загруженному перед Windows, включая саму DOS. Во время работы TEST21 программа
V86TEST обнаружила следующие вызовы INT 21h:
Вызовы INT 21h:
02: 8 08: 7 0В: 2433 19: 2 1А: 11 25: 10 29: 4
2А: 3 2С: 3 30: 1 35: 5 38: 1 ЗЕ: 15 40: 121
48: 2 49: 1 4А: 1 4В: 1 4С: 1 4D: 1 5D: 2
44: 2
71: 2
190
Неофициальная Windows 95
Если не обращать внимания на дополнительные вызовы INT 21h, обнаруженные V86TEST, все
достаточно хорошо согласуется с выводом TEST21. Не было обнаружено ни одного вызова функции
3Fh и обнаружено больше 100 вызовов функции 40h. Если вывод TEST21 переназначить в файл, то
вызовов функции 40h станет, как и ожидалось, намного меньше:
Вызовы INT 21h:
02: 18 08: 13 0В: 2915 19: 3 1А: 11 25: 10 29: 4
2А: 3 2С: 3 30: 1 35: 5 38: 1 ЗЕ: 15 40: 3 44: 1
48: 2 49: 1 4А: 1 4В: 1 4С: 1 40: 1 50: 2 71: 3
Таким образом, 32BFA как-то “отбирает” большинство вызовов функций файлового ввода-
вывода у DOS и, в WfW, даже у самого последнего обработчика INT 21h, установленного в окне
DOS. В основном это совпадает с информацией Microsoft о работе 32BFA, и это неудивительно.
Но рассмотрим этот механизм детальнее. TEST21 перехватила INT 21h непосредственно перед
выдачей прерывания INT 21h. Далее Windows как-то перехватила INT 21h. Появляется мысль о
том, что Windows демонстрирует прием пресловутого SideKick, заслужившего дурную славу в преж-
ние времена DOS. Тогда SideKick перехватывал прерывание по таймеру и на каждом импульсе тай-
мера проверял наличие прочих своих обработчиков прерываний. Однако TEST21 допускает такую
возможность и на каждой итерации цикла обращений к DOS проверяет равенство my_getvect(0x21)
== int21.
Ясно, что 32BFA не перехватывает INT 21h в обычном смысле. В превосходном руководстве к
WfW 3.11 Resource Kit (р. 1-20) отмечено, что 32BFA работает, “перехватывая прерывания MS
DOS INT 21h в защищенном режиме”. TEST21 показывает, что это — правда: действительно,
32BFA как-то преграждает доступ (вместо обычного перехвата) к INT 21h.
Я сказал “как-то”. Вас это должно заинтриговать. Как Windows перехватывает INT 21h? Я со-
бираюсь объяснить это детальнее в разделе “Прерывания 101: IDT против IVT” в конце настоящей
главы. Основная идея состоит в том, что Windows для перехвата прерываний, идущих от программ
реального режима, работающих в \786-режиме, использует таблицу дескрипторов прерываний
защищенного режима (Interrupt Descriptor Table — IDT). Как показывает программа IDTMAP, эти
прерывания отправляются к VMM и другим VxD.
Все это правильно не только для прерываний INT 21h, но и для всех прерываний, идущих из
режима V86. И это не есть заслуга 32BFA. Windows в расширенном режиме всегда перехватывает
все прерывания, приходящие из режима V86, поскольку так работает V86. Все V86-cpeflbi ведут се-
бя подобным образом: когда пользователь DOS работает с такими менеджерами памяти, как
EMM386, QEMM, 386МАХ или NetRoom, прерывания вначале обрабатываются в защищенном ре-
жиме и только затем их увидят DOS, TSR или драйвер устройства.
Однако, если вы запустите TEST21 с менеджером памяти без 32BFA, TEST21 сообщит “INT 21h
обрабатывается обычным образом”. Если менеджер памяти ведет себя в основном так же, как
32BFA, почему же TEST21 не замечает этого?
Ответ прост. Когда менеджер памяти (или Windows в расширенном режиме без 32BFA) пере-
хватывает INT из режима V86, он посылает (или “отражает”) эти вызовы обратно в режим V86.
32BFA же отличается тем, что часто не делает этого. Многие вызовы INT 21h он обрабатывает пол-
ностью в защищенном режиме, не отражая их в режим V86. Вызовы, которые TEST21 увидела при
работе под Windows, — это просто те вызовы, которые VMM (или другие VxD) решили отразить.
В случае 32BFA основным VxD является менеджер инсталируемой файловой системы (Installable
File System Manager — IFSMGR). Позднее в этой главе мы рассмотрим фрагмент кода IFSMGR,
приводящий к поведению, которое мы наблюдали в TEST21.
Важный момент состоит в том, что в 386-м расширенном режиме V86-npepbiBainiH под Windows
всегда проходят через IDT. В расширенном, режиме DOS-функция Get Interrupt Vector
(_dos_getvect, функция 35h INT 21h) никогда не определит правильно, кто первым обработает пре-
рывание. VMM и другие VxD всегда увидят прерывание первыми. На рис. 8.2 показано, что они
имеют полный контроль над тем, как обрабатывать то или иное прерывание. Они могут и не пере-
давать прерывание в программы реального режима. Возвращаясь к рис. 8.3, можно сказать, что они
Глава 8. Постепенное исчезновение DOS
191
передавали вызовы _dos_setvect (функция 25h) и _dos_write (функция 40h) на обработку програм-
мам реального режима при выводе в stdout и не передавали вызовы файлового ввода-вывода.
Строго говоря, на рис. 8.2 показано, что Windows — это операционная система, a DOS — ее
подсистема, которую Windows может на свое усмотрение использовать для обработки системных вы-
зовов. И хотя результаты TEST21 совершенно необычны, такое поведение неявно просматривается
как в документации Intel по режиму V86, так и в документации Microsoft по VxD. В частности,
посмотрите в документации Windows Device Driver Kit (DDK) описание функции Hook_
V86_Int_Chain.
Мы подобны мольеровскому персонажу из пьесы “Мещанин во дворянстве”, который удивился,
узнав, что всю свою жизнь говорил прозой, и только 32BFA обратил наше внимание на
существование 32-битовой операционной системы защищенного режима под названием Windows
VMM, которая все это время была у нас под носом. 32BFA больше не позволяет игнорировать эту
“новую” старую операционную систему.
Рис. 8.3. В зависимости от функции программное прерывание может иметь несколько путей. Если 32BFA
обрабатывает вызов в защищенном режиме, то он проходит путь от программы, вызывающей INT 21b,
через IDT к VMM, ISFMGR и обратно в программу. Если вызов отражен в DOS, путь продолжается
через цепочку VxD к IVT, затем опускается в цепочку обработчиков прерываний DOS. Драйвер реаль-
ного режима IFSHLP может посылать вызов обратно в IFSMGR. Если внутри MS DOS опять встретится
прерывание, то путь повторяется рекурсивно
Просто удивительно, как мало людей пометили соответствующую ячейку в Control Panel для
разрешения 32BFA. И, как мы увидим позже, 32BFA может вызывать определенные проблемы. Но
даже учитывая эти проблемы, приняв во внимания все жалобы на MS DOS и то, как много их
перекочевало в Windows, все равно странно, что большинство серьезных пользователей и разработ-
чиков игнорировали то, что, в сущности, было 32-битовой версией DOS защищенного режима.
192
Неофициальная Windows 95
32BFA против кэширования диска
Не ясно, почему большинство пользователей и разработчиков не прыгают от радости по
поводу 32BFA в WfW 3.11. Конечно, у 32BFA есть некоторые недостатки. Самый серьезный
из них заключается в том, что когда Windows откажет, вы потеряете все данные в кэше
32BFA. Но- такому же риску подвергаются все, кто использует кэш “с отложенной записью”
(write-behind) (32BFA, возможно, даже безопаснее, чем SmartDrv, поскольку кэш “с отло-
женной записью” в WfW 3.11 не используется).
Другое возможное объяснение — это то, что выигрыш в производительности 32BFA не-
сколько обескураживает. В журнале Byte (February 1994) в обзоре по WfW 3.1 Джон Удел
(Jon Udell) отметил следующие характеристики исполнения 32BFA:
Результаты могут быть впечатляющими. Машина Advanced Logic Research Flyer 32LCT 4DX2/66
с IDE-контроллером почти удваивала скорость последовательного файлового ввода-вывода при
использовании VFAT/VCACHE, а производительность при произвольном доступе утраивалась.
Но на Everex Step 486DX2/50 с контроллером Adaptec АНА-1742 и IBM PS/2 Model 90 ХР 486
с контроллером IBM SCSI-2 положение в корне отличалось. Здесь производительность файлового
ввода-вывода при произвольном доступе повышалась всего на 73 и 83% соответственно. Эти оцен-
ки были близки (у Everex) и немного лучше (для PS/2), чем зарегистрированные под Windows
NT на тех же самых двух машинах. Но в обоих случаях 32-битовый доступ к файлам замедляет
последовательный ввод-вывод. PS/2 теряет пятую часть своей производительности. Everex —
четвертую. Такое снижение производительности на двух SCSI-машинах привело к заметному
увеличению времени загрузки приложений.
Чем 32BFA отличается от кэширования диска? Я получал сообщения такого рода: “Как
это ни удивительно, но при разумном размере буфера кэширования SmartDrv (который в
ином случае заблокирован по умолчанию WfW) 16-битовый режим оказывается быстрее для
большинства операций”. Мое ограниченное тестирование SmartDrv 5.0 на шести машинах с
помощью TEST21 не подтверждает этих сообщений. Например, в окне DOS под WfW 3.11 на
486SL с контроллером жесткого диска IDE, как на уплотненном DblSpace дисководе, так и на
неуплотненном дисководе, я выполнил TEST21 3000 >FOO.BAR и получил следующие
результаты (время измерялось в секундах):
Конфигурация Нормальный диск Уплотненный диск
32BFA без SmartDrv 5 8
SmartDrv без 32BFA 15 171
без 32BFA и без SmartDrv 251 800
Кроме SmartDrv, я также пробовал CacheClk от Helix Software для защищенного режима.
Он был, конечно, быстрее SmartDrv, но все равно слишком уступал по времени 32BFA. По
этому тесту 32BFA выходит очевидным победителем в соревнованиях по кэшированию как
уплотненных DblSpace, так и неуплотненных дисков.
Конечно, TEST21 может показаться слегка надуманным. Поэтому я попробовал более
реалистичный пример с использованием grep для поиска несуществующей строки (“foobish”) в
3210 Кбайт данных. Поиск выполнялся дважды, так что кэш мог оптимизировать второй
поиск, если данные уже находились в памяти. На приведенных результатах цифры 1 и 2
указывают первый и второй поиск. Я выполнил поиски как на уплотненном DblSpace
дисководе, так и на неуплотненном.
Глава 8. Постепенное исчезновение DOS
193
7 Неофициальная Windows 95
Конфигурация Обычный диск Уплотненйый диск
(A) DOS реального режима без" кэша . .
1-Й поиск 15.4 16.9
2-й поиск 15.4 16.8
(В) 32ВЕА, без кэша
1-й поиск 12.6 17.5
2-й поиск 10.1 10.4
(С) CACHECLK 3400
1-й поиск 160 16.7
2-й поиск 10.1 13.0
(D) CACHECLK 3287
1-й поиск 16.1 16.9
12-й поиск 16.0 13.0
(Е) CACHECLK 1800
1-й поиск- 16.1 16.8
2-й поиск 16.1 13.0
(F) CACHECLK 1750
1-й поиск 16.1 16.8
2-й поиск 16.0 16.8
Эти результаты хорошо демонстрируют одно преимущество 32BFA. Он динамически изме-
няет размер своего кэша в зависимости от доступной памяти и потребностей пользователя. В
тесте (В) 32BFA смог оптимизировать 2-й поиск как на уплотненном DblSpace, так и на неуп-
лотненном диске без какой-либо настройки размера кэша со стороны пользователя. Заметим
также, что 1-й поиск занял значительно больше времени на уплотненном DblSpace диске, зато
2-й поиск с 32BFA практически не использовал DblSpace, который намного снизил бы произ-.
водительность. Данные были загружены из кэша 32BFA, поэтому DblSpace не оказал своего
влияния. В то же время заметим, что 32BFA выполнял 1-й поиск немного дольше на
DblSpace-диске. ',
В тесте (С) я использовал CACHECLK от Helix Software с CACHESIZE=3400. Посколь-
ку команда DIB сообщает о 3210 Кбайт данных, едва ли вызовет удивление, что кэширование
диска Смогло оптимизировать 2-й поиск как на уплотненном DblSpace, так и на неуплотнен-
ном диске. Обратите йниМание, что CACHECLK показал то же время, что и 32BFA для 2-го
поиска на несжатом диске. С другой стороны, 32BFA превосходит дисковые кэши (даже столь
хорошие, как CACHECLK) на DblSpace-дисках.
Далее, в тесте (D) (поскольку точный объем данных, выданный командой DIR, равнялся
3287421 байт) я по наивности установил CACHESIZE=3287. Как можно видеть, для неуплот-
ненных дисков такой размер кэша оказался слишком мал. 2-й поиск занял столько же време-
ни, сколько и 1-й. Это указывает на главное преимущество 32BFA над обычными кэшами дис-
ков. Кэш диска будет практически бесполезным, если его размер хоть чуть-чуть меньше, чем объем
данных, с которыми работает пользователь, a 32BFA адаптируется к конкретной ситуации.
Да, но. почему при явно недостаточном размере кэша для неуплотненного дисковода в
тесте (D) CACHECLK все же способен был “сбрить” 3.7 с при 2-м поиске на DblSpace-диске?
А потому, что CACHECLK кэшировал уплотненные данные, которые преспокойно помещают-
ся в том же самом кэше на 3287 Кбайт, в котором неуплотненные данные не поместились бы.
194
Неофициальная Windows 95 ,
Тест (С) показал, что установки CACHESlZE=3400 было достаточно для несжатых дан-
ных, команда DIR /СН показала коэффициент уплотнения 2.0:1.0. Тем не менее, когда я по-
пробовал CACHESIZE=1700, этого оказалось мало и никакого эффекта при 2-м поиске не
наблюдалось. Тесть! (Е) и (F) отражают результаты двоичного поиска, который я проводил,
чтобы найти подходящий размер кэша для уплотненных данных. Установка CACHESIZE=
1750 также оказалась недостаточной; a CACHESIZE=1800 вполне подошла. Играя размерами
кэша, можно найти истинный коэффициент уплотнения, он равен примерно 1.8:1.0
(3400/1800).
В любом случае, 32BFA устраняет необходимость настраивать размеры кэша вручную.
Кроме того, 32BFA дает дополнительные выгоды. Например, поддержка длинных имен фай-
лов (LFN) в Windows 95 действительно является побочным эффектом способности IFSMGR
обходить старый код файловой системы DOS.
В то же бремя производительность 32BFA зависит от производительности встроенного
кэша. Кэш в 32BFA управляется VxD VCACHE. Хотя VCACHE (подобно всем другим компо-
нентам 32BFA, включая IFSMGR и VFAT) не описан в WfW 3.11, предполагается, что
IFSMGR и VCACHE будут документированы в Windows Device Driver Kit (DDK) фирмы
Microsoft для Windows 95. Это могло бы дать некоторую интересную возможность независи-
мым разработчикам так же улучшать VCACHE, как они улучшали SmartDrv.
Получение и установка
текущего дисковода
В качестве альтернативы к последовательности open/read/close/open/write/close вызовов
INT 21h, которую мы использовали, цикл вызовов DOS в программе TEST21 может включать файл,
указанный в командной строке компилятора как -DTESTFILE=“filename” (см. листинг 8,1). Это по-
зволяет приспосабливать TEST21 для создания других тестов INT 21h.
В качестве одного примера, который мы проверим со всей скрупулезностью, рассмотрим неболь-
шой тест из листинга 8.2, где приведен стандартный фрагмент DOS-кода для получения установки
LASTDRIVE=. Это делается посредством вызова функции 19h INT 21h (Получить текущий диско-
вод) и передачи полученного значения функций OEh (Установить текущий дисковод), которая,
оказывается, возвращает значение LASTDRIVE (см. Undocumented DOS, 2d ed., р. 68-69).
Листинг 8.2. ТЕСТ_1 ,
/* TEST_1: 21/19 (Получить текущий диск) и 21/ОЕ (Установить текущий диск) */
_asm mov ah, 19h
_asm int 21h '
_asm mov dl, al
_asm mov ah, OEh
_asm int 21h
/* LASTDRIVE сейчас в AL */
called[0x19]++;
called[OxOe]++;
total_called += 2;
Если вы перекомпилируете TEST21 с опцией -DTESTFILE=“test_l” и запустите новую TEST21
под 32BFA, то не получите сообщения “Некоторые INT 21h обрабатываются без вызова DOS!”.
Windows не обходит DOS для таких вызовов, как получение/установка текущего дисковода. Кроме
того, рис. 8.4 демонстрирует более чем странные результаты.
Глава 8. Постепенное исчезновение DOS 195
йСгенерировано 201 вызовов Перехвачено 301 вызовов
21/ОЕ 100 вызвано 100 перехвачено
; 21/19 100 вызвано 200 перехвачено +
<21/25 1 вызвано 1 перехвачено
Были сгенерированы дополнительные вызовы INT 21h!
Рис. 8.4. В этом примере из WfW 3.11 с 32BFA DOS получила больше вызовов, чем было сгенерировано TEST21
Кто-то, как-то генерирует дополнительные вызовы функции 19h. Но кто? Как? И почему? Для
ответа на эти вопросы мы позволим себе взглянуть на код VxD IFSMGR. Мы увидим, что, даже
разрешая DOS выполнять свои дела, Windows, как говорится, держит ее на коротком поводке.
В процессе своей инициализации IFSMGR использует сервисы VMM для установки
обработчиков для всех вызовов INT 21h как из режима V86, так и из защищенного режима
(Protected-Mode — РМ). На рис. 8.5 показан фрагмент кода инициализации IFSMGR.
;;; Установить обработчик INT 21h для режима V86
OCFAO mov eax,21h
0CFA5 mov esi,offset V86_INT21_PR0C
OCFAA VMMcall Hook_v86_Int_Chain
;;; Получить предыдущий обработчик INT 21h защищенного режима
OCFBO mov eax,21h
0CFB5 VMMcall Get_PM_Int_Vector
OCFBB mov dwdrd ptr PREV_INT21_P_VECT_SEG,ecx
0CFC1 mov dword ptr PREV_INT21_P_VECT_0FS,edx
;;; Установить новый обработчик INT 21h защищенного режима
oCFC7 mov esi,offset PM_INT21_PR0C
oCFCC VMMcall Allocate_PM_Call_Back
0CFD2 movzx edx,ax
0CFD5 mov ecx,eax
0CFD7 shr ecx,10h
OCFDA mov eax,21h
OCFDF VMMcall Set_PM_Int_Vector
Рис. 8.5. Код IFSMGR для установки обработчиков INT 21Ь режима V86 и защищенного режима
Процедуры V86_INT21_PROC и PM_INT21_PROC в IFSMgr почти идентичны. В общих чертах
можно сказать, что обработчики INT 2lh режимов V86 и РМ берут номер функции DOS из регистра
АН (который доступен для VxD через структуру Client Register Structure (Структура регистров
клиента) по указателю в регистре ЕВР) и, как показано на рис. 8.6, используют его для индексации
в таблице обработчиков IFSMGR, выполняющих предварительную обработку функций INT 21h.
01947 movzx ecx,byte ptr [ebp.Client_AH]
01950 cmp cl,6Dh ;; в Windows 95 72h функций
01953 jae short elsewhere
01955 call dword ptr INT21_TAB[ecx*4] ;;; таблица IFSMGR+1700h
0195C jnc short elsewhere2
0195E retn
elsewhere:
Рис. 8.6. В этом фрагменте показано начало обработчика INT 21Ь из IFSMGR.
Таблица под названием INT21_TAB на рис. 8.6 очень важна для понимания механизма обра-
ботки INT 21h в IFSMgr. В одной конфигурации WfW 3.11 эта таблица оказалась по линейному
196
Неофициальная Windows 95
адресу 800A5600h (т.е. IFSMGR+1700h). Я использовал специально написанную программу
PROTTAB и получил дамп этой таблицы, отбросив элементы по умолчанию. Результирующий
вывод, отсортированный по адресам, дает хорошее начальное представление о вызовах INT 21h,
которые интересуют IFSMGR. На рис. 8.7 я слегка перестроил вывод и указал функции IFSMGR,
которые обрабатывают сразу несколько функций DOS. (Например, функция по адресу
IFSMGR+lAFFh обрабатывает функции ICh, 32h, 36h и 47h INT 21h, которые все ожидают номер
дисковода в регистре DL.)
C:\UNAUTHW>prottab 800a5600 6d 4 int21 800a589c IFSMgr=800a3f00 ; Table at 800А5600И ; Filter all out entries = 800А589СИ sort
; IFSMgr= :800A3F00
800A58A0 INT21_004D IFSMgr+19A0
800A58D0 INT21_004B IFSMgr+19D0
800A58E4 INT21_005F IFSMgr+19E4
800A5948 INT21_005E IFSMgr+1A48
800A5958 INT21_001A IFSMgr+1A58
800A5976 INT21_000E IFSMgr+1A76
800A59D4 INT21_0044 IFSMgr+1AD4
800A59FB INT21_001F IFSMgr+1AFB
800A59FF INT21_001C IFSMgr+1AFF 1C 32 36 47
800A5A25 INT21_004F IFSMgr+1B25
800A5A7E INT21_0045 IFSMgr+1B7E 45 46
800A5A8C INT21_003E IFSMgr+1B8C 3E-40 42 57 5C 68
800A5AD1 INT21_0011 IFSMgr+1BD1 11-13 17
800A5B5C INT21_006C IFSMgr+1C5C
800A5B63 INT21_0039 IFSMgr+1C63 39-3D 41 43 4E 56 5B
800A5D04 INT21_005D IFSMgr+1E04
800A5E60 INT21_000D IFSMgr+1F60
800A5E80 INT21_000B IFSMgr+1F80
Рис. 8.7. Эта таблица обработчиков IFSMGR INT 21Ь из WfW 3.11 неплохо демонстрирует, вызовы каких функций-INT
21Ь интересуют IFSMGR
На рис. 8.7 вряд ли представлена законченная картина того, как IFSMGR обрабатывает вызовы
INT 21h, но примерно очерчены те вызовы DOS, которые WfW 3.11 может обработать в 32-битовом
защищенном режиме.
Сейчас нам интересно узнать, почему при 100 вызовах функций OEh и 19h INT 21h, проведен-
ных TEST_1, возникает дополнительных 100 обращений к функции 19h. При изучении рис. 8.7
видно, что IFSMGR не делает ничего особенного с функцией 19h. Его обработчик является общим и
поэтому не попал в нашу таблицу. Однако в таблице есть обработчик функции OEh, расположенный
по адресу IFSMgr+lA76h. На рис. 8.8 показано, что мы обнаружим, если дизассемблируем код по
этому адресу.
121_000Е proc near ;; Установить Текущий дисковод
01А76 test dword ptr [ebx.CB_VM_Status],VMSTAT_PM_EXEC
01A7C jnz short i210e_done
!210e_v86:
01A7E sub eax,eax
01A80 mov esi,offset I21_0E_CALL_WHEN_RET
01A85 VMMcall Call_When_VM_Returns
01A88 i210e_done:
01A8B stc ;; CF установлен - перейти на следующий обработчик
01Ф8С retn
121_000Е endp
Рис. 8.8. Дизассемблированный код обработчика функции OEh INT 21h (установить текущий дисковод)
Глава 8. Постепенное исчезновение DOS
197
В коде на рис. 8.8 IFSMGR проверяет, пришел ли вызов функции OEh из защищенного режима
(например, из Windows-приложения). Если это так (бит VMSTAT_PM_EXEC установлен во фла-
гах текущего состояния виртуальной машину), IFSMGR не делает ничего особенного. Он устанав-
ливает флаг CF, указывая, Что вызов должен быть передан следующему обработчику INT 21h
защищенного режима. Если вызов функции.OEh пришел из режима V86, IFSMGR также передает
вызов следующему обработчику, и, в конечном счете, в DOS. Но сначала он вызывает функцию
VMM Call_When_VM_Retums, передавая ей адрес другой функции в IFSMGR.
Функция Call_When_VM_Returns, о чем свидетельствует ее имя, устанавливает обработчик,
который VMM вызовет при возврате текущей виртуальной машины. (VM) из обработки прерывания.
Этот механизм, документированный в DDK, заставляет по IRET возвращаться не к командам,
следующим за INT, а в VMM. Хотя на первый взгляд, результат TEST21 указывает, что функция-
OEh “обрабатывается обычным образом” (TEST21 видит все свои собственные вызовы функции
OEh), код IFSMGR указывает, что происходит нечто странное. IFSMGR перехватывает функцию
OEh после передачи ее DOS и программам DOS типа TEST21. Это известно как перехват после
отображения (post-reflection hook).
Это очень важный момент. Хотя сообщение TEST21 “Некоторые INT 21h обрабатываются без
вызова DOS!” не вызывает подозрений, пример с функцией OEh показывает, что сообщение “INT
21h обрабатывается обычным образом” означает только то, что для TEST21 обработка INT 21h
прошла нормально. DOS по-прежнему может подвергаться со стороны VxD таким специфическим
приемам, как перехват после отображения. Использование перехвата после отображения йри обра-
ботке функции OEh (и даже возможность полного перехвата со стороны IFSMGR) указывает, что
взаимоотношения DOS и Windows нельзя охарактеризовать столь просто и прямолинейно —
“Windows выполняется поверх DOS”, как думают многие пользователи и программисты.
Итак, что же делает IFSMGR, получив управление после того, как DOS завершает обработку
функции OEh INT 21h? Можно увидеть, что он делает, изучив рис. 8.9, на котором представлена
функция обработчика завершения вызова (callback), установленная IFSMGR с помощью
' Call_When_VM_Returns.
I21_QE_CALL_WHEN_REt proc near
01A§D Push_Clien_State ' -
; 01A9C VMMcall Begin_Nest_V86_Exec
01AA2 mov byte ptr [ebp.Cllent_AH],19h ;; Получить текущий дисковод
' 01AA6 mov eax,21h •
. 01AAB VMMcall Exec_Int ;Вызвать INT 21h AH-19h
O1AB1 mov.dl,byte ptr [ebp.Client_AX] ;; А1_=текущий дисковод
01AB4 mov ecx,dword p£r DEVICE_CB_AREA ;; данные VM'
01ABA mov byte pt'r [ebx+OAh][ecx],dl ;; сохранить текущий дисковод
O1ABE VMMcall End_Nest_Exec
01AC4 Pop_Client_State
01AD3 retn / f
I21_0E_CALL_WHEN_RET '
Рис. 8.9. Обработчик Call_When_yM_Returns для функции OEh INT 21h
•Л 1
Мы хотели узнать, почему TEST_1 регистрирует дополнительные вызовы функции 19h INT 21h.
Рис. 8.9 дает точный ответ. После того Как приложение в режиме V86 вызовет функцию OEh для
установки текущего дисковода и как DOS обработает вызов, IFSMGR получает управление, вызы-
вает функцию 19h для получения текущего дисковода и записывает это значение в структуру
данных, которая имеется у каждой VM в блоке управления VM (VM Control Block — VMCB. VxD
используют функцию VMM_Allocate_Device_CB_Area для распределения памяти в VMCB).. *
Для вызова функции 19h INT 21h IFSMGR использует стандартный набор фушсций VMM,
показанных на рис. 8.9: Begin_Nest_V86_Exec, Exec_Int и т.д. Фразы, использованные мной в этой
главе, вроде “Windows передает вызовы й DOS” и “Windows отражает прерывание в режим V86”,
! как раз и соответствуют последовательности вызовов Begin_Nest_V86_Exec и Exec_Int.
198 Неофициальная Windows 95
На самом деле, при работе под Windows в расширенном режиме — как с 32BFA, таки без него —
все вызовы INT 21h, обнаруженные обработчиком INT 21h в TEST21 (а фактически, все прерыва-
ния, обнаруженные любым обработчиком прерываний программы реального режима), являются
результатом того, что VMM или какой-то VxD вызывал Begin_Nest_V86_Exec и Exec_Int. Это
означает отсутствие обязательной корреляции между вызовами INT 21h, выданными под Windows и
обнаруженными в DOS.
Между прочим, IFSMGR мог бы использовать единственную функцию Exec_Vxd_Int вместо бо-
лее длинной последовательности вызовов, показанных на рисунке. К сожалению, Exec_Vxd_Int
почти не используется из-за ошибки в другой функции Begin_Nest_Exec. (Джеф Чапелл нашел про- ’
стую ошибку в коде и послал файл с под названием NESTFIX.ASM, содержащий исправленный
вариант, на форум CompuServe WINSDK).
Итак, мы ввдим, что всякий раз при вызове функции OEh INT 21h из режима V86 IFSMGR
добавляет также вызовы Begin_Nest_V86_Exec и Execjnt для функции 19h INT 2 lh/Вы также
могли заметить, что IFSMGR принимает значение, возвращенное функцией 19h, и записывает его в
DEVICE_CB_AREA+OAh.
Но зачем? Если IFSMGR хочет сохранять текущий дисковод в каждой VM (что вполне разум- .
но), почему он не может взять это значение из регистра DL, когда пользователь вызывает функцию
OEh? Вместо представленной функции i21_000E, которая использует дополнительные вызовы,
IFSMGR, казалось бы, мог просто выполнять следующее:
wrong_i21_000E proc near
mov dl.byte ptr [ebp.Client_DL]
mov ecx.dword ptr DEVICE_CB_AREA
mpv byte ptr [ebx+OAh][ecx],dl
stc
wrong_i21_000E endp
01_=нрвый текущий дисковод
данные VM
сохранить новый текущий дисковод
CF установлен - перейти на след, обработчик
И что же здесь неправильного?
Проблема в том, что нет гарантии, что пользователь передаст функции OEh допустимое значе-
ние! К сожалению, функция OEh не возвращает код ошибки (вместо этого она возвращает в AL
значение LASTDRIVE). Поэтому единственный способ узнать, успешен ли вызов функции OEh —
это вызвать функцию 19h, что и делает IFSMGR.
Почему IFSMGR не делает этого, когда VM работает в защищенном режиме
(VMSTAT_PM_EXEC)? Без какой-либо специальной обработки функции OEh в защищенном режи-
ме VMM автоматически посылает вызов по цепочке V86-o6pa6oT4HKdB прерываний. Таким образом,
IFSMGR увидит вызов функции OEh вторично, и на этот раз он сможет вызвать функцию 19h и
сохранить текущее значение дисковода.
Другими словами, для каждого вызова функции OEh из защищенного режима осуществляется
дополнительный вызов функции 19h в режиме V86. И моя программа WLOG212F подтверждает
это. Как мы видели в главе 1, WLOG212F — это Windows-приложение защищенного режима,
которое перехватывает прерывание INT 21h в трёх местах: дважды в защищенном режиме и один
раз (используя DPMI-обработчик завершения вызова) в режиме V86. WLOG212F может сравнить
прерывания, выданные в защищенном режиме, с прерываниями, просматриваемыми из режима V86.
После запуска нескольких Windows-приложений WLOG212f выдала следующие результаты для
функций OEh и 19h INT 21 h:
Func KernelDOSProc (Prot mode)
OEh 688
19h 674
V86 mode
.'688 Passed down
1’362 Passed down
WLOG212F обнаружила 1362 вызова функции 19h из режима V86. Из них только 674 были вы-
даны в защищенном режиме. Откуда взялись вызовы к функции 19h? Вычислим: 1362 - 674 = 688,
это число вызовов функции OEh в защищенном режиме. Как и следовало ожидать, для каждого вы-
зова функции OEh из защищенного режима существует дополнительный вызов функции 19h из
режима V86.
Гдова 8. Постепенное исчезновение DOS
199
После такого тщательного рассмотрения функции OEh напрашивается вывод: даже для простого
вызова INT 21h, передаваемого Windows в DOS, существует целый слой VMM/VxD, который
управляет обработкой этого вызова. VxD IFSMGR обнаруживал вызовы до DOS и снова следил за
ними при завершении DOS обработки этого вызова. Единственная причина, по которой DOS обна-
руживала вызов, состоит в том, чтр IFSMGR позволял ей это. IFSMGR может даже, получив один
вызов INT 21h (функция OEh), генерировать другой (функция 19h). Отмечу еще раз: между теми
вызовами, которые выдаются под Windows (даже DOS-программами, такими как TEST21), и теми,
которые идут на обработку в DOS реального режима, нет обязательной зависимости.
Важно также понимать, что вызовы, переданные в DOS, зависят от того, какие VxD были загру-
жены. Хотя IFSMGR встроен в WfW 3.11 и Windows 95, возможность написания VxD, которые об-
ходят DOS, является стандартной частью программного интерфейса VMM и документирована в DDK.
Возьмем, к примеру, функцию 19h. Как отмечено в Undocumented DOS (2d ed., р. 69), реали-
зация этой функции в реальном режиме DOS тривиальна. Она только помещает содержимое пере-
менной DOS CURR_DRIV, расположенной по смещению 336h в сегменте данных DOS (смещение
16h в SDA) в регистр AL:
-и fdc9:4c64
FDC9:4C64 AD3603 MOV AL,[0336] ; D0S_DS:0336 = CURR_DRIV
FDC9:4C67 C3 RET
Таким образом, VxD, который обрабатывает эту функцию полностью в 32-битовом защищенном
режиме, — просто детская игрушка. CURRDRIV.ASM из листинга 8.3 — именно такой VxD. Мы
много говорили о том, как VxD стали операционной системой и как они могут обрабатывать DOS-
вызовы в защищенном режиме, поэтому было бы полезно изучить один маленький VxD и по-
смотреть, как все это работает.
Листинг 8.3. CURRDRIV.ASM
comment %
CURRDRIV.ASM
Пример VxD, который полностью обрабатывает одну из функций INT 21h в
защищенном режиме.
Шульман, 1994
masm5 -р -w2 currdriv.asm
link386 currdriv.obj, currdriv.386,,,currdriv.def
addhdr currdriv.386
Поместите строку device=\-••.\currdriv.386 в SYSTEM.INI [386Enh]
currdriv. def:
LIBRARY CURRDRIV
DESCRIPTION ’INT 21h Functions DEh, 19h Handler'
EXETYPE DEV386
SEGMENTS
.LTEXT PRELOAD NONDISCARDABLE
.LDATA PRELOAD NONDISCARDABLE
.ITEXT CLASS 'ICODE' DISCARDABLE
.IDATA CLASS 'ICODE' DISCARDABLE
.TEXT CLASS PCODE' NONDISCARDABLE
.DATA CLASS PCODE' NONDISCARDABLE
EXPORTS CURRDRIV.DDB @1
%
. 386p
200
Неофициальная Windows 95
INCLUDE VMM.INC
INCLUDE V86MMGR.INC
Declare_Virtual_Device CURRDRIV, 1, 0, \
Control_Proc, \
Undefined_Device_ID, \
Undefined_Init_Order, . ,\
VxD_DATA_SEG
;;; нужно добавлять к линейному адресу сегмента данных DOS
Lin_CurDrv dd 0336b
VxD_DATA_ENDS
VxD_CODE_SEG
BeginProc Int2!V86
movzx eax, [ebp.Client_AX]
crop ah, 19h
je short Do_GetCurDrv
stc
ret •
Do_GetCurDrv:
mov eax, [Lin_CurDrv]
mov al, byte ptr [eax]
mov byte ptr [ebp.Client_AL], al
clc
ret
EndProc Int21V86
VxD_CODE_ENDS
VxD_LOCKED_CODE_SEG
BeginProc Control_Proc
Control_Dispatch Device_Init, Do_Device_Init
clc
ret
' EndProc Control_Proc
VxD_LOCKED_CODE_ENDS
VxD_ICODE_SEG
BeginProc Do_Device_Init
Push_Client_State
VMMcall Begin_Nest_V86_Exec
mov [ebp.Client_AH], 52h
mov eax, 21h
VMMcall Exec_Int
movzx eax, [ebp.Client_ES]
shl eax, 4
add Lin_CurDrv, eax
VMMcall End_Nest_Exec
Pop_Client_State
mov eax, 21h
mov esi, offset32 Int21V86
Глава 8. Постепенное исчезновение DOS
201
VMMcall Hook_V86_Int_Chain
ret
/EndProc Do_Device_Init
VxD_ICODE_ENDS
vxd.realinit^seg
realCinit proc near
xor ax, ax
xor bx, bx
• xor si, si
xor edx, edx
ret
real_init endp
VXD_REAL_INIT_ENDS
END
Как и все VxD, CURRDRIV. ASM имеет оператор Declare_Virtual_Device, который в конечном
счете превратился в блок дескриптора устройства (Device Descriptor Block — DDB). Он содержит
указатель CURRDRIV на Процедуру Control_Proc. VMM вызывает Control_Proc каждого VxD при
различных событиях, таких как создание/завершение VM или ветви (thread).
Единственное событие, о котором заботится CURRDRIV, — это Device_Init. Процедура
Do_Device_Init вначале использует стандартный блок кода Begin_Nest_V86_Exec/Exec_Int,
чтобы вызвать функций 52h прерывания INT 21h в режиме V86. Эта функция DOS возвращает в
ES:BX указатель на недокументированную структуру DOS SysVars (список списков). Однако
CURRDRIV игнорирует ВХ и использует ES как сегмент данных DOS. Он сдвигает ES на 4 бита
влево для образования линейного адреса и добавляет его к пе'ременной Lin_CurDrv, которая до
этого имела значение 0336h. Lin_CurDrv теперь содержит линейный адрес переменной текущего
. дисковода DOS».
После настройки Lin_CurDrv драйвер CURRDRIV использует функцию VMM Hook_
V86_Int_Chain для установки процедуры Int21V86 в качестве обработчика INT 21h. Эта процедура
активизируется в момент вызова INT 21h. Обратите внимание, что обработчик INT 21h будет
* вызываться не только тогда, когда вызов INT 21h придет из VSG-режима, но также для любых вы-
* зовов Exec_Int 2lh, проведенных VxD во вложенной блоке. V86 Ехес, как показано на рис. 8.9.
Процедура Int21V86 использует [ebp.Client_AH], чтобы проверить, какая функция JJfr 2th
вызывается. Для любой функции DOS, отличной от 19h, CURRDRIV устанавливает флагЧСЕ. Это
дает сигнал VMM передать вызов следующему VxD в цепочке перехватчиков V86.
, » Когда приходит вызов функции 19h INT 21h, CURRDRIV переходит к процедуре Do_Get
CurDrv. Она читает значение текущего дисковода из Lin_CurDrv, перемещает его в регистр AL
клиента (ebp.Client_AL) и возвращает управление, сбросив флаг CF. Это сигнализирует VMM, что
прерывание было обслужено и не должно передаваться в какой-либо другой VxD (вроде IFSMGR)
в цепочке V86-nepeXBaT4HKOB или в цепочку прерываний DOS в режиме V86. (Важно понимать
. .различие между этими двумя цепочками: первая поддерживается функцией Hook_V86_Int_Chain и
состоит из 32-битового кода, а вторая цепочка давно знакома по программированию в DOS).
Итак, функция 19h прерывания INT 21h теперь полностью обработана в 32-битоврм защищен-
ном режиме, в обход DOS и любых других VxD, которые могли бы перехватить вызов этой функ-
ции. Использовав инструментальные средств^ DDK для формирования CURRDRIV.386 (см. инст-
рукции в верхней части листинга 8.3) и поместий device=currdriv.386 в раздел [386enh] вашего
SYSTEM.INI, можно запустить версию TEST21 TEST_1 опять и увидеть, что CURRDRIV действи-
тельно обходит DOS:
202
Неофициальная Windows 95
Сгенерировано 201 вызовов Перехвачено 101 вызовов,.
21/0Е 100 вызвано 100 перехвачено
21/19 100 вызвано 0 перехвачено -
21/25 1 вызвано 1 перехвачено
Некоторые INT 21h обрабатываются без вызова DOS!
Обратите внимание на отличие от рис. 8.4. Вместо сообщения “Были сгенерированы дополни-
тельные вызовы INT 21h!” мы теперь получаем сообщение “Некоторые INT 21h обрабатываются без
вызова DOS!” — и все из-за простого кода в CURRDRIV.ASM.
CURRDRIV.ASM не использует никаких функциональных возможностей 32BFA. Функции
VMM, которые он использует, особенно Hook_V86_Int_Chain, существовали уже с 1990 г.
CURRDRIV делает в основном то же, что и 32BFA, хотя и в гораздо меньшем масштабе. Все
необходимые для исчезновения DOS ресурсы давным-давно находились в VMM и только ожидали
своего использования.
' * , 1
Windows 95: продолжает обходить DOS,
но поддерживает TSR
До сих пор мы использовали TEST21 только для экспериментов с 32BFA в WfW 3.11. Теперь
нам понадобится запускать TEST21 под Chicago, которая, конечно, также поддерживает 32BFA. На
рис. 8.10 показан результат этого тестирования.
Прошло 0 секунд
Сгенерировано 701 вызовов
21/25 1 вызвано
21/3D 200 вызвало
21/ЗЕ 200 вызвано
21/3F 100 вызвано
21/40 *200 вызвано
Перехвачено 701 вызовов
1 перехвачено
200 перехвачено
200 перехвачено
100 перехвачено
200 перехвачено
INT 21h обрабатывается обычным, образом
. г......w
Рис. 8.10. Результаты работы TEST21 под Chicago
Что?! Сообщение “INT 2^ обрабатывается обычным образом” на рис. 8.10 уж никак не вяжется
с Windows 95, особенно учитывая, что 32BFA в WfW 3.11 (которая, как сказано в собственной
рекламе Microsoft, “усилена 32-битовой технологией из нашего проекта Chicago!”) вызывает сооб-
щение TEST21 “Некоторые INT 21h обрабатываются без вызова DOS!”.
Более того, программа V86TEST из главы 10, загруженная перед Windows, показывает, что при
работе TEST21 .(или любой другой программы, перехватывающей INT 21h) в окне DOS Windows
передает эти вызовы в DOS илй, по крайней мере, глобальным (т.ё. загруженным перед Windows)
программам DOS, вроде V86TEST. При выполнении TEST21 локально (т.е. в окне DOS) V8&TEST
обнаружила следующие вызовы INT 21h:
02: 42 08: 5 0В: 1846 19: 3 1А: 1 25: 12 29: 4 ’• 2А: 3
2С: 3 30: 1 35: 5 38: 1 3D: 200 ЗЕ: 215 3F: 100
40: 203 44: 1 48: 2 49: 1 4А: 1' 4В: 1 4С: 1 4D: 1 5D: 1
Заметим, что обращения к функциям 3Dh—40h (Файл открыть, закрыть, читать и писать)
довольно хорошо согласуются с вызовами, выполненными TEST21. Вспомните, под WfW 3.11 с
32BFA V86TEST видела только функцию 40h, используемую TEST21 для вывода в stdout.
Глава 8. Постепенное исчезновение DOS
203
По крайней мере в смысле взаимодействия с DOS результаты TEST21 показывают, что, по всей
видимости, Windows 95 делает шаг назад от WfW 3.11!
Однако не будем торопиться: хотя TEST21 действительно видела все вызовы INT 21h, сообще-
ние “Прошло 0 секунд” на рис. 8.10 ставит вопрос, а действительно ли MS DOS видела эти вызо-
вы? Обращаясь к рис. 8.1, мы видим, что TEST21 на этой же машине без 32BFA затратила 21 с. Ес-
ли TEST21 затрачивает приблизительно такое же время под Chicago, как и под WfW 3.11 с 32BFA,
значит, сообщение “INT 2 ^ обрабатываются нормальным образом” в некотором смысле обманчиво.
Кроме того, если вы установите VxD CURRDRIV из листинга 8.3 под Windows 95, а затем
запустите версию TEST_1 программы TEST21 (листинг 8.3), то увидите сообщение “Некоторые INT
21h обрабатываются без вызова DOS!”, подтверждающее то, что CURRDRIV так же успешно
перехватывает и обрабатывает функцию 19h прерывания INT 21h, как и под WfW 3.11. Ясно, что
механизм Hook_V86_Int_Chain для обработки вызовов в 32-битовом защищенном режиме работает
правильно, поэтому должно быть что-то различное в реализации 32BFA в Windows 95.
Увидеть Windows 95 в истинном свете нам поможет опция -MYSETVECT программы TEST21.
С опцией -MYSETVECT TEST21 будет получать и устанавливать вектор прерывания не с помощью
функций 25h и 35h прерывания INT 21h (_dos_setvect и _dos_getvect), а с помощью функций
my_setvect и my_getvect, которые непосредственно прочитывают и записывают векторы в таблице
прерываний IVT (см. листинг 8.1 и раздел “Прерывания 101: IDT против IVT” в этой главе).
Угадайте, что при этом произойдет? Как показано на рис. 8.11, запуск TEST21 -MYSETVECT
под Chicago приводит к тому же самому результату (“Некоторые INT 21h обрабатываются без
вызова DOS!”), что и выполнение TEST21 под WfW 3.11 с 32BFA.
C:\THIS IS A LONG WINDOWS 95 DIRECTORY NAME>test21 -mysetvect > log
C:\THIS IS A LONG WINDOWS 95 DIRECTORY NAME>type log
Использование my_setvect
Прошло 1 секунд
Сгенерировано 701 вызовов Перехвачено 0 вызовов
21/3D 200 вызвано 0 перехвачено -
21/ЗЕ 200 вызвано 0 перехвачено -
21/3F 100 вызвано 0 перехвачено -
21/40 200 вызвано 0 перехвачено -
Некоторые INT *21h обрабатываются без вызова DOS!
Рис. 8.11. Эти результаты получены при запуске TEST21 -MYSETVECT под Chicago
И опять V86TEST подтверждает, что вызовы функций INT 21h 3Dh-40h не передаются в DOS:
02: 12 08: 11 0В: 2608 19: 3', 1А: 1 25: 8 29: 4 2А: 3
2С: 3 30: 1 35: 4 38: 1 ЗЕ: 15
40: 3 44: 1 48 : 2 49: 1 4А: 1 4В: 1 4С: 1 4D: 1 5D: 2
Почему использование my_setvect вместо _dos_setvect для установки обработчика INT 21h при-
водит к столь отличному результату?
Можно предположить, что версия IFSMgr Chicago должна перехватывать функцию 25h пре-
рывания INT 21h (установить вектор прерывания) и использовать ее для установки некоторого
флага, указывающего, что обращения DOS для данной VM должны быть переданы по цепочке
обработчиков INT 21h.
Действительно, использовав программу PROTTAB для создания таблицы IFSMGR Chicago,
подобной той, что создавалась для WfW 3.11 (см. рис. 8.7), мы убедимся, что таблица IFSMGR об-
работчиков функций INT 21 h в Chicago содержит обработчик функции 25h. Чтобы не надоедать вам
новой таблицей, я здесь привожу только отличия между таблицами IFSMGR Chicago и WfW 3.11:
204
Неофициальная Windows 95
• Четыре функции работы с файлами через FCB обрабатываются в WfW, но не
обрабатываются в Windows 95: llh, 12h, 13h и 17h.
• Пять функций обрабатываются в Windows 95, но не обрабатываются в WfW: IBh, 25h, 60h,
69h и 71h. Функция IBh получает информацию о распределении текущего дисковода. Функ-
ция 60h — это недокументированная функция TRUENAME (см. Undocumented DOS, 2d ed.,
р. 148-151, 428-430). Функция 69h — это недокументированная функция получения и
установки серийного номера диска. Функция 71h — новая. Она обеспечивает поддержку
длинных имен файлов (LFN) в Windows 95. Например, если функция 60h — это
TRUENAME, функция 7160h — ее LFN-версия.
В любом случае нас интересует функция 25h. Как мы и ожидали (исходя из различных
результатов работы TEST21 и TEST21 -MYSETVECT), проверка кода IFSMGR для функции 25h
показывает, что при установке вектора INT 21h обработчик функции 25h устанавливает или
сбрасывает флаг в IFSMGR DEVICE_CB_AREA. Как было отмечено раньше, DEVICE_CB_AREA
является частью VMCB, т.е. имеется у каждой VM.
Обработчики IFSMGR INT 21h в Windows 95 отличаются от соответствующих обработчиков для
WfW 3.11 (см. рис. 8.6). В частности, они проверяют флаг, устанавливаемый функцией 25h. Если
установлен флаг, указывающий, что VM перехватила INT 21h, они передают вызов INT 2 lh дальше.
Очевидно, так Microsoft отреагировала на поток жалоб по поводу того, что 32BFA в WfW 3.11
не передает дальше вызовы INT 21h. Например, те же самые обходные маневры 32BFA, которые мы
наблюдали в TEST21, не дают программе дискового сжатия Stacker нормально работать в WfW 3.11!
Согласно PC Week (“Stacker clashes with WfW's file access; Stac working on fix,” March 7, 1994)
“VCACHE блокирует определенные обращения DOS к драйверу Stacker”. Не ясно, почему
VCACHE выбран в качестве преступника, ведь на самом деле виноват IFSMGR, а не VCACHE, но
фраза “блокирует определенные обращения DOS”, конечно же, справедлива, что и следует из ре-
зультатов работы программ TEST21 и V86TEST под WfW 3.11.
Microsoft стоит перед дилеммой: 32BFA обходит MS DOS, но обход DOS означает, что такие
ключевые утилиты DOS, как Stacker, не увидят потока вызовов INT 21h. Если бы TEST21 дейст-
вительно была полезной программой, делающей нечто большее, чем простая регистрация вызовов
INT 21h, и если бы ее функциональность полностью зависела от наблюдаемых вызовов INT 21h, мы
не были бы так оптимистичны относительно ее способности обнаруживать присутствие 32BFA в
WfW 3.11. Мы сказали бы, что TEST21 просто не работает под Windows.
Компьютерная пресса любит издеваться над Microsoft за кажущуюся неудачу попыток быстрее
отойти от реального режима DOS. А когда Windows действительно начинает удаляться гигантскими
шагами от DOS реального режима, как в 32BFA, компьютерная пресса опять же обвиняет или
Microsoft, или производителей переставших работать DOS-утилит, или же и тех, и других — только
на этот раз они рассматривают движение прочь от совместимости с DOS ошибочным! Очевидно,
каждый хочет совместимости с DOS, но только не DOS.
Да уж, достаточно разумно. В некотором смысле... Microsoft должна обходить DOS, но все-
таки поддерживать программы DOS (такие, как Stacker), которые должны видеть вызовы DOS. Мы
увидим позднее, что 32-битовый дисковый доступ (32BDA, также известный как Fastdisk),
появившийся в Windows 3.10, содержит красивое решение подобной проблемы.
В Windows 95 32BFA использует не столь красивое решение. Обратите внимание, как легко
смог TEST21 -MYSETVECT заставить Windows 95 думать, что мы не перехватили INT 21h. Хотя
код my_setvect мог показаться надуманным, DOS-программы далеко не всегда пользуются функ-
циями 25h и 35h, а напрямую работают с IVT. Вот вам один пример: Windows сама так делает.
На момент написания книги Chicago еще пребывала в состоянии бета-версии. Возможно,
Microsoft еще заделает дыру с my_setvect к выпуску коммерческой версии Windows 95. Когда это
произойдет, время, затраченное на выполнение TEST21, будет единственным указанием на 32BFA, а
TEST21 -MYSETVECT выдаст ложное сообщение “INT 21h обрабатываются обычным образом”.
Откровенно говоря, это не более чем предположение, что TEST21 должна выдавать это ложное
сообщение. TEST21 так хорошо вычисляет 32BFA под WfW 3.11 только из-за дефекта в 32BFA.
Этот недостаток остался, .и в Windows 95, но он уже не столь велик. Не считая времени,
____________________di
Глава 8. Постепенное исчезновение DOS 205
затраченного на выполнение файлового ввода-вывода, 32BFA должен быть “прозрачен” (т.е. не-
- видим, незаметен ддя приложений), а мы запросто обнаружили присутствие 32BFA с помощью
TEST21, совершенно “законной” DOS-программы, которая не использует никаких недокументи-
рованных или неразрешенных приемов.
INTVECT: еще один пример
. VxD-перехватчика INT 21 h
! В качестве еще одного примера того, как просто можно помешать IFSMGR проверить, перехва-
тывает ли VM прерывание INT 21h, рассмотрим VxD, который захватывает вызовы функции 25h до
того, как 'их отследит IFSMgr. VxD, перехватывающий функцию 25h (и 35h тоже), особенно важен в
качестве дополнительной демонстрации того, как VxD могут управлять DOS в защищенном режиме.
VxD CURRDRIV (см. листинг 8.3) демонстрирует, насколько просто, по крайней мере для про-
стых вызовов INT 21h, Можно обойти DOS. INTVECT.ASM из листинга 8.4 — аналогичный VxD.
INTVECT.ASM перехватывает функции 25h (Установить вектор прерывания) и 35h (Получить
вектор прерывания) прерывания INT 21h и обрабатывает эти вызовы полностью в 32-битовом
защищенном режиме. INTVECT в основном использует код функций my_setvect и my_getvect из
листинга 8.1, переведенных в 32-битовый ассемблер.
ЛИСТИНГ 8.4. INTVECT. ASI4
comment %
INTVECT.ASM
, Пример VxD, который полностью обрабатывает две функции INT 21h в
защищенном режиме г
INT 21h функция 25h; Установить вектор прерывания
INT 21h функция 35h: Получить вектор прерывания
Шульман, 1994
masm5 -р -w2 intvect.asm
link386 intvect.obj,intvect.386,,,intvect.def
. addhdr intvect.386 4
INTVECT.DEF:
LIBRARY INTVECT
DESCRIPTION 'INT 21h Functions OEh, 19h Handler'
. EXETYPE DEV386
SEGMENTS
_LTEXT PRELOAD NONDISCARDABLE
_LDATA PRELOAD NONDISCARDABLE
_ITEXT CLASS 'ICODE' DISCARDABLE
_IDATA CLASS ICODE' DISCARDABLE
_TEXT CLASS 'PCODE' NONDISCARDABLE
_DATA CLASS 'PCODE' NONDISCARDABLE
EXPORTS INTVECT_DDB @1
*
. 386p
INCLUDE VMM.INC
INCLUDE V86MMGR.INC
IFSMgr_Init_Order EQU ОАОО11ОООИ ; определено при запуске VXDSHOW IFSMGR.386
206
Неофициальная Windows 95
Declare_Virtual_Devlce INTVECT, 1, 0, \
Cont r01_P roc, \
Undefined_Device_ID, \
IF§Mgr_Init_0rder+10000H, , ,\
Vxp_C00E.SEG
s
BeginProc Int21V86
movzx eax, [ebp.Client_AH]
cmp al, 25h
je short DoJSetVect
cmp al, 35h
je short Do_GetVect
stc
ret
по цепочке к предыдущему обработчику
Do_SetVect:
movzx eax, [ebp.Client_AL]
movzx edx, [ebp.Client_DS]
shl edx, 16 ; из DS:DX клиента
mdy dx,-[ebp.ClientJJX]'-
mov ecx, [ebx.CB_High_Linear]
mov dword ptr [ecx + 4 * eax], edx
clc
- ret
; номер прерывания
; получить обработчик прерывания
; записать в IVT[eax]
; сделано - возврат!
Do_Get'Vect:
movzx eax, [ebp.Client_AL] ; номер прерывания
mov ecx, [ebx.CB_High_Linear]
mov edx, dword ptr [ecx * 4 * eax] ; получить обработчик из IVT[eax]
mov [ebp.Client_BX], dx ; поместить в ES:BX клиента
ehr edx, 16
mov [ebp.Client_ES], dx ,,
clc , ; сделано - возврат!
ret • .»
EndProc Int21V86
VxD_CODE_ENDS
VxD_LOCKED_CODE1_SEG • ,
BeginProc Control_Proc
Control_Dlspatch Device_Init, Do_Device_Init
clc'
ret ' .
EndProc Control_Proc
VxD_LOCKED_CODEiiENDS
VxD_ICODE_SEG
BeginProc Do_Device_Init
mov eax, 21h
mov esi, offset32 Int21V86
VMMcall Hook_V86_Int_Chain
ret
EndProc Do_Device_Init
VxD_ICODE_ENDS .
Глава 8. Постепенное исчезновение DOS
207
VXD_REAL_INIT_SEG
real_init proc near
xor ax, ax
xcr bx, bx
xor si, si
xor edx, edx
ret
real_init endp
VXD_REAL_INIT_ENDS
END
Как и в предыдущем примере CURRDRIV, в INTVECT процедура Do_Device_Init использует
функцию VMM Hook_V86_Int_Chain для установки Int21v86 в качестве V86-o6pa6or4HKa INT 21h.
Впрочем, в отличие от CURRDRIV, INTVECT должен заботиться о порядке, в котором он
встроится в цепочку перехватчиков INT 21h. Мы знаем, что IFSMGR в Windows 95 перехватывает
функцию 25h, Из-за этого я и выбрал такой пример. По причинам, которые вскоре станут понят-
ными, нужно, чтобы INTVECT перехватывал функцию 25h до того, как ее перехватит IFSMGR.
Расположение VxD в цепочке перехватчиков INT 21h V86 зависит от того, когда VxD вызывал
функцию Hook_V86_Int_Chain. Это, в свою очередь, частично зависит от порядка инициализации
VxD, поэтому я дал INTVECT порядок инициализации несколько больший, чем у IFSMGR. Многие
аспекты 32BFA зависят от порядка инициализации VxD. Такое программирование VxD подобно ра-
боте с TSR, за исключением того, что авторы VxD сами вынуждены играть в “загружай меня по-
следним” вместо того, чтобы потребовать этого от пользователя.
Когда INTVECT получает вызов функции 25h, он переходит к процедуре Do_setvect. Эта
процедура получает номер прерывания из [ebp.Client_AL], а новый обработчик — из
[ebp,Client_DS] и [ebp.Client_DX]. INTVECT помещает новые координаты обработчика прямо в
IVT, как это делала функция my_setvect в листинге 8.1. VxD имеют доступ к 4 Гбайт адресного
пространства, и адресное пространство текущей VM отображено с линейного адреса 0, поэтому
INTVECT может обращаться к IVT с помощью простого выражения DWORD PTR [intno*4]. Одна-
ко адресное пространство текущей VM также доступно через поле [ebx.CB_High_Linear] в VMCB,
и INTVECT использует его.
В случае вызова функции 35h INTVECT переходит к Do_getvect, которая получает адрес обра-
ботчика из IVT и возвращает его в [ebp.Client_ES] и [ebp.Client_BX]. Это подобно коду my_getvect
BTEST21.
После создания и установки INTVECT.386 самое время проверить, работает ли он. Вспомните,
что TEST21 всегда видела свой собственный вызов функции 25h. С установленным INTVECT.386
можно запустить TEST21 и... Вуаля! TEST21 больше не видит вызова функции 25h. Видите, как
просто обойти DOS?
В качестве более интересного теста установим INTVECT.386 в Windows 95 в SYSTEM.INI и за-
пустим TEST21 снова, на этот раз без параметра -MYSETVECT. Теперь TEST21 выводит свое сооб-
щение “Некоторые INT 21b обрабатываются без вызова DOS!”, указывая на присутствие 32BFA.
Происходит следующее: INTVECT захватил функцию 25h перед IFSMGR, обработал функцию
с помощью записи непосредственно в IVT и сбросил флаг CF, чтобы VMM не передал вызов кому-
нибудь еще. Эта способность V86-o6pa6oT4HKOB прерываний VxD обходить не только DOS и TSR,
но и другие VxD, безусловно впечатляет, однако может вызывать и неприятности. Она показывает,
что VxD играет в Windows ту же роль, что и TSR в DOS. Вы получите ту же мощь, но и те же са-
мые проблемы, когда тысячи независимых производителей ПО получат свободу во “взламывании”
операционной системы.
VxD становятся частью операционной системы Windows. Это одновременно и хорошо, и плохо.
Хорошо потому, что слой VxD делает Windows относительно открытой системой. Плохо потому, что
слой VxD делает Windows относительно открытой системой! Основной системный компонент, вроде
IFSMGR, может основываться на определенных предположениях (как, например, то, что локальные
208
Неофициальная Windows 95
перехватчики INT 21h могут быть обнаружены перехватом функции 25h прерывания INT 21h), а
некоторые VxD независимых производителей типа INTVECT могут совершенно законным образом
(согласно законам, изложенным в Windows DDK) подрывать эти предположения.
Итак, обойти DOS в действительности не так уж и трудно. Мы делали это с помощью двух
ключевых (хотя и простых) функций DOS в 100 строках кода. Однако мы вмешались в функциони-
рование не только локальных перехватчиков INT 21h, но и самого IFSMGR. Обход DOS не пред-
ставляет большого труда, но поддержка другого программного обеспечения (включая другие VxD),
ожидающего поток вызовов INT 2 lh, — достаточно сложная, а учитывая тысячи разработчиков, соз-
дающих VxD, практически неразрешимая проблема.
Глобальные и локальные
перехватчики INT 21 h
My_setvect, конечно, интересный пример, но гораздо важнее то, что обработчик IFSMGR функ-
ции 25h ничего не может поделать с программами Stacker, которые перехватывают INT 21h перед
запуском Windows. IFSMGR обнаруживает перехватчик INT 21h, исключительно контролируя обра-
щения к функции 25h. Поскольку IFSMGR начинает свою работу намного позже обращения про-
грамм Stacker к функции 25h, он заключает, что INT 21h не нуждается в дальнейшей передаче.
На самом деле, такая политика преднамеренна. В статье Расса Аруна (Russ Arun), “Chicago
File System Features — Tips&Issues” {Microsoft white paper, April 22, 1994) содержится следующее
объяснение:
По умолчанию все прерывания INT 21h, за исключением файловых, передаются вниз. Файловые вы-
зовы передаются именно обработчикам VM (локальным), а не глобальным (AUTOEXEC.BAT) обра-
ботчикам. Это делается потому, что имеются новые, поддерживающие длинные имена файловые API
(новые функции прерывания INT 21), для удаления, переименования и т.д. Старый обработчик их в
любом случае не поймет. Более того, далеко не все вызовы файлового API являются обращениями к
INT 21. В частности, сервер и менеджер виртуальной памяти обращаются к файловой системе не
через INT 21.
Хотя это утверждение в основном объясняет наблюдаемое нами поведение TEST21 в Win-
dows 95, оно поднимает больше вопросов, чем дает ответов (оно также озадачивает нас словосо-
четаниями “глобальный обработчик”, “локальный обработчик” и “старый обработчик”, но мы не
будем вникать в эти детали):
• “По умолчанию все прерывания INT 21 h, за исключением файловых, передаются вниз”.
Поскольку MS DOS сама по себе (WINBOOT.SYS) есть обработчик INT 21h, не означает ли
это, что все функции DOS, не связанные с файловым вводом-выводом, передаются вниз и
обрабатываются DOS? Не противоречит ли это частый заявлениям Microsoft, что Windows
95 не требует DOS? (Предвосхищая 10 главу, предыдущий вывод программы V86TEST
продемонстрировал, что Windows использует достаточное количество функций DOS
прерывания INT 2lh, включая функции 2Ah и 2Ch получения даты и времени).
• “Файловые вызовы передаются именно обработчикам VM (локальным), а не глобальным
(AUTOEXEC.BAT) обработчика^’. Не является ли обработчик вроде Stacker именно
глобальным обработчиком, который больше всего нуждается в отслеживании файловых
вызовов INT 21h? Почему с этими, так называемыми локальными, обработчиками особое
обращение?
• “Это делается потому, что имеются новые, поддерживающие длинные имена файловые
API (новые функции прерывания INT 21), для удаления, переименования и т.д. Старый
обработчик их в любом случае не поймет”. Но если это справедливо для глобальных
обработчиков, не будет ли оно таковым и для локальных обработчиков? Почему глобальные
обработчики считаются старыми?
Глава 8. Постепенное исчезновение DOS 209
• “Более того, далеко не все вызовы файлового API являются обращениями к INT 21. В
частности, сервер и менеджер виртуальной памяти обращаются к файловой системе не
через INT 21”f Это относится и к некоторым VxD из .Windows 95, вроде Dynapage,
заменившего PageFile, который непосредственно вызывает IFSMGR. Но, как отметил Джеф
Чапелд, “любые нетривиальные DOS-перехватчики из глобальной памяти не будут видеть ни
» работы VxD с файлами, ни вызовов INT 21h”. Как глобальный обработчик INT 21h может
, узнать, что файловый ввод-вывод инициирован в Windows? Для глобального обработчика
INT 21h вся Windows и все программы, выполняющиеся под Windows, выглядят как одна
большая DOS-программа.
Оставив в стороне эти основные вопросы, мы, по крайней мере, можем видеть, что Windows 95
предполагает поддержку глобальных обработчиков INT 21h для не-файловых вызовов и локальных
обработчиков INT 21h для всех вызовов. IFSMGR использует функцию 25h для отслеживания ло-
кальных обработчиков INT 21h. Microsoft не предполагает отсылать файловые вызовы глобальным
обработчикам INT 2 lh. ,
Постойте! Хотя V86TEST не видела файловых вызовов TEST21 -MYSETVECT, она видела все
файловые вызовы TEST21 (без опции -MYSETVECT). Кроме того, V86TEST — это Глобальный об-
работчик вызовов INT 21h, загруженный перед Windows, которого, как сообщает Microsoft, фай-
ловые вызовы INT 21h не будут и не должны достигать! Итак, почему же V86TEST видит вызовы
файлового ввода-вывода из TEST21?'
- Да потому, что TEST21, подобно любому другому перехватчику, встраивается в цепочку обра-
ботчиков прерывания (см. листинг 8.1). Любые вызовы, посланные к TEST21, в конечном счете по-
падут к V86TEST. Таким образом, когда IFSMGR позволяет перехватывать вызовы локальному
обработчику вроде TEST21, глобальные обработчики вроде V86TEST также будут их видеть. Это
означает, что хотя Windows 95 и не предполагает отсылку файловых вызовов INT 21h глобальным
обработчикам, это, тем не менее, будет случаться при наличии каких-либо локальных обработчиков.
Можно легко написать TSR, который ничего не делал бы, кроме перехвата INT 21h и встраивания в
цепочку с предыдущим обработчиком. Запуск такого TSR в каждой VM заставлял бы Windows 95
передавать все вызовы INT 2 lh, включая файловые, глобальным обработчикам.
Роль IFSHLP.SYS и V86-o6pa6oT4MKOB
завершения вызова
с
Впрочем, некоторая мистика все же остается: хотя запуск локального обработчика вроде
TEST21 приводит к передаче вызовов INT 21h глобальным обработчикам вроде V86TEST и хотя
сама MS DOS рассматривается как глобальный обработчик INT 21h, тем не менее Windows 95 не
может позволить MS DOS получать файловые вызовы INT 21h. Разрешение DOS обрабатывать эти
вызовы не только вызовет огромные неприятности, но и сведет на нет все повышение произво-
дительности 32BFA.
Сообщение TEST21 “Прошло 0 секунд” на рис. 8.10 показывает, .что Windows 95, в действи-
тельности, не позволяет этим вызовам Доходить до DOS. Однако TSR V86TEST видит их, и (как
можно видеть из листинга V86TEST.C в главе 10) ее обработчик передает все вызовы предыдущему
обработчику. Как TSR типа V86TEST может видеть файловые вызовы INT 21h и передавать их
предыдущему обработчику, a MS DOS — не видеть их?
Ответ кроется в IFSHLP.SYS — DOS-драйвере устройства, поставляемом как с Windows 95,
так и с WfW 3.11. 32BFA требует IFSHLP.SYS и не будет загружаться без него. Если он не сможет
найти IFSHLP (с именем устройства IFS$HLP$), IFSMGR выведет на экран сообщение: “Microsoft
Installable File System Manager (IFSMGR) cannot find the helper device. Please ensure that
IFSHLP.SYS has been installed!” (IFSMGR не может найти вспомогательный драйвер. Пожалуйста,
убедитесь, что IFSHLP.SYS был установлен).
210
Heoc|3nunaAbHaR:Windows 95
Запуск программы INTCHAIN из Undocumented DOS (2d ed., р. 302-308) показывает, что
IFSHLP находится между MS DOS (включая DblSpace-драйвер) и прочими TSR, вроде V86TEST:
С:\UNDOCDOS>intchaln 21/3000
Tracing INT 21 АХ=3000
242 instructions
Skipped over 1 INT
2032:0981
020E:04A8
01EF:0023
0A29:1956
00A0:0FAC
FE9E:4249
V86TEST
IFS$HLP$
D:
DBLSYSHS
DOS
HMA
Теперь, что же делает IFSHLP, когда получает вызов DOS, пришедший от обработчика TEST21
или V86TEST? Узнав с помощью INTCHAIN, что обработчик прерывания INT 21b из IFSHLP в
этой конфигурации расположен по адресу реального режима 0215:04А8, мы можем использовать от-
ладчик Soft-ICE/Windows для установки контрольной точки (breakpoint, точка останова, точка пре-
рывания) по этому адресу. При следующем вызове INT 2 th, посланном по каким-то причинам вниз
по У86-цецочке прерываний, эта контрольная точка сработает, и мы сможем протрассировать код.
Я получил листинг, представленный на рис. 8.12, постоянно нажимая в отладчике Soft-ICE
клавишу <F8> (трассировка), воспользовался WLOG для сохранения результатов в файле, затем
обработал файл с помощью утилиты VXDNAME И после этого добавил комментарии. Эти 100
инструкций являются замечательным примером того, как VxD внедряются в DOS и как даже самые
обыденные вызовы DOS, которые Windows отражает в DOS, являются не тем, чем должны быть.
0215:04А8 СМР AH,72 ; ;; '^обработчик INT 2lh внутри IFSHLP ’
0215:04АВ JAE 0215:04AD TEST 04EB ; ; BYTE PTR CS:[0035],02 ;; в данном случае АН = OBh t
0215:0483 JZ 04D5 (нет перехода)
0215:0485 TEST 0215:04ВВ JZ 0215:0405 PUSH 0215:0406 PUSH 0215:04D7 MOV 0215:04D9 MOV 0215:04DB MOV 0215:04E0 MOV 0215:04E2 POP 0215:04E3 ADD 0215:04E6 CALL BYTE PTR CS:(0035],0C 04D5 AX BX BL, AH BH,00 AL,CS:[BX+042A] ; AH,00 ; BX AX, 04FC AX ' ; (переход) ;; таблица обработчиков INT 21h ;; АХ = 2Bh , ;; вызвать обработчик (04fch + 2Bh = 0527h)
0215:0527 TEST 0215:052D JNZ 0215:0531 RET 0215:04E8 JAE 0215:04F0 POP 0215:04F1 PUSH 0215:04F2 MOV BYTE PTR CS:[0Q35],02 ; 0531 04F0 (переход) AX BX BL, AH ;;; обработчик для 0В,0D,0Е,71 (переход) ;; ; поместить номер функции INT 21h в ВХ
0215:04F4 SUB 0215:04F6 JMP BH,BH‘ FAR CS:[0012] ;;; перейти на точку косвенного вызова V86
FBCA:23A2 ARPL 1 VMM+240 SUB [BX+SI+6E], BP ESP,+04 ;;; косвенный вызов V86 по FBCA:23A2 ;;; обработчик INT 06h IDT (неправильный код команды)
Глава в. И'бЬгепенное исчезновение DOS 211
VMM+243 PUSHAD
'VMM+244 MOV ESI,00000018 ;;; 18h = 6 * 4
VMM+249 JMP VMM+2B0
VMM+2B0 CLD i ; общий обработчик прерываний VMM
VMM+2B1 MOV EBP,ESP
VMM+2B3 MOV DI,0030
VMM+2B7 TEST BYTE PTR [EBP.Client_EFLAGS+2],02
VMM+2BB JZ VMM+320 (нет перехода)
VMM+2BD MOV DS, DI
VMM+2BF MOV EBX, [VMM+F6E4] дескриптор текущей VM
VMM+2C5 MOV ES, DI
VMM+2C7 MOV EDI,[VMM+F670] ;;; дескриптор текущей ветви выполнения
VMM+2CD XCHG ESP,[EDI+48] ;;; переключить стеки
VMM+2D0 PUSH VMM+2E0 ;;; адрес возврата (см. ниже)
VMM+2D5 JMP [ESI+VMM+E410] ;;; перейти на обработчик INT 06h
VMM+928 STI ;:' INT 6: неправильный код команды
VMM+929 MOVZX EAX,WORD PTR [EBP.Client_CS] ;;; FBCAh
VMM+92D MOV ECX,EAX
VMM+92F MOV EDX,[EBP.Client_EIP] ;;; 23А2П
VMM+932 SHL EAX.04
VMM+935 ADD EAX,EDX ;;; FBCAOh + 23A2h = FE042h
VMM+937 CMP EAX,000FE042 ; ; ; все косвенные вызовы V86 == FE042h
VMM+93C JNZ VMM+952 (нет перехода)
VMM+93E SUB ECX,0000FB04 ; ; ; FBCAh - FB04h (база) = 0C6h
VMM+944 MOV EDX,[C41DB004+8*ECX] ;;; таблица ссылок на V86 СВ
VMM+94B JMP [C41DB000+8*ECX] ;;; перейти на обработчик для этого V86
св
IFSMGR+4B0 CALL [Simulate_Pop]
IFSMGR+4B6 CALL [Simulate_Iret]
IFSMGR+4BC MOVZX ECX,WORD PTR [EBP.Client_BX] ;;; IFSHLP поместил номер ф-ции в BX
IFSMGR+4C0 MOV [EBP.Client_BX],AX
IFSMGR+4C4 CMP CL, 72
IFSMGR+4C7 JAE IFSMGR+4E1 (нет перехода)
IFSMGR+4C9 MOV EDX, 00000002
IFSMGR+4CE MOV ESI.FFFFFFFF
IFSMGR+4D3 MOV EAX,00000021
IFSMGR+4D8 CALL [IFSMGR+C78+4*ECX] ;;; вызвать обработчик функции INT 21h
IFSMGR+F6C TEST DWORD PTR [EBX],00000020 ;;; внутри обработчика 21/OB
IFSMGR+F72 JNZ IFSMGR+FB6 (нет перехода)
IFSMGR+F74 TEST EDX,00000001
IFSMGR+F7A JNZ IFSMGR+FB6 (нет перехода)
IFSMGR+F7C MOV EAX,[IFSMGR+39D8]
IFSMGR+F81 MOVZX ЕАХ,WORD PTR [ЕАХ+10]
IFSMGR+F85 SHL ЕАХ,04
IFSMGR+F88 MOVZX EDX.WORD PTR [ЕАХ+36]
IFSMGR+F8C SHL EDX, 04
IFSMGR+F8F MOVZX ЕАХ,WORD PTR [ЕАХ+34]
IFSMGR+F93 LEA EDX,[EAX+EDX]
IFSMGR+F96 MOVZX EDX, BYTE PTR- [EDX]
IFSMGR+F99 MOV EDI,[IFSMGR+2EB0]
IFSMGR+F9F ADD EDI,[EBX+04]
IFSMGR+FA2 TEST BYTE PTR [EDX+EDI+2E], 04
IFSMGR+FA7 JZ IFSMGR+FB6 (переход)
IFSMGR+FB6 IFSMGR+FB7 STC RET
IFSMGR+4DF JB IFSMGR+507 (переход)
212
Неофициальная Windows 95
IFSMGR+507 MOV EDX,[IFSMGR+2EB0] ;;; 2174 - таблица IFSHLP
IFSMGR+50D MOV CX, [EDX_O1CE] ;;; 01F6 - предыдущий INT 21h
IFSMGR+514 MOVZX EDX, [EDX+O1CC] ;;; 0023h
IFSMGR+51B CALL [Build_Int_Stack_Frame] ;;; не показана
IFSMGR+521 RET
VMM+2E0 MOV EBX,[VMM+F6E4] ;;; обратно в VMM по адресу, помещенному в стеке
VMM+2E6 VMM+2EC VMM+2ED VMM+2EF MOV CLI XOR CMP EDI,[VMM+F670] EAX, EAX EAX,[VMM+DC88]
VMM+2F5 JNZ VMM+304 (нет перехода)
VMM+2F7 TEST BYTE PTR [EBX],20
VMM+2FA JNZ VMM+2FC XCHG VMM+2FF POPAD VMM+300 ADD VMM+303 IRETD 01F6:0023 JMP VMM+358 ESP,[EDI+48] ESP,+04 03BC:1956 (нет перехода)
03BC:1956 PUSHF
03BC:1957 STI
Рис. 8.12. Этот вывод Soft-ICE показывает, что IFSHLP может посылать вызовы INT 2th обратно IFSMGR
Основная мысль, если не разбирать каждую строку на рис. 8.12, такова: в первом блоке кода,
например, IFSHLP.SYS проверяет, вызов ли это функции INT 21h OBh (Получить состояние клави-
атуры). Как мы видели в нескольких примерах вывода программы V86TEST, при работе
COMMAND.COM в окне Windows часто вызывается функция OBh. IFSHLP использует номер
функции (OBh в этом случае) как индекс в таблице, расположенной на рис. 8.12 по адресу
0215:042А. Для функции п прерывания INT 21h, tablefn] + 4FCh — это адрес маленького кусочка
кода, сообщающего IFSHLP, как обрабатывать соответствующую функцию. Полезно получить дамп
этой таблицы и отсортировать его по адресам:
C:\UNAUTHW>ftab 215:42а
table[n] + 4FC
72 121 1 | sort | massage
INT 21h function n
4FC
510
520
527
532
550
570
5F
5E
00-0A, 00, 0F-30, 41, 43, 45, 46, 48-56, 58-58
5D, 60-67, 69-70
OB, OD, OE, 71
44
47
3E, 3F, 40, 42, 57, 5C, 68
Как видно, IFSHLP использует код по смещению 520h для обработки большинства функций
INT 21h:
-и 215:520
0215:0520 58 POP АХ
0215:0521 58 POP АХ
0215:0522 2EFF2EEC04 JMP FAR CS:[04ЕС]
-dd 215:4ec
0215:04EC 01F6:0023
Глава 8гПостепенное исчезновение DOS
213
В выводе INTCHAIN, показанном раньше, адрес 01vF6:0023 принадлежал драйверу блочного
устройства, управляемому DblSpace, который был загружен перед IFSHLP. Другими словами, для
большинства функций прерывания INT 21h, которые по каким-либо причинам попали в IFSHLP,
IFSHLP просто передает обработку этих функций предыдущему обработчику INT 21h. Вызов в
конечном счете доходит до DOS реального режима.
Оставшиеся функции INT 21h (OB, 0D, ОЕ, ЗЕ, 3F, 40, 42, 44, 47, 52, 57, 5С, 5Е, 5F, 68 и
71) —возможные кандидаты для то, чтобы IFSHLP послал их на обработку в IFSMGR.
В нашем случае IFSHLP посылает вызов функции OBh к IFSMGR. То, как IFSHLP, являю-
щийся 16-битовым кодом реального режима (выполняемым в режиме V8.6), вызывает IFSMGR,
являющийся 32-бйтовым кодом защищенного режима, — весьма интересно и заслуживает следую-
щего довольно длительного отступлейия по поводу обратных вызовов (callback). V86 и контрольных
точек (breakpoint).
В конце первого блока кода на рис. 8.12 имеется инструкция JMP FAR CS:[0012], которая от-
правляет нас по странному адресу FBCA:23A2. Он странен потому, что находится в ROM BIOS;
почему IFSHLP осуществляет переход в ROM BIOS? Более того, адрес, на который переходит
IFSHLP, на большинстве машин не просто указывает на ROM BIOS, но попадает прямо в середину
сообщения об авторских правах!
С:\UNAUTHW>debug
-d fbca:23a2
FBCA:23A0 63 68 6E 6F 6C 6F-67 69 65 73 20 4C 74 64 ' chnologies Ltd
FBCA:23B0 2E 00 FF FF FF FF FF FF-FF FF FF E9 3B 20 4E 45 ............; NE
FBCA:23C0 43 20 43 6F 72.70 6F 72-61 74 69 6F 6E 0D 0A 55 C Corporation..U
FBCA:23D0 6C 74 72 61 4C 69 74 65-20 56,65 72 73 61 00 00 ItraLite Versa..
Что за ...? Если вы посмотрите на трассировку Soft-ICE на рис- 8.12, То увидите, что данные по
адресу FBCA:23A2 могут, как и все данйые, интерпретироваться (или неправильно интер-
претироваться) как код:
FBCA:23A2 63686Е ARPL [BX+SI+6E],ВР
Так случилось, что буква , с, ASCII-код 63h, соответствует инструкции ARPL. Вовсе не
обязательно пускаться в рассуждения, что должна делать эта инструкция. Важно лишь то, что эта
инструкция недопустима в режиме V86.
Итак, что случится, когда "программа, работающая в окне DOS под Windows, выполнит эту *
запрещенную команду? А то, что Windows должна вывести на экран диалоговое окно, сообщающее
“Приложение совершило отвратительное преступление и по воле людей будет остановлено”,
правильно ли? ।
Нет. Недопустимые инструкции, ощибки нарушения защиты памяти, отказы страниц и тому по-
добное — не обязательно рассматриваются как преступления против человечества. Они просто при-
водят к вызову прерываний. В нашем случае выполнение недопустимой ‘c’/ARPL/63h инструкции
приводит к прерыванию INT 6. Если кто-то перехватил INT 6 и собирается интерпретировать отказ
как преступление, то он модсет, конечно, создать угрожающего вида диалоговое окно и закрыть окно
DOS (возможно, используя функцию VMM под названием — это приДумал Me я — Nuke_VM). Но -
с другой стороны, более доброжелательный обработчик INT 6 может решить это дело помягче.
На рис. 8.12 обработчик INT 6 находится в .VMM, который, как вы знаете, к настоящему мо-
мецту является 32-битбвым защищ...
Давайте разберемся, что же только что произошло. Вспомните вопрос о том, как IFSHLP, явля-
ясь 16-битовым кодом реального режима (работающим в режиме V86), мог добраться до 32-битового
защищенного режима для вызова IFSMGR. IFSHLP именно это и сделал. Просто выполнив запре-
щенную команду, IFSHLP сумел 'переключить себя из 16-битового режима V86 в 32-битовый защи-
щенный режим.
Довольно хитроумно? Да, настолько хитроумно, что определенное число программистов выс-
шего класса в Microsoft, включая Фила.Баррета (Phil Barrett), Ральфа Лайла (Ralph Lipe, RAL) и
Аарона Рейнольдса (Aaron Reynolds, AAR), решили защитить эту идею патентом:
214 Неофициальная Windows 95
6
США. Патент 4,974,159. ,
Метод передачи управления в многозадачной операционной, системе — запись инструкции контрольной
точки виртуальной машины и исполняемый код определенных процедур дисковой операционной
системы (DOS).
Правопреемник патента: MICROSOFT CORP.
Автор (Изобретатель): HARGROVE R.R; BARRET P.R; LIPE R.A; REYNOLDS A.R; WILSON M.D.
Метод, обЬспечивающий вставку инструкции контрольной точки виртуальной машины (VMBP) в код
DOS в месте, где DOS будет выполняться в определенном состоянии, называется размещением
контрольной точки (Break Point Location — BPL). При выполнении инструкции VMBP процессор
80386 передает управление из DOS менеджеру виртуальной машины (VMM). Когда VMM получает
управление, он Определяет, что переход был вызван выполнением инструкции. VMBP. После этого
VMM может запустить другую, задачу или выполнить другую функцию без разрушения структур
данных DOS, VMM, который модифицирует DOS, называется монитором виртуальной машины DOS
(Virtual DOS Monitor Machine — VDMM).
Предположительно, VDMM использует копию содержимого выбранной процедуры, где должна быть
установлена контрольная точка, для того, чтобы найти эту процедуру внутри DOS. В качестве альтер-
‘ нативы VDMM мог бы загружать адреса BPL из файла для конкретной версии DOS. Адреса BPL также
могут быть жестко “зашиты” в самом VDMM.
ПРЕИМУЩЕСТВА: Позволяет избежать неэффективности при вызовах неопределенной продол-
жительности.
Хитроумность сама по себе не будет веским основанием для защиты патентом. Другое дело —
оригинальность. Использование Microsoft запрещенной команды для выполнения “гиперпростран-
ственного” перехода из 16-битового реального режима в 32-битовый защищенный режим порази-
тельно похоже на хорошо известную технику двадцатилетней давности, применявшуюся в операци-
онной системе IBM VM, которая использовала запрещенную команду как обращение к супервизору.
Несуществующая инструкция, часто упоминаемая как DIAGNOSE, вызывала исключительную
ситуацию (exception) в виртуальной машине, которая отслеживалась ядром VM и рассматривалась
как запрос. Знакомо? (Для справок по истории этого большого взлома см. письма редактору в
Windows/DOSDeveloper's Journal, July 1993, p. 99-100 ).
Во всяком случае, этот прием с ARPL — одна из опорных точек Windows, включая Win-
dows 95. VxD вроде IFSMGR могут вызывать функцию VMM Allocate_V86_Call_Back, передавая в
ESI адрес 32-битовой процедуры косвенного вызова в защищенном режиме (как смещение 4B0h в
IFSMGR). Allocate_V86_Call_Back возвращает в ЕАХ адрес segment:offset для режима V86, такой
как FBCA:23A2. Этот адрес указывает на инструкцию ARPL. После этого VxD может передать
адрес segment:offset любому клиенту режима V86, такому как IFSHLP, который захотел обратиться1
к VxD. ARPL — это то, что Microsoft иногда (не вполне точно) называет thunk (в некотором
смысле, переключатель. — Прим, ред.), в этом случае thunk из 16-битового режима V86 в 32-бито- , '
вый защищенный режим.
В. примере IFSHLP/IFSMGR,. IFSMGR в течение своей инициализации вызывал АПо-
cate_V86_Call_Back, передавая ей адрес IFSMGR+4B0h. Allocate_V86_Call_Back вернула адрес
FBCA:23A2. Затем IFSMGR передал этот'адрес в IFSHLP: IFSHLP теперь знает, что ЕВСА:23А2
является thunk-переключателем для IFSMGR+4B0h.
Слова “передает этОт адрес IFSHLP” не совсем ясны. Как IFSMGR передает адрес косвенного
вызова V86 в IFSHLP? — Используя важный, но, к сожалению, неописанный доступ к интерфейсу
IOCTL. Джеф Чапелл основательно (как обычно!) описал этот интерфейс для WfW3.ll. Эту доку- .
ментацию стоило бы обновить для Windows 95 и издать целиком, а пока вот вам некоторая его
часть, объясняющая, как IFSMGR передает в IFSHLP адрес косвенного вызова V86:
INT 21 - IFSHLP.SYS - Получить точку входа (GET API ENTRY POINT)
AX = 4402h ,
BX = дескриптор, файла для устройства “IFS$HLP$"‘
'СХ = 0008h
DS:DX -> буфер для структуры точки входа
Глава 8.Т1астепенноё исчезновение DOS ‘ 215
гц i n.--, , 11 .................. — „ .
Возврат: CF сброшен - успех (АХ = количество действительно
прочитанных байтов (должно быть 0008И)
CF установлен - ошибка (АХ = код ошибки)
Формат структуры точки входа при возврате:
Смещение Размер Описание
00h DWORD EF703734h
04h DWORD адрес точки входа
Замечание: Эту структура можно получить посредством IOCTL READ, IOCTL WRITE или обычного чтения. В случае
с IOCTL первое двойное слово при вызове должно содержать 3734E970h.
Вызов точки входа IFSHLP:
Стек: WORD - номер функции (OOOh - OOOCh)
Некоторые функции требуют дополнительного аргумента DWORD,
помещенного в стек перед номером функции.
Возврат: стек не изменен
si, di, bp, ds не изменены
bx изменен
II... IFSHLP функция 00h ...
Вызов IFSHLP функции 01h:
Стек: DWORD - адрес ловушки
WORD - 0001h (функция “Set trap")
Возврат: dx:ax = 0000:0000 - успех
0000:0001 - ловушка уже установлена
bx,cx,es изменены
Замечание:
Процедура-ловушка, переданная при вызове IOCTL, получит управление при возникновении прерываний 08h, 17h,
21h, 2Ah и 2Fh в случае, когда это заинтересует IFSHLP.
При установленном IFSHLP.SYS прерывания 21h и 2Fh перехватываются трив&льно (обычный дальний переход к
прежнему обработчику заменяет первые пять байтов полного обработчика).
Прерывания 08h, 17h и 2Ah перехватываются, если вызов сделан IOCTL.
Прерывание 08h не перехватывается, если в байте по смещению 11h в таблице, переданной функцией 00h IOCTL,
бит 0 установлен.
Когда вызывается процедура-ловушка, все регистры имеют такие значения, как если бы эта процедура была
вызвана непосредственно, кроме регистра Ъх, значение которого помещено в стек. Поэтому ловушка должна
делать POP ВХ и IRET.
Таков, по крайней мере, основной принцип (существуют некоторые исключения, но они довольно сложны). При-
веденный ниже список не значит, что IFSHLP отлавливает все условия данного типа. Просто если ловушка была
вызвана с каким-то конкретным значением в ВХ, то описание этого значения можно найти в следующем списке:
bx < 0069h Соответствующая функция прерывания INT 21h
0076h <= bx < 00A6h INT 2Fh функция 11h подфункция bx-0076h
00A6h <= bx < 00B7h INT 2Fh функция 11h подфункция bx-0026h
bx = 00C7h INT 2Fh функция 16h подфункция != 80h
bx = 00C9h INT 2Ah функция 84h или INT 2Fh функция 1680h
bx = OOCAh или OOCBh INT 08h (до конца не изучена)
bx = OOCCh INT 17h (до конца не изучена)
bx = OOCDh INT 2Fh функция 02h или BFh
bx = OOCEh INT 2Fh функция 1606h
bx = 00D2h INT 2Fh функция 05h подфункция != OOh
Одно из последствий, вызываемых данной функцией: она настраивает слово 0040:0010 для индикации присут-
ствия четырех параллельных портов. Это не кажется окончательным.
216
Неофициальная Windows 95
Обратите внимание, что функция по адресу IFSMGR+4B0, thunk-переключатель ARPL, кото-
рый IFSMGR передает в IFSHLP, используя функцию 1 (Set Trap — установить ловушку) интер-
фейса IOCTL, предназначена не только для INT 21h.
Кроме взаимодействия между IFSHLP и IFSMGR, существует еще много ситуаций, в которых
Windows использует эти thunk-переключатели для косвенных вызовов V86. Например, рассмотрим
функцию 1684h прерывания INT 2Fh (Get Device Entry Point Address), возвращающую дальний
указатель на функцию, который DOS- или Windows-приложения могут вызывать для связи с VxD.
В разделе “DOS-флаг IN_WIN3E” в главе 4 мы видели, что MS DOS в определенных ситуациях
использует функцию 1684h для обращения к DOSMGR. Вас может заинтересовать, как это 16-бито-
вый код управляет переходом из режима V86 в 32-битовый защищенный режим, просто вызывая
указатель, возвращенный функцией 1684h.
Услышав ответ, вы уже не удивитесь — ARPL. Этот момент хорошо демонстрирует (по крайней
мере, я так думаю) маленькая программка VXD86API, которая находит API всех VxD в режиме
V86, вызывая в цикле функцию 1684h прерывания INT 2Fh для каждого возможного иденти-
фикатора VxD (от 0 до 0FFFFH). Если функция 1684h (через процедуру GetDeviceAPI) возвра-
щает не нуль, VXD86API печатает идентификатор VxD, адрес segment:offset точки входа VxD, тот
же самый адрес, представленный в линейной форме (segment « 4 + offset), и, наконец, первый
байт по этому адресу. В листинге 8.5 показана программа VXD86API.C, которая является самой
обычной DOS-программой реального режима.
ЛИСТИНГ 8.5. VXD86API.C
/* VXD86API.C */
«include <stdlib.h>
«include <stdio.h>
«include <dos.h>
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef DWORD (far *FUNCPTR)(void);
// вызов функции Windows “Получить адрес точки входа устройства"
// Функция 1684h прерывания 2Fh
FUNCPTR GetDeviceAPI(WORD vxd_id)
{
_asm {
push di
push es
xor di, di
mov es, di
mov ax, 1684h
mov bx, vxd_id
int 2fh
mov ax, di
mov dx, es
pop es
pop di
}
// возвращаемое значение в DX;AX
}
int IsEnhancedMode(void)
{
_asm {
mov ax, 1600h
int 2fh
Глава 8. Постепенное исчезновение DOS
217
test al„ 7fh
j z no
}.
return 1;
no: return 0;
}
void fail(const char *s, ...) { put's(s); exit(1); }
int main()
{
WORD i;
FUNCPTR fp;
if(!IsEnhancedMode())
fail(“3Ta программа должна запускаться в расширенном режиме Windows/’);
puts(“)/irtual-8086 (V86) VxD APIs:");
// для каждого id устройства проверить, имеется ли API
for (i=0; i<Oxffff; i++)
if (fp = GetDeviceAPI^i)) ,
printf(“%04Xh %Fp %01X %02X\n",
. i, //.VxD ID
fp, // точка входа V86
((DWORD) FP_SEG(fp) « 4) + FP_OFF(fp), // линейный адрес1
«((BYTE far *) fp)); // байт (ARPL)
return 0;
VXD86API ищет только VxD API для режима V86, ее можно легко модифицировать для ис-
пользования библиотеки DPMISH й поиска VxD API защищенного режима. Если функция 1684h
вызывается иэ защищенного режима, а не из режима V86, она возвращает адрес защищенного
режима selectorioffset, по которому Winl6- или DOS-приложение в защищенном режиме, выполня-
емое на уровне 3, может обратиться для связи с VxD, имеющим уровень привилегий 0. Возвращен-
ный адрер указывает на команду INT ЗОЬ. В то время как косвенным вызовом V86 является инст-
рукция ARPL, INT ЗОЬ является косвенным вызовом защищенного режима. В Windows 3.0 для кос-
венных вызовов защищенного режима используется инструкция HLT. Забавно, что фундаменталь-
ный код переключения режима в Windows пытается остановить процессор, тем более, что Microsoft
однажды уже использовала технику переключения режима, описанную Гордоном Литвином (Gordon
Letwin) в U.S. Patent 4, 825, 358 (“Method and Operating System for Executing Programs in a Multi
Mode Microprocessor”^ April 25>, 1989), которая действительно останавливала процессор.
Вывод нашей простой программы VXD86API, на первый взгляд, довольно скучен. Но если вы
посмотрите на него'пристальнее, он станет намного интереснее. Конечно, в целом это — безделица,
но вы действительно сможете почерпнуть из вывода VXD86API массу информации о Windows 95;
Virtual-8086 (V86) VxD APIs: Byte
’ VxD V86 tntry Linear
0001 h FBE9:21B2 1 FE042 63.
0003h FBEA:21A2 FE042 63
0005h FBEB:2192 FE042 63
OOOAh FBEC:2182 FE042 63
OOOCh FBED:2172 FE042 63
0010h FBEE:2162 FE042 63
0015h FBEF;2152 . FE042 63
0017h FBF0:2142 FE042 63
0026h FBF1:2132 FE042 63
0027h FBF2.2122 FE042 63
218
Неофициальная Windows 95
002Bh FBF3:2112 FE042 63
0033h FBF4:2102 FE042 63
0037h FBF5:20F2 FE042 63
0038h FBF6:20E2 FE042 63
003Bh FBF7:20D2 FE042 63
0040h FBF8:20C2 FE042 63
0202h FBF9:20B2 FE042 63
0483h FBFA:20A2 FE0.42 63
048Bh FBFB:2092 FE042 63
220Qh FBFC:2082 FE042 63
28CCh FBFD:2072: FE042 63
28C2h FBFE:2062 FE042 63
296Eh FBFF:2052 FE042 63
В этом примере около 20 различных VxD- обеспечивают некоторую форму API для програм-
много обеспечения реального режима, работающего в режиме V86 под Windows 95. Например, VxD
0015h — это DOSMGR. В главе 4 показано, что MS DOS иногда вызывает DOSMGR API. VxD
0027h — это VXDLDR. DOS-программы реального режима могут вызывать VXDLDR API для
динамической загрузки и выгрузки VxD (функция 1 вызывает VXDLDR_LoadDevice, а функция 2 —
VXDLDR_UnloadDevice). VxD OOOlh — это VMM, и его V86 API обеспечивает DOS-программы
реального режима доступом к реестру Windows 95 (функция OlOOh — RegOpenKey, 0102h —
RegCloseKey, 0105h — RegQueryValue, 0106h — RegEnumKey и т.д.). Другими словами, один
вызов функции 1684h прерывания INT 2Fh может раскрыть для DOS-программ, работающих под ,
Windows, обширный мир новых возможностей.
Более тесно с темой косвенных вызовов V86 и с тем, как IFSHLP передает вызовы в IFSMGR,
связан тот факт, ^то все VxD V86 API указывают ца инструкцию ARPL (63h) и все они имеют один
и тот же линейный адрес (FE042h). Это выглядит, как будто все VxD V86 API одинаковы!
Сейчас мы увидим, как VMM, получив единственную команду ARPL, расположенную по един-
ственному линейному адресу, определяет, какой VxD .API должен быть вызван. Пока вспомним, что
все косвенные вызовы V86 указывают на один и тот же байт 63h, просто представленный раз-
личными способами (FBE9:21B2 — это тот же адрес реального режима, что и FBEA:21A2, и
FBEB-.2192, и т.д.)
Обычно этот единственный байт 63h располагается в сообщении системной ROM об авторских
правах. В процессе инициализации VMM находит байт ‘c’/ARPL/63h, используя функцию с ниче-
го не говорящим именем VMM Locate_Byte_In_ROM. Почему в ROM? Потому что тогда VMM
убежден, что никто не изменит это значение на что-либо другое, отличное от 63h. Это предположе-
ние может не сработать, если вы используете менеджер памяти, который отображает ROM BIOS в
RAM. Поэтому Windows имеет установку SysterriROMBreakPoint=off. Если SystemROMBreak
Point=off, все косвенные вызовы V86 будут по-прежнему указывать на единственный байт 63h, но
он будет расположен в потенциально изменяемой нижней памяти (в Global V86 Data Area —
глобальной области данных V86, постоянно видимой во всех виртуальных машинах), а не в ROM.
До сих пор мы видели некоторые ситуации, в которых один из этих странных адресов ARPL
передавался запросившему его приложению. Однако Windows также использует схему ARPL для
еще одной ситуации. Предположим, что существующий кусок кода реального режима должен быть
перехвачен VMM или VxD. Если код запускается при выполнении инструкции INT или какой-либо
другой, которую без труда можно перехватить в режиме V86, все нормально. Но бывают ситуации,
в которых Windows хочет перехватывать код, в котором не используются инструкции, отлавли-
ваемые в V86-peжимe.
И вот что получается: VMM может взять произвольный адрес режима V86, сохранить байт по
этому адресу и заменить его новым байтом, который позволит VMM или VxD перехватить все
обращения по этому адресу. Новый байт — ARPL. Функция Install_V86_Break_Point принимает
адрес режима V86, по которому будет помещен код ARPL, и 32-битовый адрес защищенного режима
процедуры VxD для косвенного вызова при каждой активизации контрольной точки.
Программисты, недавно начавшие- изучать внутреннее строение Windows, часто недопонимают
роль контрольных точек V86, думая, что они как-то относятся к отладке или обработке ошибок.
Глава 8. Постепенное исчезновение DOS 219
Несмотря на термин “контрольная точка” (breakpoint), эти объекты не имеют ничего общего с от-
ладкой или обработкой ошибок. Вместо этого, подобно косвенным V86- или PM-вызовам, они игра-
ют чрезвычайно важную роль в нормальной повседневной работе Windows, позволяя выполнять код
реального режима, переключающий вызов в 32-битовый код защищенного режима. Документация
DDK по Install_V86_Break_Point дает хороший пример: “Например, драйвер XMS в виртуальном
устройстве V86MMGR устанавливает контрольную точку в драйвер XMS реального режима во вре-
мя инициализации устройства. С этого момента все вызовы драйвера XMS реального режима преры-
ваются виртуальным драйвером XMS”. (Через некоторое время мы изучим этот пример подробнее).
Как уже было отмечено, все косвенные вызовы V86 в Windows указывают на один и тот же
байт ARPL в памяти. Контрольная точка отладчика, помещенная на этом байте, будет вызываться
постоянно. Замечательно, что даже в чистой системе Win32 Windows 95, без выполняемых DOS-
или Winl 6-приложений, косвенный вызов V86 используется так часто, что даже контрольная точка
Soft-ICE ВРХ С=50 (контроль каждого 50-го раза), помещенная на месте инструкции ARPL, сраба-
тывает так часто, что просто не дает нормально работать.
Эти контрольные точки V86 ARPL лежат в самой основе Windows, даже для чистой версии
Win32 Windows 95. А если вы не верите мне, вот эксперимент, который можете попробовать: уста-
новите SystemROMBreakPoint=off в SYSTEM.INI, перезапустите Windows 95, вычислите, где в
нижней памяти Windows расположила байт ‘c’/ARPL/63h (можно запустить программу WINBP,
рассмотренную дальше), закройте все, что не является Win32-пpилoжeниeм, используйте Soft-ICE
для изменения значения байта ‘c’/ARPL/63h на что-либо другое и увидите, как Windows
мгновенно “грохнется”. Итак, даже самая чистая Win32 Windows 95 зависит от контрольных точек
V86. (И конечно же, намного лучше сбросить установку SystemROMBreakPoint=on).
Некоторые энтузиасты OS/2 тихо посмеиваются по поводу того, как нелепа Windows, которая
базируется на использовании незаконных операций.’Но OS/2 2.x использует ту же схему, и вы
сами можете убедиться в этом:
Контрольные точки VDM используются компонентом эмуляции .8086 для управления выполнением в
режиме V86. При установке контрольных точек VDM инструкция ARPL вставляется в таблицу векторов
прерываний VDM и фрейм стека прерываний. Выполнение инструкции ARPL в режиме V86 вызывает
исключительную ситуацию нарушения общей защиты [точнее, отказ при использовании недопустимой
инструкции], и система, в конце концов, передает управление компоненту эмуляции 8086. Контрольные
точки VDM используются для перехода из режима V86 для запуска ядра MVDM.
— Harvey Deitel and Michael Kogan, The Design of OS/2, p. 297.
Выражение в конце концов вполне уместно заключить в кавычки. Как и путь от одной темы к
другой в этой книге, путь от инструкции ARPL к соответствующему 32-битовому обработчику кос-
венного вызова далеко не прям. Например, когда DOS-приложение вызывает точку входа, возвра-
щенную функцией INT 2Fh 1684h, выполняется инструкция ARPL, которая приводит к генерации
INT 6, обработчик которого находится в VMM, а тот передает вызов подходящему обработчику
VxD. Те читатели, которые играли в своей юности в игру Мильтона—Бредли (Milton—Bradley)
“Мышеловка”, или те, кто смотрел мультфильмы Руба Голдберга (Rube Goldberg), имеют достаточ-
но опыта, чтобы понять этот процесс.
Давайте посмотрим, как это все поможет нам понять, что происходит, когда IFSHLP обратно
вызывает IFSMGR на рис. 8.12. Выполнение единственной инструкции ARPL по адресу FBCA:23A2
приводит к переходу на обработчик INT 06h (недопустимый код операции) по адресу VMM+240H.
VMM теперь должен определить, что он имеет дело с косвенным вызовом V86, а не действительно с
недопустимым кодом операции. Он это делает, взяв адрес, по которому произошла ошибка
(Client_CS « 4 + Client_ElP), и сравнив его с числом (в нашем случае) FE042H. Любая ошибка
недопустимого кода операции, произошедшая по этому адресу, считается обратным вызовом V86.
Теперь VMM должен определить, какой 32-битовый код защищенного режима обработчика кос-
венного вызова Соответствует этому косвенному вызову V86. На первый взгляд, это кажется невоз-
можным потому, что, как мы только что видели, все косвенные вызовы V86 указывают на один и
тот же байт. Различна только форма адреса segment:offset. Однако этого вполне достаточно. На рис.
8.12 можно увидеть, что VMM берет значение Client_CS (FBCAh в нашем примере) и вычитает
220
Неофициальная Windows 95
число (здесь, FB04H), которое является базовым сегментом для всех косвенных вызовов V86. VMM
прописывает это число в своем собственном коде (см. SUB ECX, 0FB04H по адресу VMM+93E на
рис. 8.12) в момент инициализации, благодаря чему впоследствии может выяснить, где расположен
ARPL косвенного вызова V86. Он также сохраняет линейный адрес обратного вызова (см. СМР
ЕАХ, 0FE042 по адресу VMM+937 на рис. 8.12).
В примере IFSHLP/IFSMgr, приведенном на рис. 8.12, адрес ARPL был равен FBCA:23A2.
VMM проверяет, что FBCAh « 4 + 23A2h = FE042h, а затем вычитает FB04H из FBCAh, получая
0C6h. Это число является номером косвенного вызова V86. Далее VMM использует этот номер для
индексирования в таблице косвенных вызовов V86. На рис. 8.12 таблица располагается по адресу
C41DB000h. Каждая запись в этой таблице содержит восемь байтов: четыре байта для адреса про-
цедуры обработки косвенного вызова V86 и четыре байта для дополнительных данных, переда-
ваемых VMM этой процедуре.
VMM+929 MOVZX EAX,WORD PTR [EBP.Client_CS] ;;; FBCAh
VMM+92D MOV ECX, EAX
VMM+92F MOV EDX,[EBP.Client_EIP] ;;; 23A2h
VMM+932 SHL EAX,04
VMM+935 ADD EAX,EDX ;;; FBCAOh + 23A2h = FE042h
VMM+937 CMP EAX,000FE042 ;;; все обратные вызовы V86 == FE042h
VMM+93C JNZ VMM+952 (нет перехода)
VMM+93E SUB ECX,0000FB04 ;;; FBCAh - FB04h (база) = 0C6h
VMM+944 MOV EDX,[C41DB004+8*ECX] ;;; таблица ссылок на V86 СВ
VMM+94B JMP [C41DB000+8*ECX] ;;; перейти на обработчик для этого V86 СВ
Можно просмотреть эту таблицу с помощью специальной утилиты PROTDUMP:
С:\UNAUTHW\PROTDUMP>protdump c41dbOOO -dword
C41DB000 | С022С708 С4520298 С0003244 0010В210
C41DB010 | | С0003268 0010В210 С02200В8 0010В210
C41DB020 | С0004184 0000005Е C0227BBF 00000208
C41DB030 | С0226А1С 00000208 С0227348 00000208
C41DB040 | С0228СЕ8 0010В210 С0229764 00000000
C41DB050 | С0229798 00000000 С022973С 00000000
C41DB060 | С022973С 00000000 C00026FC 00000000
C41DB070 | C0Q699AC С006А774 С00699А8 С006А774
Например, косвенный вызов 0 обрабатывается по адресу С022С708Н, а дополнительные данные
находятся по адресу C4520298h. Имея номер обратного вызова и V86-адрес первого косвенного
вызова, мы могли бы вычислить V86-aдpec, соответствующий любому другому обратному вызову.
Однако это достаточно утомительно, а контрольные точки Windows достаточно важны. Поэтому сто-
ит иметь отдельную программу для получения таблицы обратных вызовов.
WINBP из листинга 8.6 является DOS-программой защищенного режима, показывающей все
контрольные точки, обратные вызовы V86 и обратные вызовы защищенного режима. WINBP может
вычислить как размер таблицы (который соответствует установке MaxBPs= в SYSTEM.INI, увекове-
ченной Брайаном Ливингстоном в Inf eWorld от Января 24, 1994, в колонке “Correct most Windows
instability with just a single command”), так и ее местоположение с помощью не более чем одного
косвенного вызова V86 и одного косвенного вызова защищенного режима. WINBP может их легко
получить, вызывая функцию 1684h прерывания INT 21h для произвольного VxD, который обеспе-
чивает интерфейс как для V86, так и для защищенного режима.
Листинг 8.6. WINBP. с
/*
WINBP.С -- Выводит контрольные точки Windows
Шульман, 1994
bcc -DDPMI_APP -2 -I..\include -с prot.с dpmish.c ctrl_c.asm
tiib ..\lib\unauthw+prot+dpmish+ctrl_c
Глава 8. Постепенное исчезновение DOS
221
bcc -DDPMI_APP -2 -В -I..\include -L..\llb winbp.t unauthwdilib
*/
«include <stdlib.h> -
«include <stdio.h>
«include <ctype.h> .
«include <dos.h> ,
«include “dpmish.h”
«include “prot.h"
typedef void far «FP; ? ' . . *
FP Ge.tVxDAPI(WORD vxd_id);
DWORD GetGDTSelectorBase(WORD seg);
DWORD GetGDTSelectorLimit(WORD seg); . ч .
«define PUT(s) { fputs(s, stderr); fputs(“\n”, stderr); }
void fail(const char *s, ...) { PUT(s); _dos_exit(1); }
. static DWORD v86bp_lin, pmbp^base, bp_tab_lin; , .
static WORD pmbp_size, max_bps;
static int win_ver =0;
«pragma pack(1)
typedef struct { // V86/PM косвенный вызов/контрольн,ая точка
unibn { . ,
struct {
DWORD callback, refdata;
} CALLBACK;
struct { ... .
DWORD brk_addr; - ’
union {
struct { // в Chicago лучше упаковать
WORD bp_num;
BYTE replaced; .
} WIN4; ;
' struct { •
BYTE replaced;
WORD bp_num;
} WIN3;
} REPLBP; " .
BYTE ff; ". ’
} V86;
} u;
’// дополнительные DWORD при отладке ,
} СВ;
«define ARPL 0x63 ’ '
«define INT30 0x30CD ' _ ¥
int real_main(int argc, char *argv[])
' {
FP v86_api;
BYTE far *fp; ‘
PUT("WINBP Проверка контрольных точе/ и косвенных вызовов Windows'');
PUT(“Авторское право (с) 1994 Эндрю Шульман. Все права защищены”,); , 1
РиТ(“Из V’Unauthorizeo Windows\” (IDG Books, 1994)\n");
222 •, Неофициальная Windows 95
if(!(v86_api = GetVxDAPI(5))) // получить любую точку обратного вызова V86
fail("Эту программу необходимо запускать в расширенном режиме Windows”);
fp = (BYTE far *) v86_api;
if(*fpi!= ARPL) '
fail(“4TO-TO не так! 2F/1684/5 V86 должен возвращать указатель на ARPL”);
v86bp_lin = MK_LIN(v86_api);
printf(“точки прерывания V86 @ %081Xh (’”, v86bp_lin);
while(isprint(*fp))
{ putchar(*fp); fp++; }
printf("’)\n”);
'. _asm mov ax, 1600h V
_asm int 2fh
_asm mov byte ptr win_ver + 1, al ' “
return 0;
int pmode_main(int argc, char *argv[J)
CB far *cb_tab, far *cb;
FP pm_api; ;
DWORD api_cb_addr;
WORD avail_bps;
WORD v86_seg, v86_ofs, pm_seg, pm_ofs;
WORD first_v86_bp_seg, flrst_v86_bp_ofs;
WORD last_v86_bp_seg, last_v86_bp_ofs-;
int verbose =0;
int i;
char *s = argv[1];
if( -
(argc > 1) &&
((s[0] == -') || (s£0] == V)) &&
((s[1] == V) || (s[1] == -V-))
X •
verbose++;
if(!(pm_api = GetVxDAPI(5))) // получить любую контрольную точку РМ
fail(“3Ty программу необходимо запускать^ расширенном режиме Windows’’);
if(*((WORD far *) pm^api) != INT30) , /
fali(“4To-TO не так! 2F/1684/5 PM должен возвращать указатель на INT 30h”);’
pm_seg = FP_SEG(pm_api); : •
pmbp_base =GetGDTSelectorBase(pm_seg);_
printf(“контрольные точки PM %081Xh\nw, pmbp base); • . -
pmbp_size = GetGDTSelectorLimit(pm_seg) + 1;
max_bps = (pmbp_size » 1) - Qx100;
printf(“Макс. Ктрл. T; = %u (%04Xh)\g", max_bps, max_bps); ' '
last_v86_bp_seg = v86bp_lin » 4;
last_v86_bp_ofs = v86bp_lin & OxOF;
first_v86_bp_seg = (v86bp_lin » 4) - max_bps;
first_v86_bp_ofs = v86bp_lin - ((DWORD) .first_v86_bp_seg « 4); .
printf(.“Первая Ктрл. T. V86 = %04X:%04X\n", first_v-86_bp_seg, first_v86_bp_ofs);
printf(“Последняя Ктрл. T. V86 = %04X:%04X\n”, last_v86_bp_seg, last_v86_bp_ofs);
Глава 8. Постепенное йсчезновение DOS
223
bp_tab_lin = pmbp_base - (max_bps « 3);
if(bp_tab_lin & OxFFF) // не на границе страниц!
fail((bp_tab_lin & OxFFF) == (max_bps * 4) ?
“Похоже на то, что Отладочная Windows содержит больше контрольных точек!” :
“Не могу получить адрес таблицы контрольных точек!’’);
printf(“Ta6nnpa контрольных точек © %O81Xh\n”, bp_tab_lin);
if(!verbose)
ба11(“\пЗапустите WINBP -VERBOSE для получения всех контрольных точек и косвенных вызовов”);
cb_tab = (СВ far *) map_linear(bp_tab_lin, max_bps * sizeof(CB));
if(!cb_tab)
ба11(“Невозможно отобразить таблицу косвенных вызовов!”);
printf(“\пКонтрольные точки V86:\n");
printf(“BP# V86 адр. Зам. CB# Callback Данные\п’’);
printf(“--------------- ----- -------- ------------ ----------\п");
for(i=0, cb=cb_tab; i<max_bps; i++, cb++)
{
if(Cb->u.V86.ff == Oxff)
{
WORD bp_num;
BYTE replaced;
if(cb->u.V86.brk_addr > Ox1O1OFFEFL) // только память режима V86
continue; // пропускать фиктивный входы
if(win_ver < 4)
{
bp_num = cb->u.V86.REPLBP.WIN3.bp_num;
replaced = cb->u.V86.REPLBP.WIN3.replaced;
}
else
{
bp_num = cb->u.V86.REPLBP.WIN4.bp_num;
replaced = cb->u.V86.REPLBP.WIN4.replaced;
}
printf(“%04X %081X %O2X %04X”,
i,
cb->u.V86.brk_addr,
replaced,
bp_num);
if(bp_num <= max_bps)
printf(“%O81XЧ081Х”,
cb_tab[bp_num].u.CALLBACK.callback,
cb_tab[bp_num].u.CALLBACK.refdata);
printf(“\n”);
ftifdef SANITY_CHECK
{
BYTE far *fp = (BYTE far *) map_linear(cb->u.V86.brk_addr, 1);
if(lfp)
fail(“Heвoзмoжнo отобразить контрольную точку”);
if(*fp != ARPL)
printf(“Что-то не так!!\п");
free_mapped_linear(fp);
)
ftendif
)
if(ctrl_c_hit)
224
Неофициальная Windows 95
goto done;
}
/* обнаружить косвенные вызовы V86, PM API 2f/1684 */
api_cb_addr = cb_tab[((FP_OFF(pm_api) « 2) - 0x800) » 3].u.CALLBACK.callback;
printf(“\пКосвенные вызовы V86 и PM:\n");
printf(“BP# Callback Cc. дан. V86 PM \n”);
printf(“-------------------------------------------\n");
v86_seg = first_v86_bp_seg;
v86_ofs = first_v86_bp_ofs;
pm_ofs = 0x200;
for(i=0, cb=cb_tab;
i<max_bps;
i++, cb++, v86_seg++, v86_ofs -= 0x10, pm_ofs += 2)
{
if(cb->u.V86. ff != Oxff)
{
if(cb->u.CALLBACK.callback == 0)
continue; // пропускать пустые
printf(“%04X %081X %081X %04X:%04X %04X:%04X",
i,
cb->u.CALLBACK.callback,
cb->u.CALLBACK.refdata,
v86_seg, v86_ofs,
pm_seg, pm_ofs);
if(cb->u. CALLBACK.callback == api_cb_addr)
{
DWORD far *fp = (DWORD far*)
map_linear(cb->u. CALLBACK, refdata, sizeof(DWORD));
if(lfp)
fail(“Невозможно отобразить адрес косвенного вызова API”);
// показать действительный адрес косвенного вызова API
printf(“ -> %081Х”, *fp);
free_mapped_linear(fp);
}
printf(“\n”);
}
if(ctrl_c_hit)
goto done;
}
done:
f ree_mapped_li nea r(cb_tab);
return 0;
у**********************************************************************y
// вызов функции Windows “Получить адрес точки входа устройства"
// Прерывание 2Fh Функция 1684b
FP GetVxDAPI(WORD vxd_id)
{
_asm {
push di
push es
xor di, di
mov es, di
8 Неофициальная Windows 95
Глава 8. Постепенное исчезновение DOS
225
mov ax, 1684h
mov bx, vxd_id
int 2fh
mov ax, di
mov dx, es
pop es
pop di
}
// возвращаемое значение в-0Х:АХ
/*»»*»*»*****»»*»***»»*»**»***»»»»»****».....»»*»***.*.»*******».*»***»/
«pragma pack(1)
typedef struct {
WORD rpl : 2;
WORD ti : 1;
WORD index : 13;
} SELECTOR;
typedef struct {
WORD limit_lo, base_lo;
BYTE base_hi, rts_lo, limitrts_hi, base_xhi;
} DESCRIPTOR;
typedef struct { WORD limit; DWORD base; } GDTR;
DESCRIPTOR far *get_gdt(WORD *plimit);
void sgdt(GDTR far *pgdtr)‘;
static DWORD get_desc_base(DESCRIPTOR far »desc);
static DWORD get_desc_limit(DESCRIPTOR far *desc);
DWORD GetGDTSelectorBase(WORD seg)
{
SELECTOR sei = ‘((SELECTOR *) &seg);
DWORD base;
if(sel.ti == 0) // GDT
{
WORD gdt_limit;
DESCRIPTOR far *gdt = get_gdt(&gdt_limit);
if((seg & "8) > gdt_limit)
fail(“HenpaBMnt>Hbiii селектор GDT”);
base = get_desc_base(&gdt[sel.index]);
f ree_mapped_linea r(gdt);
}
else // LDT; что за черт! Ну что ж, сделаем, что просят.
base = GetSelectorBase(seg);
return base;
DWORD GetGDTSelectorLimit(WORD seg)
{
SELECTOR sei = ‘((SELECTOR •) &seg);
DWORD limit;
if(sei.ti == 0) // GDT
{
WORD gdt_limit;
DESCRIPTOR far *gdt = get_gdt(&gdt_limit);
if((seg & ”8) > gdt_limit)
226
Неофициальная Windows 95
fail(“Неправильный селектор GOT”);
limit = get_desc_limit(&gdt[sel.index]);
f ree_mapped_linear(.gdt);
}
else // LOT: аналогично
limit = GetSelectorLimit(seg);
return limit;
// когда Закончите работу, освободите с помощью free_mapped_linear
DESCRIPTOR far *get_gdt(WORD *plimit)
{
DESCRIPTOR far *gdt;
GDTR gdtr;
WORD sei;
/* получить линейный базовый адрес и размер (верхний предел) Таблицы
глобальных дескрипторов (GDT) с помощью инструкции процессора SGDT */
sgdt(&gdtr);
gdt = (DESCRIPTOR far *) map_linear(gdtr.base, gdtr.limit + 1);
if(!gdt)
fail (“Невозможно отобразить GDT!’’);
«plimit = gdtr.limit;
return gdt;
void sgdt(GDTR far *pgdtr)
{
_asm les bx, pgdtr
_asm sgdt fword ptr es:[bx]
DWORD get_desc_base(DESCRIPTOR far *desc)
{
return ((DWORD) desc->base_xhi « 24L) +
((DWORD) desc->base_hi « 16L) + desc->base_lo;
DWORD get_desc_limit(DESCRIPTOR far *desc).
{
DWORD limit = ((DWORD) (desc->limitrts_hi & OxOF) « 16L) +
(DWORD) desc->limit_lo;
if(desc->limitrts_hi & 0x80) // размер в страницах или в байтах?
limit *= 4096;
return limit;
}
Я хотел бы иметь достаточно времени, чтобы подробно объяснить, как работает WINBP, в
частности, как он вычисляет значение MaxBPs= и как он связывает контрольные точки V86 с
косвенными вызовами V86, но это заняло бы много страниц. Позвольте мне хотя бы привести
формулу, используемую для вычисления Maxbps:
pm_api = GetVxDAPI(5); // получить какой-то косвенный вызов РМ
pm_seg = FP_SEG(pm_api); // взять его сегмент
pmbp_size = GetGDTSelectorLimit(pm_seg) + 1; // получить размер сегмента
max_bps = (pmbp_bps / 2) - 0x100; // вычислить MaxBPs=
Вызов функции 1684h прерывания INT ^Fh (GetVxDAPI) в защищенном режиме возвратит кос-
венный вызов защищенного режима. Все, что нас интересует, — это сегмент. Все косвенные вызовы
Глава 8. Постепенное исчезновение DOS
227
защищенного режима находятся в одном и том же сегменте, а размер этого сегмента напрямую свя-
зан со значением MaxBPs. Было бы неплохо получить размер этого сегмента с помощью Get
SelectorLimit из API Windows (или ее эквивалент в DPMI, функции 8 прерывания INT 31h).
Однако эти функции работают только для локальной таблицы дескрипторов (Local Descriptor Table —
LDT), а селектор сегмента находится в глобальной таблице (Global Descriptor Table — GDT),
поэтому WINBP реализует свои собственные процедуры GetGDTSelectorLimit и GetGDTSelector
Base (WINBP использует непривилегированную инструкцию SGDT доступа к GDT). Каждый
косвенный вызов защищенного режима — это два байта (инструкция INT 30h — CDh 30h), поэтому
WINBP делит размер сегмента пополам. Затем вычитает 0x100, поскольку VMM всегда резервирует
этот объем (см, Chapell, DOS Internals, р. 71).
При запуске WINBP выдает некоторую статистику:
Контрольные точки V86 000FE042H (’chnologies Ltd.')
Контрольные точки PM (® C41DC800h
Макс. Ктрл.Т. = 768 (ОЗООИ)
Первая Ктрл.Т. V86 = FB04:3002
Последняя Ктрл.Т. V86 = FE04:0002
Таблица контрольных точек C41DB000h
Полезно также запустить WINBP при SystemROMBreakPoint=off:
Контрольные точки V86 0001D6C06 (’С)
Контрольные точки PM C41DC8006
Макс. Ктрл.Т. = 768 (0300b)
Первая Ктрл.Т. V86 = 1А6С:3000
Последняя Ктрл.Т. V86 = 1D6C:0000
Таблица контрольных точек C41DB000h
Если вы запустите WINBP -VERBOSE, то она напечатает все контрольные точки V86 и все
косвенные вызовы V86 и защищенного режима. Полезно прогнать вывод через утилиту VXDNAME,
чтобы вместо адресов вида C00735ACh можно было иметь дело с адресами вида IFSMGR+4B0h.
Например:
Контрольные точки V86:
BP# V86 адр. Зам . CB tt Callback Данные
02F1 000С0525 90 ООСА SHELL.10+01С CO5OO0O3
02F2 0001D73A 90 00С2 V86MMGR.9+000 00000000
02F3 00010731 90 00С1 V86MMGR.3+131А 00000000
02F4 0001D72D FF ООСО V86MMGR.3+2777 00000000
02F5 0001D733 90 OOBF V86MMGR.3+12F5 00000000
02F6 0001072В 90 ООВЕ V86MMGR.3+12Е0 00000000
02F7 0001D729 90 OOBD V86MMGR.3+12C4 00000000
02F8 00000953 90 00А5 VKD.2+3B8 00000A17
02F9 0001DAFC 90 00А4 VMOUSE.2+1154 00000000
02FA 0001DAF5 90 00A3 VMOUSE.2+112Е 00000000
02FB 0001DADD 90 00А0 VDD+21B6 00000000
02FC 0001DAD6 90 009F VDD+2114 00000000
02FF 0001D6DC 00 008F VMOUSE.2+118 00000000
Косвенные вызовы V86 и РМ:
BP# Callback Cc. дан. V86 PM
0000 VMM.9+264 C4520298 FB04:3002 0038:0200
0001 VMM+2244 0010B210 FB05:2FF2 0038:0202
0002 VMM+2268 0010B210 FB06:2FE2 0038:0204
0003 VMM.2+0B8 0010В21Г| FB07:2FD2 0038:0206
00С6 IFSMGR+480 00000007 FBCA:23A2 0038:038C
228
Неофициальная Windows 95
00Е5 VMM+32E4 VMM+DC44 FBE9:21В2 003B:03CA -> VMM+1A11
00Е6 VMM+32E4 VPICD+1938 FBEA:21A2 003B:03CC -> VPICD+F88
00Е7 VMM+32E4 VTD+33C FBEB:2192 003B:03CE -> VTD.3+000
00Е8 VMM+32E4 VDD+4AD4 FBEC:2182 003B:03D0 -> VDD+2DA4
00 Е9 VMM+32E4 VM0USE+5EC FBED-.2172 003B.03D2 -> VM0USE.2+78C
ООЕА VMM+32E4 I0S+1A98 FBEE:2162 003B:03D4 -> IOS.2+1864
ООЕВ VMM+32E4 D0SMGR+1C8 FBEF:2152 003B:03D6 -> DOSMGR.12+000
00 ЕС VMM+32E4 SHELL+9E8 FBFO:2142 003B:03D8 -> SHELL.2+0C5
OOED VMM+32E4 VP0WERD+344 FBF1:2132 003B:03DA -> VPOWERD.2+000
ООЕЕ VMM+32E4 VXDLDR+084 FBF2:2122 003B:03DC -> VXDDR.3+1C9
OOEF VMM+32E4 VC0MM+2CC FBF3:2112 003B:03DE -> VC0MM.2+1A8
00 F0 VMM+32E4 C0NFIGNG+30C FBF4:2102 003B:03E0 -> CONFIGMG.3+000
00F1 VMM+32E4 ENABLE+39C FBF5:20F2 003B:03E2 -> ENABLE.2+2C4
00F2 VMM+32E4 VC0ND+060 FBF6:20Е2 003B:03E4 -> VCOND.2+000
00F3 VMM+32E4 DSVXD+1EC FBF7:20D2 003B:03E6 -> DSVXD+057
00 F4 VMM+32E4 IFSMGR+2C18 F8F8:20С2 003B:03E8 -> IFSMGR.2+1CA7
00F5 VMM+32E4 WINICE+24F28 FBF9:20В2 003B:03EA -> WINICE+7AC
00F6 VMM+32E4 VSHARE+3FC F8FA:20А2 003B.03EC -> VSHARE+290
00F7 VMM+32E4 VCACHE+5DC F8FB:2092 003B:03EE -> VCACHE+52D
00F8 VMM+32E4 VFINTD+25C F8FC:2082 003B:03F0 -> VFINTD+022
00F9 VMM+32E4 VXD+320 FBFD:2072 003B:03F2 -> VXD+02A
OOFA VMM+32E4 CR3+050 FBFE:2062 003B:03F4 -> CR3+002
OOFB VMM+32E4 PSVXD+8DC FBFF:2052 003B:03F6 -> PSVXD+0E3
00 FC VMM+32E4 VTD+340 FC00:2042 003B:03F8 -> VTD.3+000
В первой части показаны контрольные точки V86. Например, вспомните предыдущую цитату из
документации DDK по поводу того, как VxD V86MMGR использует контрольную точку V86 для
блокировки XMS. Сейчас, имея дело с WINBP, легко проверить, как это работает. Вначале
используйте отладчик для вызова функции 4310h прерывания INT 2Fh (Get XMS Driver Address —
Получить адрес драйвера XMS) и пройдите по цепочке обработчиков XMS (см. Chapell, DOS
Internals, р. 505-512, где хорошо объясняется этот малоизвестный аспект XMS):
C:\>symbeb
-а
7C0D:0100 mov ах, 4310
7C0D:0103 int 2f
7C0D:0105
-Р
АХ=4310 ВХ=ОООО СХ=ОООО DX=OOOO SP=82E2 ВР=ОООО SI=0000 DI=0000
DS=7C0D ES=7C0D SS=7C0D CS=7C0D IP=0103 NV UP El PL NZ NA P0 NC
7C0D:0103 CD2F INT 2F
-P
AX=4310 BX=OOCF CX=OOOO DX=OOOO SP=82E2 BP=OOOO 51=0000 DI=0000
DS=7C0D ES=0313 SS=7C0D CS=7C0D IP=0105 NV UP El PL NZ NA P0 NC
7C0D:0103 0000 ADD [BX+SI],AL DS:OOCF=OO
-u es:bx
0313:OOCF EA45006F1D JMP 1D6F:0045
-u 1d6f:45
1DF6:0045 EB03 JMP 004A
1DF6:0047 90 NOP
1DF6:0048 90 NOP
10F6.0049 90 NOP
1DF6:004A 63 DB 63
По адресу 1D6F:OO4A находится инструкция ARPL (которую SYMDEB выводит как DB 63h).
lD6F0h + 4Ah = lD73Ah. Можете проверить, что WINBP действительно сообщит нам об этой
контрольной точке V86 по линейному адресу lD73Ah:
Глава 8. Постепенное исчезновение DOS
229
C:\UNAUTHW\WINBP>winbp -verbose | grep 1D73A
02F2 0001D73A 90 DDC2 C0239EBC 00000000
C:\UNAUTHW\WINBP>winbp -verbose | grep 1D73A | vxdname
02F2 0001D73A 90 00C2 V86MMGR.9+000 00000000
Все это означает, что по адресу lD73Ah находится (и мы знали это!) контрольная точка V68,
соответствующая косвенному \786-вызову 90h, что инструкция ARPL заменила байт C2h и что по ад-
ресу C0239EBCh (или по смещению 0 в сегменте 9 в V86MMGR) находится обработчик XMS.
Теперь мы можем установить контрольную точку (я имею в виду контрольную точку отладчика)
по адресу C0239EBCh, сделать что-нибудь, использующее XMS, и посмотреть-, срабатывает ли эта
контрольная точка. Я запускал утилиту МЕМ из DOS, которая, кроме всего прочего, выдает размер
доступной памяти XMS. Естественно, контрольная точка в V86MMGR сработала, и я смог про-
следить ряд вызовов XMS, приходящих в V86MMGR.
Например, был вызов функции 6 XMS (Local Disable А20 — Локальный запрет А20), за кото-
рым следовал вызов функции 5 XMS (Local Enable А20 — Локальное разрешение А20). При
получении этих вызовов XMS V86MMGR в свою очередь вызывает функцию VMM_MMGR_
Toggle_HMA. На самом деле эти два вызова никак не связаны с утилитой МЕМ из DOS. Из-за
нехватки времени и места для более детального рассмотрения (см. Chapell, DOS Internals, р. 207-
209), скажу лишь, что DOS вызывает эти функции всякий раз при загрузке исполняемого модуля.
Эти вызовы из глубоких недр ядра DOS обрабатываются в 32-битовом защищенном режиме
функцией VMM_MMGR_Toggle_HMA.
Сама МЕМ вызывает функцию 8 XMS (Query Free Extended Memory — Запросить размер сво-
бодной дополнительной памяти). Для обслуживания этого вызова V86MMGR в свою очередь вы-
звал PageSwap_Get_Version, PageSwap_Test_IO_Valid и _PageGetAllocInfo. И снова DOS
завершает обработку вызовом Windows.
Теперь давайте взглянем на часть вывода WINBP, где отображены косвенные вызовы V86 и
защищенного режима. Намного раньше этого большого отступления о косвенных вызовах V86 про-
грамма VXD86API демонстрировала присутствие некоторых интерфейсов VxD для режима V86:
0001 h
0015h
0027h
FBE9:21В2
FBEF:2152
FBF2:2122
FE042
FE042
FE042
63
63
63
;; VMM
;; DOSMGR
;; VXDLDR
Мы знаем, что адреса FBE9:21B2 не содержат фактического кода, обслуживающего запрос V86
VxD API. Вместо этого по этим адресам находится не что иное как инструкции ARPL. (Как мы ви-
дели, это одна и та же ARPL, расположенная по одному и тому же линейному адресу). Итак, где же
фактический код, обеспечивающий интерфейсы V86 VMM, или DOSMGR, или VXDLDR? Доста-
точно найти в выводе WINBP адреса FBE9:21B2 или FBEF:2152, и вы найдете местоположение 32-
битового кода защищенного режима:
00Е5 VMM+32E4
ООЕВ VMM+32E4
ООЕЕ VMM+32E4
VMM+DC44
D0SMGR+1C8
VXDLDR+084
FBE9:21В2
FBEF:2152
FBF2:2122
003В:03СА -> VMM+1A11
003B:03D6 -> DOSMGR.12+000
003В:03DC -> VXDDR.3+1C9
Давайте взглянем на DOSMGR. В главе 4 мы видели, что DOS в определенных ситуациях вы-
зывает функцию 1 (Set Focus — установить фокус) интерфейса V86 DOSMGR. Просматривая код
по DOSMGR. 12+0 (здесь он находится по адресу C023DAD0h), мы увидим, что DOSMGR получает
номер функции из АХ и использует его для индексации в таблице указателей функций:
:u cO23dadO
002В:C023DAD0 MOVZX EAX,WORD PTR [EBP.Client_AX]
002B:C023DAD4 СМР EAX,06
002В:C023DAD9 JAE C023DAE3
002В:C023DADB CALL [C02C380C+4*EAX]
002В:C023DAE2 RET
230
Неофициальная Windows 95
Мы можем использовать утилиту PROTDUMP для просмотра этой таблицы. СМР ЁАХ,06 под-
сказывает нам, что V86 DOSMGR API поддерживает шесть функций:
С:\UNAUTHW\PROTDUMP>protdump с02с380с -dword
С02С380С | C023DAEE C023DAFD C023DB07 C023DB24
С02С381С | C023DB3D C023DB42
Код, который DOS иногда вызывает для функции 1, довольно прост. Он в основном вызывает
функцию VMM System_Control с AX=0Fh:
: u c023dafd
0028:C023DAFD CALL C023B524
0028:C023DB02 AND BYTE PTR CEBP+2C],FE
0028:C023DB06 RET
:и сО23Ь524 0028:С023В524 MOV EAX.OOOOOOOF
0028.С023В529 XOR EDX,EDX
0028:С023В52В XOR ESI,ESI
0028:C023B52D INT 20 VXDCall System_Control
System_Control посылает управляющее сообщение каждому VxD в системе. Сообщение OFh —
это Set_Device_Focus. Если EDX равно нулю (как здесь), все VxD получают фокус. Между про-
чим, существует почти идентичная функция — функция 168Bh прерывания INT 2Fh, использующая
System_Control Set_Device_Focus для установки фокуса на определенную VM. Дэвид Лонг (David
Long) подробно обсуждает эту функцию в статье (“TSR Support in Microsoft Windows Version 3.1”)
на MSDN CD-ROM. Согласно Лонгу, “Использование этой функции имеет некоторый риск, свойст-
венный ей”, при установке фокуса на оконный сеанс DOS, поскольку клавиатура и фокус вы-
полнения настраиваются на определенную VM, но (так как DOS выполняется в окне) фокус
дисплея и мыши должен быть установлен на системную VM. Статья Лонга содержит такие слова:
“эксперименты с Setfocus вы можете проводить дождливыми вечерами в тиши собственного дома”.
Во всяком случае, обращения MS DOS к DOSMGR, реализованные через инструкцию ARPL,
завершаются вызовом System_Control Set_Device_Focus. То, что DOS вызывает Windows (как уже
говорилось в главе 4), — является превосходным контрпримером для расхожего суждения
“Windows выполняется поверх DOS”.
Возвращаясь назад к нашему примеру с IFSHLP после длительного, но необходимого отступ-
ления о контрольных точках Windows и косвенных вызовах, давайте рассмотрим, что WINBP мо-
жет сообщить нам о ARPL, размещенном по адресу FBCA:23A2 на рис. 8.12:
; Из рис. 8.12
FBCA:23A2 ARPL [BX+SI+6E],BP ;;; косвенный вызов V86 по FBCA:23А2
; из WINBP | VXDNAME
00С6 IFSMGR+480 00000007 FBCA: 23А2 003B:038C
WINBP говорит нам, что, когда IFSHLP выполняет ARPL по адресу FBCA:23A2, он переходит
на IFSMgr+4B0, что мы и видим на рис. 8.12.
Раз IFSHLP передает вызов функции OBh прерывания INT 21h ему, IFSMGR имеет дело с
отраженными вызовами INT 2lh. В некоторых случаях IFSMGR обрабатывает вызовы в 32-битовом
защищенном режиме. В других случаях IFSMGR отражает вызовы обратно по цепочке обработ-
чиков прерывания DOS в V86-peжим. Конечно, бессмысленно передавать его в начало цепочки
обработчиков INT 21h, поскольку тогда IFSHLP увидит этот вызов опять. IFSMGR должен посы-
лать вызов обработчику INT 21h, установленному перед IFSHLP. Как видно из рис. 8.12, IFSMGR
выполняет это, передавая адрес segment:offset предыдущего обработчика функции Build_Int_
Stack_Frame. IFSMGR знает адрес обработчика INT 21h, установленного перед IFSHLP, потому
хранит этот адрес в таблице; он передает IFSMGR указатель на эту таблицу.
Глава 8. Постепенное исчезновение DOS
231
Теперь IFSMGR возвращает управление VMM, который на прощанье выполняет IRETD. Но
вместо возврата к участку, который сгенерировал INT 21h и инициировал весь этот процесс, инст-
рукция IRETD переходит по адресу, который IFSMGR передал функции Build_Int_Stack_Frame.
Таким образом управление передается на обработчик, установленный перед IFSHLP. Даже когда
вызов INT 21h был послан вниз к DOS (как это часто случается с вызовами функции OBh в окне
DOS), он часто выталкивается обратно на уровень VxD для некоторой обработки.
Чтобы сделать эту нелепо длинную историю покороче, одна из задач IFSHLP.SYS состоит в
предотвращении достижения DOS определенными файловыми вызовам INT 21h. Вог, что сказал по
этому поводу Джеф Чапелл:
IINT 21h и пр. могут отражаться в VM, поскольку IFSHLP может передать управление обратно на
уровень 0 до того, как вызов достигнет гадкого, противного старого ядра DOS. IFSHLP выступает в
роли фильтра: все, что перехватывает INT 21h после него, — поддерживается, а все, что перехватило до
него, — заменяется.
У IFSHLP.SYS есть и другие задачи, в частности, он не позволяет эмулированным INT (см.
раздел “Замещение кода реального режима: уже не ново” дальше в этой главе) достигнуть DOS.
IFSHLP.SYS также перехватывает не только INT 21h, но и некоторые другие прерывания, включая
INT 08h, 17h, 2Ah и 2Fh.
На рис. 8.13 схематически представлен путь, который проделывает У86-вызов INT 21h в Chicago.
INT 21h в DOS-программе
- > VMM
- > цепочка перехватчиков прерывания V86
- > ...(другие VxD, перехватчики INT 21h, VMPOLL, SHELL и др)...
- > IFSMGR
если (VM перехватила INT 21h через 21/25)*
- > локальные перехватчики INT 21h (такие как TEST21)
- > глобальные перехватчики INT 2th (такие как V86TEST)
- > IFSHLP.SYS
- > (через ARPL) VMM
- > IFSMGR
если (файловая функция, эмулированная IFSMGR)
эмуляция функции
иначе
передача обработчику, установленному перед IFSHLP
* Это проверка, которую делает IFSMGR, но возможен и другой вариант:
если (вектор INT 21h != оригинальный вектор обработчика INT 21h IFSHLP.SYS)
Рис. 8.13. Путь У86-вызова INT 2 lh в Chicago
Один важный момент: результаты TEST21 на рис. 8.10 показали, что даже с вызовами INT 21h,
отраженными к обработчикам INT 21h, 32BFA оказывается все-таки заметно быстрее файлового
ввода-вывода DOS. Кроме того, в то время как тест TEST21 -MYSETVECT 3000 >FOO.BAR
выполнялся под Chicago 12 с, тест TEST21 3000 > FOO.BAR (без -MYSETVECT) занял только 3 с.
Я был весьма удивлен этим обстоятельством и попросил Джефа Чапелла прокомментировать то, что
отраженный вызов INT 21h, блокированный и перенаправленный IFSHLP, выполнялся почти со
скоростью вызова, обработанного сразу же. Вот его объяснения:
Да, я и ожидал, что он будет работать почти так же быстро. Отражение прерываний в VM не должно
выполняться намного дольше, чем другие операции DOS. Производительность 32BFA довольно высока
благодаря замене ядра DOS, которое, как мы знаем, выполняет большую часть кода в критических сек-
циях и, самое плохое, одновременно отслеживает только одно обращение к драйверу устройства, всегда
ожидая его завершения. Конечно, дисковые кэши с отложенной записью сводят вторую проблему на нет,
создавая впечатление, что драйвер устройства выполняет операции быстро, но это работает только на опе-
рациях записи, которые редки по сравнению с операциями чтения (для которых ожидание неизбежно).
232
Неофициальная Windows 95
К настоящему моменту вы, несомненно, чувствуете, что решение Windows 95 проблемы “Под-
держка TSR без вызова DOS” далеко от идеального. Почему все это так сложно? Интересно сопо-
ставить это решение с тем, как Microsoft решила эквивалентную проблему для 32-битового диско-
вого доступа (32BDA).
Так же как 32BFA обходит DOS для файловых вызовов INT 21h, 32BDA обходит прерывание
BIOS INT 13h прямого доступа к диску. И так же как с 32BFA, Windows должна как-то поддер-
живать обработчики INT 13h и не позволять BIOS обрабатывать вызовы INT 13h. Но, в отличие от
INT 21h в ситуации с 32BFA, существует более простое решение для INT 13h: супервизор ввода-
вывода (VxD IOS), ранее известный как BLOCKDEV, использует недокументированную функцию
13h прерывания INT 2Fh, которая манипулирует цепочкой обработчиков INT 13h, находящихся под
обработчиком IO.SYS. Эта функция описывается детально в книге Джефа Чапелла, DOS Internals,
р. 57-95 (также см. документацию DDK для функций Intl3_Hooking_BIOS_Int и Intl3_
Unhooking_BIOS_Int, которые вызываются IOS/BLOCKDEV при манипуляции цепочкой обра-
ботчиков INT 13h). На рис. 8.14 показаны маршруты вызова INT 13h под 32BDA.
INT 13h из DOS-программы
-> VMM
- > VFD (Virtual Floppy Device проверяет, не относится ли это INT 13h к гибкому диску)
- > BLOCKDEV/IOS
- > перехватчик INT 13h в DOS .
- > обработчик, установленный через функцию 13h прерывания INT 2Fh
- > BLOCKDEV/IOS
Рис. 8.14. Путь вызова INT 13h при 32-битовом доступе к диску
Между прочим, такая форма обработки INT 13h означает, что если бы мы захотели написать
программу TEST 13 для 32BDA, подобную TEST21 для 32BFA, мы не могли бы просто перехватить
INT 13h. Мы должны были бы использовать функцию 13h прерывания INT 2Fh. К сожалению, MS
DOS не помещает в данные экземпляра (см. в главе 4 о данных экземпляра) указатель обработчика
функции INT 13h, который находится по адресу 0070:00В4, и поэтому вызов функции 13h пре-
рывания INT 2Fh под Windows приводит к зависанию. И опять я спросил Джефа Чапелла об этом.
Вот его ответ:
Это столь же просто, как заставить Windows отнести указатели в IO.SYS к данным экземпляра. Я
думаю, IO/MSDOS должен это делать с помощью 2F/1605... Если вы хотите написать VxD для
поддержки 2F/13, которую VM использовала бы для установки локального кода в качестве
обработчика INT 13h под IO.SYS, то должны знать или определить адреса соответствующих указателей
(см. IOS.386 в качестве примера проверки предположения относительно 0070:00В0/0106 и 0070:00В4)
или перехватить 2F/13 в V86-neno4Ke прерываний (и оказаться перед сложной проблемой
эмулирования интерфейса, как будто он является локальным все время). Я заявляю, что все это должна
делать Windows именно потому, что программа, использующая 2F/13 в VM, не может организовать
себя так, чтобы этот интерфейс стал сколь-нибудь полезным.
Особенно с тех пор, как Windows 95 развивает предположительно новую версию DOS
(WINBOOT.SYS), просто стыдно, что функцию 13h прерывания INT 2Fh нельзя безопасно вызвать
в окне DOS под Windows и что нет эквивалентного интерфейса INT 2Fh функции 21h для
управления цепочкой INT 21h. При отсутствии такой функции IFSHLP.SYS работает как разно-
видность DOS-эквивалента функции 13h прерывания INT 2Fh. И снова Джеф Чапелл:
Единственная возможная причина существования IFSHLP состоит в том, что он ограничивает цепочку
INT 21h в виртуальных машинах. 32BDA предназначен для замены обработчика BIOS INT 13h, как и
32BFA предназначен для замены DOS INT 21h. Конец цепочки INT 13h, т.е. то место, откуда уже по-
падаешь в BIOS, можно легко найти, хотя бы с помощью неописанного интерфейса (функция INT 13h
прерывания 2Fh). Однако совершенно отсутствует очевидный способ найти конец цепочки INT 21h, т.е.
последнюю остановку перед достижением ядра DOS. В этом и есть, видимо, единственная благо-
разумная роль IFSHLP.
Глава 8. Постепенное исчезновение DOS
233
Некий разработчик ПО из Microsoft, предлагая глупые извинения по поводу WfW3.ll, утверждал, что
вопрос стоял именно так: или отражать всегда, или не отражать никогда, — и что Microsoft остано-
вилась на том, чтобы можно было разрешать/запрещать 32BFA.
Вместо этого, при получении прерывания на уровне 0, необходимо проверять вектор INT V86: если он
не был перехвачен вне IFSHLP, тогда можно быть уверенным, что его отражение только ухудшит про-
изводительность, но если кто-либо перехватил его, тогда необходимо отражать прерывание в VM и
поддерживать старое программное обеспечение ценой потери производительности. Взамен озадаченным
пользователям представлена полная свобода выбора между их старыми драйверами и TSR и пре-
красным новым 32BFA.
Некоторые в Microsoft думают, что предположительно мимолетная WfW3.ll могла бы не только под-
держать заинтересованность рынка в период разработки Chicago, но и ускорить смерть этих неуклюжих
драйверов и TSR под DOS, а следовательно, и избавить разработчиков Chicago от лишней головной
боли. Как вы знаете, они очень близки к этому.
Конечно, Microsoft хочет перебросить разработчиков с TSR на VxD. Для приложений, которым
необходимо отслеживать доступ к файлам в Windows 95, IFSMGR предоставляет новую функцию
FileHook, которая может быть вызвана только (во всяком случае, непосредственно) из VxD.
По правде говоря, 32BFA в WfW3.ll был уже задолго до появления пред-бета-версии Windows
95. Реклама Microsoft цо WfW3.ll говорит, что продукт “включает 32-битовую технологию из
нашего проекта Chicago”. Учитывая, что в то время Chicago не была даже на стадии альфа-тести-
рования, можно сделать некоторые печальные заключения о состоянии кода 32BFA в WfW 3.11, что
й подтверждает строка описания “Win386 HPFS Driver (Prototype)” в VFAT. 386 из коммерческого
выпуска WfW 3.11. Прототип?!
Итак, использование в WfW3.ll кода 32BFA пред-альфа Chicago было превосходным способом
привлечения миллионов пользователей. Было не ясно, что эти пользователи платили всего лишь за
прототип, особенно когда стандартный ответ Microsoft на любую жалобу по поводу 32BFA в WfW
3.11 состоял в том, что пользователь должен запретить его. Один представитель Microsoft поместил
в форум по WINSDK в CompuServe следующее: 32BFA — это “прекрасная возможность, по моему
скромному мнению, но если она войдет в противоречие с вашей жизнью, запретите ее”. В то время
это была основная версия продукта Microsoft Windows на рынке. Даже сейчас, когда я пишу эти
строки, WfW 3.11 с ее версией прототипа 32BFA все еще является основной распространяемой
версией Microsoft Windows.
32BFA и сети, CD-ROM, дискеты
Возможно, к этому моменту вы уже сомневаетесь в результатах TEST21, на которых мы столь
долго сосредоточивали свое внимание. Хотя мы запускали TEST21 в различных средах, — обычная
DOS, DOS с дисковым кэшем, WfW 3.11 с 32BFA и Windows 95 — файловый доступ всегда рабо-
тал с жестким диском. Раньше мы сравнивали 32BFA на DblSpace- и не-DblSpace-диске, но мы
ничего не говорили о других носителях, таких как гибкие диски, CD-ROM, RAM-диски и, самое
важное, — сетевые устройства.
Во-первых, 32-битовый сетевой доступ к файлам обходит DOS тем же способом, что и 32-
битовый доступ к файлам/диску. Под WfW 3.11 сообщение “Некоторые INT 21h обрабатываются
без вызова DOS!” выводится как при выполнении TEST21 на сетевом сервере, работающем на
Windows NT Advanced Server, так и при выполнении TEST21 через одноранговый доступ на другой
WfW 3.11 машине.
Должно ли это вызывать удивление? В самых распространенных сетях для PC, NetWare 2.x и
3.x, рабочие станции выполняют оболочку NetWare, NETX, которая перехватывает INT 21h и
замещает большие куски DOS (см. Undocumented DOS, 2d ed., р. 195-205). Вы должны были
ожидать, что доступ к сетевым файлам осуществляется в обход DOS.
Но подход NETX, состоящий в перехвате INT 21h перед DOS, довольно типичен в том смысле,
что NetWare является самой распространенной сетью для PC, и вовсе не типичен, когда вы рас-
сматриваете весь диапазон сетевого программного обеспечения PC, включая NetWare 4.x. Вместо
234 Неофициальная Windows 9
перехвата INT 21h, рабочие станции для DOS используют недокументированный, но хорошо извест-
ный интерфейс сетевого редиректора (см. Undocumented DOS, 2d ed., р. 494-540, 769-783).
Редиректор работает под DOS: программы выдают файловые запросы INT 21Ь к MS DOS, и,
если это обращение к сетевому диску, DOS вызывает фуйкцию llh прерывания INT 2Fh. Реди-
ректоры перехватывают INT 2Fh, отлавливают вызовы функции llh и выполняют множество под-
функций, таких как 1108h (Чтение из удаленного файла), 1109h (Запись в удаленный файл),
И IBh (Поиск первого удаленного файла) и lllCh (Поиск следующего удаленного файла). Обычно
редиректор посылает запрос по сети к другой машине и, получив ответ, возвращается из INT 2Fh.
Затем DOS передает информацию пользователю, вызвавшему INT 21h.
Итак, с 32BFA на сетевом диске можно предположить, что вызовы INT 21h передаются DOS, а
32BFA, вероятно, реализован на уровне функции llh прерывания INT 2Fh. Однако TEST21 пока-
зывает, что по крайней мере для сетевых дисков это не так.
Драйверы CD-ROM фирмы Microsoft (MSCDEX) также используют интерфейс редиректора.
Если MSCDEX был загружен перед WfW3.11, to IFSMGR передает вниз все вызовы INT 21h (за
исключением записи через функцию 40h), активизируя драйвер CD-ROM. Проведенные DOS
вызовы функции llh прерывания INT 2Fh в ответ на INT 21h к драйверу CD-ROM (или к любому
другому перенаправляющему драйверу) тоже передаются вниз.
Любопытно, что в 32BFA под WfW3.ll (но не в Windows 95) жесткие диски обозначены как
сетевые диски редиректора. IFSHLP.SYS имеет обработчик прерывания INT 2Fh и может направ-
лять запросы редиректора к IFSMGR, у которого тоже есть обработчик прерывания INT 2Fh. Все
это должно измениться в Windows 95, где будет VxD-драйвер CD-ROM, который заменит
MSCDEX, обеспечивая 32-битовый доступ к CD-ROM в защищенном режиме. Впрочем, этого
можно добиться и без Windows 95, используя версию MSCDEX из пакета Helix Softwares's
Multimedia Cloaking.
Возвращаясь к 32BFA на настоящем сетевом диске (в отличие от CD-ROM, который MSCDEX
выдает за сетевой диск), важно понять, что приложения продолжают использовать именно INT 21h.
Приложения Windows и Win32 как в Windows 3.x, так и в Windows 95, используют INT 21h либо
напрямую (через DOS3Call или непосредственно INT 21h), либо косвенно через функцию Windows
API, которая может использовать DOS (как, например, CommDlg-функции GetOpenFileName и
GetSaveFileName). Несколько предвосхищая обсуждение программ WV86TEST и WSPY21, описан-
ных в главах 12 и 13, заметим, что Windows-приложения, осуществляющие доступ к файлам,
включая сетевые, только думают, что обращаются к DOS. Например, когда WinWord обращается к
файлу на другой машине, WISPY регистрирует большое число вызовов прерывания INT 21h,
включая следующие:
<WINWORD> (51) GET PSP
<WINWORD> (2f) GET DTA
<WINWORD> (la) SET DTA 4CCF:79DA
<WINWORO> (50) SET PSP 00a7
<WINWORD> (4e) FIND FIRST \\T_NEUHAUS\TRUDY\MSS\ANDREW\JUNK.DOC
<WINWORD> (3d) OPEN E:\MSS\ANDREW\JUNK.DOC
Подобным же образом MS Mail использует INT 21h для обращения к почтовым файлам на сер-
вере (который в данном случае работал под Windows NT):
<MSMAIL> (3d) OPEN \\progress1\WGP0\nme\gal.nme
<MSMAH> (3d) OPEN \\progress1\WGP0\nme\admin.nme
<MAILSPL> (3d) OPEN \\progress1\WGP0\glb\master.gib
Однако программа WLOG212F показывает, что обращения к функциям вроде 3Dh и 4Eh
обрабатываются внутри Windows и не отсылаются в DOS. Функции lAh и 2Fh для получения и
установки DTA также обрабатываются внутри Windows. Но WLOG212F также демонстрирует, что
функции 50h ц 51 h получения и установки текущего PSP передаются в DOS.
Глава 8. Постепенное исчезновение DOS
235
Поскольку IFSMGR не передает сетевые файловые вызовы INT 2lh в DOS, не удивительно, что
также не было почти никаких вызовов функции 1 lh прерывания INT 2Fh.
Итак, кто управляет 32BFA в сети? Вызовы INT 21h, адресованные к сетевым дискам, попа-
дают в IFSMGR, как и все обращения к INT 21h. IFSMGR (как следует из его имени) не занима-
ется доступом к жесткому диску: он является менеджером инсталируемой файловой системы (IFS).
Интересно, что старый интерфейс сетевого редиректора тоже назывался IFS, поэтому IFSMGR дей-
ствительно является преемником сетевого редиректора, за исключением того, что теперь весь
файловый доступ, включая встроенный доступ, в частности к жестким дискам, требует драйвера
инсталируемой файловой системы.
VFAT — VxD, который управляет 32BFA на жестких дисках с использованием старой файло-
вой системы DOS FAT (Таблица размещения файлов), — является как раз одним из драйверов,
включенных в IFSMGR. Существуют и другие: среди них VREDIR, являющийся устройством вир-
туального редиректора, и VSERVER, который действует как равноправный сервер, обрабаты-
вающий файловые запросы других машин в сети.
Что можно сказать о 32BFA и гибких дисках? В WfW 3.11 32BFA не поддерживает дискеты.
Если TEST21 запускается с гибкого диска, она довольно долго с ним работает и завершается сооб-
щением: “INT 21h обрабатываются обычным образом”.
Windows 95 уже поддерживает 32BFA для дискет. Выполнение TEST21 с дискеты под Windows
95 занимает примерно половину времени по сравнению с WfW 3.11, и TEST21 -MYSETVECT вы-
дает такое же сообщение “Некоторые INT 21h обрабатываются без вызова DOS!”, что и при
выполнении TEST21 на жестком диске.
Замещение кода реального режима:
уже не ново
Два примера VxD из этой главы, CURRDRIV (см. листинг 8.3) и INTVECT (см. листинг 8.4),
обрабатывающие определенные вызовы DOS полностью в 32-битовом защищенном режиме и
заставляющие TEST21 выводить сообщение “Некоторые INT 21h обрабатываются без вызова DOS!”,
могли быть написаны и запущены под Windows 3.0 в расширенном режиме. VMM и любой VxD,
поддерживаемый Microsoft или написанный независимыми разработчиками, всегда имел полную
свободу действий относительно того, должны ли прерывания отражаться в режим V86 {обратно в
V86, если прерывание было инициировано именно там) или обрабатываться в 32-битовом защи-
щенном режиме.
Так почему WfW 3.11 и Windows 95 кажутся такими новыми и отличными от предыдущих
версий Windows? В основном потому, что 32BFA широко использует способность обрабатывать пре-
рывания вместо передачи их в режим V86. В этой способности нет ничего нового, за исключением
степени ее реализации в WfW 3.11 и Windows 95.
Несколько раз уже указывалось, что способность 32BFA обходить DOS не основана на какой-
либо новой технологии. Она представляет собой не более чем (но также и не менее чем) исполь-
зование достаточно долго скрываемых возможностей. Впрочем, вас по-прежнему может интересо-
вать, откуда такая суета вокруг способности Windows обходить цепочку прерываний DOS. Несом-
ненно, мы уже об этом слышали!
Действительно, достаточно давно уже известно, что Windows иногда не позволяет програм-
мному обеспечению DOS отслеживать прерывания. Но вместо понимания этого явления как первого
признака того, что Windows становится истинной операционной системой, подобное поведение
считалось ... ошибкой! Время от времени кто-либо, работающий с TSR, начинал жаловаться по
поводу некоторых сервисов функции 16h прерывания INT 2Fh, обеспечиваемых Windows — дес-
кать, как получается, что их TSR не видят этих сервисов?
Например, когда я в марте 1990 г. был в Phar Lap Software, некоторые из нас послали в Micro-
soft список ошибок и перечень проблем, обнаруженных при работе с 286-м расширителем DOS под
Windows 3.0 в расширенном режиме. Ошибка № 4 в этом списке была:
236
Неофициальная Windows 95
I Windows перехватывает прерывание 2Fh через IDT вместо перехвата его через вектор прерывания
реального режима. Это означает, что INT 2Fh должен вызываться с помощью INT, и не может
имитироваться командами PUSHF и CALL FAR. Эта ошибка также нарушает работу трассировщиков
(например, утилиты INTRSPY из Undocumented DOS), которые отслеживают вызовы INT.
В известном смысле эта “ошибка”, примененная к вызовам файлового ввода-вывода INT 21h, и
есть 32BFA. Поставщики обычно заявляют, что пользователи часто называют ошибками то, что на
самом деле является функциональными особенностями. В данном случае это заявление абсолютно
точно отражает действительность! Является это ошибкой или нет (как мы видели с случае
IFSHLP.SYS, Windows может обходить DOS и все же отражать прерывания к TSR, которым не-
обходимо их видеть), такое поведение INT 2Fh унаследовано от функциональных особенностей
расширенного режима Windows. Это было первым признаком того, что Windows потихоньку
“отчаливает” от DOS.
Но сообщение об ошибке, тем не менее, поднимает один интересный вопрос: поскольку преры-
вания перехватываются через IDT, команда INT в режиме V86 может вести себя не так, как ком-
бинация PUSHF и CALL FAR. По различным причинам DOS-программы иногда используют комби-
нацию PUSHF/CALLF вместо INT, а многие программисты DOS приучены к мысли, что INT —
это не более чем прихотливый способ реализации PUSHF/CALLF. Но в режиме V86, где инст-
рукции INT пропускаются через IDT, такой эквивалентности уже не существует: процессор ничего
не знает о том, что ваш PUSHF/CALLF — на самом деле замаскированный INT.
Более того, если обработчик INT, установленный в IDT, не отражает прерывания обратно в
режим V86, то эти два способа вызова прерывания могут дать и вовсе различные результаты!
Например, рассмотрим один из сервисов прерывания INT 2Fh в Windows, по поводу которого
поступала жалоба из Phar Lap. Функция 1600h указывает, работает ли Windows в расширенном
режиме. Значения 0 или 80h, возвращенные в AL, сообщают, что расширенный режим не работает.
Любое другое возвращенное значение указывает номер версии Windows. Например, Windows 95
(известная и как Windows 4.0) возвращает AX=0004h.
Для демонстрации TEST1600.C (листинг 8.7) вызывает функцию 1600h прерывания INT 2Fh
как через INT, так и через выражение (* intfuncX), где intfunc объявлена с ключевым словом
-interrupt, поэтому компилятор автоматически генерирует PUSHF/CALLF (к сожалению, при этом
он забывает вставить инструкцию CLI, которую я добавил вручную). TEST 1600 использует также
популярную функцию int86, поскольку в прошлом некоторые библиотеки реализовали ее через
PUSHF/CALLF.
Листинг 8.7. TEST1600.C
/*-TEST1600.С */
«include <stdlib.h>
«include <stdio.h>
«include <dos.h>
main()
{
union REGS r;
struct SREGS s;
void (interrupt far *int2f)(void); // будет делать PUSHF
unsigned char retval, retva!2;
_asm mov ax, 1600h
_asm int 2fh
_asm mov retval, al
printf(“Через INT, 2F/1600. возвращает %02Xh\n", retval);
// бывает, что int86() реализуется с использованием дальнего вызова!
г.х.ах = 0x1600;
segread(&s);
int86x(0x2f, &r, &r, &s);
Глава 8. Постепенное исчезновение DOS
237
printf("4epe3 int86x, 2F/1600 возвращает %02Xh\n”, r.h.al);
int2f = (void (interrupt far *)(void)) _dos_getvect(0x2f);
.asm mov ax, 1600h
_asm cli // Borland забыл это сделать
(»int2f)(); // аналогично PUSHF, CALL FAR
_asm mov retva!2, al
printf(“Через CALL, 2F/1600 возвращает %02Xti\n”, retval2);
if(retval2 == retval)
printf(“\nPUSHF/FAR CALL работает так же, как INT\n”);
else
printf(“\nPUSHF/FAR CALL и INT - разные вещи!\п”);
return 0;
Достаточно простая, на первый взгляд, программа. Если расширенный режим Windows не
работает, то три различных способа вызова функции 1600h INT 2Fh дают один и тот же результат:
AL=0 указывает, что, действительно, расширенный режим Windows не работает.
Когда же расширенный режим Windows работает, хотелось бы надеяться, что все три метода
вызова функции также отразят этот факт. Но вот что происходит на самом деле:
С:\UNAUTHW>test1600
Через INT, 2F/1600 возвращает 03h
Через int86x, 2F/1600 возвращает 03h
Через CALL, 2F/1600 возвращает 00h
PUSHF/FAR CALL и INT - разные вещи!
Когда функция получения состояния расширенного режима Windows вызывается через
PUSHF/CALLF, возвращенное значение равно 0, как будто Windows нет! Причина состоит в том,
что хотя каждое прерывание и передается в VMM, CALLF этого не делает. VMM содержит обра-
ботчик для этих запросов INT 2Fh. VMM никогда не видит CALLF и, следовательно, никогда не
обрабатывает такие запросы, поэтому создается впечатление, как будто Windows нет. При обработке
вызовов INT 2Fh Windows полагается на механизм IDT.
Может показаться неестественным имитировать прерывание с помощью (* intfuncX) вместо ис-
пользования обычных инструкций. Однако у программного обеспечения DOS иногда имеются веские
основания для имитации прерываний. В конце концов, Windows сама обеспечивает функцию
Simulate_Int, реализация которой использует вектор из таблицы IVT.
В качестве примера, зачем DOS имитировать прерывания, давайте рассмотрим утилиту
INTCHAIN из Undocumented DOS (2d ed., гл. 6). INTCHAIN использует режим пошаговой отлад-
ки, чтобы проследить и вывести на экран цепочку прерываний. Код содержит комментарий,
“трассировка не проходит через INT, поэтому вместо INT используются PUSHF и дальний CALL”
(р. 305). Не тут-то было! При выполнении под Windows это может привести к самым неожиданным
результатам, поскольку INT пропускается через IDT, a PUSHF/CALLF — нет. (Еще больше
вводит в заблуждение отказ программы реального режима INTCHAIN при демонстрации “путешест-
вия” прерывания через 32-битовый защищенный режим).
Использование имитирования INT для достижения эффекта трассировки через инструкцию INT
является обычной практикой для отладчиков. В расширенном режиме Windows, однако, это может
привести к неправильным результатам. Использование DEBUG для трассировки вызова функции
1600h INT 2Fh, например, дает AX=1600h вместо чего-то подобного AX=0B03h (Windows 3.11) или
AX=0004h (Windows 4.0):
238
Неофициальная Windows 95
C:\UNAUTHWXiebug
;;; введем вызов функции 1600h INT 2F
-а
7781:0100 mov ах,1600
7781:0103 int 2f
7781:0105
;;; Шаг (выполнить)
-₽
АХ=1600 BX=0000 СХ=ОООО DX=OOOO SP=FFEE ВР=ОООО SI=0000 DI=0000
DS=77B1 ES=77B1 88=7781 CS=77B1 IP=0103 NV UP El PL NZ NA PO NC
7781:0103 CD2F INT 2F
-P
AX=0004 BX=0000 CX=0000 DX=OOOO SP=FFEE BP=OOOO 81=0000 DI=0000
08=7781 ES=77B1 SS=77B1 CS=77B1 IP=O1O5 NV UP El PL NZ NA PO NC
;;; Получили правильный результат: AX=0004
; Теперь вернемся на начальную позицию и воспользуемся трассировкой
-rip
IP 0105
: 0100
-t
АХ=1600 ВХ=ОООО' СХ=ОООО DX=0000 SP=FFEE ВР=ОООО 81=0000 DI=0000
08=7781 ES=77B1 SS=77B1 CS=77B1 IP=0103 NV UP El PL NZ NA PO NC
7781:0103 CD2F INT 2F
-t
;;; Здесь нет ничего интересного, теперь идем на следующую
;;; инструкцию после INT 2F
-д 77Ь1:0105
АХ=1600 8Х=0000 СХ=ОООО DX=OOOO SP=FFEE ВР=ОООО 81=0000 DI=0000
DS=77B1 ES=77B1 SS=77B1 CS=77B1 IP=0105 NV UP El PL NZ NA PO NC
77B1:0103 CD21 INT 21
;;; Ага! Получили неправильный результат: AX=1600 вместо 0004
;;; И все, что мы сделали, это использовали Т вместо Р!
Как мы отмечали раньше, некоторые библиотеки реализуют функцию int86 посредством имита-
ции инструкции INT. Причина в том, что номер прерывания для инструкции Intel INT должен быть
частью самой инструкции, например, INT АХ — неправильная команда. Таким образом, функции,
которые получают номер прерывания как параметр (например, int86) должны либо модифи-
цировать свой код для задания номера прерывания в команде, либо имитировать INT с помощью
PUSHF/CALLF. Сегодня большинство версий int86 использует самомодифицирующийся код. Воз-
можно, поставщики уже выяснили, что под Windows PUSHF/CALLF != INT.
Как же 32BFA справляется с программами DOS, использующими PUSHF/CALLF для вызовов
файлового ввода-вывода? Опять-таки, на помощь приходит IFSHLP.SYS: он располагается в
соответствующем месте, чтобы доставлять эти вызовы в IFSMGR.
Глава 8. Постепенное исчезновение DOS
239
Еще один старый пример: TEST2F16
Программа V86TEST,' описанная в главе 10, содержит обработчик INT 2Fh. Когда работает
Windows, V86TEST вызывает функцию 1683h прерывания INT 2Fh (Получить текущий иденти-
фикатор виртуальной машины) из обработчика INT 2Fh! Я видел коммерческий код, который делает
то же самое, вызывая функцию 16Ь прерывания INT 2Fh Windows из обработчика INT 2Fh. Почему
это не приводит к бесконечному зацикливанию?
Причина в том, что при работе в Windows прерывание INT 2Fh адресуется VMM через IDT, и
большинство запросов функции 16h прерывания INT 2Fh немедленно обслуживается VMM и не
отражается обратно. Обработчик INT 2Fh программы никогда не видит своих собственных вызовов
функции 1683b.
Чтобы продемонстрировать такое поведение Windows, TEST2F16.C (листинг 8.8) генерирует и
перехватывает вызовы функции 16h прерывания INT 2Fh наподобие того, как TEST21 делала это с
прерываниями INT 21h.
Листинг 8.8. TEST2F16. с
/*
TEST2F16.C -- Демонстрирует, что Windows не отображает функцию 16h INT 2Fh в режим V86
Шульман, 1994
Ьсс -2 -Р- test2f16.c
*/
«include <stdlib.h>
«include <stdio.h>
«include <dos.h>
typedef unsigned short WORD;
typedef unsigned long DWORD;
«pragma pack(1)
typedef struct {
«ifdef __TURBOC__
WORD bp, di, si, ds, es, dx, ex, bx, ax;
«else
WORD es,ds,di,si,bp,sp,bx,dx,ex,ax; /* порядок такой же, как в PUSHA */
flendif
WORD ip,cs,flags;
} REG_PARAMS;
void interrupt far int2f(REG_PARAMS r);
void (interrupt far *old_2F)();
static DWORD int2f16_calls[0xl00] = {0};
static DWORD total_int2f16_calls = 0;
static DWORD int2f_calls = 0;
static DWORD int2f1200_calls = 0;
void fail(const char *s) { puts(s); exit(1); }
main(int argc, char *argv[])
{
DWORD i;
DWORD num_iter = (argc < 2) ? 100 : atol(argv[1]);
240
Неофициальная Windows 95
/* перехватываем INT 2Fh */
old_2F = _dos_getvect(0x2F);
_dos_setvect(0x2f, int2f);
/* генерируем кучу вызовов 2F/168x •/
for(i=0; i<num_iter; i++)
{
_asm mov ax, 1200h // проверка
_asm int 2fh
_asm mov ax, 1680h
_asiri int 2fh
_asm mov ax, 1681h
_asm int 2fh
_asm mov ax. 1682h
_asm int 2fh
_asm mov ax, 1683h
_asm int 2fh
/* восстановить INT 2Fh */
_dos_setvect(Dx2F, old_2F);
/* на всякий случай */
if(int2f120D_calls < num_iter)
fall(“4To-To не так!");
/* вывести результаты */
printf(“TEST2F16 сгенерировал %lu вызовов 2F/1200\n”, num_iter);
printf(“TEST2F16 сгенерировал %lu вызовов 2F/1680-2F/1683\n”, num_iter);
printf(“TEST2F16 обнаружил %lu вызовов INT 2Fh\n”, int2f_calls);
if(total_int2f16_calls)
{
printf(“TEST2F16 обнаружил следующие вызовы 2F/16:\n’’);
for(i=0; i<0x1DD; i++)
if(int2f16_calls[i])
printf(“%021X;%lu\t”, i, int2f16_calls[i]);
printf(“\n’’);
}
else
printf(“TEST2F16 обнаружил 0 вызовов INT 2Fh/16h!\n”);
if(total_int2f16_calls < num_iter)
printf(“\nINT 2Fh функция 16h не отражается в режим V86!\n”);
return 0;
void interrupt far int2f(REG_PARAMS r)
{
int2f_calls++;
if(r.ax == 0x1200)
int2f1200_calls++;
if((r.ax » 8) == 0x16)
{
total_int2f16_calls++;
int2f16 calls[r.ax & 0xff]++;
}
_chain_intr(old_2F);
Глава 8. Постепенное исчезновение DOS
241
TEST2F16 производит следующие вызовы прерывания INT 2Fh Windows, документированные в
Windows DDK:
1680h Release Current VM Time-Slice
1681h Begin Critical Section
1682h End Critical Section
1683h Get Current Virtual Machine ID
При работе в расширенном режиме Windows (любая версия, включая Windows 3.0 от!990 г.)
TEST2F16 не видит этих обращений:
TEST2F16 сгенерировал 100 вызовов 2F/1200
TEST2F16 сгенерировал 100 вызовов 2F/1680-2F/1683
TEST2F16 обнаружил 100 вызовов INT 2Fh
TEST2F16 обнаружил 0 вызовов INT 2Fh/16h!
INT 2Fh функция 16h не отражается в режим V86!
Сообщение “INT 2Fh функция 16h не отражается в режим V86!” демонстрирует, что VMM обра-
батывает INT 2Fh и, не считая нужным передавать вызов кому-либо еще, возвращается из своего об-
работчика Hook_V86_Int_Chain со сброшенным флагом CF. Microsoft не предполагала, что какие-то
резидентные программы захотят отслеживать эти вызовы, даже если их обрабатывает Windows.
Главная проблема многих сервисов на основе прерываний состоит в “перекрытии” интерфейса:
помимо обработки прерываний для обслуживания запросов, большая часть программного обеспече-
ния DOS также обрабатывает прерывания для обнаружения соответствующих событий. Достаточно
трудно внести что-либо новое, более эффективное в обслуживание прерываний и продолжать при
этом позволять другим программам получать события, которые они хотят увидеть.
Это сообщение “INT 2Fh функция 16h не отражается в режиме V86!” — в действительности
лишь указание на то, что, когда мы запускаем DOS-программы реального режима под Windows, все
становится не таким, каким кажется. Мы больше не в Канзасе. А где же мы? Мы в режиме V86.
Прерывания 101: IDT против IVT
"Все, что вы знаете, — неправильно"
— Firesign Theatre.
Кое-где в этой главе я упомянул без дополнительных объяснений, что прерывания в V86 про-
ходят через структуру данных под названием IDT, а не через IVT в нижней памяти.
Поскольку мы получили необычные результаты, вроде того, что Windows каким-то образом
незаметно отбирает управление INT 21h у недавно загруженного обработчика, я должен объяснить
это подробнее. Некоторые материалы здесь покажутся хорошо знакомыми DOS-программистам.
Однако потерпите, вы должны четко понимать такие моменты. Итак, начнем...
Большинство операционных систем использует некоторую форму прерываний или ловушек для
предоставления приложениям различных сервисов. MS DOS предоставляет свой интерфейс через
программные прерывания, и прежде всего через INT 2th. Например, когда приложение DOS от-
крывает файл, оно либо прямо, либо косвенно выполняет код следующего вида:
mov ah, 3Dh ; 3Dh = номер функции открытия файла
mov al.0 ; только для чтения
Ids dx,dword ptг „name ; имя файла в DS:DX
int 21h ; вызов DDS!
Когда программа Windows открывает файл, сама она, возможно, и не содержит подобного кода,
но зато вызывает некоторую функцию API Windows для обращения к другой функции, которая, в
2^
Неофициальная Windows 95
конечном счете, содержит код, подобный этому. Это справедливо даже, для приложений Win32, за-
пущенных йод Windows 95 или Win32s.
Теперь комментарий “Вызов DOS!”, следующий за командой INT 21Ь, не является столь необ-
ходимым. Одна из идей этой главы состоит в том, что INT 21Ь не обязательно означает “Вызов
DOS!”. Это все больше и больше означает “Вызов некоторого VxD!”. Давайте, однако, вернемся на
мгновение в старое доброе время реального режима. Не V86, а настоящего реального режима.
В реальном режиме команда INT обращается к таблице векторов прерываний (IVT), рас-
положенной по адресу 0 в первых 400Ь байтах памяти. Эта таблица имеет 100Ь элементов, по
одному для каждого прерывания (от INT Oh до INT OFFh). Каждый элемент может содержать четы-
рехбайтовый дальний указатель на участок кода, обслуживающий прерывание. В реальном режиме
команда INT извлекает дальний указатель из IVT и обращается по нему:
; код для INT п
pushf ; сохранить флаги
cli ; не забывайте запрещать прерывания
xor ах, ах
mov es, ах ; es = 0
mov bx, _n
shl bx, 2 ; bx = intnum * 4
call far ptr es:[bx] ; call [00D0:intnum * 4]
Именно такая опосредованность — вызов по INT п функции по указателю в IVT[n] — и делает
эту команду столь полезной для операционной системы. Приложения могут обращаться к опера-
ционной системе, даже не зная ее расположения в памяти. Все, что им требуется, — это магическое
число 21h или 2Fh.
Более того, эта опосредованность позволяет разработчикам программного обеспечения достаточ-
но легко расширять операционную систему. Они просто изменяют элементы IVT так, чтобы они ука-
зывали на их собственный код. Рассмотрим функцию MS DOS установки вектора прерывания
(функция 25h прерывания INT 21h): как мы видели раньше в функции my_setvect (см. листинг 8.1)
и Do_SetVect (см. листинг 8.4), назначение функции с указателем fp в качестве нового обработчика
INT п включает всего лишь установку IVT[n] = fp. Аналогично, функция получения вектора пре-
рывания (функция 35h прерывания INT 21h), которая возвращает текущий обработчик для INT п,
должна всего лишь вернуть IVT[n] (см. my_getvect в листинге 8.1 и Do_GetVect в листинге 8.4).
Совместное использование этих функций позволяет программам создавать целые цепочки обра-
ботчиков прерываний:
previous_handler = get_vector(n);
set_vector(n, my_handler);
my_handler:
// сделать что-нибудь
if (previous_handler != C)
call previous_handler
Эти цепочки прерываний позволяют независимым разработчикам программного обеспечения
легко расширять операционную систему и в значительной степени являются причиной долговечности
MS DOS.
Книга Undocumented DOS (2d ed., гл. 6) содержит программу INTVECT, которая отображает
IVT. Ниже приводится вывод INTVECT при запуске его под WfW3.ll:
C:\UND0CD0S\CHAP6>intvect
INT OOh 2315:D94E INTVECT
INT 01h 0070:026D 10 iret - NOP function
INT 02h 120A:0016 DBLSYSH$
INT 03h 0070:026D 10 iret - NOP function
INT 04h 0070:026D 10 iret - NOP function
Главё'^'Пёстёпенноёисчезновение DOS
INT 05h E000:27C4
INT D6h F000:B6DD
INT 07h F000:B6DD
INT D8h 1055:0000 win386
INT 09h 1208:0028 DBLSYSH$ jmp 1208:0020
INT OAh 1208:003A DBLSYSH$ jmp 1208:0040
... И T. Д. ...
INT 21h 020E:0498 IFS$HLP$
INT 22h 1E11:020B COMMAND
INT 23h -1E11:0168 COMMAND
INT 24h 1E11:0173 COMMAND
INT 25h 00A0:0FB6 DOS
INT 26h 01EF:0037 D: jmp03A4:1B60
INT 27h 00A0:0FCA DOS
INT 28h 00А0:1069 DOS iret - NOP function
INT 29h 0070:026E 10
INT 2Ah 020E:059C IFS$HLP$
INT 2Bh 00A0:1069. DOS iret - NOP function
INT 2Ch 00A0:1069 DOS iret - NOP function
INT 2Dh 00A0:1069 DOS iret - NOP function
INT 2Eh 12D1:015D COMMAND
INT 2Fh 1B8E-.0424 win
... И T. Д. ...
К сожалению, вывод INTVECT под Windows может оказаться обманчивым. INTVECT может за-
ставить вас поверить, например, что INT 2Fh обрабатывается программой WIN по адресу 1В8Е:0424
и что первичный обработчик для INT 21Ь — это драйвер устройства IFS$HLP$ по адресу
020Е-.0498.
Результаты TEST21 и TEST2F16, описанные в данной главе, показывают, что это не совсем так.
Если обработчики TEST21 и TEST2F16 не видят определенных вызовов, то обработчики WIN и
IFS$HLP$, загруженные ранее, и подавно их не увидят. В конце концов прерывания обрабаты-
ваются по принципу LIFO (Last In — First Out). Все, что INTVECT показывает под Windows, это
то, куда бы могло попасть прерывание, если бы VMM или VxD решили отобразить его в режим
V86. В Undocumented DOS я предупреждал (не очень убедительно), что для работы в расширенном
режиме Windows необходима более сложная версия программы INTVECT.
Конечно же, я не имел в виду нагрузить INTVECT Windows-интерфейсом. Иногда поставщики
пытаются превратить старые DOS-утилиты и диагностические пакеты в “понимающие Windows”,
просто добавляя в них привлекательный пользовательский интерфейс, и поэтому уже появился ряд
инструментальных средств диагностики для Windows, которые послушно выводят на экран таблицу
векторов прерываний реального режима. К сожалению, отображение этой старой внутренней струк-
туры реального режима в окне списка Windows совсем не демонстрирует понимания Windows. Как
и в случае с INTVECT, результаты не имеют смысла, поскольку таблица векторов прерываний
реального режима, неважно каким образом выведенная на экран, совершенно не показывает, где же
в действительности обрабатываются прерывания.
В Windows INT п больше не является просто элегантным способом вызова функции, располо-
женной по IVT[n]. Все INT в режиме V86 передаются через совершенно другую структуру данных
под названием Таблица дескрипторов прерываний (IDT). Формат IDT определяет микропроцессор
Intel. Операционная система такого типа, как Windows, ответственна за организацию этой струк-
туры и сообщение процессору ее адреса. Невзрачная на вид текстовая программа, которая выводит
на экран IDT (например, IDTMAP), намного больше понимает Windows, чем миловидная GUI-
программа, которая по-прежнему выводит IVT.
По структуре IDT подобна таблице локальных дескрипторов (LDT) и таблице глобальных
дескрипторов (GDT). IDT может располагаться в любом месте памяти. Операционная система, бази-
рующаяся на защищенном режиме процессора Intel, вроде Windows, устанавливает адрес IDT с
помощью команды LIDT. Адрес IDT можно получить командой *SlDT. SIDT не привилегированна,
поэтому получить адрес IDT может любая программа. Эта команда SIDT и используется в про-
244
Неофициальная Windows 95
грамме IDTMAP, которая печатает IDT под Windows (или под любой другой операционной си-
стемой, обеспечивающей обслуживание DPMI).
Между прочим, последнее замечание в скобках оказывается достаточно важным: менеджеры па-
мяти вроде QEMM, 386МАХ или же NetRoom в действительности являются 32-битовыми опера-
ционными системами защищенного режима. Когда пользователь запускает один из таких менед-
жеров памяти, INT 21Ь вначале обрабатывается в защищенном режиме, как и в расширенном ре-
жиме Windows.
Это не кажется очевидным, поскольку такие менеджеры памяти передают все вызовы в DOS.
Но они могут этого и не делать. Например, рассмотрим Cloaking API от Helix Software, который
может работать как со своим собственным менеджером памяти NetRoom, так и с другими про-
граммами, вроде QEMM и 386МАХ. Подобно драйверам VxD в Windows, Cloaking является
интерфейсом для написания 32-битового системного программного обеспечения защищенного режи-
ма: DOS продолжает вызывать INT, и они, как обычно, отлавливаются менеджером памяти. Но,
вместо слепой отсылки этих вызовов обратно в режим V86, интерфейс Cloacking позволяет обраба-
тывать вызов в 32-битовом защищенном режиме. Helix работала с Award Software для создания
Cloacking BIOS, лицензировала у Microsoft CD-ROM Extentions для создания Cloacking MSCDEX,
написала Cloacking-версию драйвера мыши Logitech и т.д.
Поэтому способность обходить код реального режима на самом деле является особенностью ре-
жима V86, а не расширенного режима Windows, и потенциально доступна любому менеджеру памя-
ти. Итак, расширенный режим Windows — что же это такое, если не хитроумный менеджер памяти?
Вернемся, однако, к IDT. Каждый элемент этой таблицы — восьмибайтовый дескриптор шлюза,
содержащий адрес обработчика прерываний в формате selectoroffset и некоторые флаги, отражаю-
щие тип шлюза. IDT может содержать шлюзы ловушек (trap gates), шлюзы задач (task gates) и
шлюзы прерываний (interrupt gates). Существуют также шлюзы вызовов (call gates), но они не
используются в IDT (конечно, нет, IDT не может содержать Билла Гейтса, этого даже не смогут
сделать ни Антимонопольная комиссия США, ни Департамент юстиции).
В Windows чаще всего встречаются 32-битовые шлюзы прерываний. Windows также использует
16-битовые шлюзы ловушек для захвата прерываний, созданных программами защищенного режи-
ма, например Windows-приложениями.
Windows больше не использует эзотерических функциональных возможностей IDT, вроде шлю-
зов задач, которым уделено столько внимания в руководствах по программированию Intel 386. На
самом деле, большинство операционных систем использует только малую часть этих функцио-
нальных возможностей (на что особое внимание обращают защитники RISC-технологии). Поскольку
Windows существенно базируется на IDT, документация Intel оказывается основным дополнением к
документации по программированию в Windows. Только не слишком увлекайтесь всеми этими раз-
ными “звоночками и свистульками”, которые предоставляет Intel.
Когда процессор замечает INT п в V86 или защищенном режиме, он обращается к щлюзу в
IDT[n] и вызывает функцию по адресу, содержащемуся в нем. Таким образом, невинный на вид
INT 21h или INT 2Fh в программе DOS или даже — и это довольно удивительно и важно — в про-
граммном обеспечении DOS, загруженном перед Windows, не будет вызывать процедуру реального
режима, чей адрес располагается по IVT[21h] или по IVT[2Fh]. Напротив, он вызовет 32-битовый
код защищенного режима из круга 0, расположенный по адресам IDT[21h] или IDT[2Fh]. Адреса
представляют собой 48-битовые дальние указатели: 16 бит для селектора защищенного режима и
32 бита для смещения. Следовательно (вы можете в этом убедиться, если запустите программу
IDTMAP), 32-битовый код защищенного режима будет расположен по некоторому странному на вид
адресу в VMM —вроде 0028:80006FAA или 0028:8000701А.
Как и IVT, IDT может содержать до 256 (100h) элементов, по одному для каждого прерывания
(от 0 до 0FFH). В Windows, однако, IDT содержит только 60h элементов в диапазоне от INT 0 до
5Fh. INT 60h или выше, например INT 67h для работы с расширенной памятью (EMS), приводят к
ошибке нарушения общей защиты, которая снова приходит в IDT как INT 0DH, с кодом ошибки,
содержащим оригинальный номер прерывания.
Да, все правильно: в Windows каждое обращение к EMS вызывает ошибку нарушения общей
защиты (GP). Вы, наверное, приучены к мысли, что GP-ошибки — это не более чем то, что
Глава 8-. Постепенное исчезновение DOS
245
Microsoft когда-то назвала UAE; однако ошибки нарушения общей защиты и другие ошибки (вроде
INT 6 для недопустимых кодов операций) являются не просто ошибками. Windows использует эти
ошибки как один из своих основных механизмов для реальной работы.
Windows разделила IDT для V86 и защищенного режимов. Во многих случаях соответствующие
элементы для V86 и защищенного режимов в каждой IDT указывают на один и тот же обработчик
прерываний внутри VMM. В других случаях соответствующие элементы указывают на разные обра-
ботчики. Например, Windows обрабатывает INT 2th в защищенном режиме не так, как в режиме V86.
Что делает VMM с этими прерываниями? В некоторых случаях он обрабатывает их непосред-
ственно. Функция DPMI INT 31h является важным примером: DPMI-сервер располагается внутри
VMM, поэтому любые вызовы INT 3th из приложений защищенного режима также обслуживаются
непосредственно VMM. То же относится ко многим функциям Windows INT 2Fh.
В других случаях, однако, VMM не обрабатывает прерывания сам. Вместо этого он передает их
VxD, которые запрашивали их вызовом функции V86_Int_Chain. Подобным же образом VMM
передает ошибки тем VxD, которые запрашивали их с помощью вызова такой функции VMM, как
Hook_VMM_Fault или Hook_V86_Fault.
Ключевой момент в том, что IDT передает VMM контроль над тем, что происходит в Windows,
отсылая ему все ключевые прерывания и ошибки. VMM или обслуживает эти события, или отсы-
лает их одному или нескольким VxD для дальнейшей обработки, или же выполняет и то и другое.
Значит ли это, что Windows полностью игнорирует IVT из нижней памяти? — Не совсем.
Windows использует IVT для отражения прерываний, что, как мы видели, позволяет обработчику
прерываний защищенного режима вызывать прерывание в режиме V86: машина переключается в
режим V86 и прерывание передается через IVT по цепочке обработчиков реального режима (по
крайней мере они думают, что находятся в реальном режиме).
Windows использует отражение для обработки некоторых прерываний, исходящих из режима
V86: прерывание сначала попадает в 32-битовый защищенный режим, но затем отражается обратно
в режим V86. Расширитель DOS в Windows также использует отражение прерываний, например,
для обработки некоторых вызовов INT 2 th, исходящих от приложений защищенного режима. Когда
приложение Windows осуществляет вызов INT 2 th, расширитель DOS может воспользоваться DOS
для его обслуживания. Ключевые слова в предыдущих двух предложениях — некоторый и может1,
как мы видели в этой главе, VxD вовсе не обязаны отражать вызовы INT 2th в DOS. С другой сто-
роны, Windows, несомненно, будет по-прежнему отражать некоторые вызовы в DOS, пока Microsoft
(или какой-то независимый производитель ПО) не решит переписать всю MS DOS и BIOS как VxD.
Мы видели, каким образом INT п передается через шлюз IDT[n], но для чего нужны эти шлю-
зы? Шлюзы обеспечивают защиту при передаче управления между сегментами кода, работающими
на различных уровнях привилегий. Например, поскольку прерывание в режиме V86 проходит через
IDT и, следовательно, использует шлюз прерывания, программа пользовательского уровня (та же
TSR), которая ничего не знает о Windows, может вызывать привилегированный код VMM.
Другими словами, шлюзы прерываний — это именно то, что делает VMM операционной систе-
мой. Поскольку прерывания через шлюзы прерываний IDT попадают в VMM, DOS больше не явля-
ется операционной системой. Ею является VMM. DOS в реальном режиме получает вызов INT 2th
только в том случае, если VMM или VxD приложат некоторые сознательные усилия для посылки
вызова в DOS. Если копнуть еще глубже (поскольку VxD могут заставить VMM вызывать их при
получении прерываний), операционная система Windows является на самом деле комбинацией VMM и
VxD. И это продолжается со времени появления VMM в расширенном режиме Windows 3.0! Такие
новшества, как 32-битовый доступ к файлам в WfW 3.11 и Windows 95 — это всего лишь усовер-
шенствования. Основа новой технологии Windows 95 была заложена еще в расширенном режиме
Windows. Windows 95 — это не такая уж новая 32-битовая версия DOS защищенного режима. Она
давным-давно была в нашем распоряжении, но мы не обращали на нее внимания.
246
Неофициальная Windows 95
Глава 9
Кто главнее:
Windows или DOS?
В предыдущих главах я утверждал, что Windows — не только Windows 95, но также
Windows 3.x в расширенном режиме — является настоящей 32-битовой операционной системой
защищенного режима, использующей реальный режим DOS только как вспомогательный. В
главе 4 я даже пытался доказать, что во многих случаях скорее DOS работает поверх Windows, а не
Windows поверх DOS.
Это заявление довольно трудно принять на веру. Ведь если брать с самого начала, Windows за-
пускается из командной строки DOS или из файла AUTOEXEC.BAT, или, как лучше всего ска-
зать, — из реального режима DOS, который в Windows 95 называется WINBOOT.SYS. Реальный
режим все еще присутствует. Из него запускается Windows. Вот и все, не правда ли?
Не совсем. Как вы увидите на самом деле, Windows выполняет DOS в виртуальном 8086-м ре-
жиме (V86). Я подразумеваю не только то, что DOS-программы, выполняемые в окне DOS, ис-
пользуют режим V86. Я имею в виду, что с момента запуска Windows все загружаемое программное
обеспечение: DOS драйверы, резидентные программы и даже собственно DOS — выполняются в
режиме V86. Как вы понимаете, это, в свою очередь, означает, что Windows может управлять DOS.
“Одно на другом"?
Фанатичные сторонники таких больших операционных систем, как OS/2, Windows NT и
UNIX, считают, что комбинация Windows и MS DOS не доросла до звания полноценной операци-
онной системы. В принципе, они правы. Windows не создавалась “с нуля” и не является само-
достаточной системой. Действительно, в течение нескольких лет Microsoft представляла Windows
скорее как среду, а не как полноценную операционную систему. Как заявила однажды компания
WordPerfect в рекламе своего, ныне почившего продукта для OS/2, Windows — это только
“надстройка” {thing on thing — одно на другом).
Хотя защитники OS/2, Windows NT и UNIX часто высмеивают Windows из-за ее зависимости
от DOS, в то же время все они признают, что операционные системы должны иметь возможность
выполнять старые программы DOS. И все они признают, что эти операционные системы могут
управлять программами реального режима DOS в виртуальной машине DOS (VDM) или в окне
DOS. VDM защищает остальную систему от “преступлений” на нижнем уровне (как, например,
прямая запись в память или в порты ввода-вывода), которые могут совершить программы DOS.
Как же старый код реального режима может выполнять все это, когда им управляет другая
программа? Дело в том, что в микропроцессорах Intel 80386 и более поздних моделях режим V86
позволяет операционной системе защищенного режима имитировать одну или больше одномега-
байтовых машин реального режима 8086. Такие имитированные (виртуальные) машины могут быть
многозадачными, выгружаться на диск (в определенных обстоятельствах) и защищаться от вмеша-
тельства других приложений или операционной системы. Как показано на рис. 1.9, операционная
система, запускающая программы DOS в режиме V86, может перехватывать выполняемые про-
граммами DOS обращения к памяти, прерывания, обращения к портам и т.д. Затем операционные
Глава 9. Кто главнее: Windows или DOS? 247
системы могут имитировать, запрещать или игнорировать действия программ DOS либо позволять
выполнить эти действия, если те кажутся допустимыми.
Уверенность в работе таких VDM, основанных на V86, настолько велика, что фирма IBM зая-
вила, будто ее OS/2 2.x обеспечивает “DOS лучшую, чем сама DOS”.
Конечно, Windows 3.x также обеспечивает У86-среду для программ DOS, используя схему, по-
хожую на применяемую в OS/2 и в версиях Windows NT и UNIX для Intel-процессоров. Но есть
одно фундаментальное отличие между схемой VDM, используемой Windows, и схемами, которые
используются другими вышеуказанными операционными системами. OS/2, Windows NT и UNIX
получают вызовы INT 21h или INT 2Fh, поступающие от DOS-программ, и преобразуют их в обра-
щения к собственному API операционной системы (см. Undocumented DOS, 2d ed., гл. 4). Эти опе-
рационные системы не могут передать вызов INT 21h в DOS, поскольку DOS просто отсутствует.
Напротив, когда DOS-программа, запущенная из Windows, обращается к DOS, DOS все еще
выполняется и соответственно может обработать вызовы INT 21h или INT 2Fh. Кроме того,
некоторые вызовы Windows API из защищенного режима могут преобразоваться в вызовы INT 21h
реального режима. Итак, если запуск программ рванного режима в окне DOS под OS/2, Windows
NT и UNIX — это хорошо и надежно, a Windows сама запускается под DOS, то, следовательно, все
ее основание шатко, не правда ли?
Рис. 9.1. В реальном режиме все прерывания обрабатываются через IVT, запросы ввода-вывода
посылаются непосредственно в порты, и все операции чтения и записи выполняются непосредственно
в память. В режиме V86 под VMM все эти действия выполняются по-другому: все прерывания и
недопустимые инструкции (например, ARPL, используемая для контрольных точек V86) отлавли-
ваются в VMM. Все запросы ввода-вывода выполняются с использованием карты разрешения ввода-
вывода (I/O permission bitmap — ЮРВ). Доступ к памяти происходит через таблицу страниц
(в действительности, через кэшируемые элементы доступа к страницам)
Прежде чем делать какие-либо заключения, вернемся на минутку назад и зададим вопрос,
который может показаться наивным: действительно ли операционная среда, работающая поверх
DOS, например Windows, так отличается от DOS-окна. Другими словами, есть ли фундаментальное
различие между тем, как Windows, или OS/2, или NT, или UNIX обращаются с программами,
запущенными в окне DOS, и тем, как Windows управляет программным обеспечением (включая
саму DOS), запущенным до нее.
Как ни странно, ответ отрицательный — между окном DOS и операционной средой, сидящей
поверх DOS, нет фундаментальных отличий. (В главе 4 обсуждалось одно отличие, относящееся к
данным экземпляров.) Когда Windows выполняется поверх DOS, она имеет те же возможности
управления DOS, которые имеют OS/2, Windows NT и UNIX для управления программами DOS,
запущенными в VDM. Таким образом, “надстройка” на самом деле превращается в то же самое, что
и “DOS лучшая, чем сама DOS”!
248
Неофициальная Windows 95
Запуск DOS в защищенном режиме
Выполнение операционной системы поверх DOS не так сильно отличается от выполнения DOS
в VDM внутри операционной системы. Чтобы убедиться в этом, мы должны исследовать, как Win-
dows относится к программному обеспечению, загруженному перед ней. Другими словами, нам
нужно выяснить, как Windows относится к резидентным программам и драйверам DOS, собственно
к DOS и к ROM BIOS. Программа FAKEWIN, описанная в главах 3 и 4, показывает некоторое вза-
имодействие между Windows и DOS. Но есть один аспект, не отраженный программой FAKEWIN:
как именно Windows обращается вниз к DOS.
Хотя Windows-приложения запускаются в защищенном режиме, DOS является операционной си-
стемой реального режима. DOS ничего не знает о селекторах защищенного режима и не имеет до-
ступа к дополнительной памяти. Следовательно, для вызова DOS Windows должна переключаться в
реальный режим, не так ли? Не в этом-то ли кроется основной источник нестабильности Windows?
Нет. Вспомним хорошо знакомое окно DOS. Когда OS/2, Windows NT, UNIX или Windows
запускают программы реального режима в окне DOS, эти программы выполняются не в реальном
режиме; они запускаются в режиме V86. Точно так же, когда Windows в расширенном режиме обра-
щается к программному обеспечению, загруженному до нее, — например, DOS — это делается не в
реальном режиме, а в режиме V86.
Позвольте мне повторить: Windows вызывает DOS в режиме V86. Пожалуйста, не восприни-
майте это замечание как явное проявление педантизма. Важно заметить разницу между двумя
утверждениями: “Windows, обращаясь к DOS, переключается в реальный режим” и “Windows
вызывает DOS в режиме V86”, поскольку реальный режим и режим V86, как говорится, — две
большие разницы.
Когда программное обеспечение реального режима — такое, как DOS-программы или (что здесь
уместнее) собственно MS DOS — запускается в режиме V86, оно “думает”, что запущено в реаль-
ном режиме. Однако, как описано в документации Intel, V86 “работает подобно защищенному режи-
му”. Заметьте, что Intel не говорит, что V86 работает подобно реальному режиму. Intel говорит, что
V86 работает подобно защищенному режиму.
На самом деле V86 — удивительная штука: разновидность защищенного режима, запускающего
программы реального режима. V86 похож на реальный режим только в одном смысле: оба режима
используют один и тот же механизм доступа к адресам памяти. Например, когда программа DOS
ссылается на ES:[BX], V86 и реальный режим для вычисления нужного адреса используют одну и
ту же формулу — они берут значение ES, умножают его на 16 и прибавляют значение ВХ. На этом,
однако, сходство между V86 и реальным режимами заканчивается.
Зато различий хоть отбавляй. Вот несколько примеров.
• В режиме V86 результирующий адрес (ES*16)+BX не обязательно является физическим ад-
ресом, как это происходит в реальном режиме. Вместо этого, он является линейным адресом,
который используется процессором как индекс в таблице страниц. Таблица страниц, соответ-
ственно, может содержать физический адрес, равный линейному адресу (ES*16)+BX (linear=
=physical), или физический адрес, отличающийся от линейного адреса (linear! =physical), или
даже указание на то, что соответствующие данные отсутствуют в памяти и выгружены на
диск. Следовательно, часть окна DOS может быть расположена где угодно в памяти, или
даже совсем не в памяти. Если окно DOS осуществляет доступ к памяти, которая помечена в
таблице страниц как “отсутствующая”, процессор автоматически вызывает обработчик
защищенного режима для отказа страницы, который подгружает данные с диска.
• Если программа DOS, запущенная в режиме V86, выдает инструкцию INT (например, INT
21h для вызова DOS), она не пропускается через таблицу векторов прерываний в нижней
памяти, как это делается в реальном режиме. Вместо этого, INT обрабатывается с помощью
таблицы дескрипторов прерываний (IDT), как это происходит с инструкциями INT, содер-
жащимися в программе защищенного режима. В Windows все прерывания, исходящие из
режима V86, вначале обрабатываются в 32-битовом защищенном режиме.
Глава 9. Кто главнее: Windows или DOS?
249
• Если программа реального режима, запущенная в режиме V86, выдает инструкции IN или
OUT (например, если ROM BIOS требует доступа к портам ввода-вывода), процессор сна-
чала ищет номер порта в карте разрешения ввода-вывода (ЮРВ). Эта карта может заставить
процессор запретить операцию ввода-вывода, приводя к “отлавливанию” этой операции в
операционной системё защищенного режима.
• Операционная система защищенного режима может указать, чтобы некоторые инструкции
(например, CLI, STI, PUSHF, POPF и IRET) отлавливались операциошюй системой. Эти
инструкции управляют значением флага прерываний (IF). CLI и STI сбрасывают и устанав-
ливают IF. Инструкции PUSHF, POPF и IRET также влияют на IF. А сделано это потому,
что если У86-процесс сбросит настоящий флаг IF, то он тем самым запретит прерывания
всем процессам, выполняющимся в системе. Вместо этого операционная система может
сделать так, чтобы программы реального режима работали с виртуальным флагом IF.
Например, CLI может очистить виртуальный IF, отключив возможность прерываний только
для текущего процесса. Операционная система управляет этим виртуальным флагом IF,
сообщая процессору, что инструкции, влияющие на IF, недопустимы. Таким образом, имею-
щие вполне невинный вид внутри MS DOS инструкции CLI или IRET могут привести к
переходу куда-то вглубь Windows.
• Некоторые инструкции в режиме V86 всегда недопустимы и автоматически порождают ис-
ключительные ситуации, которые можно отловить и обработать в 32-битовом защищенном
режиме. Хороший пример — ARPL. Как уже обсуждалось в главе 8, Windows довольно ин-
тенсивно использует инструкцию ARPL для управления выполнением программ, запущенных
в режиме V86.
Во всех этих случаях код реального режима, запущенный в режиме V86, может порождать
ловушку (trap), отказ (fault) или исключительную ситуацию (exception) (разница между этими
понятиями пока не имеет значения).
Менеджер виртуальных машин (VMM)
Так куда же направляются все эти ловушки, отказы и исключения? Они перехватываются и
обрабатываются привилегированной программой 32-битового защищенного режима, которую иногда
называют монитором V86, или монитор виртуальной машины (VMM). Все аппаратные прерывания
также направляются в VMM. Как указано в 80386 System Software Writer's Guide (Intel):
Довольно удобно собрать код, отвечающий конкретно за исключительные ситуации V86, в процедуру
(или набор процедур), называемую монитор виртуальной машины (VMM). VMM имитирует инст-
рукции 8086, которые 80386 не будет исполнять в режиме V86.
Как отмечалось в предыдущей главе, основной компонент WIN386.EXE в расширенном режиме
Windows и VMM32.VXD в Windows 95 называется VMM. (В 1988 г. в Windows/386 2.x он назы-
вался VDMM или менеджер виртуальных машин DOS). Как указано в Virtual Device Adaptation
Guide из Windows Device Driver Kit (DDK) фирмы Microsoft:
VMM — это 32-битовая операционная система защищенного режима. Ее главное назначение — соз-
давать, запускать, контролировать и останавливать виртуальные машины. VMM обеспечивает функции
управления памятью, задачами, прерываниями и ошибками защиты. VMM работает с виртуальными
устройствами — динамически компонуемыми библиотеками 32-битового защищенного режима, позволяя
им перехватывать прерывания и отказы для управления доступом приложения к физическим устрой-
ствам и установленному программному обеспечению.
Термин VMM впервые появился в работах центра научных исследований IBM в Кембридже
(Массачусетс) в конце 60-х — начале 70-х годов; это исследование было описано в нескольких
основных статьях в IBM Systems Journal и достигло своей кульминации в операционной системе
IBM VM/370, анонсированной в 1972 г. Прямо или косвенно, операционная система Windows во
многом основана на этой работе.
250 Неофициальная Windows 95
VMM — это операционная система, запускающая виртуальные машины (VM). В случае
VM/370, VMM (известный как программа управления, или СР) запускается поверх IBM
System/370, создает и контролирует виртуальные машины типа 370. В каждой такой виртуальной
машине VM/370 может запустить другую операционную систему, включая даже еще одну копию
VM/370. Такая операционная система думает, что она запущена на голом оборудовании IBM 370,
хотя на самом деле она работает в VM. Фактически нет возможности (кроме хронометража) опре-
делить, работает она собственно на аппаратном обеспечении или в VM.
При такой схеме пользователи могут одновременно запускать приложения для различных
операционных систем. Виртуальные машины относительно изолированы: приложение, запущенное в
одной VM, не может случайно разрушить приложение, запущенное в другой VM. Но все же эта схе-
ма предоставляет несколько интересных возможностей взаимодействия между VM.
Схема VM предоставляет разработчику операционной системы важное преимущество — логи-
ческое разделение операционной системы на две части. VMM обрабатывает многозадачность и
выполняет функции, необходимые операционным системам, а эти операционные системы, в свою
очередь, обеспечивают обслуживание приложений. Это очень похоже на концепцию микроядра
(microkernel), используемую такими операционными системами, как Mach и Windows NT, в кото-
рых многие задачи, обычно ассоциируемые с операционной системой, выносятся на подсистемы
пользовательского уровня.
В операционных системах, базирующихся на VMM, приложение обращается к операционной
системе обычным образом (например, через инструкции SVC или INT). Само обращение к операци-
онной системе может отлавливаться VMM или продолжать выполняться операционной системой
(которая фактически выполняется как пользовательское приложение под управлением VMM). Опера-
ционная система в ходе обработки запросов будет генерировать инструкции, отлавливаемые VMM.
Во имя живучести (robustness — робастность) важно, чтобы каждая чувствительная инструк-
ция (т.е. каждая инструкция, которая воздействует или проверяет состояние VM) была также
привилегированной инструкцией (т.е. вызывать в VMM ловушку). В то же время для увеличения
производительности нужно, чтобы как можно меньшее число инструкций вызывало ловушки. Это
отличает VMM от эмуляторов CPU (таких, как программное обеспечение Insignia Soft PC,
применяемое в Windows NT для запуска программ 80x86 на не-интеловском оборудовании). В об-
щем, для получения приемлемой производительности виртуальная машина должна быть как можно
ближе к основной. VMM создает иллюзию нескольких копий основной машины.
Поскольку некоторые инструкции отлавливаются VMM, скорость работы на VM будет меньше,
чем на основной машине. Однако иногда встречается “аномалия производительности виртуальной
машины”, при которой программы на VM выполняются быстрее, чем на голом аппаратном обеспе-
чении, поскольку виртуализация устройств позволяет кэширование, перегруппировку запросов и
т.д., что может увеличить эффективность по сравнению с реальными устройствами. Превосходным
примером является 32-битовый доступ к диску в Windows.
' Говоря о виртуализации устройств, следует знать, что эта идея (и необходимость в драйверах
виртуальных устройств) также исходит из исследований IBM операционных систем виртуальной
машины в конце 60-х — начале 70-х годов. Для создания иллюзии, что каждая VM представляет
полную отдельную машину, каждая VM должна думать, что она имеет собственный диск, дисплей,
принтер и т.д. Вместо того чтобы встраивать такую возможность непосредственно в VMM, для
управления каждым устройством создается драйвер виртуального устройства. Такой драйвер может
распределять запросы от нескольких VM, запрещать их, направлять их непосредственно в устройст-
во или полностью их имитировать.
В Windows VMM действительно является операционной системой. В главах 6 и 7 вы видели,
что ядро Windows, KRNL386.EXE, по существу является приложением, которое можно заменить
любым другим приложением, имеющим такое же имя (COPY \COMMAND.COM KRNL386.EXE).
Конечно, ядро Windows является, по своим правам, почти операционной системой. Но оно запус-
кается под VMM, как операционные системы типа CMS или MVS запускаются под VM/370.
Из глав 6 и 7 должно быть понятно, что существует потенциальная возможность запускать под
VMM другую операционную среду. Иначе говоря, потенциально вы можете запустить несколько ко-
пий KRNL386.EXE, каждую в своей собственной VM. Windows не позволит вам сделать это, однако
Глава 9. Кто главнее: Windows или DOS?
251
не существует объективной причины для такого запрета. Фактически, набрав WIN в окне DOS в
Windows 3.0, вы могли бы получить другую копию Windows (выполняемую в реальном режиме) в
этой VM. Без сомнения, многих пользователей это смутило бы, поэтому WIN.COM при запуске про-
веряет присутствие Windows. В некоторых случаях VMM мог бы поддерживать несколько операци-
онных сред, подобно тому, как микроядро Windows NT может поддерживать несколько подсистем.
Я уже отмечал, что VM/370 даже обладала способностью запускать в VM другую копию
VM/370. Многие руководства по операционным системам указывают, что такие “рекурсивные” VM
могут использоваться для отладки, тестирования новых версий VM/370, сбора экспериментальных
данных о поведении программы и т.д. VMM, однако, не является рекурсивным. Во-первых, хотя
VMM содержит DPMI-сервер, он не является DPMI-клиентом. Это единственная причина, по
которой VMM/VxD-часть Windows не может запускаться под OS/2 (и, как отмечалось в главе 5,
даже высокоуровневая часть KRNL386 требует значительной модификации для возможности запуска
под OS/2).
Эта концепция VMM может натолкнуть на кое-какие мысли о том, как Windows взаимодейст-
вует с программным обеспечением, загруженным до нее, например с MS DOS. VMM — это опера-
ционная система, предназначенная для запуска других операционных систем. MS DOS — это опе-
рационная система и (не считая ее огромной пользовательской базы и гигантского числа написанных
для нее приложений) не очень сложная. VMM может запустить MS DOS. VMM запускает MS DOS!
Поначалу кажется, что Windows, усевшаяся на верхушке операционной системы реального ре-
жима, подобной MS DOS, имеет весьма шаткую основу. Но, как уже отмечалось бессчетное число
раз, когда Windows (в частности, VMM) обращается к DOS, она делает это в режиме V86. И, как
тоже отмечалось, VMM может заставить широкий спектр действий реального режима порождать
исключительные ситуации V86, которые VMM и VxD могут перехватывать и обрабатывать любым
способом, который сочтут подходящим. В сущности, VMM запускает все загруженные ранее про-
граммное обеспечение в защищенном режиме. Для увеличения производительности и по другим
причинам VMM не всегда использует все преимущества этой защиты.
Главное — это понять, что, запускаясь поверх DOS, VMM сам запускает DOS, и никак не ина-
че. Хотя Windows загружается после DOS и (за одним исключением, описанным в главах 3 и 4)
выглядит для DOS, как и любое другое приложение, расширенный режим является боссом VMM.
DOS реального режима находится под контролем Windows.
Выражаясь другими словами, Windows запускает DOS в DOS-окне, как если бы DOS была
некоторым приложением, запускаемым пользователем под Windows. Когда резидентная программа
DOS выполняет вызов прерывания INT 21h, вызов поступает к VMM, который решает, что с ним
делать. Когда драйвер устройства DOS выполняет инструкцию IN или OUT, VMM или VxD могут
перехватить запрос ввода-вывода и выполнить соответствующие действия.
Заявление, что Windows может запускать DOS в защищенном режиме, звучит так странно, что
стоит написать тестовую программу, дабы убедиться в этом, что мы и сделаем в следующей главе.
Это резидентная программа реального режима DOS, названная V86TEST, загружается перед Win-
dows и перехватывает INT 21h и INT 2Fh. Каждый раз при вызове программы она проверяет,
находится ли процессор в режиме V86.
Режим V86 и бит РЕ
Но как же программа определяет, находится ли процессор в режиме V86? Функции
Set_V86_Exec_Mode и Set_PM_Exec_Mode, обеспечиваемые VMM, устанавливают и сбрасывают
режим V86, управляя 17-м битом (флаг VM) в регистре EFLAGS. VMM определяет режим V86,
тестируя 17-й бит. EFLAGS — это 32-битовый регистр флагов, младшие 16 бит которого представ-
ляют известный многим PC-программистам регистр FLAGS.
Из-за того, что флаг VM является 17-м битом, он недоступен 16-битовому коду. Даже используя
32-битовый код в 16-битовой программе, вы все равно не можете добраться до 17-го бига, так как инст-
рукция PUSHF сбрасывает флаг VM перед помещением его. в стек. Это показано в псевдокоде для
PUSHF в превосходной книге Ракэша Агарваля (Rakesh Agarwal) 80x86 Architecture and Programming:
252
Неофициальная Windows 95
/* == push [E]FLAGS == */
if (os == 32)
{ eflg = EFLAGS;
/* сбросить флаги VM и RF перед помещением в стек •/
eflg<17> = 0;
eflg<16> = 0;
push4(eflg);
}
else /* os = 16: поместить в стек младшее слово EFLAGS */
push2(ELAGS<15:0>);
Вскоре мы поймем, почему Intel прячет от приложений флаг VM. Сейчас важно понять, что вы
не можете прочитать флаг VM.
Далее, Intel 80286 и более поздние микропроцессоры имеют слово статуса машины (Machine
Status Word — MSW), содержащее бит РЕ (Protect Enable). Когда машина находится в защи-
щенном режиме или в режиме V86, бит РЕ установлен, так что вы можете опознать режим V86,
считывая бит РЕ. Это лишнее подтверждение того, что режим V86 на самом деле является формой
защищенного режима.
В 80386 и более поздних микропроцессорах MSW расширяется, превращаясь в DWORD-
регистр CR0. РЕ является младшим (нулевым) битом как MSW, так и CR0. Поскольку режим V86
отсутствует в 80286, можно предположить, что CR0 обеспечит лучший путь для проверки РЕ.
mov еах, сгО
and еах, 1
// бит РЕ в регистре ЕАХ
Однако, если вы попытаетесь выполнить этот код в режиме V86 под Windows, он не даст жела-
емого результата. Инструкция MOV оставляет регистр ЕАХ неизменным!
Тот факт, что MOV CR0 “не работает” под Windows, на самом деле отлично демонстрирует то,
насколько режим V86 отличается от реального режима, и то, как Windows использует режим V86
для управления программами реального режима.
Инструкция MOV CR0 в режиме V86 является привилегированной. Согласно документации
Intel, эта инструкция (вместе с другими инструкциями перемещения в/из специальных регистров)
порождает ошибку общей защиты (General Protection Fault), если выполняется в режиме V86.
Ошибка общей защиты — это INT ODh. VMM устанавливает обработчик этой ошибки, помещая ука-
затель функции в IDT.
Почему MOV CR0 порождает ошибку общей защиты? Потому же, почему Intel умышленно
прячет флаг VM. Вспомним указание, сделанное раньше в дискуссии о IBM VM/370: для
непривилегированных программ не должно быть способа (кроме хронометража) определить,
выполняются ли они в VM или на голом аппаратном обеспечении. По этой причине MOV CR0 и
порождает ошибки. VMM может решить, должна ли программа быть осведомлена о том, что
выполняется в VM или нет.
Вот что происходит: если программа реального режима, запущенная в режиме V86, пытается
исполнить инструкцию MOV ЕАХ, CR0, эта инструкция порождает исключительную ситуацию,
перехватываемую VMM. Машина внезапно переключается с пользовательского уровня (круг 3)
режима V86 на привилегированный уровень (круг 0) 32-битового защищенного режима. VMM
обрабатывает код-нарушитель любым подходящим ему образом. Судя по результатам, VMM
обрабатывает MOV ЕАХ, CR0, обходя эту инструкцию без каких-либо действий. Собственно, VMM
просто игнорирует ее!
Обработчик ошибки общей защиты (INT ODh) в VMM прежде всего исследует породившую
ошибку инструкцию, расположенную по CS:EIP программы режима V86. Он делает это,
обращаясь к структуре регистров клиента (Client Register Structure, CRS) по указателю в
регистре ЕВР:
Глава 9. Кто главнее: Windows или DOS?
253
movzx esi, [ebp.Client_CS]
shl esi, 4
add esi, [ebp.Client_EIP]
mov ex, [esi]
movzx edi, cl
jmp dword ptr ds:[0PC0DE_TABLE][edi*4]
esi = (CS « 4) + EIP
ex = *esi
cl = код операции
к обработчику соответствующей операции
Инструкция MOV ЕАХ, CR0 кодируется тремя байтами OF 20 СО. VMM имеет единый обра-
ботчик для всех инструкций от 0F 20 до 0F 23, что включает перемещения в/из управляющих и
отладочных регистров (CR2, CR3, DR0 и т.д.). Вот обработчик:
add [ebp.Client_EIP], 3
ret
Вот оно! VMM только переставил указатель инструкций клиента (EIP) через инструкцию-
нарушитель и ничего больше. Это объясняет, почему MOV ЕАХ, CR0 в программе DOS, запу-
щенной под Windows, оставляет ЕАХ неизменным.
Еще важнее то, что исследование настоящего кода VMM демонстрирует нам, как VMM (и
любой VxD) может контролировать программное обеспечение реального режима, включая саму
DOS. Возможность VMM изменять значение EIP клиента в CRS показывает, насколько широкой
свободой действий обладает VMM в отношении поведения всех программ, работающих в режиме
V86. Это касается как программ, запущенных до Windows, так и работающих под ней. (Вскоре вы
увидите, что отличие до/после не так уж существенно.)
Понятно, что MOV CR0 не поможет прочесть бит РЕ. Однако VMM и VxD представляют код
привилегированного уровня и поэтому могут прочесть регистр CR0 и обработать ошибочную инст-
рукцию MOV ЕАХ, CR0, переместив CR0 в Client_EAX:
mov еах, сгО ; сделать то, что необходимо
mov [ebp.Client_EAX], еах ; поместить результат в ЕАХ клиента
add [ebp.Client_EIP], 3 ; перейти на следующую инструкцию
ret
Хотя VMM не ведет себя таким образом, мы можем легко написать действующий VxD, который
обеспечит прозрачную поддержку инструкций перемещения в/из специальных регистров. Однако
нам нужно только прочесть бит РЕ, который, как отмечалось раньше, также доступен как часть
MSW. SMSW (Store MSW), инструкция Intel для чтения MSW, не является привилегированной.
Выглядит странным то, что SMSW не является привилегированной инструкцией, тогда как
MOV CR0 таковой является. Но такие важные инструкции, как Store Global Descriptor Table
(SGDT), Store Interrupt Descriptor Table (SIDT), Store Local Descriptor Table (SLDT) и Store Task
Register (STR) также ue являются привилегированными. Вспомните, что VM должна выглядеть
идентично основной машине, и становится ясным, что возможность SMSW читать бит РЕ — это
просто упущение разработчиков.
Это упущение (которое Intel, вероятно, не может устранить) позволяет легко определить,
находитесь ли вы в режиме V86. Если ваш компилятор поддерживает встроенный ассемблер и
инструкции 286, вроде SMSW (используя для Borland переключатель -2 или -G2 для Microsoft), то
следующая простая функция позволяет считывать значение бита РЕ:
int pe(void)
{
_asm smsw ax
_asm and ax, 1
// результат в AX
}
Если эту функцию вставить в программу защищенного режима (например, приложение
Windows), она, естественно, возвращает 1. Как и следовало ожидать, в программе защищенного
режима РЕ всегда включен.
254
Неофициальная Windows 95
И наоборот, если эту функцию вставить в программу реального режима DOS, то разумно ожи-
дать, что она всегда будет возвращать 0, показывая, что бит РЕ выключен. В конце концов, в реаль-
ном-то режиме РЕ всегда выключен, не правда ли?
Нет! В этом-то и суть режима V86. Даже если вы запускаете программу реального режима, но
находитесь в среде V86, бит РЕ включен. На самом деле вы находитесь в защищенном режиме, он
только выглядит, как реальный. Например, если вы скомпилируете программу РЕ.С из листин-
га 9.1 и запустите ее под менеджером памяти (например, EMM386, QEMM или 386МАХ) или в
окне DOS под Windows, OS/2 или Windows NT, РЕ выдаст сообщение “Бит РЕ (protect enable)
УСТАНОВЛЕН”.
Листинг 9.1. РЕ. С
/*
Ьсс -2 ре.с
cl -G2 ре.с
*/
#include <stdio.h>
int get_pe(void)
{
_asm smsw ax
_asm and ax. 1
// возвращаемое значение в ax
}
main()
{
int pe = get_pe();
printf(“BMT PE (protect enable) %s\n”, ре ? “УСТАНОВЛЕН”: “HE установлен”);
return pe;
}
Все правильно. При запуске одного из этих менеджеров памяти то, что выглядит как обычное
приглашение DOS реального режима, на самом деле работает в режиме V86 под управлением
VMM, который обеспечивается менеджером памяти. Тот факт, что бит РЕ включен, показывает, что
вы находитесь в защищенном режиме: вы имеете программу реального режима, Но каждое
выполнение инструкции INT обрабатывается через IDT, при каждом выполнении инструкций IN или
OUT процессор консультируется с ЮРВ и т.д. Добро пожаловать в режим V86, в котором ничто не
является тем, чем выглядит, и на котором Microsoft строит расширенный режим Windows и
Windows 95!
Далее, а что же случится, если DOS сама будет содержать этот код для проверки состояния
бита РЕ? Тот факт, что бит РЕ оказывается включенным в программах, запускаемых в окне DOS
под Windows, проверить достаточно легко, чего не скажешь о программах, загруженных перед
Windows. Может ли Windows действительно управлять программами, загруженными перед ней? В
следующей главе вы увидите, что может.
Глава 9. Кто главнее: Windows или DOS?
255
Глава 10
Как Windows
запускает DOS
В предыдущей главе я утверждал, что Windows использует режим V86 не только для запуска
программ в окне DOS, но и для запуска программного обеспечения, — включая саму DOS —
которое пользователь загружает перед запуском Windows. Выводы из этого заявления весьма
обширны. В предыдущей главе вы видели, что режим V86 фактически является разновидностью за-
щищенного режима. Следовательно, если Windows действительно обращается к DOS в режиме V86,
а не в реальном режиме, Windows потенциально может управлять DOS тем же способом, каким она уп-
равляет программами защищенного режима Windows или программами, запущенными в окне DOS.
Крошечная программа РЕ из предыдущей главы (см. листинг 9.1) показала, как можно исполь-
зовать процессорный бит РЕ для проверки режима V86. Теперь, чтобы тест режима V86 вызывался
при обращении Windows к DOS, вы должны только поместить его вовнутрь некоторого кода, загру-
жаемого перед Windows.
Эта глава представляет резидентную программу V86TEST, которая и реализует этот тест.
V86TEST может легко доказать, что Windows обращается к DOS в режиме V86, а не в реальном ре-
жиме, и, следовательно, Windows может управлять DOS.
V86TEST
V86TEST не является резидентной программой. Это программа-оболочка (wrapper), которая пере-
хватывает INT 21h и INT 2Fh и затем вызывает ту программу, которая указана как параметр в ее ко-
мандной строке. В общем, вам следует запустить V86TEST WIN, и V86TEST загрузит Windows. Ког-
да Windows обращается к DOS, она также вызывает V86TEST. Все обработчики прерываний V86
TEST вызывают предыдущие обработчики, но прежде они вызывают функцию check_state для получе-
ния статистических данных о том, сколько раз они вызывались, сколько раз устанавливался бит РЕ и
т.д. При выходе из Windows V86TEST восстанавливает обработчики прерываний и выводит статистику.
V86TEST также обеспечивает маленький INT 2Fh API, так что вы можете получать статистику с
помощью V86TEST -QUERY, даже когда Windows работает, или с помощью версии V86TEST для
Windows. Ключ -VERBOSE обеспечивает выдачу подробной информации о вызовах функций INT
21h и INT 2Fh. В следующей главе эта информация о вызовах DOS рассматривается более детально
и используется версия V86TEST для Windows, которая обращается к резидентной DOS-версии.
V86TEST поддерживает различные счетчики для пяти разных состояний Windows:
Состояние Windows Описание
Перед запуском Windows Не было ли еще INT 2Fh AX=1605h
Во время инициализации Windows Принятые INT 2Fh AX=1605h (Win Init Notify)
Пока Windows работает Принятые INT 2Fh AX=1608h (Win Init Complete)
Во время выхода из Windows Принятые INT 2Fh AX=1609h (Win Exit Begin)
После выхода из Windows Принятые INT 2Fh AX=1606h (Win Exit Notify)
256
Неофициальная Windows 95
На рис. 10.1 представлена схема работы V86TEST.
main:
если (-QUERY или -CLEAR)
вызвать уже установленную копию V86TEST и выйти
если (уже запущена под Windows или уже в режиме V86)
отказ
перехватить INT 21h -> функция int21
перехватить INT 2Fh -> функция int2f
запустить команду (обычно Windows)
восстановить INT 21h и INT 2Fh
вывести результаты
int21:
вызвать check_state
вызвать предыдущий обработчик INT 21h
int2f:
следить за оповещением Windows по запуску/завершению
следить за функцией FFh для возврата статистики
вызвать check_state
вызвать предыдущий обработчик INT 21h
check_state:
увеличить кол-во вызовов текущего состояния Windows
если (бит РЕ установлен)
увеличить кол-во \/86-вызовов для текущего состояния Windows
если (Windows запущена)
проверить, в какой VM работаем
посмотреть текущий IOPL
увеличить статистики INT 21h и INT 2Fh
Рис. 10.1. Работа V86TEST
V86TEST знает состояние Windows, так как ее обработчик INT 2Fh перехватывает оповещение
по функции 16h из расширенного режима Windows. Для более детальной информации об этих опо-
вещениях см. программу FAKEWIN, описанную в главах 3 и 4. (Между прочим, программы
V86TEST и FAKEWIN — отличные партнеры, и я использовал “V86TEST FAKEWIN” для тестиро-
вания обеих программ.)
Состояние “Пока Windows работает” — это то, на чем мы сосредоточимся для определения, как
Windows обращается к DOS. Это период после того, как Windows выдает оповещение Win Init
Complete, но до выдачи оповещения Win Exit Begin: в это время Windows действительно работает.
Когда V86TEST принимает сигналы Win Init Complete и Win Exit Begin, она вызывает С-функцию
определения времени. Это помогает V86TEST определить, как долго работала Windows.
Хотя работа V86TEST довольно проста (она устанавливает INT 2 ih и INT 2Fh на обработчики
прерываний, собирающие статистику, выполняет команду, восстанавливает INT 21h и INT 2Fh и
показывает статистику), код получается достаточно сложным. Листинг 10.1 демонстрирует исход-
ный код V86TEST.
ЛИСТИНГ 10.1. V86TEST.C
/*
V86TEST. С — перехватывает INT 21 h и INT 2Fh, подсчитывает вызовы в режиме V86
Шульман, 1994
Некоторые из наворотов достаточно искусственны и сделаны для того, чтобы можно было
тестировать V86TEST не только с Windows, но и с такими программами, как FAKEWIN.
9 Неофициальная Windows 95
Глава 10. Как Windows запускает DOS
257
Можно использовать с WV86TEST
bcc -2 -Р- v86test.c
*/
«include <stdlib.h>
«include <stddef.h>
«include <stdio.h>
«include <string.h>
«include <ctype.h>
«include <process.h>
«include <time.h>
«include <dos.h>
typedef unsigned short WORD;
typedef unsigned long DWORD;
«define VM_MAX 8
«define VM.OTHER VM.MAX
«define GET_STATS OxFFFF
«define SIGNATURE “V86TEST”
«define VXD_MAX 0x100
«define VXD_OTHER VXD_MAX
«if 0
struct {
WORD vxd_id;
DWORD num_calls;
} VXDCALLS;
VXDCALLS vxdcalls[100] = {0};
int num_vxds = 0;
«endif
«pragma pack(1)
typedef struct {
«ifdef __TURBOC__
WORD bp,di,si.ds.es,dx,ex,bx,ax;
«else
WORD es,ds,di,si,bp,sp,bx,dx,ex,ax; /* так же как в PUSHA */
«endif
WORD ip.es,flags;
} REG_PARAMS;
void interrupt far int21(REG_PARAMS r);
void interrupt far int2f(REG_PARAMS r);
void (interrupt far *old)();
void (interrupt far *old_2F)();
«define WIN_INIT_NOTIFY 0x1605
«define WIN_INIT_COMPLETE 0x1608
«define WIN_EXIT_BEGIN 0x1609
«define WIN_EXIT_NOTIFY 0x1606
typedef enum {
NO.WIN,
WIN_INIT_BEGIN, // получено WIN_INIT_NOTIFY
WIN_INIT_DONE, // получено WIN_INIT_COMPLETE
258
Неофициальная Windows 95
WIN.FINI.BEGIN, // получено WIN_EXIT_BEGIN
WIN_FINI_DONE, // получено WIN_EXIT_NOTIFY
NUM_STATES
} STATE;
static char *state_str[NUM_STATES] = {
“Перед запуском Windows’’,
“В процессе инициализации Windows”, Ц получено WIN_INIT_NOTIFY
“Во время работы Windows", // получено WIN_INIT_COMPLETE
“При выходе из Windows", // получено WIN_EXIT_BEGIN
“После выхода из Windows”, // получено WIN_EXIT_NOTIFY
typedef struct {
char signature[8];
DWORD calls[NUM_STATES], v86_calls[NUM_STATES];
DWORD iopl_count[4];
DWORD vm[VM_MAX+1];
DWORD int21[0x100];
DWORD int2f[0x100], int2f16[0x100], int2f1607[VXD_0THER+1];
time_t start, end;
} STATS;
static STATE state = NO_WIN;
static STATS ‘stats;
static STATS far ‘fpstats;
static int not_verbose = 1;
static int not_filter = 1;
static int v86_okay = 0;
char ‘usage = "использование: v86test [-okv86] [-filter | -verbose]"
“[-query | -clear | win] <args...> ”;
«define PUT(s) { fputs(s, stderr); fputs(“\n”, stderr); }
«define FAIL(s) { PUT(s); exit(1); }
int win3e(void) // в расширенном режиме Windows?
{
int maj = 0;
_asm mov ax, 1600h
_asm int 2fh
_asm mov byte ptr maj, al
return (maj && (maj != 0x80));
}
int pe(void) // установлен ли бит PE?
{
_asm smsw ax
_asm and ax, 1
// результат в AX
}
int iopl(void) // получить уровень привилегий ввода-вывода (IOPL) из флагов
{
_asm pushf
_asm pop ax
_asm shr ax, 12
_asm and ax, 3
// результат в AX
Глава .10. Как Windows запускает DOS
259
}
int vmid(void) // Получить ID виртуальной машины Windows. Если запущен
{ // расширенный-режим, то V86TEST никогда не отловит этот
// вызов 2f/1683! И если бы мы попытались вызвать с помощью
// PUSHF/CALLF, а не через INT, то получили бы неверный результат!
_asm mov ах, 16836
_asm int 2fh
_asm mov ax, bx
// результат в AX
}
/**********************************************************************/
void display_results(STATS far *fp2)
{
STATS *fp;
DWDRD elapsed;
time_t start, end;
int i;
if(!(fp = malloc(sizeof(STATS))))
FAIL(“Недостаточно памяти’’);
// скопировать чтобы STATS не изменился в процессе чтения
_fmemcpy(fp, fp2, sizeof(STATS));
printf(“\n");
for(i=NO_WIN; i<NUM_STATES; i++)
printf(“%s:\t%lu вызовов INT 21/2F, %lu в режиме V86\n",
state_str[i], fp->calls[i], fp->v86_calls[i]);
printf(“\nBo время работы Windows:\n”);
if(fp->end) end = fp->end; else time(&end);
if(fp->start) start = fp->start; else time(&start);
if((elapsed = end - start) != 0)
{
'printf(“Windows активна %lu секунд\п", elapsed);
printf(“%lu вызовов INT 21/2F/ceK.\n",
fp->calls[WIN_INIT_DONE] / elapsed);
}
for(i=0; i<4; i++)
if(fp->iopl_count[i])
printf(“IOPL=%d — %lu вызовов\п", i, fp->iopl_count[i]);
for(i=0; KVM.MAX; i++)
if(fp->vm[i])
printf(“VM #%d -- %lu вызовов\п”, i, fp->vm[i]);
if(fp->vm[VM_OTHER])
printf(“VM > #%d — %lu вызовов\п", VM_MAX, fp->vm[VM_OTHER]);
// это нужно сделать перед проверкой флага not_verbose:
// всегда показывать вызовы 2f/16
printf(“\пВызовы INT 2Fh AH=16h, отловленные V86TEST:\n");
for(i=0; i<0x100; i++)
if(fp->int2f16[i])
printf(“%02X: %lu\t”, i, fp->int2f16[i]);
printf(“\n”);
if(not_verbose)
return;
260
Неофициальная Windows 95
// выполняется по V86TEST -VERBOSE
printf(“\пВызовы INT 21h:\n”);
for(i=0; 1<0х100; i++)
if(fp->int21[i])
printf(“%02X: %lu\t”, i, fp->int21[i]);
printf(“\п\пВызовы INT 2Fh:\n”);
for(i=0; i<0x100; i++)
if(fp->int2f[i])
printf(“%02X: %lu\t", i, fp->int2f[i]);
printf(“\п\пВызовы INT 2Fh AX=1607h:\n");
for(i=0; i<VXD_MAX; i++)
if(fp->int2f1607[i])
printf(“%02X: %lu\t", i, fp->int2f1607[i]);
if(fp->int2f1607[VXD_OTHER])
printf(“VxD>#%04X: %lu\n”, VXD_MAX, fp->int2f1607[VXD_0THER]);
printf(“\n\n");
free(fp);
STATS far *get_stats(void) // вызвать резидентную копию V86TEST
{
STATS far *fp;
_asm mov ax, GET_STATS
_asm int 2fh
_asm mov word ptr fp+2, es
_asm mov word ptr fp, bx
return (_fstrcmp(fp->signature, SIGNATURE) == 0) ? fp : 0;
main(int argc, char *argv[])
{
int i;
PUT(“V86TEST -- Проверка, как реагирует Windows на программы, загруженные перед ней’’);
Рит(“Из V’Unauthorized Windows\" (IDG Books, 1994)");
PUT(“Авторские права .(с) 1994 Эндрю Шульман. Все права защищены.\п");
/* проверка ключей командной строки */
while(argv[1][0] == ’-’)
{
STATS far *fp;
switch(toupper(argv[1][1]))
{
case 'F': not_filter = 0; break; // фильтр
case 'O’: v86_okay = 1; break;
case V: not_verbose = 0; break;
case 'Q': // запрос
if(!(fp = get_stats()))
FAIL(“HeB03M0*H0 получить статистику V86TEST’’);
display_results(fp);
exit(0);
case ’С: // очистить
if(!(fp = get_stats()))
FAIL(“Невозможно очистить статистику V86TEST”);
_fmemset(&fp->calls, 0, sizeof(STATS) - offsetof(STATS, calls));
exit(0);
default:
FAIL(usage);
Глава 10. Как Windows запускает DOS
261
}
argv++; argc—;
}
ok:
if(argc < 2) FAIL(usage);
•if(win3e()) FAIL(“y*e запущен в расширенном режиме Windows\n”
“Выйдите из Windows и попробуйте еще раз’’);
if(!v86_okay)
if(ре()) FAIL(“y*e в режиме V86 -- тестирование будет бесполезным\п”
“Уберите менеджер памяти 386 из CONFIG.SYS и перезагрузитесь’’);
if((stats = calloc(1, sizeof(STATS))) == 0)
РАИ(“Недостаточно памяти’’);
fpstats = (STATS far •) stats;
strcpy(stats->signature, SIGNATURE);
/* перехватить INT 21h и INT 2Fh */
old = _dos_getvect(0x21);
_dos_setvect(0x21, int21);
old_2F = _dos_getvect(0x2F);
_dos_setvect(0x2f, int2f);
/* выполнить команду */
spawnvp(P_WAIT, argv[1], &argv[1]);
/* восстановить INT 21h, 2Fh */
_dos_setvect(Dx2F, old_2F);
_dos_setvect(0x21, old);
display_results(fpstats);
return 0;
}
/**********************************************************************/
void check state(int intno, int ah, int al, int bx)
{
stats->calls[state]++;
if(pe())
stats->v86_calls[state]++;
if(state == WIN_INIT_DONE)
{
/* Следующий код может вас несколько озадачить. Вообще-то V86TEST может
вызывать Windows INT 2Fh (такие как vmid()) из обработчика INT 2Fh,
не приводя к зацикливанию, поскольку VMM (менеджер виртуальной машины)
отлавливает эти вызовы и не отражает их обратно в V86. Следовательно,
обработчик V86TEST прерывания INT 2Fh никогда не получит вызовы 2F/1680
и 2F/1683. Однако полезно проверить, как V86TEST работает с программами,
отличными от Windows, такими как FAKEWIN. В этом случае вызовы 2F/168X
будут попадать обратно в обработчик V86TEST, приводя к зацикливанию.
Таким образом, если Windows VMM не запущен (определяется с помощью 2F/1680),
V86TEST не вызывает 2F/1683, и 2F/1680 вызывается только один раз; на это
время отслеживание приостанавливается, чтобы избежать зацикливания в случае,
когда работает какой-то другой VMM. */
int cur_vm;
262
Неофициальная Windows 95
static int logging = 1;
static int is_win = Oxff;
if(logging == 0) return;
else if(is_win == Oxff) // один раз - инициализация
{
logging = 0;
is_win = win3e();
logging = 1;
}
cur_vm = (is_win) ? vmid() : 0;
stats->vm[(cur_vm < VM_MAX) ? cur_vm : VM_0THER]++;
// это мы делаем только во время работы Windows!!
stats->iopl_count[iopl()]++;
}
if(intno == 0x21)
stats->int21[ah]++;
else if(intno == 0x2f)
{
stats->int2f[ah]++;
if(ah == 0x16)
{
stats->int2f16[al]++;
if(al == 7) // 2f/1607/18 наиболее распространен (VMPOLL)
stats->int2f1607[(bx < 0x100) ? bx : VXD OTHER]++;
}
}
void interrupt far int21(REG PARAMS r)
{
check_state(0x21, r.ax » 8, r.ax & Oxff, r.bx);
_chain_intr(old);
void interrupt far int2f(REG PARAMS r)
{
switch(r.ax)
{
case WIN_INIT_NOTIFY: state = WIN_INIT_BEGIN; break;
case WIN_INIT_COMPLETE: state = WIN_INIT_DONE; time(&stats->start); break;
case WIN_EXIT_BEGIN: State = WIN_FINI_BEGIN; time(&stats->end); break;
case WIN_EXIT_NOTIFY: state = WIN_FINI_DONE; break;
case GET_STATS: r.es = FP_SEG(fpstats);
r.bx = FP_OFF(fpstats);
break;
)
if(not_fliter || (r.ax != 0x1607 && r.bx != 0x18))
check_state(0x2f, r.ax » 8, r.ax & Oxff, r.bx);
#if 1
if(r.ax == 0x1607 && r.bx == 0x10 && r.cx == 3)
{
r.cx = 0;
return; // поддерживает FastDisk
)
#endif
_chain_int r(old_2F);
Глава 10. Как Windows запускает DOS
263
Поскольку задачей V86TEST является демонстрация того, что Windows вызывает DOS в режи-
ме V86, не имеет смысла запускать V86TEST, если машина уже находится в режиме V86. Все вызо-
вы прерываний будут осуществляться в режиме V86 независимо от того, запустили вы Windows или
нет, и таким образом V86TEST не сообщит ничего полезного об интерфейсе Windows-DOS.
Итак, V86TEST при запуске проверяет бит РЕ. Если бит РЕ уже включен (возможно, потому,
что вы работаете под менеджером памяти, вроде EMM386, QEMM или 386МАХ), V86TEST выдает
сообщение об ошибке и прекращает свою работу. За исключением обработки опций командной стро-
ки -VERBOSE и -CLEAR, описанных в следующей главе, V86TEST также прекращает работу,
выдавая сообщение об ошибке, если вы уже работаете под Windows. Для V86TEST интересной
альтернативой представляется переключение машины из V86 в реальный режим и затем запуск
Windows, но это намного сложнее сделать, чем сказать (см. врезку “Переключение из V86 в реаль-
ный режим”).
Поскольку следующая глава использует V86TEST для целей, несколько отличающихся от
определения режима, в котором Windows обращается к DOS, V86TEST также включает в себя ключ
-OKV86, позволяющий программе запуститься, даже если процессор уже находится в режиме V86.
Переключение из V86 в реальный режим
Если машина находится в режиме V86, когда запускается V86TEST, и если пользователь
не указал опцию -OKV86, программа аварийно прекращает работу, выдавая сообщение об
ошибке:
Уже врежиме V86 -- тестирование будет бесполезным
Уберите менеджер памяти 386 из CONFIG.SYS и перезагрузитесь
Для V86TEST представляется интересной альтернативой перед запуском Windows пере-
ключить компьютер из режима V86 обратно в реальный режим. Менеджеры памяти EMM386
и QEMM, как правило, обеспечивают опцию командной строки OFF для блокировки менед-
жера памяти, так что, похоже, V86TEST могла бы вызвать некоторую функцию менеджера
памяти для его деактивации и возвращения в реальный режим. Действительно, Windows сама
должна вызывать такую функцию при своем запуске. Расширенный режим Windows не может
переключаться в защищенный режим, если машина уже находится в защищенном режиме (т.е.
в V86). Как мы видели в примере с FAKEWIN (главы 3 и 4), функция 1605h прерывания INT
2Fh позволяет установленному менеджеру памяти сообщить Windows адрес функции пере-
ключения V86.
Как и. FAKEWIN, V86TEST может симулировать Windows и использовать функцию
1605h INT 2Fh для получения адреса функции переключения. Затем, перед запуском Win-
dows, она вызовет эту функцию для переключения из V86 назад, в реальный режим. Если ме-
неджер памяти уже был выключен или если функция переключения из V86 уже была вызва-
на, менеджер памяти возвратит NULL для адреса переключающей функции.
Однако функция переключения V86, возвращаемая менеджером памяти через функцию
1605b. INT 2Fh, не является точным аналогом EMM386 OFF или QEMM OFF. Эти опции
командной строки не срабатывают, если менеджер памяти в настоящий момент поддерживает
UMB (Upper Memory Blocks) или EMS, функция же переключения V86 делает только то, что
приказано и отключает режим V86. При этом, к сожалению, некоторые ключевые драйверы
или TSR, загруженные в верхнюю память, вдруг станут невидимыми и, в результате, “подве-
сят” систему.
Что же в подобных ситуациях делает Windows? Как показано в главах 3 и 4, V86MMGR
VxD в Windows использует интерфейс импорта глобальной ЕММ для доступа к таблицам
страниц (page tables) менеджера памяти. Однако в случае с V86TEST, которая, в конце
концов, является всего лишь тестовой программой, лучше просто прекратить работу, чем вла-
зить во внутренние дела импорта глобальной ЕММ.
264 Неофициальная Windows 95
Как отмечалось раньше, V86TEST выдает статистику, показывающую, сколько вызовов она об-
наружила и сколько из них появились в V86, а не в реальном режиме. На рис. 10.2 представлен об-
разец вывода V86TEST. Для увеличения числа вызовов DOS я временно отключил 32-битовый
доступ к файлам. (На самом деле переключатель /D:C, отключающий 32-битовый доступ к фай-
лам, здесь необязателен, так как перенаправление стандартного вывода Windows (STDOUT) в файл
делает это все равно.)
C:\>v86test /0:С > v86test.log
C:\>type v86test.log
Перед запуском Windows:
В процессе инициализации Windows:
Во время работы Windows:
При выходе из Windows:
После выхода из Windows:
Во время работы Windows:
Windows активна 256 секунд
682 вызовов INT 21/2F/ceK.
IOPL=O - 2929 вызовов
I0PL=3 - 171783 вызовов
VM #1 - 135326 вызовов
VM #2 - 39386 вызовов
109 вызовов INT 21/2F. 0 в режиме V86
5216 вызовов INT 21/2F, 354 в режиме V86
174712 вызовов INT 21/2F, 174712 в режиме V86
13 вызовов INT 21/2F, 10 в режиме V86
12 вызовов INT 21/2F, 0 в режиме V86
Вызовы INT 2Fh AH=16h, отловленные V86TEST:
00: 2 05: 1 06: 1 07: 125887 08: 1 09: 1 0А: 2 0В: 2 8А: 1
Рис. 10.2. Пример вывода программы V86TEST, показывающей, что Windows вызывает DOS в режиме V86
Третья строка вывода в рис. 10.2 сообщает нам, что каждый из 174712-ти вызовов INT 21h и
INT 2Fh, сделанных в процессе работы Windows, происходил в режиме V86. В режиме V86 не было
вызовов DOS ни до старта Windows, ни после окончания процедуры выхода из Windows. Из этого
рисунка видно, что переключение из реального режима в V86 происходит во время инициализации
Windows, а переключение обратно в реальный режим — в процессе завершения сеанса Windows.
В процессе работы Windows V86TEST, в дополнение к проверке состояния бита РЕ, также
проверяет флаги процессора для получения уровня привилегий ввода-вывода (IOPL) и вызывает
функцию 1683h INT 2Fh (Get Current Virtual Machine ID) для получения номера ID текущей
виртуальной машины (VMID). Вы можете спросить, как V86TEST осуществляет вызов INT 2Fh из
обработчика INT 2Fh? Это превосходный вопрос, и ответ находится очень близко к сущности того,
как Windows управляет программами, работающими в режиме V86. Оставайтесь с нами, чтобы
услышать это захватывающее разъяснение.
Но прежде всего, чтобы понять следующую часть вывода V86TEST, отраженную на рис. 10.2
(две строки, показывающие числа для IOPL=0 и IOPL=3), необходимо выяснить, каким будет
первый шаг обходного маневра, позволяющего взглянуть на уровень привилегий ввода-вывода в
Intel 80386 и более поздних микропроцессорах. Это неполный обзор, но все же.
IOPL и флаг прерывания (Interrupt Flag)
Архитектура Intel 80x86 имеет головокружительное количество механизмов защит и приори-
тетов. Книги на эту тему обычно “бомбят” читателя терминами типа Requestor Privilege Level
(RPL), Descriptor Privilege Level (DPL), Current Privilege Level (CPL) и I/O Privilege Level
(IOPL). Одна из самых разумных книг по микропроцессорам после обязательного раздела об уров-
нях приоритета Intel 80386 представляет раздел, озаглавленный “Имеет ли все это смысл?”. Авторы
замечают:
Глава 10. Как Windows запускает DOS
265
Предыдущий раздел, видимо, непонятен. Быть может, вам понадобится прочесть его несколько раз, что-
бы хоть что-то понять, но вы по-прежнему будете безнадежно путать DPL, CPL и RPL. Никакие другие
распространенные микропроцессоры не имеют такой запутанной защиты, и мы можем задаться вполне
закономерным вопросом: действительно ли все эти особенности полезны и стоят затраченных усилий
(с точки зрения Intel) или же представляют собой бред сумасшедшего (с точки зрения многих других).
— Robert В.К. Dewar and Matthew Smosna, Microprocessors: A Programmer's View, p. 95^-96.
Я не собираюсь надоедать вам еще одной дискуссией по поводу изощренного механизма уровней
приоритета процессоров Intel 80386, 80486 и Pentium. Если хотите, вы можете прочесть об этом в
руководствах Intel (или во многих других широкодоступных книгах, списанных с руководств Intel)
или в некоторых действительно хороших книгах.
Но один аспект этой путаницы нам нужно изучить более-менее серьезно: IOPL. Чтобы упро-
стить некоторые понятия (я вынужден сделать это, чтобы не оказаться втянутым в дискуссию на
тему о CPL), примем, что IOPL определяет, какие инструкции может исполнять программа, запу-
щенная в режиме V86. Если I/O Privilege Level меньше, чем 3 (например, IOPL=0), программа,
запушенная в окне DOS, не может выполнять следующие инструкции:
Инструкция PUSHF POPF INT п IRET CLI STL Описание Помещает флаги в стек Извлекает флаги из стека Программное прерывание Возврат из обработки прерывания Очищает флаг прерываний (запрещает прерывания) Устанавливает флаг прерываний (разрешает прерывания)
Вы можете поинтересоваться, как эти шесть инструкций связаны между собой или с уровнем
приоритета ввода-вывода. Более того, поскольку все эти инструкции часто употребляются в про-
граммах реального режима, вы наверняка заинтересовались, как же такие программы могут рабо-
тать в У86-режиме.
Для обсуждения этих пунктов в обратном порядке (как бы выталкивая их из стека) прежде
всего отбросим мнение о понятии “не может выполнять”, которое могло сложиться у вас. Когда при
рассмотрении процессоров Intel заходит речь о том, что некоторое событие “не может произойти”,
это значит Только то, что генерируется определенная исключительная ситуация. В случае этих
запретных инструкций генерируется ошибка общей защиты (General Protection Fault, GPF).
Другими словами, если программа реального режима, запущенная в режиме V86 с IOPL=0,
генерирует, скажем, INT 2lh для вызова MS DOS, то это вызывает ошибку общей защиты.
Ошибка общей защиты? Звучит не очень-то приятно. Именно нарушение общей защиты застав-
ляет Windows выдавать свои угрожающие сообщения “Unrecoverable Application Error!” (Не
проходите! Не поскупитесь на $200!) Чем же генерация ошибки GP отличается от высказывания,
что инструкция не может быть исполнена?
Расслабьтесь: не стоит проливать слезы над ошибками GP. На самом деле, ошибка GP — это
всего лишь INT ODh. Операционная система защищенного режима (как VMM в Windows) будет
иметь обработчик INT ODh, перехватывающий ошибки общей защиты и делающий с ним все, что
заблагорассудится. VMM может прекратить работу приложения-нарушителя, проигнорировать
инструкцию, эмулировать инструкцию и т.д. Таким образом, программа реального режима, работаю-
щая в окне DOS с IOPL=0 и обратившаяся, скажем, через INT 21h к MS DOS, попадет в обра-
ботчик ошибки общей защиты, принадлежащий VMM.
Этот обработчик вовсе не обязан выдавать сообщение об ошибке и прекращать работу
приложения. В действительности, он почти никогда не реагирует на ошибку GP таким образом. Это
происходит потому, что GP, вместе с исключительными ситуациями Invalid Opcode (Недопустимый
код операции), являются нормальной и важной частью скрытой системной архитектуры Windows, и
принадлежащий VMM обработчик ошибки GP каждую секунду молча обрабатывает многие GPF.
266
Неофициальная Windows 95
Вернемся к тому, чем же связаны шесть “нелегальных” инструкций: PUSHF, POPF, INT n,
IRET, CLI и STI. Чем эти инструкции отличаются от всех остальных? Тем, что они воздействуют на
флаг прерываний (IF). CLI и STI, естественно, сбрасывают и устанавливают IF, но это также могут
делать POPF и IRET, выталкивая верхушку стека в регистр флагов. Инструкции PUSHF и INT (не-
явно выполняющая PUSHF) не воздействуют, непосредственно на регистр флагов, но они симмет-
ричны по отношению к POPF и IRET. (Это не совсем точная причина их исключения, но настоящая
причина еще некоторое время будет не понятна.)
Вот что важно знать об IF. Как уже указывалось в конце главы 9, разрешение процессу V86
сбрасывать действительный IF процессора не является хорошей практикой для операционной систе-
мы защищенного режима, так как это запрещает прерывания для всех процессов. Если программа,
запущенная в окне DOS, будет свободно исполнять CLI или использовать POPF и IRET для блоки-
ровки прерываний, она также блокирует прерывания для любой другой программы, включая
Windows-приложения и другие окна DOS, а это нехорошо.
Поэтому Intel предоставляет операционным системам защищенного режима возможность пере-
хватывать эти инструкции. Если IOPL<3 (например, IOPL=0), тогда каждый раз, когда DOS-
программа — или программа, загруженная перед Windows, в том числе и сама DOS — генерирует
такую инструкцию, ее перехватывает VMM, который сохраняет “виртуальный” IF для каждой VM.
(Интересно, что процессор Pentium имеет некоторые недокументированные расширения V86,
поддерживающие виртуальный IF на аппаратном уровне.) Процессору необходимо отлавливать
PUSHF и INT, даже если они и не воздействуют на IF, чтобы монитор V86, вроде VMM, смог об-
служивать этот виртуальный IF. (Вот точная причина, о которой я недавно упоминал.)
Это хороший пример того, как режим V86 позволяет операционной системе защищенного режи-
ма обращаться с программами реального режима. Но почему правилом отбора является IOPL<3? А
зачем же позволять какой-нибудь “блатной” программе DOS запрещать прерывания для всех дру-
гих? Дело в том, что если каждая инструкция PUSHF, POPF, INT, IRET, CLI и STI будет поро-
ждать ошибку общей защиты, обрабатываемую VMM, это сильно повлияет на производительность
(см. CLISTI.C в листинге 10.4). Когда IOPL=3, эти инструкции не порождают ошибку, они испол-
няются так, как в реальном режиме.
На действительности это не совсем так. В руководстве Intel говорится, что “Если IOPL меньше
3, инструкция INT п перехватывается виртуальным монитором 8086”, из чего вроде бы следует, что
если IOPL не меньше 3, то инструкция INT п не перехватывается монитором V86. На самом деле
они также перехватываются. Как объясняет Intel в другом месте руководства, все инструкции INT
из режима V86, независимо от IOPL, направляются через таблицу дескрипторов прерываний
(Interrupt Descriptor Table, IDT), Таким образом, каким бы ни был IOPL, все INT 21h, INT 2Fh и
прочие, исходящие от программ DOS — и, повторю еще раз, от программ, загруженных перед
Windows, включая собственно DOS — будут прежде всего (и иногда исключительно) обрабаты-
ваться VMM и VxD. Это очень важный момент, и я вернусь к нему еще не раз.
В любом случае, если IOPL=3, никакая инструкция, воздействующая на IF, не порождает
ошибку. Программа DOS, запущенная в режиме V86 с IOPL=3, действительно сбросит IF машины,
если исполнит CLI. Итак, IOPL=0 предоставляет вам более устойчивую операционную систему с
некоторым ухудшением производительности программ DOS, a IOPL=3 предоставит вам менее устой-
чивую операционную систему с лучшей производительностью. Следует снова заметить, что процес-
сор Pentium имеет некоторые недокументированные расширения V86, которые когда-нибудь в
будущем сделают несущественным противостояние устойчивости и производительности.
Что же делает Windows с этой дилеммой “устойчивость (живучесть) или производительность”?
Вывод программы V86TEST на рис. 10.2 как раз это и демонстрирует.
IOPL=O - 2929 вызовов
I0PL=3 - 171783 вызовов
Несмотря на случайные IOPL=0, подавляющее большинство IOPL=3. Это имеет огромное зна-
чение для производительности и устойчивости Windows. Установка IOPL=3 означает, что CLI/STI,
PUSHF/POPF и IRET не будут отлавливаться Windows VMM. В результате — увеличение произ-
водительности по сравнению с установкой IOPL=0 (при которой каждая такая инструкция отлав-
Глава 10. Как Windows запускает DOS • 267
ливалась бы VMM); это также означает, что программы DOS могут свободно манипулировать фла-
гом прерываний.
Джеф Чапелл порекомендовал прекрасную демонстрацию, иллюстрирующую оба этих положе-
ния и то, как Windows обеспечивает приоритетную (вытесняющую) многозадачность VM. Находясь
в окне DOS, запустите DEBUG и введите маленький цикл, разрешающий прерывания.
C:\>debug
-а
7713:0100 sti
7713:0101 jmp 0100 ; jmp или 0101
7713:0103
-g
Это динамический останов’, процессор исполняет инструкции, но в окне DOS вы не можете сде-
лать ничего. Нажатие <Ctrl+C> или <Ctrl+Break> не дает эффекта. Если бы вы находились в
DOS, у вас бы не было иного выхода, кроме перезапуска машины.
Однако это — окно DOS в Windows, так что вы можете выполнять другие программы в фоно-
вом режиме и переключиться на другие программы. Вы можете даже перевести сеанс DOS в
оконный или полноэкранный режимы, перенести данные из окна зависшей DOS в буфер обмена и
т.д. Виртуальные машины оказываются лучше настоящих! Вы можете нажать <Ctrl+Alt+Del> для
“местной перезагрузки” и затем запустить другое окно DOS. Эта возможность выйти из зависшей
DOS-машины и запустить другую делает Windows (или любые приоритетные многозадачные среды)
великолепными орудиями для разработки программ.
А теперь воспользуемся DEBUG для создания другого цикла, запрещающего прерывания.
С: \>debug
-а
7713:0100 СИ
7713:0101 jmp 0100 ; или jmp 0101
7713:0103
-g
И снова окно DOS зависает: <Ctrl+C> и другие клавиши не дают эффекта. Однако на этот раз,
поскольку мы использовали CLI (clear interrupt flag), а не STI (set interrupt flag), и поскольку
Windows почти всегда использует IOPL=3, работающие в фоновом режиме программы останав-
ливаются, переключиться на другую программу невозможно и даже <Ctrl+Alt+Del> не приводит ни
к какому результату. Вам остается только нажап? кнопку Reset. (Между прочим, не пытайтесь пов-
торить этот эксперимент со включенным 32-битовым доступом к файлам, так как вы можете поте-
рять все данные, которые хранятся в памяти VCACHE VxD.)
Вот демонстрация того, что любая программа DOS может отключить прерывания для всей си-
стемы Windows. Эта заметная брешь в системе безопасности Windows, тем не менее не является
неотъемлемым следствием того, что система защищенного режима Windows выполняется поверх
системы реального режима DOS. Установка уровня IOPL ставит проблему, которую должны решать
все операционные системы: устойчивость против производительности. V86TEST показывает, однако,
что Windows иногда устанавливает IOPL=0. Зачем? Ответ находится в книге, посвященной внутрен-
нему устройству OS/2. Не является сюрпризом то, что OS/2 дает ключи к разгадке внутренней
работы расширенного режима Windows, поскольку Microsoft сконструировала значительную часть
OS/2 2.0 перед своим разрывом с IBM. Поэтому, как и в Windows, виртуальные машины DOS
(VDM) в OS/2 большую часть времени используют IOPL=3, но иногда им требуется IOPL=0:
IOPL устанавливается в 0 для отдельной VDM только тогда, когда эта VDM требует, чтобы флаг пре-
рываний был виртуализован. Например, когда некоторому драйверу виртуального устройства OS/2
(Virtual Device Driver, VDD) требуется сымитировать прерывание в VDM, он должен иметь возмож-
ность обнаружить, когда VDM готова принять прерывание. Поэтому IOPL уменьшается до уровня
меньшего 3, благодаря чему флаг прерываний может быть виртуализован для VDM, и система может
определить, когда в этой VDM разрешены прерывания. IOPL вновь увеличивается до 3, когда сымити-
рованное прерывание попадает в VDM.
— Н.М. Deitel and M.S. Kogan, The Design of OS/2, p. 296.
268
Неофициальная Windows 95
Это похоже на описание служебной функции VDHArmSTIHook в OS/2, позволяющей драйве-
рам виртуальных устройств OS/2 устанавливать обработчик, принимающий управление, когда в ок-
не DOS разрешены (STI) прерывания. Соответствующей функцией в Windows является Са11_
When_VM_Ints_Enabled. Исследования кода VMM показывают, что Call_When_VM_Ints_Enabled
устанавливает IOPL=0, и устройство Windows VPICD, имитирующее аппаратные прерывания в
VM, использует Call_When_VM_Ints_Enabled точно так же, как вы прочли в описании OS/2.
V86TEST обнаруживает, что IOPL=0 потому, что VPICD хочет сымитировать аппаратное прерыва-
ние (обычно импульсы таймера) в VM.
Невзирая на такое сходство между OS/2 и Windows (которое становится особенно заметным,
когда вы наблюдаете ссору между Microsoft и отделом PSP фирмы IBM, занимающимся марке-
тингом OS/2), между ними есть и любопытное отличие. Как указано у Дейтеля и Когана:
Чтобы удостовериться, что приложение DOS не запретило прерывания и не вошло в бесконечный цикл,
подвесив систему, OS/2 использует сторожевой таймер (whatchdog timer). Сторожевой таймер уста-
новлен на определенный временной интервал. Если таймер будет сброшен до истечения этого интервала,
он не выдает прерывание. Если же таймер выдаст прерывание, система прекратит работу приложения
DOS. Поэтому установка IOPL=3 позволяет системе достигать максимальной производительности, а ис-
пользование сторожевого таймера предохраняет приложения DOS от развала системы или разрушения
приложений защищенного режима.
— Н.М. Deitel and M.S. Kogan, The Design of OS/2, p. 296.
Даже здесь, однако, разница между OS/2 и Windows не столь велика. Сторожевой таймер OS/2
доступен только на EISA- и PS/2-машинах, обеспечивающих дополнительный таймер, способный
инициировать немаскируемое прерывание (nonmaskable interrupt, NMI), если прерывания были за-
прещены слишком долго. На машинах со сторожевым таймером OS/2 отвечает на цикл CLI сооб-
щением об ошибке и позволяет вам завершить VDM. На стандартных ISA-машинах, не имеющих
этой чудесной особенности, тем не менее, цикл CLI в окне DOS подвешивает OS/2 точно так, как
это происходит с Windows.
Выполнение цикла CLI в Windows NT, однако, не подвешивает машину. NT запускает програм-
мы DOS с IOPL=0, и каждый одиночный CLI перехватывается частью NT, называемой NTVDM.
NTVDM может сохранять виртуальный флаг прерываний для каждой VDM. С IOPL=0 каждая
PUSHF, POPF, INT, IRET и STI также отлавливается NTVDM, так что производительность полу-
чается не очень хорошей.
Заметьте, что здесь нет ничего, присущего только NT в ее статусе полноценной операционной
системы, предохраняющей приложения DOS от подвешивания машины. Точно так же, нет ничего
присущего только Windows в ее статусе кажущейся надстройки над реальным режимом DOS, что
делает ее уязвимой для приложений DOS. Просто Windows для приложений DOS выбирает (в
подавляющем большинстве случаев) IOPL=0. Это только примеры различных инженерных реше-
ний, здесь нет никакого волшебства.
За всеми этими разговорами о флаге прерываний я упустил ответ на один вопрос: какое отно-
шение флаг прерываний имеет к уровню приоритета ввода-вывода. В режиме V86 IOPL довольно
слабо связан с вводом-выводом. В защищенном режиме IOPL (вместе с картой разрешения I/O)
определяет, когда приложение может выдать инструкции ввода-вывода — IN, INS, OUT и OUTS —
без генерации ошибки GP. Но в режиме V86 эти четыре инструкции обращаются к карте разре-
шения ввода-вывода (I/O permission bitmap) без согласования с IOPL, и IOPL используется для
другой цели — управления флагом прерываний. IOPL просто имеет разный смысл в защищенном
режиме и в режиме V86, и его название в режиме V86 совершенно не отражает вложенного смысла.
Говоря о защищенном режиме, стоит повторить предыдущие циклические тесты CLI и STI,
использовавшие DEBUG в режиме V86, но в этот раз создать маленькое приложение Windows
защищенного режима (листинг 10.2).
ЛИСТИНГ 10.2. CLITEST.C (версия для Windows)
/* Ьсс -WS clitest.c */
ffinclude “windows.h”
int PASCAL WinMain(
HANDLE hlnstance,
Глава 10. Как Windows запускает DOS
269
HANDLE hPrevInstance,
LPSTR IpszCmdLine,
int nCmdShow)
{
char *s = “CLITEST";
int mb = MB_YESND | MB_ICONQUESTION;
if(MessageBox(0, “Делать цикл STI?”, s, mb) == IDYES)
{
for(;;)
asm sti
}
if(MessageBox(0, “Делать цикл CLI?", s, mb) == IDYES)
{
for(:;)
asm cli
}
return 0;
}
Когда вы запускаете CLITEST, цикл STI ведет себя как и в DOS-версии для режима V86: машина
выглядит зависшей, но вы можете выполнить местную перезагрузку (<Ctrl+Alt+Del>) для прекра-
щения работы приложения CLITEST. Вместо системной VM останавливается только приложение.
С другой стороны, во время работы CLITEST вы не можете переключиться на другое при-
ложение Winl6, поскольку CLITEST не читает очередь сообщений. К сожалению, в бета-версии
Chicago (май 1994 г.) цикл STI мешал работать всем Win32-BCTBHM (в частности, принадлежащим
Clock и WinBezMT), и возможность Windows 95 “Close Hung Application” (Закрыть зависшее
приложение) не помогала, поскольку программа CLITEST не имеет окна.
Намного интереснее то, что в цикле CLI Windows-программа CLITEST ведет себя подобным же
образом. Вспомните, что, когда этот цикл выполняла DOS-программа, машина полностью блоки-
ровалась. Но здесь вы можете просто нажать <Ctrl+Alt+Del> для прекращения зависшей про-
граммы и продолжать делать что-нибудь другое.
Какими же волшебными свойствами, которых не имеют DOS-приложения, располагают
Windows-приложения? На самом деле, никакими. Разница состоит в том, что раньше мы выполняли
цикл CLI из программы реального режима (DEBUG), запущенной в режиме V86. Программа же
для Windows выполняется в защищенном режиме. И разница здесь заключается не “между Win-
dows и DOS”, а “между защищенным режимом и V86”. А теперь, в листинге 10.3, мы попробуем
воспользоваться библиотекой DPMISH для создания DOS-версии CLITEST защищенного режима.
Листинг 10.3. CLITEST2.C (DPMI-версия)
/* clitest2.c */
#include <stdlib.h>
«include <stdio.h>
«include “dpmish.h"
void fail(const char *s, ...) { puts(s); _dos_exit(1); }
int real_main(int argc, char »argv[]) { return 0; }
int pmode main(int argc, char *argv[])
<
if(argc < 2) // нет аргументов в командной строке
{
for(;;)
_asm sti
}
else // в командной строке какой-то аргумент
{
for(;;)
_asm cli
}
}
270
Неофициальная Windows 95
Достаточно легко убедиться в том, что в версии цикла CLI для защищенного режима DOS вы
по-прежнему можете сделать местную перезагрузку, чтобы прекратить зависшее приложение без об-
щей перезагрузки машины. Это демонстрирует определенную симметрию между системной VM и
окном DOS: DOS-программы защищенного режима ведут себя в точности так, как Windows-прило-
жения. На самом деле, в этом случае DOS-программа даже немного лучше, чем Win 16-программа.
Поскольку программы DOS не должны обслуживать очереди сообщений, они не мешают запуску
других программ, Clock продолжает работать, вы можете переключиться на другую программу и
т.д. Итак, цикл CLI из программы защищенного режима DOS или Windows не подвешивает
машину. Но почему?
Да потому, что VxD REBOOT в Windows, устанавливающий обработчик комбинации клавиш
<Ctrl+Alt+Del> и управляющий возможностью местной перезагрузки, содержит код, который,
принимая <Ctrl+Alt+Del> из программы защищенного режима, проверяет, заблокирован ли флаг
прерываний — и если да, то разблокирует его!
Но более веской причиной является то, что в защищенном режиме в Windows IOPL=0. (Вы мо-
жете увидеть это, если вызовете функцию iopl, приведенную в листинге 10.1, из Windows-приложе-
ния; листинг 10.4.) Это означает, что инструкции CLI и STI из программ защищенного режима DOS
и Windows всегда порождают ошибку GP, обрабатываемую VMM.
Так что же делает VMM, когда его обработчик ошибки GP вызывается по поводу инструкции
CLI? Когда IOPL=0 (в защищенном режиме или в V86), инструкция СЫ вызывает следующий
фрагмент кода VMM.
CLI_HANOLER:
inc [ebp.Client_EIP]
and byte ptr [ebp.Client_FLAGS+1], Ofdh
retn
Этот код пропускает 1-байтовую инструкцию СИ, очищает флаг прерываний в виртуализо-
ванной копии регистра флагов и осуществляет возврат. Обработчик STI устроен немного сложнее
из-за того, что он должен работать с функцией Call_When_VM_Ints_Enabled, описанной раньше:
STI_HANDLER:
inc [ebp.Client_EIP]
bts [ebp.Client_FLAGS], 9
jnc ENABLED_INTS ; если до этого были запрещены
retn
ENABLEOlINTS:
;;; Проверить, ждет ли кто-то VM_Ints_Enabled
;;; Если да, вызвать через свою процедуру CallWhen
;;; (Здесь идет приличный кусочек кода)
Раз STI_HANDLER выглядит так просто, то почему же VMM не осуществляет постоянную вир-
туализацию флага прерываний. К сожалению, при обработке ошибки GP VMM должен выполнить
большое количество кода, прежде чем он сможет сделать что-то полезное (в данном случае вызвать
CLI_HANDLER или STI HANDLER). Это не совсем то, чего бы вы хотели. Даже максимально
избегая IOPL=0 в режиме V86, Windows все равно страдает от слишком большого числа внутренних
ошибок GP.
Не нужно исследовать весь код обработчика ошибок GP в VMM, чтобы увидеть, что виртуали-
зация IF достается дорогой ценой. Спецификация DPMI 1.0 предупреждает, что в защищенном ре-
жиме инструкции СЫ и STI “начинают работать значительно медленнее”. В ранней версии этой спе-
цификации конкретно указывалось, что виртуализованные CLI и STI требуют по 300 тактов каждая.
Руководства Intel 80x86 говорят, что CLI и STI требуют по 3-5 тактов каждая. Можно довольно
легко проверить, действительно ли такую огромную дань берет IOPL=0 (в защищенном или V86-
режимах), просто подсчитав секунды, затраченные на исполнение большого числа СЫ или STI в
защищенном режиме (где под Windows IOPL=0). В защищенном режиме IOPL=0, так что тестовая
программа защищенного режима подает нам идею, как должен вести себя V86 при этом условии.
Глава 10. Как Windows запускает DOS
271
CLISTI из листинга 10.4 — это DPMI-программа, которая сначала выполняет цикл CLI/STI в
режиме V86 или в реальном режиме, а затем еще раз в защищенном режиме. В каждом режиме она
выводит затраченное время.
ЛИСТИНГ 10.4. CLITEST1. С
/» clisti.c */
«include <stdlib.h>
«include <stdio.h>
«include <time.h>
«include “dpmish.h"
void fail(const char *s, ...) { puts(s); _dos_exit(1); }
«define ITER 1000000L
int pe(void) // установлен ли бит PE?
{
_asm smsw ax
_asm and ax, 1
// результат в AX
}
int iopl(void) // получает уровень привилегий ввода/вывода
{
_asm pushf
_asm pop ax
_asm shr ax, 12
_asm and ax, 3
// результат в AX
}
int cpl(void) // получает Ring X из двух младших битов CS
{
_asm mov ах, cs
_asm and ax, 3
// результат в AX
}
void clisti_loop(int pmode)
{
time_t t1, t2;
unsigned long i;
printf(“PexMM: %s - “,
pmode ? “Защищенный” : pe() ? “V86" : “Реальный”);
printf(“IOPL=%d", iopl());
if(pmode) printf(“ CPL=%d", cpl());
printf(“\n”);
time(&t1);
for(i=0; KITER; i++)
{
_asm cli
_asm sti
}
time(&t2);
printf(“%lu CLI/STI за %lu секунд\п", ITER, t2-t1);
}
int real_main(int argc, char *argv[]) { clisti_loop(0); return 0; }
int pmode_main(int argc, char »argv[]) { clisti_loop(1); return 0; }
272
Неофициальная Windows 95
Вот результат выполнения CLISTI в разных режимах на одной и той же машине:
Реальный режим MS DOS:
Режим: Реальный I0PL=3
1000000 CLI/STI за 1 с
В окне DOS под WfW 3.11
Режим: V86 I0PL=3
1000000 CLI/STI за 1 с
Режим: Защищенный I0PL=0 CPL=3
1000000 CLI/STI за 70 с
В окне DOS под Windows 3.0
Режим: V86 I0PL=3
1000000 CLI/STI за 1 с
Режим: Защищенный I0PL=3 CPL=1
1000000 CLI/STI за 23 с
Выполнение программы под Windows 3.1 с IOPL=0 заняло примерно в 70 раз больше времени,
чем с IOPL=3, так что оценка из спецификации DPMI в 300 тактов вполне подходит для реа-
лизации VMM. Интересно, что производительность Windows 3.0 в этой ситуации была заметно
выше. Хотя собственно обработчики CLI и STI делают не так уж много (впрочем, обработчик STI
работает несколько больше за счет обращений к Call_When_VM_Ints_Enabled), даже простой вход
и выход из VMM стоят довольно дорого.
Важно запомнить, что нет существенных различий между V86 и защищенным режимами. По-
рождают CLI и STI эти дорогостоящие ошибки GP или нет — целиком зависит от IOPL. Когда
IOPL=0 в окне DOS, любая CLI и STI — даже расположенная внутри DOS или BIOS — будет
исполнять код VMM, показанный раньше. Исходя из рис. 10.2, IOPL=0 примерно 10 раз в секунду;
вероятно, когда VPICD хочет послать аппаратные прерывания, например импульсы таймера, в VM.
Мне следовало бы сознаться, что абсолютная величина IOPL на самом деле не имеет значения.
Процессору интересно соотношение IOPL и CPL. Например, если IOPL<CPL, CLI или STI генери-
руют ошибку GP. CPL — это то, что иногда называют уровнем (кругом, кольцом) приоритета,
например, уровень 0 или уровень 3. Режим V86 всегда работает на уровне 3, так что при любом
IOPL<3 использование PUSHF, POPF, INT, IRET, CLI и STI приводит к ошибке общей защиты.
Поэтому IOPL=0 и IOPL=3 являются удобными сокращениями.
В защищенном режиме CPL определяется из двух младших битов регистра CS (см. функцию
CPL в листинге 10.4). В Windows 3.0 программы защищенного режима запускались на уровне
(CPL) 1. В Windows 3.1 и выше они запускаются на уровне 3. В обоих случаях IOPL=0, так что
вызовы CLI, STI, IN, INS, OUT и OUTS из Windows-приложений и DOS-приложений защищенного
режима, запущенных под Windows, порождают ошибку GP.
В VMM и VxD есть один плоский (flat) кодовый сегмент размером 4 Гбайт, значение которого
равно 0028h. Хотя собственно это значение не так важно и может изменяться, заметьте, что два
нижних бита нулевые: VMM и VxD работают на уровне 0, следовательно, когда они выдают CLI,
STI, IN, OUT, INT, IRET и т.д., ошибка GP не генерируется. VMM и VxD могут делать все, что им
заблагорассудится. VMM и VxD отвечают за виртуализацию, и сами не могут быть виртуализован-
ными. Это делает VxD превосходным инструментом для системного программирования низкого
уровня, а, с другой стороны, затрудняет жизнь операционным средам, вроде OS/2, пытающимся
запустить Windows как подзадачу. Виртуализатор Windows не может быть легко виртуализован.
Windows не является рекурсивным VMM.
И последнее замечание по поводу IOPL. Обратите внимание, что в функции iopl из листингов
10.1 и 10.4 первой инструкцией является PUSHF: функция помещает флаги в стек, чтобы можно
было извлечь биты IOPL. Здесь есть одна проблема: при IOPL=0 в режиме V86 PUSHF генерирует
ошибку GP, с которой операционная система может обращаться, как хочет. Вполне возможно, что
Глава 10. Как Windows запускает DOS
273
обработчик PUSHF операционной системы изменил биты IOPL в эмулируемом регистре флагов так,
чтобы IOPL стал равным 3, и программа V86 не будет об этом знать. Таким образом, если функция
iopl возвращает IOPL=0, вы знаете, что IOPL=0, но если она возвращает что-либо другое, вы не
можете быть уверены в результате: может быть, IOPL=0 и операционная система эмулировала
PUSHF таким образом, что IOPL=3. Режим V86 — это странный мир, в котором вы даже не може-
те полностью доверить PUSHF то, что она делает. Есть только два пути узнать, что делает PUSHF:
генерировать большое количество CLI и STI и посмотреть, как долго они будут выполняться (как в
листинге 10.4), или исследовать обработчик PUSHF операционной системы. Давайте посмотрим, как
выглядит обработчик PUSHF в VMM:
PUSHF.HANDLER:
test byte ptr [ebx.CB.VM.Status], VMStat_PM_Exec_Bit ;; защищенный режим?
jnz STD.OPCODE.HANDLER
inc [ebp.Client.EIP]
mov ex, 2
test edi,20000h
jz short ADJUST.STACK
mov ex, 4
ADJUST.STACK:
sub word ptr [ebp.Client_SP],cx
movzx esi,word ptr [ebp.Client.SS]
shl esi,4
add esi,dword ptr [ebp.Client.ESP]
MUCK.WITH.FLAGS:
mov ecx,[ebp.Client.EFLAGS]
and ecx,OFFFCFFFFh
test edi,20000h
jnz short PUSHF32
PUSHF16:
mov [esi],ex
retn
PUSHF32:
mov [esi],ecx
retn
;; это только для режима V86
;; пропустить PUSHF
;; предположительно 16-битовая PUSHF
;; 32-битовое расширение (66h)?
;; 32-битовая PUSHF
;; сбросить бит VM/V86
;; 32-битовое расширение (66h)?
;; 16-битовая PUSHF
;; 32-битовая PUSHF
Windows не портит биты IOPL, так что (по крайней мере, в текущей реализации) функция iopl
может использовать PUSHF. С другой стороны, VMM сбрасывает биты VM и RF, как это делает
сам процессор, выполняя PUSHF в режиме V86 (см. главу 9, раздел “Режим V86 и бит РЕ”). Вы
можете вспомнить, что эта дискуссия об IOPL, флаге прерываний, инструкциях, которые
перехватываются или не перехватываются VMM и т.д., была вызвана двумя строками вывода
программы V86TEST. Чтобы не терять зря времени, давайте перейдем к двум следующим строкам
вывода V86TEST.
Запуск DOS в виртуальной машине
В главе 9 утверждалось, что Windows запускает саму DOS в окне DOS и было обещано, что
V86TEST докажет это. Что ж, статистика VMID из рис. 10.2 представляет доказательство:
Во время работы Windows:
VM #1 - 135326 вызовов
VM #2 - 39386 вызовов
174712 вызовов, 174712 в режиме V86
Другими словами, каждый вызов DOS, сделанный в процессе работы Windows, выполняется в
контексте определенной VM, как если бы DOS была запущена из окна DOS.
274 с
Неофициальная Wihddws 95
При отсутствии открытых окон DOS, V86TEST показывает, что все вызовы DOS исходят из
VM #1, системной VM, в которой запускаются Windows-приложения. Системная VM, используемая
для запуска приложений Windows, на самом деле является всего лишь еще одним окном DOS,
работающим как клиент DPMI и вызывающим KRNL386.EXE. Окна DOS (и скрытые VM, напри-
мер, созданные VSERVER VxD для одноранговой сети) начинаются с VM #2.
При выполнении V86TEST для получения вывода, приведенного на рис. 10.2, я открыл окно
DOS, выполнил поиск по всему жесткому диску (grep -di foo.c), и закрыл окно DOS. Все другие
вызовы DOS исходили из Windows приложений, таких как WinWord, Clock, Control Panel и
Program Manager.
Выделенное в главе 9 утверждение', что каждый вызов DOS, сделанный в процессе работы Win-
dows, выполняется в контексте определенной VM, верно: выполнение Windows поверх DOS почти
не отличается от выполнения программы в окне DOS. Различие до/после почти не имеет значения,
и “DOS, лучшая, чем сама DOS” оказывается практически тем же, что и “одно на другом”. Обыч-
ный рекламный лозунг.
Но зачем окружать это высказывание словами “почти” и “практически”? Потому, что есть одна
существенная разница между программами, загруженными до Windows (которые, как мы только
что видели, выполняются Windows в сфере действия определенной VM), и программами, запущен-
ными в окне DOS после загрузки Windows. Когда мы ссылались на “программы, загруженные до
Windows”, мы имели в виду то, что Microsoft называет глобальным У86-кодом (Global V86 Code)
(см. справочник Windows 3.1 SDK, функцию Install_V86_Break_Point). Обычно память, распреде-
ленная до запуска Windows, называется глобальной У86-памятью (Global V86 Memory). Термин
“глобальная” как раз и является главным отличием программ, загруженных до Windows, от про-
грамм, загруженных после. Как объясняет Microsoft в документации DDK, касающейся
_TestGlobal V 86Mem:
(Глобальная У86-память имеет адреса, которые допустимы и идентичны во всех виртуальных машинах.
Локальная память имеет адреса, которые допустимы только в одной виртуальной машине. Память эк-
земпляров имеет одинаковые адреса во всех виртуальных машинах, но содержимое этой памяти отли-
чается для каждой виртуальной машины.
Таким образом, различие до/после на самом деле является результатом противопоставления
глобальная/локальная/экземплярная. Любая ненадежность Windows практически не связана с тем,
что DOS является операционной системой реального режима. Как мы видели, Windows может
управлять DOS, запуская ее в режиме V86, который на самом деле является формой защищенного
режима. Любая возможная нестабильность проистекает из того факта, что программы, загруженные
перед Windows, являются глобальными', это значит, что они видимы во всех VM. Изменение гло-
бальных данных в одной VM перетекает во все прочие VM. Это не относится к программам,
запущенным в окне DOS, такие программы являются локальными.
Имитация против отражения прерываний
Кроме демонстрации того, что VMM запускает DOS в VM, в V86TEST есть еще один инте-
ресный момент, касающийся кода определения VM. V86TEST загружается перед Windows; однако
для определения VMID функция vmid из листинга 10.1 вызывает сервис, предоставляемый Win-
dows — функцию 1683h INT 2Fh. Я уже отмечал, что режим V86 направляет многие инструкции
реального режима непосредственно в VMM. Вызывая функцию 1683h INT 2Fh, V86TEST умыш-
ленно попадает в ловушку VMM. Вы уже видели в главе 4 (TEST 1684.С), что сама DOS иногда
вызывает ловушки Windows, обращаясь к функции 1684h прерывания INT 2Fh и осуществляя вызов
по возвращенному указателю API.
Термин “ловушка” (trap) является здесь вполне корректным. Обратите внимание, что в листин-
ге 10.1 V86TEST.C вызывает функцию INT 2Fh 1683h из своего собственного обработчика INT 2Fh!
Почему же вызов INT 2Fh не попадает в собственный обработчик V86TEST прерывания INT 2Fh,
который вызывает, вызывает, вызывает INT 2Fh?
Глава Ю- КОк Windows 'запускает DOS
275
Почему это не приводит к бесконечному циклу? Потому что, если запущен расширенный режим
Windows, V86TEST не видит собственных вызовов этой функции. Подобное происходит и с боль-
шинством остальных вызовов Windows INT 2Fh с AH=16h. Вспомните рис. 10.2, где, среди выдан-
ного V86TEST списка принятых вызовов функции INT 2Fh 16h, не показано ни одного вызова
функции 1683h:
Вызовы INT 2Fh AH=16h, отловленные V86TEST:
00: 2 05: 1 06: 1 07: 125887 08: 1 09: 1 0А: 2 0В: 2 8А: 1
(Наиболее заметным здесь является огромное число вызовов функции INT 2Fh 1607h. Мы вер-
немся к этому в следующей главе.)
Как и все системные вызовы в режиме V86, вызов INT 2Fh, сделанный функцией V86TEST
vmid, проходит через таблицу дескрипторов прерываний (Interrupt Descriptor Table, IDT, см. главу
12) и вначале обрабатывается VMM. Разница здесь заключается в том, что Windows никогда не
посылает INT 2Fh обратно в режим V86. Вызов полностью обрабатывается на уровне VMM/VxD.
Тот факт, что VMM и VxD могут абсорбировать (или поглощать — в терминах Microsoft, или
имитировать — в терминах Intel) вызовы INT таким образом, достаточно важен. Согласно этому
логическому заключению, VMM и VxD могут абсорбировать/поглощать/имитировать все вызовы
INT без какой-либо передачи обращений обратно DOS.
Знаете почему? — Если Windows 95 действительно делает это, Microsoft может заявить, что
для Windows больше не требуется DOS, подразумевая что-то другое, а именно то, что Windows 95
объединяет DOS и Windows в одно целое. VMM и VxD потенциально могут обрабатывать все
вызовы INT в 32-битовом защищенном режиме, не отражая их в режим V86. Эта возможность уже
присутствует в 32-битовом доступе к файлам, обеспечиваемом WfW 3.11. Такая возможность су-
ществовала в Windows с тех пор, как она начала перехватывать прерывания через IDT защи-
щенного режима.
Дело в том, что нет жесткой границы между Windows, работающей поверх DOS, и Windows, не
требующей DOS. Как показано на рис. 10.3, это кажущееся цельным архитектурное решение на
самом деле собрано по частям, по принципу функция-за-функцией. Проблема выбора между по-
глощением обычного вызова в 32-битовом защищенном режиме или его отражением в V86 годами
решалась расширенным режимом Windows. ,
Более того, в предыдущем разделе мы видели, что, даже когда Windows отражает вызов в
DOS, она делает это в режиме V86. Это значит, что Windows может контролировать DOS тем же
путем, каким она управляет приложениями Windows.
Технология нижнего уровня операционной системы Windows 95 достаточно стара. Windows
имела возможность “поглотить-или-отразить” с 1988 г., когда Microsoft представила WIN386.EXE и
VxD в Windows/386 2.x. Подобным образом, возможность “поглотить-или-отразить” была зало-
жена много лет назад в руководствах Intel, но только теперь мы начинаем получать выгоду от этой
возможности на машинах 80x86. Intel говорит об имитации системных вызовов, и это то же самое,
что мы называем поглощением или абсорбцией системного вызова:
Многие 8086-операционные системы используют инструкцию INT п для системного вызова... VMM
может обрабатывать 8086-системный вызов одним из двух способов: имитировать этот вызов, выдав эк-
вивалентный вызов в 80386-операционную систему, или отразить вызов в копию 8086-операционной
системы, загруженную в адресное пространство У86-задачи.
Для имитации 8086-системного вызова VMM должен раскодировать вызов, преобразовать его и его
параметры в эквиваленты 80386-операционной системы, и вызвать 80386-операционную систему. Когда
система возвращает результаты, VMM должен преобразовать их в формат, ожидаемый У86-задачей,
продвинуть сохраненный этой задачей EIP и вернуться в V86-3afla4y инструкцией IRET.
— 80386 System Software Writer's Guide, 1987.
274
Неофициальная Windows 95
Рис. 10.3. Windows обрабатывает вызов DOS по принципу функция-за-функцией. В зависимости от
установленных VxD, некоторые вызовы DOS обрабатываются в защищенном режиме, а некоторые
отсылаются DOS в режиме V86. Заметьте, что это не имеет никакой связи с режимом программы,
которая выдала настоящий вызов INT 21h. INT 21h из приложения DOS, запущенного в режиме
V86 (жирная стрелка), может быть полностью обслужен в Windows, тогда как другой INT 2 lh из
программы защищенного режима Winl6 или Win32 (тонкая стрелка) может быть послан в DOS
Вспомните фрагмент из главы 9, где говорилось, что VMM выполняет операционную систему в
VM. Все это происходит в Windows, за исключением того, что операционная система (DOS) стар-
тует до VMM. Windows, Windows-приложения и DOS-приложения— все думают, что они обраща-
ются к DOS. Но каждый системный вызов INT поступает в VMM, который направляет его в раз-
личные VxD. Эти VxD могут передать системный вызов в DOS, вызывая функции VMM
Begin_Exec_V86_Mode и Exec_Int, но по большей части они этого не делают. Вместо этого многие
системные вызовы обрабатываются прямо в 32-битовом защищенном режиме с помощью таких VxD,
как IFSMGR, а результаты возвращаются в вызывавшее приложение, точно как описывала в 1987
году Intel.
Расширенный режим Windows всегда имел возможность выбора между имитацией или отраже-
нием вызовов DOS. Отсюда следует, что возможность Windows 95 обрабатывать вызовы операцион-
ной системы без надоедливых обращений к DOS (даже к DOS, которую Windows выполняет в
режиме V86) не является радикальным отходом от предыдущих версий Windows.
Хотя операционная система в Windows 95 включает многочисленные важные улучшения, эта
предположительно “новая” операционная система просто доводит многие возможности расширенного
режима Windows до их логического завершения. Это соответствует действительности, особенно
когда вы имеете в виду WfW 3.11. Здесь Microsoft взяла 32-битовый доступ к файлам из Windows
Глава 10. Как Windows запускает DOS
277
95 и привила его к оставшейся в остальном неизменной базе расширенного режима Windows 3.1. С
разрешенным 32-битовым доступом к файлам Windows избегает многих взаимодействий с DOS.
Однако это было возможно без заимствования всех прочих особенностей Windows 95! Это хороший
показатель того, что Windows 95 не относится к DOS совершенно по-другому, чем предыдущие
версии Windows. В любом случае, это даже хорошо (если только вы не любите нововведений лишь
ради них самих).
Опции управления DOS
В этой главе я неоднократно утверждал: благодаря тому очевидному факту, что Windows запус-
кает DOS в режиме V86, Windows может контролировать DOS. Однако, кроме демонстрации того,
что происходит с инструкциями CLI и STI, выдаваемыми DOS в редких случаях (примерно десять
раз в секунду), когда IOPL=0, я мало говорил о том, как Windows осуществляет этот контроль. В
основном это так, если в реальном режиме определенные инструкции обрабатываются процессором,
в защищенном режиме эти же инструкции обрабатываются VMM. VMM может делать с этими инст-
рукциями все, что хочет. Вот несколько способов, которыми VMM может получить контроль над
DOS, запущенной в режиме V86.
• Как и в защищенном режиме, все вызовы прерываний из режима V86 пропускаются через
IDT. Элемент IDT может содержать шлюз прерываний, который указывает на код уровня 0 в
VMM. Обработчик прерываний VMM может обработать или отразить прерывание — как
сочтет нужным. Таблица векторов прерываний реального режима по адресу 0 запрашивается
> только для отражения прерывания. Это отражение, как показано на примере функции 1683h
INT 2Fh, совершенно не обязательно.
• Все функции ввода-вывода (IN, OUT, INS и OUTS) обращаются к карте разрешения ввода-
вывода (IOPB, не перепутайте с IOPL). Если указанный порт отлавливается, IN/OUT по-
рождает ошибку общей защиты (INT ODh). Обработчик ошибки GP в VMM определяет, что
инструкция IN/OUT попыталась получить доступ к отлавливаемому порту, и вызывает обра-
ботчики, которые VxD установил с помощью функции Install_IO_Handler. Обработчик запро-
сов ввода-вывода может имитировать IN/OUT, запрещать их, игнорировать их, запрашивать
о них пользователя и т.д.
• Как вы видели раньше, когда в режиме V86 IOPL=0, любое выполнение инструкций
PUSHF/POPF, INT/IRET и CLI/STI порождает ошибку GP, перехватываемую VMM.
Заметьте, что при IOPL=0 любое прерывание попадет в VMM как INT ODh, а не как INT п.
Обработчик INT ODh VMM должен декодировать порождающий ошибку код и передать
управление соответствующему обработчику кода. В этой главе мы наблюдали обработчики
VMM для CLI, STI и PUSHF.
• Любая 4-Кбайтовая страница памяти V86 может быть помечена как “отсутствующая”, так что чте-
ние или запись этой страницы генерирует ошибку отказа страницы (page fault), обрабатывае-
мую VMM. VxD может перехватить это событие с помощью функции VMM Hook_V86_Page.
• Многие инструкции в режиме V86 являются недопустимыми, выполнение одной из этих инст-
рукций генерирует исключительную ситуацию недопустимого кода операции (Invalid Opcode,
INT 6) или ошибку общей защиты (INT ODh), перехватываемые VMM. VMM может обраба-
тывать эту исключительную ситуацию любым удобным ему способом. Например, в главе 9
показано, как VMM обрабатывает ошибку GP, порожденную использованием в режиме V86
инструкции MOV в/из специального регистра, например CR0: в результате манипулирования
Client_EIP инструкция превращается в NOP. (На самом деле, VMM может даже изменить в
памяти инструкцию MOV на NOP, и, таким образом, предохраниться от дальнейших исклю-
чительных ситуаций.) В качестве другого примера инструкция ARPL генерирует ошибку INT
6 в VMM. Windows умышленно использует эту нелегальную инструкцию для задания
контрольных точек V86.
278
Неофициальная Windows 95
Заметьте, что в каждом из этих случаев VMM перехватывает управление у программ DOS из-за
того, что встретилось прерывание, исключительная ситуация, ошибка или сработала ловушка. Про-
граммисты привыкли рассматривать программные прерывания как путь для вызова операционной
системы, и если вы когда-нибудь занимались системным программированием на Motorola 680x0,
термин ловушка не приведет вас в ужас. Но программисты обычно рассматривают исключительные
ситуации и системные ошибки как недостатки программы. Как же они могут помочь Windows
контролировать DOS?
Вы должны рассматривать исключительные ситуации и системные ошибки в новом свете. Вме-
сто того чтобы видеть в них что-то плохое, неправильное или непреднамеренное, вы можете рассмат-
ривать их как обеспечение возможности передачи контроля VMM и VxD. Они действительно явля-
ются всего лишь другой формой прерываний, которые могут перехватываться VMM и VxD. VMM и
VxD могут перехватить почти все,- что происходит в DOS. Чтобы уяснить их гибкость в этом отно-
шении, поищите в документации DDK некоторые функции, обеспечиваемые VMM. Каждая из этих
функций устанавливает свой обработчик для событий:
Allocate_V86_Call_Back
Call_When_Idle
Call_When_VM_Ints_Enabled
Call_When_VM_Returns
Hook_Device_V86_API
Hook_V86_Fault
Hook_V86_Int_Chain
Hodk_V86_Page
Install_IO_Handler
Install_Mult_IO_Handlers
Install_V86_Break_Point
Set_V86_Int_Vector
Этот широкий диапазон возможностей разительно отличается от простой DOS, которая всего-
навсего обеспечивает функцию для установки вектора прерываний (функция 25h INT 21h). С дру-
гой стороны, если Windows постоянно использует все эти опции, производительность заметно сни-
жается. Например, как вы видели в прошлой дискуссии, Windows старается сократить исполь-
зование IOPL=0.
В этой главе было подчеркнуто, что способность Windows работать поверх системы реального
режима DOS не умаляет ее статуса как полноценной операционной системы защищенного режима.
Поскольку Windows выполняет DOS в режиме V86 внутри VM, она может управлять DOS. Не
желая уменьшать производительности, Windows не берет на себя полное управление, но такая
возможность имеется.
Главаi ТО. Как Windows запускает DOS 279
Глава 11
Кому нужна DOS?
В предыдущей главе рассказывалось о том, что Windows обращается к DOS в режиме V86, т.е.
DOS используется как подсистема. В этой главе объясняется, почему Windows вообще прихо-
дится обращаться к DOS. Почему и в каких случаях Windows использует DOS? И, что почти
так же важно, почему и каких случаях Windows не использует DOS? Программа V86TEST, о кото-
рой говорилось в предыдущей главе, поможет нам ответить на эти вопросы.
Используя опцию -VERBOSE программы V86TEST, мы убедимся, что Windows обходит боль-
шинство функций DOS и что в этом отношении нет значительных различий между Windows 95 и
предыдущими версиями Windows. Впрочем, в этом нет ничего удивительного: на протяжении по-
следних лет почти все коммерческие DOS-приложения обходят DOS, отказываясь, к примеру,
использовать встроенные в DOS средства вывода на экран и ввода с клавиатуры. С самого начала
Windows, как и большинство DOS-приложений, избегала этого аспекта DOS; кроме того, уже до-
вольно давно Windows предлагает свои собственные средства управления памятью вместо рудимен-
тарных средств, предлагаемых DOS.
Но почти все приложения DOS и Windows полагались на DOS по крайней мере в одном: в фай-
ловом вводе-выводе. Используя 32BFA (32-битовый доступ к файлам), Windows избегает и этого
средства DOS.
Итак, с чем же мы остаемся? Windows, и даже Windows 95, хоть и использует 32BFA, но все
еще иногда обращается к DOS. Так что утверждения, вроде “Если в Chicago запущен VxD VMM32,
обращения к функциям MS DOS целиком обрабатываются VMM32 с помощью совершенно нового
32-битового кода” (.Microsoft Systems Journal, August 1994, p. 29), не совсем справедливы.
И вот почему. Во-первых, “совершенно новый 32-битовый код”, на который ссылается
Microsoft Systems Journal, не является совершенно новым — почти в том же виде он появился в
WfW 3.11. Во-вторых, функции DOS не обрабатываются “целиком” в 32-битовом защищенном ре-
жиме, мы увидим, что некоторые все еще передаются в реальный режим DOS. Как покажет
V86TEST, это касается функций DOS, которые получают и устанавливают текущие PSP, дату и
время и диск по умолчанию. Windows 95 полагается на DOS и в поддержании некоторых структур
данных, таких как CurrentDirectoryStructure (CDS) и SystemFileTable (SFT). Мы также увидим,
что Windows обращается к DOS, когда ей больше нечего делать, т.е. в холостом режиме. Конечно,
как мы знаем из предыдущей главы, даже когда Windows все же осуществляет эти обращения к
DOS, это происходит в режиме V86. Так или иначе, но, как отмечалось в главе 10, нет сущест-
венных различий между Windows, вызывающей DOS, и Windows, не обращающейся к DOS.
Важно пояснить, что я имею в виду под “вызовом DOS”. Если вам придется просматривать код
ядра Windows или приложения вроде Word For Windows, вы можете заметить много вызовов
прерывания INT 21h. Для чтения и записи файлов Word использует функции 3Fh и 40h прерывания
INT 21h. Для загрузки программ ядро Windows использует функцию 4Bh прерывания INT 21h. Но
эти обращения не обязательно являются обращениями к DOS. Если вызов прерывания INT 21h
обрабатывается где-либо в Windows, оно просто имеет удобную и узнаваемую форму команды DOS.
280 Неофициальная Windows 95
Это касается даже вызовов INT 2lh, осуществляемых программами DOS, работающими под Windows.
Если реальный режим DOS не замечает этих вызовов, то они не являются тем, о чем мы сейчас говорим.
Напротив, даже если нечто не выглядит на первый взгляд обращением к DOS, это может быть
именно вызовом DOS. Например, Win32 API обеспечивает такие функции, как CopyFile, CreateFile,
GetFileSize, LockFile, MoveFile и ReadFile. Когда Win32-пpилoжeниe (32-битовое Windows-прило-
жение) обращается к одной из этих функций, и реальный режим DOS в конце концов “видит” экви-
валент вызова INT 2111 DOS, мы будем полагать, что это — вызов DOS. Что существенно, так это,
загружена ли копия DOS (пусть она называется WINBOOT.SYS) до того, как Windows получит
соответствующий вызов. Как мы увидим позднее на конкретном примере, функции GetSystemTime и
GetLocalTime Windows 95 заканчиваются обращениями к функциям 2Ah и 2С11 прерывания INT 21h
MS DOS, чтобы получить значение даты и времени.
Что же на самом деле показывает V86TEST?
Напомню, что цель программы V86TEST — показать, что Windows обращается к DOS в режи-
ме V86. Но причина того, что DOS исполняется в режиме V86, заключается прежде всего в том, что
это дает Windows контроль над DOS. Например, в режиме V86 все прерывания адресуются VMM
через IDT, так что каждое прерывание, исходящее от программ, загруженных до Windows, обраба-
тывается сперва (а зачастую и только) в 32-битовом защищенном режиме.
V86TEST не показывает буквально, что все прерывания под Windows исполняются сначала в
32-битовом защищенном режиме. Мы просто делаем такие выводы на основании того, что V86TEST
показывает на самом деле, т.е. что Windows вызывает DOS в режиме V86, а не в реальном режиме.
В главе 10 упоминалось, что V86TEST не замечает своих собственных обращений к функции 1683h
прерывания INT 2Fh, поскольку они обрабатываются целиком в VMM. Эти вызовы, которые
V86TEST не замечает, — лучший пример того, как прерывания, поступающие от программ, загру-
жённых до Windows, обрабатываются сперва в 32-битовом защищенном режиме.
Все, что может V86TEST, — это замечать вызовы INT 2 lh и INT 2Fh, которые Windows переда-
ет DOS. Единственная цель V86TEST — продемонстрировать, что VMM выполняет DOS в режиме
V86 и, значит, VMM перехватывает и контролирует исходящие от MS DOS прерывания, однако в
некотором смысле программа показывает как раз обратное: VMM и VxD все же иногда возвраща-ют
прерывания обратно в режим V86, даже в тех случаях, когда они не должны этого делать. В дан-
ной и следующей главах мы постараемся извлечь все, что возможно, из этой полезной информации.
Кстати, нет необходимости прибегать к программе V86TEST, чтобы удостовериться, что Win-
dows вызывает DOS в режиме V86. Спецификация DPMI содержит функцию 0400h INT 31h,
которая возвращает информацию о текущей реализации DPMI. Эта информация включает в себя
флаг, который говорит о том, передается прерывание в реальный режим или в режим V86. Эта
функция DPMI в расширенном режиме Windows возвращает флаг режима V86 (см. листинг 6.3 и
рис. 6.2). Однако все это касается того, как Windows работает с окном DOS, а нам все-таки
необходимо загрузить V86TEST до Windows и установить, что отношения Windows-DOS практи-
чески идентичны отношениям Windows—окно DOS.
Важно понимать, что V86TEST соответствует реальйому режиму DOS: если V86TEST замечает
вызов, программа передает его дальше по цепочке к DOS. И если V86TEST не замечает вызова,
DOS тоже не замечает его, за исключением разве только случаев, когда используются основанные
не на прерываниях интерфейсы (например, интерфейс IOCTL, используемый IFSHLP.SYS для
связи с VxD IFSMGR). Итак, V86TEST показывает нам, какие вызовы будут увидены DOS.
Напомню, что V86TEST отлавливает только вызовы INT 21h и INT 2Fh. Если бы она перехва-
тила дополнительные прерывания, такие как INT 13h (обслуживание BIOS жесткого диска), INT 15h
(системный сервис) или INT 28h (оповещение о простое), V86TEST смогла бы обнаружить даже боль-
шее число вызовов, обращенных к программам, загруженным до Windows. (Краткое обсуждение
прерываний, отличных от 21h и 2Fh, см. в разделе “Как насчет вызовов BIOS?” в конце этой главы.)
Глава 11. Кому нужна DOS?
281
Windows за работой?
Вернемся к рис. 10.2. Windows работает чуть больше четырех минут, но успевает сделать более
170000 обращений к прерываниям 21h и 2Fh.
Во время работы Windows: 174712 вызовов INT 21/2F, 174712 в режиме V86
Windows активна 256 секунд
682 вызовов INT 21/2Р/сек.
Наиболее значительным результатом V86TEST, полученным в предыдущей главе, является, ве-
роятно, громадное количество обращений Windows к программам, загруженным до Windows. Сле-
дующие две строчки рис. 10.2 показывают, что почти три четверти (125887 / 174712 = 72%) вызо-
вов, обнаруженных программой V86TEST, были обращениями к VxD (функция 1607h INT 2Fh).
Вызовы INT 2Fh AH=16h, отловленные V86TEST:
00: 2 05: 1 06: 1 07: 125887 08: 1 09: 1 0A: 2 0B: 2 8A: 1
И, как видно из последней строки рис. 11,1, почти все оповещения (broadcasts) исходили от
единственного VxD VMPOLL, который отвечает за обнаружение простоя системы. Похоже, что
Windows тратит большую часть своего времени на ничегонеделание!
Кроме оповещений VxD, V86TEST обнаруживает другие вызовы функции 16h INT 2Fh, вклю-
чая четыре оповещения по инициализации и завершению Windows, рассматривавшиеся в предыду-
щей главе: функции 1600h и 160Ah, которые проверяют, запущена ли Windows; функция 160Bh,
являющаяся оповещением идентификации TSR (обсуждалась в главе 4), и функция 168Ah — вызов
DPMI Get Vendor-Specific API (если кто-либо запрашивает специфичный API поставщика, исполь-
зуя имя, отличное от “MS DOS”, VMM передает этот вызов в режим V86).
Если вы запустите V86TEST с ключом -VERBOSE, программа выведет на дисплей список всех вы-
зовов INT 21h и INT 2Fh, разбитых по номеру функции в АН. (Это напоминает список вызовов INT
2111, выводимый программой COUNTDOS, которая обсуждалась в главе 4, хотя надо заметить, что
интерфейс у программы COUNTDOS лучше, чем у V86TEST.) Для того чтобы продемонстрировать эту
опцию программы V86TEST, я запускаю Clock и около 4 минут провожу поиск в четырех больших
документах WinWord; окно DOS не запускается. Результат этого теста представлен на рис. 11.1.
C:\>v86test -verbose /0:С > v86test.log
C:\>type v86test.log
Перед запуском Windows:
В процессе инициализации Windows:
Во время работы Windows:
При выходе- из Windows:
После выхода из Windows:
91 вызовов INT 21/2F, 0 в режиме V86
5214 вызовов INT 21/2F, 352 в режиме V86
400183 вызовов INT 21/2F, 400183 в режиме V86
13 вызовов INT 21/2F, 10 в режиме V86
12 вызовов INT 21/2F, 0 в режиме V86
Во время работы Windows:
Windows активна 275 секунд
1455 вызовов INT 21/2Р/сек.
I0PL=0 - 4384 вызовов
I0PL=3 - 395799 вызовов
VM #1 - 400183 вызовов
Вызовы INT 2Fh АН= 16h, отловленные V86TEST:
00: 2 05: 1 06 : 1 07: 391147 08: 1 09 : 1 0А: 2 0В: 2 8А: 1
Вызовы INT 21h:
06: 153 09: 1 ОС : 2 00: 256 0Е: 288 19: 81 1А: 409
1С: 1 25: 12 29 : 4 2А: 492 2С: 2760 2F: 9 30: 22 32: 2 33: 6
34: 5 35: 12 ЗВ : 26 ЗС: 6 30: 344 ЗЕ: 245 3F: 1906 40: 3449 *
282- Неофициальная Windows 95
41: 6 42: 1530 43: 29 44: 282 47: 86 48: 8 4А: 3 4В: 3
4С: 7 40: 3 4Е: 85 4F: 316 50: 440 51: 4 52: 10 55: 4 57: 125
58: 7 59: 3 5В: 1 50: 1 5Е: 1 5F: 3 62: 99 65: 3
6С: 1 ОС: 5
Вызовы INT 2Fh:
08: 1 11: 774 13: 4 16: 391158 43: 7 46: 1 4А: 7 7А: 2 FE: 2
Вызовы INT 2Fh АХ= 1607И:
06: 1 ОС: 2 10 : 2 14: 1 15: 5 18: 391133 21: 2 VxD>#0100: 1
Рис. 11.1. Список вызовов INT 21h и INT 2Fh, полученных от Windows; окно DOS не запускалось
Обратите внимание, что хотя Windows много раз обращается к функциям 40h (Write File), 2Ch
(Get Time) и 42h (Move File Pointer), подавляющее большинство обращений производилось к функ-
ции 1607h INT 2Fh с BX=18h. Функция 1607h документирована в DDK как Device Call Out (опове-
щение устройств). VxD могут с помощью этого вызова связываться с программами, запущенными до
Windows; так как V86TEST запускается до Windows и отслеживает эти обращения, мы убеждаемся,
что так оно и есть. Регистр ВХ содержит идентификационный номер (ID) VxD, выдавшего опо-
вещение. Файл VMM.INC, поставляемый с Windows DDK, содержит список ID для VxD, и в нем
18h — номер VMPOLL.
В соответствии с файлом VMPOLL.INC (также включенным в DDK) этот VxD обращается к
функции 1607h INT 2Fh, когда Windows простаивает.
; Интерфейс вызовов Int 2Fh, когда система не занята
; АХ = 1607И
; ВХ = VMPoll_Device_ID
; СХ = VMPoll_Call_Out_Sys_Idle
; Если TSR или драйвер устройства хочет “скушать" холостой вызов,
; он должен установить АХ = 0 и не передавать вызов дальше по
; цепочке.
VMPoll_Call_Out_Sys_Idle EQU 0 ; СХ = О
TSR или драйвер устройства может перехватить этот вызов, чтобы убедиться, что Windows про-
стаивает, а также использовать эту возможность для осуществления какой-либо фоновой обработки.
VMPOLL берет свою функцию, которая выполняет это оповещение, и регистрирует ее с помощью
сервиса VMM Call_When _Idle.
DO. _2F_1607_18:
00246 Push_Client_State
00255 VMMCall Begin_Nest_V86_Exec ;; подготовить VM к вызову
0025В mov word ptr [ebp.Client_EAX],1607h
00261 mov word ptr [ebp.Client_EBX], 18h ;; VMPoll_Device_ID;
00267 mov word ptr [ebp.Client_ECX],0 ;; VMPoll_Call_Out_Sys_Idle ;
00260 mov eax,2Fh
00272 VMMCall Exec.Int ;; использует Simulate_Int
00278 movzx ecx,word ptr [ebp.Client_EAX] ;; “скушали” вызов?
0027С VMMCall End_Nest_Exec
00282 Pop_Client_State
00291 jecxz short IDLE_EATEN
00293 stc ;; VMM вызовет следующий обработчик
00294 retn
IDLE_ EATEN:
00295 clc ;; VMM не вызовет следующий обработч!
Глава 11. Кому нужна DOS?
283
00296 retn
D0_DEVICE_INIT:
01103 mov esi,offset D0_2F_1607_18
01108 VMMCall Call_When_Idle
Когда VMM решает, что система ничем не занята, он вызывает функции из списка вызовов при
простое, например функцию, установленную VMPOLL, или функцию, которую устанавливает VxD
PageSwap для асинхронной записи измененных страниц виртуальной памяти на диск (_PageOut
DirtyPages). VMM решает, что система простаивает, на основании вызовов функции 1689h INT 2Fh
(Windows KERNEL idle). Как объясняет Мэтт Петрек (Matt Pietrek) в главе 6 своей книги Win-
dows Internals, холостой цикл диспетчера KERNEL вызывает функцию 1689h INT 2Fh, определяя
таким образом цепочку событий, которая в конце концов приводит к вызову VMPOLL. KERNEL
также вызывает INT 28h — оповещение о простое DOS, и, как следовало ожидать, программное
обеспечение, загруженное до Windows, замечает почти одинаковое количество вызовов INT 28h и
VMPOLL (см. рис. 11.9 ниже в этой главе).
В результате дизассемблирования VMPOLL видно, что VxD осуществляет эти оповещения,
используя сервисы Begin_Nest_V86_Exec и Exec_Int из VMM; Exec_Int, в свою очередь, использует
вызов Simuhate_Int, которая обращается к загруженной в нижнюю память таблице векторов
прерываний (Interrupt Vector Table — IVT) — в предположении, что ни один VxD раньше не
перехватил прерывание через Hook_V86_Int_Chain, Именно так Windows осуществляет все свои
обращения к DOS. Таким образом, хотя V86TEST и придумана для того, чтобы показать способ-
ность VMM перехватывать прерывания через IDT, на самом деле программа показывает отра-
жение прерываний через IVT.
Но вот что странно, хотя тест на рис. 11.1, казалось бы, проверял работу Windows (были запу-
щены программы Clock и WinWord, который проводил поиск в четырех больших документах),
VMPOLL выдавал более 1000 оповещений о простое в секунду! Эти результаты создают впечат-
ление, будто Windows большую часть своего времени считает ворон. Но это неправда: иногда еще
люди используют Windows для игры в пасьянс.
А если серьезно, эти цифры, вероятно, в самом деле имеют некоторый смысл. За тот же проме-
жуток времени, даже если Windows загружена большим объемом работы, драйвер управления
энергопотреблением POWER.EXE фирмы Microsoft выдает, что процессор простаивал более 25%
всего времени.
Давайте проведем тест еще раз, на этот раз запустив окно DOS. Нет необходимости что-либо
делать в окне DOS (и слава Богу, поскольку очень трудно делать что-либо в окне DOS, когда весь
вывод перенаправляется в V86TEST.LOG). Результат приведен на рис. 11.2.
C:\>v86test -verbose /D:C > v86test.log
C:\>type v86test.log
Перед запуском Windows:
В процессе инициализации Windows:
Во время работы Windows:
При выходе из Windows:
После выхода из Windows:
Во время работы Windows:
Windows активна 187 секунд
1107 вызовов INT 21/2Р/сек.
IOPL=O - 2403 вызовов
I0PL=3 - 204771 вызовов
VM #1 - 197551 вызовов
VM #2 - 9623 вызовов
91 вызовов INT 21/2F, 0 в режиме V86
5214 вызовов INT 21/2F, 352 в режиме V86
207174 вызовов INT 21/2F, 207174 в режиме V86
13 вызовов INT 21/2F, 10 в режиме V86
12 вызовов INT 21/2F, 0 в режиме V86
284
Неофициальная Windows 95
Вызовы INT 2Fh AH’ =16h, отловленные V86TEST:
00: 2 05: 1 06: 1 07: 190292 08: 1 09: 1 0A: 2 0B: 2 8A: 1
Вызовы INT 21h:
02: 93 06: 153 09: 1 0B: 9424 0C: 3 00: 256 0E: 289 19: 84 1A: 409
1С: 1 25: 18 29: 8 2A: 430 2C: 1742 2F: 9 30: 23 32: 2 33: 6
34: 5 35: 12 38: 2 3B: 20 3C: 6 3D: 365 3E: 268 3F: 1907 40: 3454
41: 6 42: 1515 43: 28 44: 319 47: 92 48: 13 49: 1 4A: 6 4B: 4
4С: 7 40: 3 . 4E: 93 4F: 1 50: 355 51: 7 52: 10 55: 4 57: 139
58: 9 59: 3 5B: 1 5D: 3 5E: 1 5F: 3 62: 48 63: 1 65: 4
6С: 1 71: 1 DC: 5
Вызовы INT 2Fh:
08: 1 11: 799 12: 6 13: 4 16: 190303 43: 9 46: 1 48: 1 4A: 7
55: 1 7A: 2 B7: 2 FE: 2
Вызовы INT 2Fh AX=1607h:
06: 1 ОС: 2 10: 2 14: 1 15: 5 18: 190278
21: 2
VxD>#0100: 1
Рис. 11.2. Опись вызовов INT 21h и INT 2Fh из Windows с одним окном DOS
Обратите внимание на большое количество вызовов функции OBh (Проверка статуса клавиа-
туры) INT 21h. Более того, это число примерно соответствует числу вызовов в VM#2.
COMMAND.COM использует функцию OBh для опроса клавиатуры; V86TEST показывает, что
Windows передаете этц вызовы вниз в DOS. Способ, которым VMPOLL обнаруживает незанятую
VM, состоит в перехвате 21h и отлове вызовов функции OBh. VMPOLL и другие VxD могут
заметить вызов функции OBh задолго до того, как их заметят V86TEST или DOS.
Поскольку эти оповещения о простое происходят гораздо чаще, чем любые другие вызовы
прерываний, V86TEST имеет опцию -FILTER для того, чтобы игнорировать их. Вызовы продол-
жают поступать с той же огромной частотой, но они, по крайней мере, уже не вносят неразберихи в
результаты теста. Для действительного устранения этих вызовов можно воспользоваться WINICE
для замены вызова Exec_Int 2Fh в VMPOLL на NOP (см. VMPOLL+272h во фрагменте кода,
приведенного выше) или закомментировать в SYSTEM.INI строку device=*vmpoll — все без нега-
тивных последствий. Хотя управление энергопотреблением на портативных компьютерах может
основываться на оповещении VMPOLL как на сигнале к выдаче HLT для отключения процессора,
на практике, похоже, оповещение VMPOLL не используется. Например, POWER.EXE фирмы
Microsoft ничего не знает об оповещении VMPOLL и вместо этого отслеживает вызовы прерываний,
возвращающих информацию о простое: INT 28h или функции 1680h INT 2Fh.
Другой способ отключения вызовов VMPOLL действительно может нанести непоправимый
ущерб работе Windows. Если вы воспользуетесь стандартным циклом PeekMessage в вашем 16-
битовом Windows-приложении (что рекомендуют многие книги по программированию в Windows,
если необходимо осуществить какой-либо фоновый процесс), то не дадите Windows KERNEL вы-
звать функции 1689h INT 2Fh:
for(;;)
while(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
TranslateMessage(&msg);
DispatchMessage(&msg);
Мэтт Петрек в книге Windows Internals показывает, что PeekMessage предотвращает переход
диспетчера Windows в холостой цикл. Этот цикл ответствен как за проверку простоя Windows
KERNEL, так и за INT 28h, поэтому PeekMessage блокирует осуществление этих вызовов KERNEL.
Но кто знает? В дискуссиях по этому вопросу отмечалось, что PeekMessage может нарушить
управление энергопотреблением на портативных компьютерах, работающих от батареек. Хотя
POWER.EXE фирмы Microsoft ничего не знает о сообщениях VMPOLL, интересный код
Глава 11. Кому нужна DOS? 285
POWER.ASM, включенный в MS DOS OEM Adaptation Kit (OAK), действительно отлавливает INT
28h как часть задачи обнаружения простоя. Кроме того, некоторые TSR полагаются на вызовы INT
28h (см-. Undocumented DOS, 2d ed., р. 564^-565).
Но есть и другая сторона, более важная, чем управление энергопотреблением или друже-
любность к TSR. Как отмечалось, VMM получает вызовы функции 1689h INT 2Fh и использует их
в качестве переключателя для прохода по списку Call_When_Idle. Поэтому, если Windows-прило-
жение находится в цикле PeekMessage, не давая таким образом диспетчеру KERNEL вызывать
функции 1689h INT 2Fh, все процессы из списка VMM Call_When_Idle приостанавливаются. В
одной стандартной конфигурации четыре VxD имеют свои обработчики в списке Call_When_Idle:
IFSMGR, VMPOLL, PageSwap и VTD. Как отмечалось выше, PageSwap, например, использует
свой обработчик для процесса _PageOutDirtyPages. Этот полезный фоновый процесс никогда не
будет работать, пока 16-битовое Windows-приложение находится в цикле PeekMessage.
-j-----------------------------------------------------------------------------------------
Оповещение VxD
Мы видим, что взаимодействие Windows и программ, загруженных до Windows,
несколько шире, чем оповещение “нечем заняться”, выдаваемое VMPoll VxD. Что же можно
сказать о других вызовах VxD, показанных в последней строке рис. 11.1 и 11.2?
Вызовы INT 2Fh AX=1607h:
06: 1 ОС: 2 10: 2 14: 1 15: 5 18: 190278 21: 2 VxD>#0100: 1
В Windows 95 программа V86TEST замечает несколько иной набор вызовов от VxD.
Вызовы INT 2Fh AX=1607h:
06: 1 0d: 1 10: 1 14: 1 15: 3 18: 156133 21: 1 40: 1
Эти вызовы, которые VxD посылают к программам, запущенным до Windows, важны для
понимания взаимодействия между DOS и Windows.
Кроме того, вызовы от VxD дают хорошую возможность изучения исходного кода Win-
dows, предлагаемого с DDK. Хотя DDK не содержит исходного кода для таких важных ком-
понентов, как VMM, IFSMGR, DOSMGR, VFAT, VPICD или V86MMGR, он предлагает исход-
ные коды для драйверов виртуального дисплея (VDD), клавиатуры (VKD), мыши (VMD) и
для таких VxD, как PageFile, PageSwap, INT13 и VDMAD. Преобладающее большинство Win-
dows-программистов, не намеренных переписывать куски кода VDD или VKD (или какого-
либо VxD), могут извлечь пользу от изучения этих кодов. Нет лучшего способа прочувство-
вать, как работает операционная система Windows!
Прежде всего, вызовы от каких VxD (кроме VMPoll) обнаруживает программа
V86TEST? Давайте сравним список идентификаторов VxD, возвращаемых V86TEST, с
именами этих VxD, взятых из DDK:
VxD ID Имя
0006h V86MMGR — Менеджер памяти V86
000Ch VMD — Драйвер виртуальной мыши
000Dh VKD — Драйвер виртуальной клавиатуры
OOlOh BLOCKDEV в 3.1; IOS (супервизор ввода-вывода) в WfW 3.11 и Windows 95
0014h VNETBIOS — Драйвер виртуальной NetBIOS
0015h DOSMGR — DOS-менеджер
0018h VMPOLL — Обнаружение простоя
002 lh PageFile — Запросы к файлу подкачки страниц
0040h IFSMRG (менеджер инсталируемой файловой системы) в Windows 95
>100h IFSMGR в WfW 3.11 (0484h)
286
Неофициальная Windows 95
И другие VxD, кроме вышеперечисленных, могут вызывать функцию 1607h INT 2Fh, но
именно эти обнаруживаются программой V86TEST в стандартной конфигурации WfW 3.11
или Chicago.
Мы только что разобрались с VMPOLL, а в главах 3 и 4 шла речь об API оповещения
DOSMGR. Давайте бросим беглый взгляд и на другие оповещения, обнаруживаемые програм-
мой V86TEST.
В файле V86MMGR.INC, включенном в DDK, оповещение V86MMGR называется
V86CallOut_LclA20forGlblHMA. Это длинное имя означает следующее: состояние адресной
линии А20 должно быть локальным для каждой VM, даже если область верхней памяти
(НМА — High Memory Area) глобальна (т.е. разделяется всеми VM). Это отличается от
обычных вызовов XMS (хотя, возможно, и связано с ними) для локального и глобального
включения и отключения А20. Если какие-либо программы, загруженные до Windows,
реагируют на этот вызов, то это дает тот же эффект, как если бы VxD вызвал процедуру
V86MMGR_SetLocalA20, которая документирована в DDK:
|Для пользователя глобальной НМА состояние А20 связано с НМА и тоже является глобальным.
Изменение А20 в виртуальной машине меняет ее состояние во всех виртуальных машинах одно-
временно. Некоторые пользователи глобальной А20 (такие, как MS DOS 5.0) требуют локального
состояния А20, даже когда НМА глобальна.
Если DOS=HIGH (что определяется вызовом функции 3306h прерывания 21h), DOSMGR в
Windows 3.1 и более поздних версиях автоматически вызывает V86MMGR_SetLocalA20.
VMD (драйвер виртуальной мыши) осуществляет вызовы двух функций, определенных в
файле VMD.INC как VMD_CO_API_Test_Inst и VMD_CO_API_Get_Call_Back. Эти назва-
ния вряд ли чем-то нам помогут, но DDK включает также исходные коды для VMD; исход-
ный файл INT33.ASM показывает в точности, что эти функции делают. Комментарий к проце-
дуре Get_Mouse_Instance в файле INT33.ASM гласит, что оповещение Test_Inst позволяет
DOS-драйверу мыши объявлять свои собственные данные экземпляра (используя интерфейс
функции 1605h INT 2Fh, описанный в главе 3) вместо того, чтобы VMD хранил полностью весь
драйвер мыши. Процедура INT33_Init в файле INT33.ASM посылает оповещение Get_Call_
Back, чтобы DOS-драйвер мыши мог осуществить поддержку мыши в окне DOS под Windows.
Оповещение VKD (драйвера виртуальной клавиатуры) в Chicago еще не документи-
ровано, но краткое исследование MS DOS 7.0 показывает, что WINBOOT.SYS отслеживает
эти оповещения и возвращает указатель на буфер клавиатуры в ES:DI.
VxD BLOCKDEV был введен в Windows 3.1 взамен VHD (виртуального жесткого диска)
и был, в свою очередь, заменен в WfW 3.11 и Windows 95 на IOS (супервизор ввода-вывода).
Оповещения BLOCKDEV, которые также используются IOS, описаны в документации DDK и
в главе 2 книги Джефа Чапелла DOS Internals (р. 76-77). BlockDev_API_Hw_Detect_Start
сообщает TSR и драйверам устройств DOS, что устройство FastDisk (например, WDCTRL)
осуществляет распознавание аппаратного обеспечения (TSR илгйдрайверы могут воспользо-
ваться этим для отключения кэша с отложенной записью); Blo^|cDev_API_Hw_Detect_End
сообщает, что распознавание аппаратного обеспечения завершено.
Намного интереснее этих текстовых описаний оказываются исходные коды, включенные в
DDK. Хотя DDK не содержит исходника BLOCKDEV, DDK предлагает исходный код для
устройства FastDisk WDCTRL, которое использует сервис BLOCKDEV и посылает те же
оповещения, что и BLOCKDEV. Процедура WDCTRL_Real_Mode_Init в файле
WDRMINIT.ASM пытается обнаружить присутствие стандартного контроллера жесткого дис-
ка АТ-типа Western Digital путем мониторинга изменений в регистрах цилиндров после
многократных чтений. Комментарий к этой процедуре гласит:
; Теперь нужно всячески убедиться, что программы кэширования сбросили буферы на диск
; или хотя бы не будут пытаться записывать данные в процессе тестирования. Чтобы
; убедиться в этом, необходимо сделать следующее:
Глава 11. Кому нужна DOS?
287
; Вызвать оповещение BlockDev API INT 2Fh определения аппаратного обеспечения
; Прочитать сектор 0 с помощью INT 13h на обоих дисках
; Сбросить диски от С до Z с помощью функции DOS
; Установить флаг InDOS
Хотя для VxD VNETBIOS документация довольно скудна, DDK для этого VxD предлага-
ет исходный код, и это фактически компенсирует недостаток документации. В комментарии к
файлу VNETBIOS говорится: “Это виртуальное устройство служит двум целям: буферизует
асинхронные сетевые запросы и транслирует вызовы netbios для приложений защищенного
режима”. VNETBIOS.ASM посылает оповещение VxD “для запроса информации о сетевых
расширениях”. Драйвер NetBIOS может возвращать в ES:DI адрес “расширенной таблицы
NETBIOS”, которая сообщает VNETBIOS, как обрабатывать каждый вызов прерываний
VNETBIOS INT 2Ah и INT 5Ch.
При чтении файла VNETBIOS.ASM мы видим, как используются контрольные точки V86
для обслуживания пост-процедуры для вызовов NetBIOS NoWait (без ожидания). Можно
также найти (нигде больше не документированную) установку NoWaitNetIO=true, которая
преобразует все команды NetBIOS в NoWait. Сводка изменений, как и в начале каждого фай-
ла DDK с исходным кодом (почти всегда от Ральфа Лайла (Ralph Line, RAL)), также пред-
ставляет определенный интерес.
; 25-Арг-1988 Полностью переписана сетевая часть (был VND)
; 27-0ct-1988 Изменен для использования нового Get_Crit_Section_Status
; 06-Nov-1988 Перепроектирован для использования отображения вместо буферизации
; 09-Маг-1989 Не загружается без редиректора
; 05-Арг-1989 Действительно работает!
; 07-Арг-1989 Добавлен INT 2F API для получения расширенной таблицы NetBIOS
; 04-Мау-1989 Работает с 32-битовым Buil_Int_Stack_Frame
; 24-Мау-1989 Проверка NetBIOS вместо редиректора
; 12-Jun-1989 Исправлена ошибка работы со страницами
; 01-Sep-1989 Отправляет почту в правильную VM!!!
; 08-0ct-1989 Исправлена ошибка с флагом InDOS
; 29-0ct-1989 Завершены код выхода и документация
; 04-Dec-1989 Исправлена ошибка критической секции для не перехв. NCB
Следующим в списке VxD оповещений, зарегистрированных V86TEST, стоит PAGEFILE,
который управляет файлом подкачки виртуальной памяти (в Windows 95 используется другое
устройство подкачки страниц, называемое DYNAPAGE). И снова DDK содержит исходный
код для этого VxD.
; Другой PageFile не загружается. Получить у Бимбо информацию об
; указателе блокировки кэша
mov ах, (W386_Int_Multiplex SHL 8) OR W386_Device_Broadcast
mov bx,PageFile_Device_ID
xor ex,ex
int 2Fh
or ax,ax ; если AX != О то...
jnx short PF_RI_No_Bimbo ; .. Бимбо не отвечает
; Бимбо ответил на INT 2Fh - указатель на байт блокировки кэша в ES:DI
mov dx,es
shl edx, 16
mov dx,di
jmp short PageFile_RI_Exit
PF_RI_No_Bimbo:
; Бимбо нету, получаем информацию от SmartDrv
Процедура PageFile_Real_Init в файле PAGEFILE.ASM пытается послать вызов IOCTL к
SmartDrv для получения указателя на байт блокировки кэша диска. Однако, если SmartDrv не
загружен, PAGEFILE посылает VxD-оповещение, позволяя другим программам кэширования
диска, отличным от SmartDrv, возвратить в ES:DI указатель на байт блокировки кэша. (Кста-
288
Неофициальная Windows 95
ти, похоже, что “Бимбо” (“Bimbo”) — это имя, которым Microsoft называет любой кэш
диска, отличный от SmartDrv. Это довольно мило, особенно если вспомнить, что на протя-
жении многих лет целая индустрия величала SmartDrv не иначе как DumbDrv.)
В WfW 3.11 V86TEST замечает два вызова PAGEFILE, поскольку VxD VMPOLL (кото-
рый, как отмечалось выше, во время простоя системы выполняет операцию _PageOut
DirtyPages) также пытается получить указатель на байт блокировки кэша диска.
Наконец-то мы добрались до оповещения, посылаемого IFSMGR, — менеджером инстали-
руемой файловой системы — который является базой для 32BFA и длинных имен файлов, Хотя
IFSMGR играет ключевую роль в WfW 3.11, документирован он только в Chicago, и даже
здесь все равно с неполным (по крайней мере, на момент написания книги) файлом IFSMGR.
INC. Но даже без подобающей документации и исходного кода, дизассемблирование IFSMGR по-
казывает, что его оповещение VxD получает размер CDS (структуры текущего каталога) DOS;
это становится очевидным по номерам прерываний 51h в DOS 3 и 58h в DOS 4 и старше.
ОСОАС Push_Client_State
OCDBB VMMCall Begin_Nest_V86_Exec
0С1А6 mov word ptr [ebp.Client_AX],1607h
OC1AC mov word ptr [ebp.Client_BX],484h
0C1B2 mov word ptr [ebp.Client_CX],3
0C1B8 mov word ptr [ebp.Client_DX],1
OC1BE mov eax,2Fh
0C1C3 VMMCall Exec.Int
0C1C9 cmp word ptr [ebp.Client_AX],0B97Ch
OC1CF jne short NO_API_RESPONCE
OC1D1 cmp wor dptr [ebp.Client_DX],0A2ABh
0C1D7 jne short NO_API_RESPONCE
0C1D9 movzx ecx,word ptr [ebp.Client_CX]
OC1DD jmp short GOT_CDS_SIZE
NO_API_RESPONCE:
OC1DF push ebx
OC1EO VMMCall Get_Machine_Info
0C1E6 pop ebx
0C1E7 mov ecx,58h
OC1EC cmp ah,3
OC1EF ja short GOT_CDS_SIZE
OC1F1 sub ecx, 7
GOT_CDS_SIZE:
0C1F4 mov dword ptr CDS_SIZE,ecx
;; 40h в Windows 95
; магическое число DOSMGR
; магическое число DOSMGR
; получить главный номер версии DOS
; 58h = размер CDS в DOS 4+
; версия DOS > 3??
; 58h-7=51h = размер CDS в DOS 3
0С262 VMMCall End_Nest_Exec
0C268 Pop_Client_State
Что делает этот, по существу, обычный код интересным, так это то, что даже в Windows
95 IFSMGR продолжает заботиться о внутренних структурах DOS вроде CDS. Это имеет
определенный смысл (и в любом случае IFSMGR.INC упоминает о различных сервисах,
“обновляющих CDS во всех VM” при появлении в системе нового тома), но он вряд ли
совпадает с заявлением Microsoft “Windows больше не использует DOS”.
Глава 11. Кому нужна DOS?
289
10 Неофициальная Windows 95
Наиболее распространенные
вызовы Windows INT 21 h
Раз мы рассмотрели оповещения VxD, которые Windows посылает программному обеспечению,
загруженному до Windows, давайте теперь сконцентрируемся на вызовах DOS INT 21h, которые
также передаются программам, загруженным до Windows.
Вернемся к рнс. 11.1. V86TEST выводит список вызовов INT 21h, сделанных Windows при
запущенной программе Clock, пока WinWord проводил поиск в четырех больших документах (в 1,7
Мбайт данных, содержащих текст для книги DOS Internals'). На рис. 11.2 мы видели результаты
такого же теста, только при запущенном окне DOS. В обоих случаях 32BFA был отключен (чуть
позже мы повторим тот же тест, но уже с включенным 32BFA). На рис. 11.3 я взял описи INT 21h с
рис. 11.1 и 11.2 и сравнил Их с помощью утилиты diff. Сходство результатов двух тестов дает нам
некоторую уверенность в том, что эти результаты не случайны; различия же показывают влияние
окна DOS.
> 02: 93 06: 06: 153 153 09: 1 09: 1 ОВ: 9424 ОС: ОС: 2 3 0D: 0D: 256 0Е: 288 19 289 19 81 84 1А: 1А: 409 409
256 0Е:
< 1С: 1 25: 12 29: 4 2А: 492 2С: 2760 2F: 9 30 : 22 32: 2 33: 6
> 1С: 1 25: ,18 29: 8 2А: 430 2С: 1742 2F: 9 30 : 23 32: 2 33: 6
< 34: 5 35: 12 ЗВ: 26 ЗС: 6 3D: 344 ЗЕ: 245 3F: 1906 40: 3449
> 34: 5 35: 12 38: 2 ЗВ: 20 ЗС: 6 3D: 365 ЗЕ: 268 3F: 1907 40: 3454
< 41: б 42: 1530 43: 29 44: 282 47: 86 48: 8 4А: 3 4В: 3
> 41: 6 42: 1515 43: 28 44: 319 47: 92 48: 13 49: 1 4А: 6 4В: 4
< 4С: 7 4D: 3 4Е: 85 4F : 316 50: 440 51: 4 52: 10 55: 4 57: 125
> 4С: 7 4D: 3 4Е: 93 4F : 1 50: 355 51: 7 52: 10 55: 4 57: 139
< 58: 7 59: 3 5В: 1 5D : 1 5Е: 1 5F: 3 62: 99 65 : 3
> 58: 9 59: 3 5В: 1 5D : 3 5Е: 1 5F: 3 62: 48 63: 1 65 : 4
< 6С: 1 DC: 5 < - означает без окна DOS
> 6С: 1 71: 1 DC: 5 > - с окном DOS
Рис. 11.3. Сравнение результатов представленных выше тестов по вызовам INT 21Ь
Для рис. 11.4 я отсортировал эти вызовы INT 21h, начав с наиболее часто встретившихся, что-
бы представить основные вызовы DOS, сделанные Windows в этой стандартной несетевой конфи-
гурации с отключенным 32BFA. Там, где запуск DOS из Windows приводит к существенным изме-
нениям, я это отметил специально.
40h Write File or Device
2Ch Get Time
3Fh Read File
42h Move File Pointer (Iseek)
2Ah Get Date
50h Set PSP
1Ah Set DTA Меньше с окном DOS
3Dh Open File
44h I/O Control (IOCTL)
4Fh Find Next File
OEh Set Default Drive
3Eh Close File
ODh Reset Drive
Переключение задач: меньше с окном DOS
Больше с окном DOS
Практически нет с окном DOS???
290
Неофициальная Windows 95
06h Direct Console I/O
57h Get File Date and Time
62h Get PSP
02h Display Character
4Eh Find First File
47h Get Current Directory
19h Get Default Drive
43h Get/Set File Attributes
3Bh Cahnge Current Directory
30h Get DOS Version
Переключение задачи: меньше с окном DOS
Используется только в окне DOS
Рис. 11.4. Основные вызовы Windows INT 21h с отключенным 32 BFA
Что все это значит? Для ответа на этот вопрос давайте сначала взглянем на функции, для кото-
рых запуск окна DOS явно не имеет существенного значения:
• Без 32BFA Windows использует DOS для операций ввода-вывода. Естественно!
• Без 32BFA Windows использует DOS для того, чтобы получить и установить текущие диск и
каталог.
• Windows (в частности Windows-приложение Clock, или applet, как Microsoft называет
небольшие приложения) использует DOS для получения текущей даты и времени. Windows
спрашивает у DOS дату так же часто, как и время. Каждый раз, когда Clock получает
сообщение WM_TIMER, она вызывает функции 2Ah и 2Ch INT 21h. (Это справедливо и для
Win32-BepcHH Clock, но эта программа обращается к функциям DOS в несколько иной ма-
нере; мы вернемся к этому немного позже.)
Запуск окна DOS имеет значение лишь для нескольких функций DOS. Это важный момент, так
как иногда можно услышать заявления, будто Windows связывается с DOS только для выполнения
DOS-программ. Рис. 11.3 и 11.4 демонстрируют, что запуск программ DOS Мало влияет на взаимо-
отношения Windows-DOS. Windows полагается на DOS в определенных сервисных функциях,
запрашиваемых как программами DOS, так и Windows-приложениями. Наоборот, если Windows
может обойтись без DOS при обслуживании сервисных функций Windows-Приложений, то Windows
делает то же самое и для программ DOS. Именно потому, что программа реального режима DOS
выполняется в режиме V86, вызов этой программой прерывания 21h вовсе не означает, что Windows
передаст этот вызов в реальный режим DOS, исполняющийся в режиме V86. Обсудив 32BFA де-
тальнее, мы увидим, что в защищённом режиме Windows может обслуживать многие вызовы INT
21h, исходящие от программ DOS.
Небольшие изменения, действительно происходящие при запуске окна DOS (см. рис. 11.4),
главным образом обнаруживают новое во взаимоотношениях DOS-Windows в целом, нежели в част-
ности характеризуют окна DOS.
> С открытым окном DOS Windows передает вызов функции OBh INT 21h (Проверка стату-
са клавиатуры) в DOS. Но это не означает, что Windows абсорбирует функцию OBh, когда
она приходит от Windows-приложения'. На самом деле ни одно нормальное Windows-при-
ложение не вызывает функцию OBh; это единственная причина того, что эта функция не
представлена на рис. 11.1. COMMAND.COM вызывает функцию OBh для опроса клавиа-
туры, поэтому эта функция попала на рис. 11.2. Если бы Windows-приложение вызывало
функцию OBh, Windows передала бы этот вызов в DOS, как если бы этот вызов послала
DOS-программа вроде COMMAND.COM. (Я проверял это.) Программа DOS может об-
ходить DOS-фунции обслуживания клавиатуры, и многие программы так и делают.
Windows — типичный пример DOS-программы в этом смысле.
Windows-приложения не вызывают функции OBh INT 21h, поскольку обрабатывают ввод с
клавиатуры через Windows-драйвер клавиатуры, чей обработчик INT 09h вызывает
функцию KeybdEvent, которая, в свою очередь, помещает коды клавиш в очередь аппа-
Глава 11. Кому нужна DOS?
291
ратных событий Windows, и они, в конце концов, попадают в процедуру WndProc
Windows-приложения в виде сообщений WM KEYDOWN, WM KEYUP, WM CHAR.
(См. описание KeybdEvent и очереди системных сообщении в книге Undocumented
Windows, глава 6.) Это хороший пример того, что Windows с самого начала обходила
многие DOS-’’сервисы” (если вы хотите называть их именно так).
> Аналогичная история и с функцией 02h (Отобразить символ). Если Windows-приложение
использует этот вызов, то Windows передает его в DOS, как это случается и с вызовами
COMMAND.COM из окна DOS. Windows-приложения используют для вывода на дисплей
другой механизм — Windows GDI , Graphic Device Interface (Интерфейс графического уст-
ройства). Windows-приложения обходятся без DOS в этом плане еще с Windows 1.0. Про-
граммы DOS также могут легко обходить сервисные DOS-функции вывода на экран, и
многие успешно делают это.
> Для функций 50h (SetPSP) и 62h (Get PSP) прерывания INT 21h важнее всего то, что
Windows выдает эти вызовы независимо от присутствия окна DOS. Ядро Windows исполь-
зует Program Segment Prefix (PSP) DOS для переключения между DOS-приложениями
(хотя, как отмечалось в главе 5 книги Undocumented Windows, KERNEL откладывает
вызов Set PSP до тех пор, пока Windows-приложения не осуществят вызовы INT 21h). На
рис. 11.1 и 11.2 ядро переключало задания между WinWord и Clock. 32-битовые Windows-
приложения, даже в Windows 95, пользуются DOS PSP (см. программу WINPSP в главе
13). Примеры вроде PSP показывают, что Windows 95 нуждается в DOS — и обходится
без DOS — почти в той же степени, что и предыдущие версии Windows. Microsoft просто
впервые решила “погреть руки” на возможности, которая существует со времен расши-
ренного режима Windows 3.0.
Странно, что запуск окна DOS на рис. 11.2 приводит к уменьшению количества вызовов
Set PSP и Get PSP, по сравнению с рис. 11.1, на котором представлены результаты тех же
операций, но без окна DOS. Поначалу это может показаться удивительным: вы ожидаете
больше вызовов PSP, поскольку окно DOS — еще одна дополнительная задача. Однако
ядро использует DOS PSP для переключения задач в системной VM, т.е. для 16- и 32-
бито-вых Windows-приложений. Запущенное окно DOS — это отдельная VM, и
переключение задач, осуществляемое VMM, отличается от переключения задач,
осуществляемого ядром. Когда VMM переключается между VM, PSP также должны быть
переключены, но для это-го VMM использует данные экземпляра DOS. (В разделе
“Данные экземпляров DOS и SDA” в главе 4 отмечается, что Windows относит SDA к
данным экземпляра, а в ней-то и содержится DOS-переменная текущего PSP.)
Итак, VMM использует другой механизм для обслуживания PSP по сравнению с Windows
KERNEL. Но почему DOS получает меньше вызовов Set и Get PSP, когда мы запускаем
окно DOS? Да просто потому, что с дополнительной VM системная VM уже не может
обслуживаться так часто, поэтому под-диспетчер в KERNEL реже переключается между
WinWord и Clock.
С запущенным окном DOS также уменьшается количество вызовов функции lAh, Set DTA
(Disk Transfer Address), — причина та же, что и с вызовами Get и Set PSP. Под-диспет-
черу KERNEL приходится переключаться между DTA, когда он переключается между PSP.
С другой стороны, диспетчер VMM полагается на данные экземпляра для обслуживания
DTA так же, как и PSP, синхронно с VM. Когда работают несколько VM, системная VM,
естественно, не может работать так часто, как раньше, следовательно, и под-диспетчеру
KERNEL приходится реже исполнять свои обязанности. Одно важное различие между
вызовами DTA и PSP состоит в том, что с включенным 32BFA Windows осуществляет
обработку вызовов Set DTA без передачи их в DOS. С другой стороны, как мы увидим
позднее, Windows (даже с 32BFA и даже Windows 95) все еще передает вызовы Set PSP и
Get PSP в DOS.
292
Неофициальная Windows 95
> Сравнивая рис. 11.2 и 11.1, можно заметить, что с присутствующим окном DOS Windows,
похоже, посылает в DOS больше вызовов IOCTL (функция 44h INT 21h). Однако это всего
лишь побочный эффект теста: я неумышленно позволил Windows работать немного дольше
в тесте на рис. 11.2 по сравнению с тестом рис. 11.1. Более тщательная проверка показы-
вает, что Windows посылает в DOS одинаковое количество вызовов IOCTL, независимо от
того, исходят ли они от Windows- или DOS-приложений. С отключенным 32BFA два глав-
ных вызова IOCTL, которые Windows передает DOS, — это функции 4408И (использует
ли устройство съемный носитель) и 4409h (является ли диск удаленным).
> Единственным по-настоящему удивительным результатом сравнения рис. 11.1 и 11.2 явля-
ется то, что с запущенным окном DOS (на рис. 11.2) Windows практически не посылает в
DOS вызовов функции 4Fh (Find Next File), тогда как Windows без окна DOS более 330
раз проделала ту же операцию! К счастью, этому есть простое объяснение: когда я запускал
WinWord второй раз, полные имена четырех файлов, которые я хотел открыть (DOS
Internals, Ch. 1-4), уже находились в меню File, сохранившись там после первого теста.
Вместо того, чтобы использовать диалоговое окно File Open, я просто выбрал четыре фай-
ла прямо из меню. Но некоторое время я был озадачен этим “парадоксом”.
Снова возвращаясь в Windows
Последний штрих перед тем, как мы займемся изучением эффектов 32BFA: помните, что даже
когда Windows передает свои вызовы в DOS, зто происходит в режиме V86. Это означает, что даже
те вызовы INT 21h, которые посылаются в DOS, совсем не похожи на старые добрые вызовы INT
21h, которые мы использовали в DOS.
• До того как вызов появится в цепочке прерываний DOS, вызов будет предварительно обрабо-
тан каким-либо VxD, который использовал функцию VMM Hook_V86_INT_Chain (см. главу 8).
• После возврата вызов будет еще обработан каким-либо VxD, который использовал функцию
VMM Call_When_VM_Retums.
• Любые последующие прерывания, осуществляемые внутри DOS, TSR или драйвера устройст-
ва, будут перенаправлены через IDT к VMM, который перешлет прерывания на какой-то
VxD, который вызывал Hook_V86_INT_Chain.
• Один или несколько VxD Windows могут поместить в код контрольные точки. Как говорилось
в главе 8, функция VMM Install_V86_Break_Point записывает по адресу, заданному segment:
offset, инструкцию ARPL, порождающую ошибку GP в VMM, который затем вызывает обра-
ботчик, связанный с этой контрольной точкой V86. VxD могут пользоваться этим сервисом
для контроля кода реального режима.
• Если DOS вызывает драйвер устройства, то в процедурах Strategy и Interrupt драйвера уст-
ройства могут быть поставлены контрольные точки V86. Например, в WfW 3.11 драйвер
отображения реального режима VxD RMM.D32, предоставляющий интерфейс 32-битового до-
ступа к диску (32BDA) He-FastDisk-устройствам, расставляет контрольные точки V86 в проце-
дуре Strategy любого встроенного драйвера устройства.
• Если DOS, TSR или драйвер устройства осуществляют ввод или вывод через порты ввода-
вывода, вы опять-таки можете все это отловить на уровне VMM и VxD. VxD могут пере-
хватить доступ к порту ввода-вывода, используя сервисы VMM Install_IO_Handler и
Install_Mult_IO_Handlers.
• Windows устанавливает некоторые обработчики в цепочку прерываний DOS; эти обработчики
могут использовать различные протоколы для возвращения вызовов обратно в Windows. На-
пример, в главе 8 (в разделе “Роль косвенных вызовов IFSHLP.SYS и V86”) мы видели, что
IFSHLP.SYS использует косвенный вызов V86 для передачи некоторых вызовов из DOS в
VxD IFSMGR.
Глава 11. Кому нужна DOS?
293
Таким образом, внутри какого-либо вызова INT 21h, который Windows передает в DOS, вы
несколько раз можете возвращаться на уровень VMM и VxD до того, как вызов будет окончательно
обработан.
Приведу всего лишь один пример: мы видели, что Windows передает вызов функции 19h
INT 21h (получить диск по умолчанию) в DOS; скоро мы убедимся, что включение 32BFA ничего в
этом не меняет. Но исследование цепочки INT 21h с помощью утилиты INTCHAIN показывает, что
даже “внутри” DOS вызов может передаваться к Windows несколько раз.
C:\UNAUTHW>intchain -a20off 21/1900 >tmp.tmp
C:\UNAUTHW>type tmp.tmp
049Е:0498 IFS$HLP$
01EF:0023 D:
0594:1956 SETVERXX
00A0:0FAC DOS
020E:00C9 XMSXXXXO
0314:1338 $MMXXXX0
3053:0045 win386
3053:004A win386 ARPL
DOA0:1OBE DOS
FE9E.4249 HMA
290D:000A win386
FE9E:433B HMA
2900:0094 win386
FE9E:5356 HMA
0070:0166 10 ARPL
FCA1:1632 ARPL
FE9E:897C HMA
0070:0171 10 ARPL
FFFF:0040 HMA
; IFSHLP не посылает 21/19 в IFSMGR
; заглушка в нижней памяти проверяет А20
; я отключил А20, поэтому DOS вызывает XMS
; ЕММХХХХО отключен
; ктрл. точка V86 -> обработчик V86MMGR XMS
; обработчик DOS INT 21h в НМА
; INT 2Ah AH=82h ->
; -> ктрл. точка V86 -> DOSMGR V86 API -> Begin_Crit
; ктрл. точка V86 RMM.D32 в 10 Strategy
; ктрл. точка V86 VMM
; ктрл. точка V86 (в CON Interupt??)
Здесь я отключил А20 перед вызовом функции 19h для того, чтобы продемонстрировать одну из
контрольных точек V86. Обработчик INT 21h MS DOS проверяет наличие установки DOS=HIGH,
но А20 отключен, и в этой ситуации DOS вызывает функцию 5 XMS (Local Enable А20). Этот код
находится внутри DOS (см. Undocumented DOS, 2d ed., Рис. 6-6); если вы попали туда, то это вы-
глядит в точности как “в DOS”. Однако, как отмечалось в главе 8, VxD V86MMGR предлагает свои
собственные сервисные функции XMS, игнорируя XMS, загруженный под DOS, и использует
Install_V86_Break_Point для корректировки функции XMS, адрес которой возвращается функцией
4310h INT 2Fh. Таким образом, когда обработчик INT 21h DOS вызывает функцию XMS, начинает
выполняться 32-битовый код защищенного режима внутри V86MMGR.
Windows обращается к DOS, чтобы обработать вызов такой тривиальной функции 19h, но DOS
несколько раз обращается обратно к Windows. Это одна из причин того, почему различия между
DOS и Windows настолько зыбки и почему вопрос “Обращается ли Windows к DOS?” кажется
столь бессмысленным. Даже когда Windows и обращается к DOS, над этой DOS Windows осущест-
вляет очень жесткий контроль.
В то же время, намного легче представить себе, что Windows обходит DOS, нежели то, что
Windows вызывает DOS, a DOS затем несколько раз снова обращается к Windows. Так что
давайте теперь рассмотрим 32BFA, при котором, согласно Microsoft, Windows обходится без
DOS. V86TEST поможет нам подтвердить это утверждение, хотя и покажет, в чем Windows до
сих пор полагается на DOS (как мы только что убедились, на DOS, значительно прореженной
контрольными точками V86).
294
Неофициальная Windows 95
Эффект 32-битового доступа к файлам
Я предпринял несколько ошибочных шагов, прежде чем нашел подходящий путь использования
V86TEST для оценки 32BFA. Во-первых, я повторил тест с рис. 11.1 и 11.2, но на этот раз без
ключа /D:C в командной строке (который отключал 32BFA):
C:\UNAUTHW\V86TEST>v86test -verbose win > v86test.log
Однако я обнаружил, что результаты V86TEST практически идентичны результатам теста с
ключом /D:C!
Нет, 32BFA — не фикция. Просто перенаправление стандартного потока вывода Windows
отключало 32BFA, так что удаление параметра /D:C не возымело должного эффекта.
Поскольку я не мог запустить V86TEST до Windows и при этом перенаправить стандартный
вывод, и так как я слишком ленив для изменения программы V86TEST, я попробовал запустить
V86TEST с ключом -QUERY из окна DOS. При этом, естественно, между WIN /D:C и Windows с
32BFA получились значительные различия. Однако эти различия состояли в том, что 32BFA генери-
ровал больше вызовов DOS, чем WIN /D:C!
Получалось, что при включенном 32BFA система оказывалась свободной гораздо чаще и
VMPOLL посылал больше сообщений VxD. Так что я запустил тест снова, используя на этот раз
ключ V86TEST -FILTER, и получил более правдоподобные результаты. В WfW 3.11, как с 32BFA,
так и без него, я открывал и сохранял в WinWord файл размером 340 Кбайт и запускал V86TEST с
ключом -QUERY из окна DOS, перенаправляя вывод в файл. Для сравнения результатов теста с
32BFA и без 32BFA использовалась утилита diff. Результаты представлены на рис. 11.6, к которому
я добавил достаточно много комментариев.
< v86test -filter win /D:С (нет 32BFA)
> v86test -filter win (32BFA)
< означает без 32BFA
> означает с 32BFA
< В процессе инициализации Windows:
> В процессе инициализации Windows:
5203 вызовов INT 21/2F, 344 в режиме V86
5198 вызовов INT 21/2F, 339 в режиме V86
< Во время работы Windows: 25574 вызовов INT 21/2F. 25574 в режиме V86
> Во время работы Windows: 17146 вызовов INT 21/2F, 17146 в режиме V86
Как только убрали VMPOLL, с 32BFA вызовов стало на треть меньше:
25574 - 17146 = 8428 (8428 / 25574 = 32%)
< Windows активна 101 секунд
> Windows активна 77 секунд
Та же самая операция потребовала на 101 - 77 = 24 с меньше с 32BFA (24 / 101 = 23%)
< 253 вызовов INT 21/2F/C
> 222 вызовов INT 21/2F/C
Если учитывать VMPOLL, то 328FA выдавал большее количество вызовов 21/2f в секунду.
< VM #1 - 4007 вызовов
> VM #1 - 438 вызовов
Без VMPOLL в 32BFA практически нет вызовов DOS из системной VM!
Было бы неплохо узнать, что это за оставшиеся вызовы. К сожалению, в списке вызовов INT 21h перечислены
вызовы как системной VM, так и окна DOS (VM tt2). Однако большинство вызовов окна DOS - это вызовы 21/08.
См. WV86TEST в главе 12 для получения списка вызовов, который не включает вызовы окна DOS.
Глава 11. Коллу нужна DOS?
295
< VM #2 - 21569 вызовов
> VM #2 - 16708 вызовов
C 32BFA вызовов DOS намного меньше и из окна DOS: то, что 328FA может делать для Windows-приложений,
то он может делать и для DOS-приложений.
Вызовы INT 2Fh AH=16h, отловленные V86TEST:
< 00: 2 05: 1 08: 1 0А: 2 0В: 2 8А: 1
> 00: 2 05: 1 08: 1 0А: 2 0В: 2 8А: 1
Вызовы INT 21h:
< 02: 149 06: 154 08: 55 09: 1 0В: 21159 ОС: 2 0D: 255 0Е: 289 19: 87
> 02: 151 06: 150 08: 57 09: 1 0В: 16359 ОС: 2 0D: 257 0Е: 289 19: 87
Рис. 11.3 продемонстрировал, что 21/ОВ (проверить статус клавиатуры) используется только в окне DOS.
С 32BFA почти все вызовы из окна DOS (VM #2) были 21/08.
16708 - 16359 = только 349 других вызовов DOS было сделано из окна 00S с 32BFA.
< 1А: 79 25: 17 29: 16 2А: 15 2С: 17 2F: 9 30: 24 32: 2 33: 4 34: 5
> 1А: 19 25: 17 29: 16 2А: .15 2С: 17 2F: 9 30: 24 32: 2 33: 4 34: 5
С 32BFA намного меньше вызовов 21/1А (Set DTA).
< 35: 14 38: 3 ЗВ: 18 ЗС: 4 3D: 307 ЗЕ: 221 3F: 1614 40: 3480 41: 3
> 35: 14 38: 3 ЗВ: 1 ЗС: 1 3D: 110 ЗЕ: 90 3F: 266 40: 3416
С 32ВFA намного меньше вызовов:
21/ЗВ (изменить текущий каталог)
21/3D (открыть файл)
21/ЗЕ (закрыть файл)
21/3F (читать файл)
С 32BFA получается, что большинство вызовов 21/40 (запись в файл) передаются.
Минуточку: сумма-то не сходится
16359 (21/ОВ) + 3416 (21/40) = 19775 вызовов этих двух функций, но в то же время выше говорится, что с
32BFA наблюдалось только 17146 вызовов.
Этому есть простое объяснение:
Данные результаты были сгенерированы старой версией V86TEST, которая не “замораживала” статистику перед
ее печатью. Таким образом, основная масса вызовов 21/40 соответствует вызовам printf в stdout программы
V86TEST. С 32BFA вывод в stdout передается DOS. Стоп: вывод V86TEST был перенаправлен в файл. Нет, опять
вру: я захватил вывод V86TEST в буфере обработки Epsilon. Далее мы убедимся, что в 32BFA большинство
21/40 не передаются DOS.
< 42: 1238 43: 19 44: 244 45: 1 47: 86 48: 13 49: 2 4А: 7 4В: 5
> 42: 226 43: 1 44: 35 45: 1 47: 9 48: 13 49: 2 4А: 7 4В: 5
С 32BFA меньше вызовов:
21/42 (LSEEK)
21/43 (Получить/установить атрибуты файла)
21/44 (IOCTL)
21/47 (Получить текущий каталог)
< 4Е: 70 4F: 23 50: 197 51: 7 52: 10 55: 3 56: 2 57: 114 58: 8 59: 2
> 4Е: 9 4F: 1 50: 196 51: 7 52: 10 55: 3 57: 1 58: 8 59: 2
С 32BFA меньше вызовов:
21/4Е (Найти первый файл)
21/4F (Найти следующий файл)
21/57 (Получить/установить дату/время файла)
296
Неофициальная Windows 95
< 5В: 3 5D: 5 5Е: 1 5F: 3 62: 48 63: 1 65: 4 6С: 2 71: 4 DC: 5
> 5D: 5 5Е: 1 5F: 3 62: 48 63: 1 65: 4 6С: 1 71: 4 DC: 5
Вызовы INT 2Fh:
< 08: 1 11: 692 12: 11 13: 4 16: 9 43: 8 46: 1 48: 2 4А: 6
> 08: 1 11: 392 12: 11 13: 4 16: 9 43: 8 46: 1 48: 2 4А: 6
С 32BFA намного меньше вызовов 2F/11 (сетевой редиректор)
< 55: 1 7А: 2 АЕ: 3 В7: 2 FE: 2 FF: 1
> 55: 1 7А: 2 АЕ: 3 В7: 2 FE: 2 FF: 1
Вызов 2F/FF - это вызов V86TEST, посланный резидентной копии V86TEST.
Рис. 11.6. Запуск V86TEST из WfW 3.11 с 32BFA и без 32BFA
Рис. 11.6 — наглядная демонстрация того, как Windows и DOS на самом деле взаимодействуют
при 32BFA. Он дает большинство ответов на основной вопрос этой главы: почему Windows
нуждается в DOS?
• Кроме оповещений VMPOLL (и вызовов INT 28h, которые, как мы знаем, сопутствуют им),
наиболее часто встречающийся вызов DOS, для которого Windows использует DOS — это
вызов функции OBh (Проверка статуса клавиатуры). Этот вызов нужен только для опроса
клавиатуры в окне DOS.
• Если не считать функцию OBh, то довольно интенсивные операции ввода-вывода (открытие и
сохранение документа в WinWord) сгенерировали всего лишь несколько сотен вызовов DOS.
Без 32BFA те же операции сгенерировали несколько тысяч вызовов DOS.
• Без учета вызовов функции 40h для записи в стандартный поток вывода (которые
появляются только в окне DOS), почти все DOS-вызовы файлового ввода-вывода
обрабатываются в 32-битовом защищенном режиме без передачи в DOS. Рис. 11.7
показывает некоторые вызовы INT 21h, которые обрабатывает 32BFA. Этот список не полон
и просто отражает то, что мы можем получить с помощью V86TEST.
• Даже с 32BFA различные вызовы INT 21h все еще передаются в DOS. Это показано на рис.
11.8; сравните с рис. 11.4, на котором показаны основные вызовы INT 21h без 32BFA.
1Ah Set DTA
3Bh Change Current Directory
3Ch Create File
3Dh Open File
3Eh Close File
3Fh Read File
40h Write File
41h Delete File
42h LSEEK
43h Get/Set File Attributes
44h IOCTL
47h Get Current Directory
4Eh Find First File
4Fh Find Next File
57h Get/Set File Date/Time
71h Long Filename functions
Рис. 11.7. Основные вызовы INT 21h, которые обрабатываются 32BFA в защищенном режиме без обращения к DOS
Глава 11. Кому нужна DOS?
297
OBh Check Keyboard Status
ODh Reset Drive
OEh Set Default Drive
19h Get Default Drive
2Ah Get Date
2Ch Get Time
50h Set PSP
51h, 62h Get PSP
52h Get SysVars (недокументированная)
55h Create PSP
Рис. 11.8. Основные вызовы INT 21Ь, которые 32BFA посылает в DOS
Итак, нет ничего особенно волшебного в 32BFA: это просто набор VxD, среди которых
IFSMGR.386, VCACHE.386, VSHARE и VFAT.386. В WfW 3.11 Microsoft смогла прицепить эти
VxD к практически не изменившемуся VMM. Эти VxD могли бы быть написаны предприимчи-
вым независимым разработчиком. Все возможности для обхода DOS присутствуют в VMM, боль-
шинство — в сервисных функциях Hook_V86_Int_Chain и Set_PM_Int_Vector. 32BFA просто более
интенсивно использует эти сервисы. В целом взаимоотношения Windows-DOS довольно зыбки и
сводятся к тому, как используются сервисные функции VMM. Это просто другой способ выразить
то, что VMM — операционная система.
Как насчет обращений к BIOS?
Со всеми разговорами о взаимоотношениях Windows и MS DOS вполне естественно заинтересо-
ваться тем, как Windows взаимодействует с ROM BIOS. HOOKINT.C из листинга 11.1 — простая
программа, сообщающая о любом использовании приложениями программных прерываний BIOS. Я
также добавил вызовы DOS INT 25h и INT 26h чтения и записи диска и INT 28h обнаружения про-
стоя. Несложно добавить и другие возможности программе HOOKINT.
Листинг 11.1. HOOKINT.C
/*
HOOKINT.C -- Отслеживает вызовы прерываний BIOS
Шульман, 1994
*/
«include <stdlib.h>
«include <stdio.h>
«include <string.h>
«include <process.h>
«include <dos.h>
«include <bios.h>
typedef unsigned short WORD;
typedef unsigned long DWORD;
«pragma pack(1)
typedef struct {
«ifdef __TURBOC__
WORD bp,di,si,ds,es,dx,cx,bx,ax;
«else
WORD es,ds,di,si,bp,sp,bx,dx,cx,ax; /* такой же, как PUSHA */
298
Неофициальная Windows 95
«endif
WORD ip,cs,flags;
} REG.PARAMS;
typedef void (interrupt far *FUNCPTR)(REG_PARAMS);
typedef struct {
FUNCPTR old;
FUNCPTR new;
DWORD count;
DWORD *func;
int intno;
int has_func;
char *str;
} INT.DATA;
extern INT_DATA int_data[];
int check^windows = 0;
int in_windows = 0;
int int_ndx[0x100] = {0} ;
«define INT_HANDLER(rax, intno) { \
INT_DATA *pid = &int_data[int_ndx[intno]]; \
if(in_windows II (!check_windows)) { \
if(pid->has_func) \
pid->func[rax » 8]++; \
pid->count++; \
} \
_chain intr(pid->old); \
}
void interrupt far int10(REG_PARAMS r) { INT_HANDLER(r.ax,0x10); }
void interrupt far int11(REG_PARAMS r) { INT_HANDLER(r.ax,0x11); }
void interrupt far int12(REG.PARAMS r) { INT_HANDLER(r.ax,0x12); }
void interrupt far int13(REG.PARAMS r) { INT_HANDLER(r.ax,0x13); }
void interrupt far int14(REG_PARAMS r) { INT_HANDLER(r.ax,0x14); }
void interrupt far int15(REG.PARAMS r) { INT_HANDLER(r.ax, 0x15); }
void interrupt far int16(REG_PARAMS r) { INT_HANDLER(r.ax,0x16); }
void interrupt far int17(REG_PARAMS r) { INT_HANDLER(r.ax,0x17); }
void interrupt far int1A(REG_PARAMS r) { INT_HANDLER(r.ax,0x1A); }
void interrupt far int25(REG.PARAMS r) { INT_HANDLER(r.ax,0x25); }
void interrupt far int26(REG_PARAMS r) { INT_HANDLER(r.ax,0x26); }
void interrupt far int28(REG.PARAMS r) { INT_HANDLER(r.ax,0x28); }
void interrupt far int33(REG_PARAMS r) { INT_HANDLER(r.ax,Dx33); }
INT.DATA int_data[] = {
// старый, новый, кол-во, функц., прерыв., есть функц., строка
0, intIO, 0, 0, 0x10, 1, “Video",
0, intll, 0, 0, 0x11, 0, “Equipment List”,
0, int12, 0, 0, 0x12, 0, “Memory Size”,
0, int13, 0, 0, 0x13, 1, “Disk”,
0, int14, 0, 0, 0x14, 1, “Serial”,
0, int15, 0, 0, 0x15, 1, “System Services"
0, int16, 0, 0, 0x16, 1, “Keyboard",
0, int17, 0, 0, 0x17, 1, “Printer”,
0, int1A, 0, 0, 0x1A, 1, “Time”,
0, int25, 0, 0, 0x25, 0, “DOS Disk Read”,
0, int26, 0, 0, 0x26, 0, “DOS Disk Write”,
”—T------г--------------
Глава 11. Кому нужна DOS?
299
0, int28. О, О, 0x28, 0, "Idle",
О, int33. О, 0, 0x33, 1, "Mouse”,
} ;
«define NUM_INT (sizeof(int_data) / sizeof(INT_OATA))
FUNCPTR GetSetInt(int intno, FUNCPTR new)
{
FUNCPTR old = _dos_getvect(intno);
_dos_setvect(intno, new);
return old;
}
FUNCPTR prev_2f = (FUNCPTR) 0;
«define WIN_INIT_COMPLETE 0x1608
«define WIN_EXIT_BEGIN 0x1609
void interrupt far int2f(REG_PARAMS r)
{
if(r.ax == WIN_INIT_COMPLETE)
in_windows++;
else if(r.ax == WIN_EXIT_BEGIN)
in_windows--;
chain_intr(prev 2f);
}"
void fail(const char *s) { puts(s); exit(1); }
main(int argc, char *argv[J)
{
int i, j;
INT_OATA *pint;
FILE *f;
if(strnctnp(strupr(argv[1J), “-WIN , 3) == 0)
{
check_windows++;
argv++; argc--;
}
/* установить счетчики функций */
for(i=0, pint=int_data; i<NUM_INT; i++, pint++)
if(pint->has_func)
if((pint->func = (DWORD *) calloc(0x100, sizeof(DWORD))) == 0)
fail(“ Недостаточно памяти” );
/* инициализация int_ndx */
for(i=0, pint=int_data; i<NUM_INT; i++, pint++)
int_ndx[pint->intno] = i;
/* установка обработчиков */
for(i=0, pint=int_data; i<NUM_INT; i++, pint++)
pint->old = GetSetInt(pint->intno, pint->new);
if(check_windows)
prev_2f = GetSetInt(0x2f, int2f);
/* выполнить команду */
if(argc < 2)
300
Неофициальная Windows 95
system(getenv(“COMSPEC”));
else
spawnvp(P_WAIT, argv[1], &argv[1]);
/* восстановить предыдущие обработчики */
if(check_windows)
(void) GetSetInt(0x2f, prev_2f);
for(i=0, pint=int_data; 1<NUM_INT; i++, pint++)
(void) GetSetInt(pint->intno, pint->old);
/* вывести результаты */
if(f = fopen("HOOKINT.LOG", “w”))
printf(“Создание HOOKINT.LOG\n”);
else
f = stdout;
if(check_windows)
fprintf(f, “Во время работы Windows:\n");
for(i=0, pint=int_data; i<NUM_INT; i++, pint++)
if(pint->count)
fprintf(f, “INT %02Xh\t\t%-81u\t\t%s\n", pint->intno, pint->count, pint->str);
fprintf(f,”\n”);
for(i=0, pint=int_data; i<NUM_INT; i++, pint++)
if(pint->has func && pint->count)
{
for(j=0; j<Ox1OO; j++)
if(pint->func[j))
fprintf(f,”%02X/%02X: %lu\t”, pint->intno, j, pint->func[j]);
fprintf(f,”\n” );
}
fclose(f);
return 0;
Если вы запустите HOOKINT без каких-либо параметров в командной строке, то программа
создаст еще одну командную оболочку; когда вы выйдете из нее, HOOKINT выдаст отчет об исполь-
зовании перехваченных прерываний. Вместо того, чтобы направить вывод в стандартный вывод с
последующим перенаправлением вывода в файл, что может привести к недоразумениям (как про-
изошло с программой V86TEST), HOOKINT записывает результаты в файл HOOKINT.LOG.
Вы также можете указать программу, которую запустит HOOKINT, например WIN или
WIN /D:C. Наконец, ключ -WIN в командной строке сообщает HOOKINT о необходимости отсле-
живать прерывания только во время работы Windows (или, по крайней мере, между вызовами
функций 1608h и 1609h INT 2Fh). На рис. 11.9 показан типичный файл HOOKINT.LOG (после
кратковременного запуска WfW 3.11). В этой конфигурации 32-битовый доступ к диску (32BDA)
был включен. Я запустил большое количество Windows-приложений (в том числе WinWord,
Solitaire, 32-битовую Windows-версию FreeCell, ControlPanel, PrintManager и FileManager), но не
запускал окна DOS. Я не использовал ключ -WIN, так что список содержит также вызовы
Windows, сделанные во время инициализации и останова.
INT 10h 160 Video
INT 11h 6 Equipment List
INT 12h 1 Memory Size
INT 13h 2501 Disk
INT 15h 28173 System Services
INT 16h 195 Keyboard
INT 25h 4 DOS Read
INT 28h 499260 Idle
INT 33h 3 Mouse
Глава 11. Кому нужна DOS?
301
10/00: 2 10/01: 1 10/0Е: 100 1Q/0F: 4 10/10: 4 10/11: 6
10/12: 10 10/1А: 1 10/F3: 12 13/02: 2307 13/03: . 10/6F: 2 10/F0: 4 10/F1: 6 191 13/08: 3 10/F2: 3
15/53: 17968 15/87: 16/01: 98 16/11: 1А/00: 9 1А/02: 33/00: 3 2050 15/90: 4067 15/91: 4085 15/СО: 2 15/С1: 1 97 1 ' 1А/03: 1 1А/05: 1
Рис. 11.9. Пример результатов HOOKINT в WfW 3.11
На рис. 11.9 можно заметить несколько важных особенностей. Во-первых, большое количество
вызовов INT 28h; это соответствует тому, что мы видели раньше в оповещениях VMPOLL. Осущест-
вляет эти вызовы INT 28h функция “перепланировщика” в KRNL.386.
Во-вторых, присутствует довольно много вызовов INT 15h, но они не обязательно исходят от
Windows. Функция 53h INT 15h принадлежит спецификации АРМ (Advanced Power Management —
управление энергопотреблением); у меня была загружена программа POWER.EXE фирмы Micro-
soft. Функции 90h и 91h — ловушки операционной системы BIOS Device Busy и Device Post. BIOS
вызывает эти функции, и операционная система — такая, как Windows — может перехватывать их;
VxD IOS может использовать функцию 90h INT 15h как сигнал для вызова процедуры
Enable_VM_Ints. Функция 87h INT 15h — сервис Copy Extended Memory; Windows использует его
во время инициализации-
Другие вызовы, такие как INT 10h — видеосервис, INT 16h — сервис клавиатуры и INT ЗЗЬ —
сервис мыши, запрашиваются гораздо реже, чем в обычном полноэкранном DOS-приложении (како-
вым, по сути, является Windows). Для одного совершенно ненаучного сравнении я запустил из_
HOOKINT полноэкранную DOS-программу, IDE Borland C++, на гораздо более короткий период,
чем я запускал Windows, и получил почти по 200000 вызовов для каждой из функций llh и 12Ь
INT 16h опроса клавиатуры, более 11000 вызовов INT ЗЗЬ для получения статуса мыши и более
1000 вызовов функции 2h INT 10Ь для установки положения курсора. Это довольно сильно отлича-
ется от того, что происходит в Windows.
В-третьих и в-последних, мы подходим к прерыванию 13h чтения и записи на диск. Здесь дей-
ствительно есть о чем поговорить. Начиная с Windows 3.10 Microsoft предоставляет 32-битовый до-
ступ к диску (32BDA), часто называемый FastDisk. Его часто путают с 32-битовым доступом к фай-
лам (32BFA), хотя они сильно различаются: 32BFA исполняет в 32-битовом защищенном режиме
файловые вызовы ввода-вывода DOS, тогда как 32BDA исполняет в 32-битовом защищенном режи-
ме прерывания 13Ь чтения и записи на диск.
Итак, давайте подытожим взаимоотношения Windows и BIOS. Во-первых, Windows-приложе-
ния пятой дорогой обходят сервисы BIOS для видео, клавиатуры, печати и т.д.; как отмечалось
выше, Windows API предлагает свои собственные сервисы. Конечно, некоторые Windows-приложе-
ния все же используют функции BIOS; хороший пример — отладчик CodeView фирмы Microsoft.
Исходный код для драйвера Windows DISPLAY включен в DDK (DDK\286\
DISPLAY\8PLANE\V7VGA\SRC\VGA.ASM) и содержит следующий комментарий:
; почему Windows-приложения вызывают INT 10h, спросите вы. Это делает
; CodeView, поэтому нам необходимо перехватить INT 10h
Во-вторых, исследование исходных кодов, включенных в DDK, показывает, что такие драйверы
устройств Windows, как DISPLAY и KEYBOARD (это Winl6 DRV-файлы, не путайте их с 32-бито-
выми VxD) иногда используют вызовы BIOS, например INT 10Ь и INT 16Ь.
И в-третьих, исследование исходного кода VxD, включенного в DDK, показывает, что многие
; VxD перехватывают прерывания BIOS в V86 и защищенном режимах. VDD (драйвер виртуального
дисплея) перехватывает INT 10h; IOS (супервизор ввода-вывода, называемый в WIN 3.1
BLOCKDEV) перехватывает INT 13h; VKD (драйвер виртуальной клавиатуры) — INT 16h; VPD
302 Неофициальная Windows 95
(драйвер виртуального принтера) — INT 17h; VTD (драйвер виртуального таймера) — INT lAh и
VMD (драйвер виртуальной мыши) — INT 33h.
Например, VKD использует Hook_V86_Int_Chain для захвата INT 16h в режиме V86. Таким
образом VKD замечает INT 16h до любых программ реального режима. VKD также использует
Call_When_VM_Returns для установки “пост-отраженных” ловушек (post-reflected hooks), которые
вызываются при попытке BIOS реального режима выполнить инструкцию IRET.
Зачем VKD перехватывает INT 16h? Обработчик прерывания INT 16h в файле VKD.ASM
объясняет:
i
; ОПИСАНИЕ: Этот обработчик прерывания преобразует блокирующие ,
; вызовы int 16 в функции опроса int 16. Если никакая клавиша
; не была нажата, то текущая VM отдает свой квант времени
; другим задачам.
Push_Client_State
VMMCall Begin_Nest_Exec
mov dh, [ebp.Client_AH]
inc dh ; int 16 0 -> 1, 10 -> 11
I16_Poll_Loop:
mov [ebp.Client_AH], dh
mov eax, 16h
VMMCall Exec.Int
TestMem [ebp.Client_Flags], ZF_Mask ; клавиша готова?
jz short I16_Key_Ready ; Да: сделано
VMMCall Release_Time_Slice
jmp I16_Poll_Loop
I16_Key_Ready:
VMMCall End_Nest_Exec
Pop_Client_Stack
stc ; отразить блокирующий int 16
ret
В другом случае VDDINT.ASM пытается эмулировать (а не отражать) большое количество
вызовов INT 10h, включая функции 2 и 3 (получить и установить позицию курсора), функции 6 и 7
(прокрутка вверх и вниз), функцию 9 (записать символ в позиции курсора) и функцию OEh
(записать TTY). Этот код (DDK\386\VDDVGA\VDDINT.ASM) стоит просмотреть, чтобы по-
нять, как VxD может овладеть не только DOS, но и BIOS. Например:
; Эмулировать вызов GetCursor Position, если возможно
VIlOGetCurs:
call VOD_Int_Can_Emulate jnz VHO_Reflect ; Нет. movzx ecx, [ebp.Client_BH] BEGIN_T ouch_1st_Meg mov ax, word ptr ds:[460h] mov [ebp.Client_CX],ax ; CH,CL mov ax, word ptr ds:[450h+ecx*2] END_Touch_1st_Meg mov [ebp.Client_DX],ax jmp VIlOExitEmYes ; можем эмулировать INT 10h? ; ECX = страница вывода ; получить верхи./нижн. линии курсора ; клиента = тип курсора ; получить координаты ; DH.DL клиента = позиция курсора ; Вызов эмулирован, выход
Глава 11. Кому нужна DOS?303
И последний пример — VFD.ASM перехватывает INT 13h:
Это устройство перехватывает INT 13 и проверяет, сброшен ли старший
бит в номере дисковода (регистр DL). Если бит сброшен, то
вызывается Begin_Critical_Section и запрещается отслеживание порта
таймера. Когда обработчик прерывания INT 13 BIOS возвращается
из прерывания по инструкции IRET, отслеживание порта таймера
разрешается и вызывается End_Critical_Section. Вызовы INT 13,
у которых установлен старший бит в DL, просто отражаются, чтобы
их мог обработать VHD.
; Если вызов INT 13 является командой форматирования (AL=5),
; VOMAO вызывается для запрета ОМА-канала #3. Это делается потому, что
; BIOS программирует канал для больших произвольных передач данных,
; которые никогда не встречаются (контроллер диска практически никогда
; не осуществляет ОМА в процессе форматирования). Отказавшись от
; программирования канала DMA, VOMAD может работать с меньшим
; (более реальным) буфером ОМА.
Все это означает, что даже прерывания BIOS, исходящие от DOS-приложений и от програм-
много обеспечения DOS, загруженного до Windows, будут обрабатываться в 32-битовом защищен-
ном режиме каким-то VxD. В большинстве случаев VxD выполняет предварительную обработку,
отражает прерывание в режим V86 и затем, возможно, выполняет некоторую пост-обработку (через
Call_When_VM_Returns).
Другими словами, Microsoft эффективно перемещает большие части BIOS в VxD 32-битового
защищенного режима. Фактически, это длится уже некоторое время, но, вероятно, мало кто об этом
подозревал, если не считать узкого круга создателей VxD. Например, код VDD для эмуляции, а не
отражения некоторых вызовов INT 10h был написан в 1988 году предположительно для проекта
Windows/386 3.x. Чем больше все меняется, тем больше все остается по-старому.
304
Неофициальная Windows 95
Глава 12
Исследование с помощью
программы WV86TEST
Программа V86TEST из предыдущей главы помогла нам проиллюстрировать ситуации, когда
Windows нуждается и когда не нуждается в DOS. В этой главе мы продолжим изучение
взаимодействия Windows-DOS, расширив несколькими способами возможности V86TEST.
• Использование V86TEST требует либо перенаправления вывода Windows в файл, что отклю-
чает 32BFA, либо запуска V86TEST с ключом -QUERY, что искажает результаты теста по
вызовам INT 21h, исходящим от окна DOS. Windows обрабатывает окна DOS так же, как и
системную VM, в которой выполняются Windows-приложения; но DOS-программы генери-
руют такие вызовы, — например, функции OBh опроса клавиатуры — которых Windows-
приложения не используют. Эти вызовы вносят неразбериху в результаты V86TEST.
От первой проблемы (необходимость перенаправления вывода программы) избавиться доста-
точно просто: заставьте V86TEST записывать результаты в файл на диске, как это сделано в
программе HOOKINT в листинге 11.1.
Решение же второй (наличие окна DOS для запуска V86TEST -QUERY) вполне типичное
для книги о программировании в Windows: написать Windows-версию программы V86TEST —
WV86TEST. И вовсе не ради использования графического интерфейса пользователя в
V86TEST, а потому, что WV86TEST повысит точность регистрации INT 21h.
• V86TEST смешивает вызовы DOS, исходящие от Windows-приложений, от DOS-программ,
запущенных под Windows, и собственно от Windows. При каждом запуске V86TEST мы ви-
дим все вызовы DOS, включая те, которые Windows генерирует во время инициализации.
Хотя эту проблему можно частично решить тем, что V86TEST будет собирать статистику вызо-
вов INT 21h только в период работы Windows (V86TEST.C (см. листинг 10.1) может прове-
рять условие state==WIN_INIT_DONE для начала регистрации вызовов INT 21h), программа
WV86TEST предлагает лучшее решение: пункт меню Show Changes — показать изменения —
позволит нам регистрировать только вызовы DOS от 16- и 32-битовых Windows-приложений.
• Вспомним прошлую главу: V86TEST, по определению, ничего не знает о вызовах INT 21h,
которые не передаются в DOS. И именно потому, что V86TEST не замечает некоторых вызо-
вов INT 21h, нельзя сказать, что Windows эмулирует их в 32-битовом защищенном режиме.
Возможно также, что эти вызовы посылаются неизвестно кем. V86TEST не различает вызо-
вы, которые эмулирует Windows, и вызовы, которые посылаются неизвестно кем.
Для решения последней задачи в следующих двух главах мы воспользуемся WSPY21 — Win-
dows-программой, перехватывающей INT 21h и отображающей вызовы DOS, которые, как ей ка-
жется, генерируют Windows-приложения или некоторые DLL по их требованию. С помощью WSPY21
мы исследуем обращения к DOS, генерируемые некоторыми основными Windows-приложениями.
В этой главе представлен код программы WV86TEST и показано несколько примеров работы с
этой программой. В следующих двух главах основное внимание уделено работе программы Clock в
Windows 3.1 и Windows 95. Сравнение результатов WSPY21 и WV86TEST даст нам лучшее пред-
ставление о том, какие вызовы DOS Windows обрабатывает, а какие нет, в защищенном режиме.
Глава 12. Исследование с поллощью програллллы WV86Test 305
В следующих двух главах мы также предпримем несколько дополнительных экскурсов по
достопримечательностям Windows 95, включая файлы отображения памяти (memory-mapped files),
переключение (thunking) между 16- и 32-битовым кодами и подкачку страниц виртуальной памяти.
Эти экскурсы будут вызваны некоторыми странными результатами тестов WV86TEST и WSPY21.
Разрывает все связи с DOS?
Как показано на рис. 12.1, WV86TEST — это Windows-приложение, которое связывается с
DOS-версией программы V86TEST (см. листинг 10.1), запущенной до Windows.
Рис. 12.1. WV86TEST, запущенная в Windows рядом с 32-битовой Windows-программой Clock, показывает
вызовы INT 21Ь от программы Clock, которые Windows 95 передает в DOS
Например, запустив WV86TEST в Windows 95 и выбрав опции Show Changes (Показать изме-
нения) и Refresh (Обновить), я использовал приложение WordPad, поставляемое с Windows 95, для
чтения .DOC-файла размером 78 Кбайт и сохранения его в формате ASCII. WV86TEST показывает,
как все это выглядит из реального режима DOS:
Прошло 141 секунд
542 вызовов, 542 в режиме V86
3 INT 21/2F вызовов/сек.
I0PL=3 - 542 вызовов
VM #1 - 542 вызовов
306
Неофициальная Windows 95
Вызовы INT 21h:
OE: 138 2A: 25 2C: 16 30: 6 45: 2 50: 348 55: 1 57: 4 5A: 1
Вызовы INT 2Fh:
11: 1
Обратите внимание, что функции открытия, закрытия, чтения и записи файлов INT 21h не
вызываются. В то же время тест показывает значительное количество вызовов функций OEh, 2Ah,
2Ch и 50h INT 21h.
В другом тесте я запустил Calculator, небольшое приложение фирмы Microsoft, проделал неко-
торые вычисления, посмотрел интерактивную справку по Calculator и выполнил Refresh в про-
грамме WV86TEST. Вот как выглядит для DOS примерно двухминутная работа CALC.EXE и
WINHELP.EXE (зарегистрировано Программой V86TEST и выдано программой WV86TEST):
Прошло 138 секунд
200 вызовов, 200 в режиме V86
1 INT 21/2F вызовов/сек.
I0PL=3 - 200 вызовов
VM #1 - 200 вызовов
Вызовы INT 21h:
0Е: 44 2А: 3 2С: 14 30: 2 4С: 2 50: 129 55: 2
Вызовы INT 2Fh:
11: 4
При этом был загружен мой драйвер CURRDRIV.386, который обсуждался в главе 7, так что
функция 19h (Получить текущий диск), которая обычно передается DOS, обрабатывалась этим
VxD в 32-битовом защищенном режиме. Даже с учетом этого не видно слишком большой активности
DOS при работе двух Windows-приложений. DOS замечает два вызова недокументированной функ-
ции 55h (Создать PSP), достаточно много вызовов функции 50h (Установить PSP), два выхода из
программ посредством вызова функции 4Ch и т.д. Однако вызовы файлового ввода-вывода отсутст-
вуют, за исключением разве что четырех вызовов сетевого редиректора (функция 1 lh INT2Flh).
Windows 95, очевидно, обходит DOS для большинства сервисов. Но в этом нет ничего
совершенно нового или специфичного для Windows 95. WfW 3.11 с включенным 32BFA показывает
для аналогичной работы Calculator и WinHelp столь же незначительную активность DOS.
Прошло 185 секунд
59 вызовов, 59 в режиме V86
0 INT 21/2F вызовов/сек.
I0PL=3 - 59 вызовов
VM #1 - 59 вызовов
Вызовы INT 21h:
2А: 2 2С: 2 30: 2 4С: 2 50: 43 55: 2 59: 2
Вызовы INT 2Fh:
11: 4
Таким образом, в способности Windows 95 обходиться без DOS не только нет ничего нового;
более того, в этом совершенно нестрогом тесте WfW 3.11 обходит DOS даже в большей степени,
чем Windows 95. Обратите внимание, что, хотя тест с WfW 3.11 длился несколько дольше, чем тест
с Windows 95, было несколько меньше вызовов Set PSP и совсем не было вызовов функции OEh
(Set Disk).
Но какие вызовы DOS (независимо от того, обрабатываются ли они Windows или DOS) гене-
рируют программы Calc и WinHelp? Запуск WfW 3.11 без 32BFA — один из способов увидеть
Глава 12. Исследование с поллощью програллллы WV86Test
307
активность DOS, которую абсорбирует 32BFA. Здесь приведены результаты того же теста Calc/
WinHelp, но на этот раз под WfW 3.11 с отключенным 32BFA (WIN /D:C):
Прошло 223 секунд
Вызовы INT 21h:
1А: 7 2А: 3 2С: 3 30: 2 ЗВ: 3 30: 20 ЗЕ: 13 3F: 165
42: 131 43: 8 44: 21 47: 10 4С: 2 4Е: 7 50: 39 55: 2
57: 12 59: 3
Вызовы INT 2Fh:
11: 42
Здесь мы видим вызовы DOS файлового ввода-вывода (например, функции 3Dh — открытие
файлов, ЗЕЬ — закрытие, 3Fh — чтение из файла и 42h — перемещение указателя в файле),
которые Calc и WinHelp будут выдавать и с включенным 32BFA, но которые окажутся незаме-
ченными DOS, поскольку в этой ситуации обрабатываются IFSMGR.
В табл. 12.1 представлены результаты трех тестов WV86TEST для Calc.
Таблица 12.1. Результаты тестов WV86TEST для Calc/WinHelp
Функция Windows 95 WfW 3.11 c 32BFA WfW 3.11 без 32BFA
OEh 44 - •-
lAh - - 7
2Ah 3 1 3
2Ch 14 1 3
30h 2 2 2
3Bh - - 3
3Dh - - 20
3Eh - - 13
3Fh - - 165
42h - - 131
43h - - 8
44h - - 21
47h - - 10
4 Ch 2 2 2
4Eh - - 7
50h 129 35 39
55h 2 2 2
57h - - 12
59h - 2 3
Во всех трех тестах вызовы функций 55h (Создать PSP) и 4СЬ (Выход из программы) пере-
даются в DOS. Windows 95 выполняет много вызовов функции OEh (Set Disk) и 2Ch (Получить
время). И в тесте с Windows 95 было сделано наибольшее количество вызовов функции 50h
установки текущего PSP.
308
Неофициальная Windows 95
Все это выглядйт довольно странно в свете заявлений Microsoft о взаимодействии Windows-
DOS. Например:
Chicago — 32-битовая операционная система. Windows больше не является элегантным модным
“прикидом” для старушки MS DOS. Так как вся операционная система заново переработана от начала
до конца, вы получаете мощнейшие возможности, вроде ветвей исполнения, файлов отображения
памяти и асинхронного ввода-вывода.
— Dave Edson, “Seventeen Techniques for Preparing Your 16-bit Applications for Chicago”, Microsoft Systems
Journal, February 1994, p. 15.
Одно из основных правил маркетинга — стараться продавать то, что у вас уже есть сегодня, а
не то, что появится завтра. Учитывая, что приведенный выше отрывок написан, по крайней мере, за
год до начала широкого распространения Windows 95, достаточно странно видеть, как Microsoft не
только продает то, чего у нее не будет даже завтра, но попросту выбрасывает (если вы, конечно, не
считаете, что старушка еще на что-то годна) то, что у нее есть сегодня.
Ключевой фразой здесь является то, что Windows 95 “заново переработана от начала до конца”.
На самом деле Windows 95 построена на основе кода VMM, написанного Ральфом Лайлом (Ralph
Lipe), Аароном Рейнольдсом (Aaron Reynolds) и другими еще в 1988 году. Например, файл
VPICD.H, включенный в DDK, датирован “13-Apr-1988 RAL”, файл VMDA.h — “05-Мау-1988
ARR” и наиважнейший файл VMM.INC датирован “05-Мау-1988 RAL”. Ключевой код VMM
Windows 95 — какой угодно, но только не новый. Но это и хорошо, потому что знакомый черт все
же лучше незнакомого!
Однако вряд ли можно основывать свое мнение на заявлении “заново переработана”, сделанном
в феврале 1994 г. Эта же статья содержит и замечательное опровержение, что “Информация,
представленная здесь, основана на ранней предварительной версии Chicago. Все, абсолютно все, еще
может измениться”. Более того, один из С-файлов, поставляемых вместе со статьей, содержит
поистине восхитительный комментарий:
Перед использованием программы проконсультируйтесь со своим врачом. Батарейки не поставляются.
Она может вызывать сонливость. Разрешена не во всех штатах... Храните ее, как и прочие программы, по-
дальше от детей. Руководство для родителей прилагается. Ответственность за риск, связанный с этим про-
дуктом, полностью лежит на покупателе. (Последняя фраза очень близка к реальному положению дел
на рынке программного обеспечения.) В случае раздражения промойте глаза холодной водой и прокон-
сультируйтесь с врачом. Не застрахована Федеральной корпорацией страхования. ВНИМАНИЕ! Неко-
торые теории квантовой физики утверждают, что если потребитель не исследует этот продукт непосредст-
венно, то продукт прекратит существование или будет существовать только в неопределенном состоянии.
Итак, мы предупреждены. Обратимся теперь к другой статье из Microsoft Systems Journal, в
которой авторы не так основательно отгородились предупреждениями:
Большое отличие во взаимоотношениях Chicago с MS DOS от подобных отношений Windows 3.1
состоит в том, что если вы используете только Windows-приложения, то никогда не выполняете кода
MS DOS.
, — Adrian King, “Windows, An Advance Look at the Architecture of Chicago”, Microsoft Systems Journal, January
1994, p. 18.
В тесте c Calc/WinHelp мы запускали только Windows-приложения (именно для этого нам и
понадобилась WV86TEST), но в табл. 12.1 показано, что Windows 95 по-прежнему обращается к
DOS и почти в той же степени, что и WfW 3.11.
Здесь вы можете заподозрить то, что иногда называют “эффектом Гейзенберга”. WV86TEST
общается с программой реального режима V86TEST. A Microsoft Systems Journal довольно опре-
деленно говорит, что “если вы используете только Windows-приложения, то никогда не выполняете
кода MS DOS”. Возможно, V86TEST, хотя и загруженная до Windows, нарушает эти условия. Дру-
гими словами, вполне возможно, что обнаруживаемой этими программами активности DOS не было
бы, если бы они сами не были запущены.
Я рассмотрю эти замечания более подробно в главе 14, где мы окончательно убедимся, что
Windows 95 обращается к DOS практически в одинаковой степени, независимо от того, запущена
Глава 12. ИбёЛедоваНйё с помощью программы WV86Te$t 309
или нет программа V86TEST. А сейчас позвольте показать “равновесное состояние”, когда запущены
только WV86TEST и V86TEST, и я попытаюсь уверить вас в том, что WV86TEST оказывает незна-
чительное влияние на взаимоотношения Windows-DOS.
Прошло 1 секунд
2 вызовов, 2 в режиме V86
2 INT 21/2F вызовов/сек.
I0PL=3 - 2 вызовов
VM #1 - 2 вызовов
Вызовы INT 21h:
2А: 1 2С: 1
Эти два вызова DOS, которые получают дату и время, появляются при выборе опции Refresh
(обновить) из меню программы WV86TEST. Функция отображения результатов в программе
WV86TEST.C, рассматриваемой ниже в этой главе (листинг 12.2), вызывает функцию получения
времени, которая в свою очередь вызывает функции 2Ah и 2Ch INT 21h. Эти два вызова должны
исключаться из результатов любого теста WV86TEST. Но в тесте Calc/WinHelp мы все же
остаемся (как в WfW 3.11, так и в Windows 95) с вызовами создания PSP, установки PSP, DOS-
выхода и некоторыми другими. Так где же “большое отличие во взаимоотношениях Chicago с MS
DOS”? Цитируемая выше статья Адриана Кинга из Microsoft Systems Journal отвечает на это так.
IB процессе последовательного появления версий Windows каждая из них поддерживала все больше
сервисных функций MS DOS, основанных на прерываниях, и Windows-приложениям все меньше
приходится переключаться в режим V86 и из него для выполнения кода MS DOS. Большим
исключением (вплоть др WfW 3.11) была поддержка сервиса файловой системы.
Это достаточно точная история взаимоотношений Windows и MS DOS. Но в следующем
предложении статьи говорится:
Chicago, наконец, разрывает все связи с кодом реального режима DOS, и, за некоторыми исклю-
чениями, даже существующие 16-битовые Windows-приложения будут обращаться к диску в защищен-
ном режиме через новую подсистему управления файлами.
WV86TEST показывает, что это утверждение почти полностью неверно.
• Простой факт, что Windows 95 посылает вызовы установки PSP даже от Windows-прило-
жений в DOS, говорит о том, что Windows 95 не порвала все связи с реальным режимом
DOS. Я не вижу причин, почему Windows 95 должна разрывать все связи с реальным
режимом DOS, но Microsoft должна прекратить утверждать, что она уже добилась этого.
• “Новая подсистема управления файлами” — не нова. IFSMGR, VFAT, VCACHE и т.д. из
Windows 95 практически не отличаются от IFSMGR, VFAT, VCACHE из WfW 3.11. Это не
должно никого удивлять, поскольку реклама Microsoft на WfW 3.11 утверждала, что ее 32-
битовый доступ к файлам взят из Chicago: “Используя 32-битовую файловую систему из
нашего проекта Chicago, WfW 3.11 на 50% быстрее выполняет задачи с интенсивным обра-
щением к диску” (реклама Microsoft, InfoWorld, January, 10, 1994, р. 55).
32BFA в Windows 95 должен был усовершенствоваться по сравнению с WfW 3.11. В конце
концов, из хвастовства Microsoft о том, что в WfW 3.11 в январе 1994 г. был включен 32-
битовый код из Windows 95 (продукта, который не мог быть закончен по крайней мере еще
год), следует, что WfW 3.11 содержала пред-бета код. И в результате коммерческого вы-
пуска WfW 3.11 как широкомасштабного теста для пред-бета кода Microsoft улучшила
32BFA в Windows 95.
Но кое-какие из этих усовершенствований повлекли за собой восстановление некоторых свя-
зей с DOS, слишком поспешно разорванных в WfW 3.11: Windows 95 даже несколько в
большей мере связана с DOS, чем WfW 3.11. Например, вспомним главу 8 (раздел “Гло-
бальные и локальные перехватчики INT 21h”): WfW 3.11 не отражает файловые вызовы INT
21 h локальным перехватчикам, a Windows 95 — отражает.
3?0
Heo^UMaAbH^WinMdws 95
• Услышав, что “Chicago, наконец, разрывает все связи с кодом реального режима DOS”,
трудно понять, что делать со следующей фразой: “за некоторыми исключениями, даже су-
ществующие 16-битовые Windows-приложения будут обращаться к диску в защищённом
режиме...”. Может, я слишком строг к некоторым написанным в спешке словам, но есть
прямое противоречие между “все” и “за некоторыми исключениями”. Либо Windows 95 раз-
рывает все связи с кодом реального режима DOS, либо нет. WV86TEST только что подтвер-
дила нам, что не разрывает.
Степень отделения Windows 95 от реального режима DOS и так довольно впечатляет, и
Microsoft не нужно ее преувеличивать. Нет нужды также, разрекламировав, что WfW 3.11
содержит значительный кусок проекта Windows 95, разворачиваться на 180° и заявлять, что
взаимоотношения Windows 95 с DOS значительно изменились по сравнению с WfW 3.11.
Кроме того, опыт WfW 3.11 показывает, что разрыв слишком многих связей с реальным
режимом DOS может привести к проблемам совместимости.
Возможно, все, о чем пытались сказать в Microsoft Systems Journal — это то, что Windows 95
сохраняет некоторые связи с DOS для совместимости со старыми DOS-программами и 16-битовыми
Windows-приложениями, но DOS исчезает там, где пользователь запускает только 32-битовые
Windows-приложения. Фраза “за некоторыми исключениями, даже существующие 16-битовые
Windows-приложения будут обращаться к диску в защищенном режиме...”, по-видимому, означает,
что все без исключения Win32-nprnic^einui никогда не покинут 32-битовый защищенный режим.
Однако, как мы еще увидим (в разделе “Explorer из Windows 95 и DOS”), это не совсем верно.
Хотя Win32-пpилoжeния могут обходиться без DOS в большей степени, чем Winl6-приложения,
связи все еще существуют. Win32-пpилoжeния полагаются почти на то же самое, небольшое, но
важное, ядро DOS-вызовов реального режима, что и Winl6-приложения. Нет абсолютно ничего
неправильного в этом доверии к базовому коду, который был проверен на 100 миллионах машин, и
Microsoft должна прекратить потворствовать борцам за чистоту операционных систем, заявляя, что
реальный режим DOS исчез.
WinWord и DOS
На рис. 12.1 я использовал WinWord для открытия и сохранения большого документа в
WfW 3.11 как с включенным, так и с отключенным 32BFA. Затем я записал результаты WV86TEST
в файл и сравнил их с помощью утилиты diff. К сожалению, я пренебрег ключом V86TEST
-FILTER, так что оповещения VMPOLL, которые обсуждались в главе 11 (в разделе “Windows за
работой”), повлияли-таки на результат. Но раз нас здесь интересуют вызовы INT 21h, то это не
имеет значения.
< win /О:С (без 32BFA)
> win (32BFA)
< Прошло 62 секунд
> Прошло 42 секунд
< 41076 вызовов, 41076 в режиме V86
> 47318 вызовов, 47318 в режиме V86
< 662 INT 21/2F вызовов/сек.
> 1126 INT 21/2F вызовов/сек.
< IOPL=O - 339 вызовов
> IOPL=O - 403 вызовов
< I0PL=3 - 40737 вызовов
> I0PL=3 - 46318 вызовов
< - без 32BFA
> - с 32BFA
Глава ^-Исследование с помощью программы WV86Test 3-11
< VM #1 - 41076 вызовов
> VM #1 - 47318 вызовов
Вызовы INT 21h:
< 1A: 28 2A: 14 2C: 16 30: 1 ЗВ: 8 ЗС: 3 3D: 28 ЗЕ: 25
> 2А: 13 2С: 15 30: 1
< 40: 60 41: 2 42: 596 43: 5 44: 42 45: 1 47: 13
> 45: 1 47: 3
< 4Е: 28 50: 150 55: 1 56: 2 57: 14 59: 1 5В: 2 62: 41 DC: 1
> 50: 150 55: 1 57: 1 59: 1 62: 41 DC: 1
Вызовы INT 2Fh:
< 11: 80 16: 39206
> 16: 47090
Вызовы INT 2Fh AH=16h:
< 07: 39206
> 07: 47090
Вызовы INT 2Fh AX=1607h:
< 18: 39206
>18: 47090
Рис. 12.2. Операции File Open/Save в WinWord с включенным и отключенным 32BFA
На рис. 12.2 обратите внимание на то, что одни и те же операции заняли на 20 с м.ныпе при
включенном 32BFA.
< Прошло 62 секунд
> Прошло 42 секунд
Это 32%-ное (20 из 62) улучшение производительности согласуется с выводами InfoWorld
(August 1994, р. 66) из расширенного обзора одноранговых сетевых операционных систем: с
разрешенными 32BFA и 32BDA в WfW 3.11 наблюдалось повышение производительности на 30-
33% по сравнению с запрещенным 32-битовым доступом. WfW 3.11 с 32BFA и 32BDA была про-
изводительнее, чем любая другая из тестировавшихся систем, включая LANtastic фирмы Artisoft.
(Personal NetWare фирмы Novell оказалась на последнем месте, потратив 2 часа 29 минут на
выполнение задачи, занявшей у WfW 3.11 1 час 48 минут без 32-битового доступа и 1 час 23 мину-
ты с 32BFA и 32BDA; LANtastic потратила на ту же задачу 1 час 43 минуты.)
На рис. 12.2 показано, что Windows с включенным 32BFA вызывает режим V86 чаще, но это
целиком благодаря оповещению VxD VMPOLL. Очевидно, что с включенным 32BFA система про-
стаивает гораздо чаще. Обратите внимание, что все вызовы исходят от VM#1; это говорит о том, что
окно DOS не запускалось.
Я не хочу сейчас ничего говорить о подсчете программой WV86TEST вызовов с IOPL=0 (это
обсуждалось в главе 10 в разделе “IOPL и флаг прерываний”). Как говорилось в главе 10, VMM
использует IOPL=0 в режиме V86 как часть сервиса Call_When_VM_Ints_Enabled. Этот сервис
VMM используется прежде всего VPICD для имитирования аппаратных прерываний (таких, как
импульс таймера) виртуальной машиной. Чтобы обеспечить поведение настоящего аппаратного PIC
и чтобы VM выглядела подлинной машиной, VPICD не должен посылать прерывания VM, у
которой они запрещены. VPICD легко может определить, когда у VM запрещены прерывания
(просто проверкой флага [ebp.Cl;ient_FLAGS] ), но как VPICD сможет получить контроль, когда
VM снова разрешит прерывания? Конечно, с помощью функции Call_When_VM_Ints_Enabled.
312 Неофициальная Windows 95
Этот сервис устанавливает IOPL=0, благодаря чему инструкция CLI приводит к ошибке GP. Когда
VMM обрабатывает эту ошибку GP, он вызывает VPICD и восстанавливает IOPL=3.
Сравнивая вызовы INT 21h, зарегистрированные с 32BFA ич без него, составим список тех
функций INT 21h, которые могут обходиться без DOS при включенном 32BFA:
Номер функции Функция
lAh Set DTA
3Bh Set Current Directory
3Ch Create File
3Dh Open File
3Eh Close File
3Fh Read File
40h Write File
41h Delete File
42h LSEEK
43h Get/Set File Attributes
44h IOCTL
4Eh Find First File
56h Rename File
57h Get/Set File Date/Time
5Bh Create New File
Обратите внимание, что это только вызовы, которые Windows может выполнять без DOS.
Отсутствие номера функции вызова в разделе с 32BFA на рис. 12.2 не является доказательством
того, что 32BFA всегда обслуживает эти вызовы без участия DOS. Например, если бы WinWord в
приведенном выше тесте работал с файлами на гибком диске, то в WfW 3.11, — которая, в отличие
от Windows 95, не поддерживает 32BFA для гибких дисков, — программа WV86TEST отобразила
бы передачу Windows многих вызовов INT 2lh файлового ввода-вывода в DOS.
В качестве другого примера рассмотрим функцию 44h (IOCTL) INT 21h. Хотя некоторые
функции IOCTL, например 4408h (Использует ли устройство съемный носитель) или 4409h
(Является ли диск удаленным), могут обрабатываться 32BFA, совершенно очевидно, что должны
быть некоторые специфичные вызовы управления вводом-выводом, которые Windows передает
драйверам устройств DOS.
Другими словами, результаты теста WV86TEST сами по себе говорят о малом. WV86TEST
должна сравниваться с некоторыми другими программами, например с WSPY21, которая показы-
вает, какие вызовы INT 21 h исходят со стороны Windows.
Возвращаясь к рис. 12.2, мы видим, что следующие функции INT 21Ь хотя бы иногда переда-
ются в DOS, даже с включенным 32BFA:
Рис. 12.2 может ввести в заблуждение относительно обработки функции 1 lh INT 2Fh (Сетевой
редиректор). Обратите внимание, что WV86TEST показывает 80 таких вызовов без 32BFA и отсут-
ствие этих вызовов при 32BFA. Казалось бы, это означает, что 32BFA не передает вызовы сетевого
редиректора в режим V86. Но это не так. На самом деле именно в этом конкретном тесте при
включенном 32BFA просто не было сгенерировано ни одного вызова функции 1 lh INT 2Fh. Обычно
сама DOS выдает эти вызовы. Когда же 32BFA обходит DOS, вызовы сетевого редиректора сети так
же обходятся.
Глава 12. Исследование с поллощью програмллы WV86Test
313
Номер функции 2 Ah Функция Get Date
2Ch Get Time
30h Get DOS Version
45h Duplicate File handle
47h Get Current Directory
50h Set PSP
55h Create PSP
59h Get Extended Error Information
62h Get PSP
Dch Novell NetWare: Get Connection Number
Но в случаях, когда 32BFA не может обойти DOS (например, если вы используете дисковод
CD-ROM, управляемый MSCDEX под WfW 3.11), Windows заметит вызовы сетевого редиректора,
сгенерированные DOS и передаст их в режим V86.
Explorer из Windows 95 и DOS
Calc, WinHelp, WordPad и WinWord — все это 16-битовые (Winl6) Windows-приложения.
Поскольку Microsoft подразумевает, что 32-битовые Windows-приложения на самом деле разрывают
все связи с кодом реального режима DOS, я использовал WV86TEST с прекрасным средством Find
File (Найти файл) программы Explorer (Исследователь) в Windows 95, которая является Win32-
приложением (файл CAB32.EXE1 ). Я закрыл все другие программы, кроме WV86TEST. С
помощью команды Task отладчика Soft-ICE/Windows я удостоверился, что ничего не было
запущено, кроме WV86TEST и стандартных программ, которые всегда присутствуют в Windows 95:
: task
TaskName SS:SP StackTop StackBot StackLow TaskOB hQueue Events
BATMETER 0000:0000 00737000 00740000 1EEE 1EFF 0000
CAB32 * 0000:0000 00756000 00760000 1296 207F 0000
TIMER 1307:1F88 00B2 201C ’201C 132F 12EF 0000
MSGSRV32 13EF:327E 00E2 3314 3078 140F 16AF 0000
WV86TEST 27EF:40F8 222E 4198 360A 0A87 1E6F 0000
KERNEL32 012F:1218 0004FD50 0005FD4F 009F 16AF 0000
Из них BATMETER, САВ32 и KERNEL32 — 32-битовые задачи, a MSGSRV32 и TIMER —
скрытые (т.е. не имеющие окна) Winie-задачи, которые Windows 95 загружает автоматически.
MSGSRV32 описан в файле MSGSRV32.EXE как “Windows 32-bit VxD Message Server” и
“Microsoft Windows DOS386 Wshell Server”. Программа TIMER же вызывается из MMTASK.TSK
(“Multimedia background task support module”).
С загруженными программами V86TEST и WV86TEST я использовал систему поиска файлов из.
Explorer (из меню Tools) для локализации тех файлов с расширением *.С на моем жестком диске,
которые содержат строку “foo".
Таких файлов на моем жестком диске было 20. Explorer просмотрел 500 каталогов и прочитал
840 *.С-файлов. WV86TEST показывает, как этот бешеный уровень активности выглядит для ком-
понента DOS реального режима в Windows 95:
1 В последующих версиях файл называется EXPLORER.EXE. — Прим. ред.
314
Неофициальная Windows 95
Прошло 136 секунд
36 вызовов, 36 в режиме V86
О INT 21/2F вызовов/сек.
I0PL=3 - 36 вызовов
VM #1 - 36 вызовов
Вызовы INT 21h:
1А: 2 2А: 3 2С:‘ 3 44: 24 4Е: 1 50: 1
Вызовы INT 2Fh:
11: 2
Прежде чем браться за доказательство того, что \У1К32-приложения в Windows 95 продолжают
обращаться к коду реального режима DOS (“Я еще жива!”), давайте сначала обратим внимание, что
результаты теста WV86TEST просто превосходные: при весьма интенсивных операциях с диском
реальный режим DOS не получил практически ничего.
Между тем, несмотря на пропаганду Microsoft, мы видим, что Chicago (по крайней мере в
этой бета-версии) продолжает использовать DOS для получения даты и времени, для манипу-
ляций с PSP и т.д.
Но есть здесь кое-что и поинтересней: если после завершения поиска оставить окно поисковой
системы Explorer открытым, выполнить Refresh в программе WV86TEST и снова повторить тог же
поиск, WV86TEST зарегистрирует практическое отсутствие активности DOS:
Вызовы INT 21h:
2А: 2 2С: 2 50: 1
Ого! Нелишне вспомнить, что одна пара вызовов функций 2Ah и 2Ch относится к самой
программе WV86TEST. Таким образом, если Explorer уже загрузил систему поиска файлов, то
поиск в 500 каталогах и открытие 840 файлов приводит лишь к трем обращениям к DOS реального
режима. Все остальные вызовы, обнаруженные тестом, относятся к загрузке программой Explorer,
самого модуля поиска файлов.
Более того, вызовы функций 2Ah и 2Ch не имеют никакого отношения к поиску файлов. Если
вы включите опцию Show Clock (Показать часы) в программе САВ32, то при каждом получении
окном Tray Clock WClass программы САВ32 сообщения WM_TIMER (что происходит один раз в
минуту), САВ32 вызывает функцию GetLocalTime. Как мы увидим в главе 13, проверяя Win32-
версию программы Clock, GetLocalTime генерирует вызовы функций 2Ah и 2Ch, которые пере-
даются в реальный режим DOS.
Таким образом, остается лишь единственный вызов функции 50h (Установить PSP) за все вре-
мя работы Explorer с *.С-файлами на моем жестком диске.
Конечно, это один-единственный вызов DOS, но не кажется ли вам, что заявление Microsoft о
том, что “вам никогда не придется выполнять код DOS”, в свете этого вызова выглядит как вы-
ражение “быть чуть-чуть беременной”? В Windows 95 вам придется выполнять некоторый код DOS
реального режима, даже если вы работаете только с Win32-пpилoжeниями.
PSP и другие структуры данных DOS
в Windows 95
Установить PSP — это так близко к DOS, насколько это вообще возможно. Как я, вероятно,
должен был объяснить раньше, PSP (Program Segment Prefix — префикс программного сегмен-
та) — это структура данных DOS, чей дескриптор служит идентификатором процесса DOS (PID).
Сам по себе PSP — это lOOh-байтовая структура (120h байт в Windows 95), содержащая
“состояние” каждого приложения, например, таблицу дескрипторов открытых файлов и сегмент
Глава 12. Исследование с помощью программы WV86Test
315
среды. Большинство операций ввода-вывода в DOS должно выполняться в контексте определенного
PSP. В любой момент времени DOS имеет лишь один текущий PSP; программы изменяют PSP вы-
зовом функции 50h INT 21Ь. Например, резидентная программа DOS, которая активизируется во
время выполнения другой программы, должна вызывать функцию 50h INT 21Ь для изменения
PSP DOS до начала каких-либо операций файлового ввода-вывода (см. Undocumented DOS, 2d
ed„ р. 560-561).
Интересно, что любое Win32-пpилoжeниe, запущенное в Windows 95, имеет не только PID,
один или несколько ID ветвей выполнения и суррогат ID для Winl 6-задач, но и PSP. Это настоя-
щий PSP DOS, расположенный в основной памяти (хотя смещение 2СЬ содержит селектор защи-
щенного режима сегмента среды).
Вы можете убедиться в этом, если запустите в Windows 95 программу WINPSP из главы 13.
Однако, есть все же что-то необычное в том единственном вызове функции 50h (Установить PSP),
обнаруженном WV86TEST. Забегая немножечко вперед, замечу, что, хотя WV86TEST и видит этот
вызов, переданный в реальный режим DOS, программа WSPY21, которая отлавливает вызовы INT
21Ь со стороны Windows, не замечает никого, кто генерировал этот вызов! На рис. 12.3 показано,
какую активность замечает WSPY21 со стороны САВ32 в течение двух минут поиска файлов на
моем жестком диске.
Сниг. « Г Пгг » . KWK'WV.eebi-vtt , .
Set 01й ’ СС8Р
. n ин in> cni't-
.’Di UH Г|м Cijse
кчн ies. t>
UMM?' .44. loin I» Oo > 3 I 05->,
^Сйвзг> i<wi ucn. !«• c»> 0". w з .озм
LS»»U SiJWWWi
г'шнйкмтШ**®*1*6 732 в0йвмв
m (omni в (овэйю
Рис. 12.3. WSPY21 показывает вызовы от оболочки Chicago, которые проходят через Winl6 KERNEL
WSPY21 замечает вызовы INT 21Ь, генерируемые только в исполняемых файлах или в DLL,
для 16- и 32-битовых исполняемых модулей. Если VxD, вызванный 16- или 32-битовым исполня-
емым модулем Windows, сгенерирует вызов INT 2lh, то WSPY21 не заметит его. Это как раз то, что
и случилось с вызовом функции 50Ь (Установить PSP).
316
Неофициальная Windows 95
VxD DOSMGR генерирует вызов установки PSP для файла САВ32. Для того чтобы понять,
зачем это нужно, нам необходима некоторая дополнительная информация.
Расширенный режим Windows всегда передает каждой VM ее собственную копию данных DOS
SDA (Swappable Data Area), которая среди прочего содержит переменную текущего PSP. (См.
раздел “Данные экземпляров DOS и SDA” в главе 4.) Таким образом, несмотря на то, что реальный
режим DOS — это однозадачная операционная система с единственным текущим PSP, волшебство
данных экземпляров (instance data) дает каждой VM свою собственную версию текущего PSP.
Однако все многочисленные Windows-приложения, каждое со своим собственным PSP, выпол-
няются внутри одной системной VM и, таким образом, совместно используют единственную копию
данных переменной текущего PSP DOS в SDA. Единственная переменная текущего PSP в системной
VM тиражируется среди всех 16- и 32-битовых Windows-задач. Данные экземпляров работают
только по принципу “каждой VM” и бесполезны при организации многозадачности внутри си-
стемной VM. Это типичный пример разделения между верхним API-уровнем и нижним VxD-
уровнем Windows.
В Windows 95 обработчик VxD DOSMGR для вызовов INT 21Ь защищенного режима делает не-
что новое: если программа защищенного режима вызывает INT 21Ь из системной VM, то DOSMGR
проверяет, совпадает ли PSP вызвавшей программы с предыдущим PSP. Если не совпадает, то
DOSMGR осуществляет вызов функции 50Ь установки PSP! После возврата DOSMGR переходит к
исходному вызову INT 21h и передает его на дальнейшую обработку. В результате получается, что
вызов INT 21b обрабатывается при правильном PSP. Код довольно интересен:
CALL [Simulate_Iret]
MOVZX EAX,BYTE PTR [EBP.Client_AH]
CMP EAX,+71 ; макс, номер функции INT 21h
JA Too_High
MOVZX EDX,[Xlat_Script_Type+EAX]
MOV EOX,[Xlat_Script_Tab+4»E0X]
CMP EBX,[Sys_VM_handle] ; вызов INT 21h из системной VM?
JNZ Oo_Xlat ; Нет - ничего особенного не делаем
XOR ЕСХ,ЕСХ
CALL [Begin_Critical_section]
PUSH ЕАХ
PUSH ЕСХ
PUSH ЕОХ
MOVZX ЕСХ,WORD PTR [Prev_PSP]
JECXZ PSP_Okay ; первый проход: не переключать
MOV ЕОХ,[Ptr_This_PSP]
MOV ОХ, [ЕОХ]
CMP СХ.ОХ ; тот же PSP, что и прошлый раз?
JZ PSP_0kay
;;; Windows-приложение, вызвавшее INT 21h последний раз, имело другой
;;; PSP, поэтому PSP необходимо переключить.
Switch_PSPs:
MOV EAX,00005000 Set PSP (21/50)
XCHG EAX,[EBP.Client_AX] временно меняем вызов на 21/50
XCHG EDX,[EBP.Client_BX] помещаем новый PSP в ВХ
CALL Nested_V86_INT21 вызов DOS (см. ниже)
XCHG' EDX,[EBP.Client_BX] восстановить прежний ЕВХ
MOV [Prev_PSP],DX сохранить до следующего раза
MOV [EBP.Client.EAX], EAX восстановить изначальный вызов
PSP_0kay:
CALL [V86MMGR_Xlat_API]
уже здесь PSP правильный
Глава 12. Исследование с поллощью програмллы WV86Test
317
JMP [End_Critical_Section]
Db_Xlat:
JMP [V86MMGR_Xlat_API]
Too_High:
MOV EDX,C02C0F70
JMP C022016D
Nested.V86_INT21:
PUSH EAX
CALL [Begin_Nest_V86_Exec]
MOV EAX,00000021
CALL [Exec.Int]
CALL [End_Nest_V86_Exec]
POP EAX
RET
В Windows 3.x 16-битовая DLL KRNL386 делает нечто похожее для предохранения вызовов из
16-разрядных Windows-приложений от использования PSP других Winl6-пpилoжeний. У KERNEL
есть свой собственный обработчик INT 21Ь защищенного режима, который вызывает
NoHookDOSCall для функции 50h во время первого вызова INT 21 h после переключения задач (см.
Undocumented Windows, р. 346—347). Почему KERNEL просто не переключает PSP вместе с
переключением задач? Мэтт Петрек дает неплохое объяснение этому в книге Windows Internals,
(р. 422).
Теперь RescheduleO переходит к следующей задаче... Заметьте, однако, что функция 50h INT 21h не
вызывается для переключения того, что DOS воспринимает как текущий PSP... Поскольку Windows так
сильно полагается на PSP и файловый ввод-вывод DOS [который был написан до WfW 3.11], вы,
наверное, думаете, что Windows обращается к DOS для изменения текущего PSP каждый раз, когда
происходит переключение задач. Как оказывается на самом деле, KERNEL откладывает переключение
PSP до тех пор, пока это не станет действительно необходимым, например, при операциях файлового
ввода-вывода. Переключение PSP требует перехода к DOS и является довольно медленным процессом.
Если переключение на конкретную задачу происходит только для того, чтобы послать сообщение с
помощью SendMessage, то было бы слишком неэффективно вызывать для такой задачи DOS. Таким
образом, KERNEL задерживает переключение текущего PSP до тех пор, пока это не станет неизбежным,
что может привести к некоторым проблемам, так как текущий PSP DOS может не соответствовать
текущему TDB в Windows.
Таким образом, этот механизм уже многие годы является частью KERNEL. Зачем же тогда
Windows 95 отслеживает PSP в DOSMGR вместо того, чтобы просто продолжать использовать код
KERNEL? Да потому, что Win32-nporpaMMbi в Windows 95 не отлавливаются обработчиком INT 21h
из KERNEL. Win32-пpилoжeния не могут осуществлять прямых вызовов INT 21Ь и должны вместо
этого просить VxD VWIN32 осуществить эти вызовы для них (см. “Win32-o6pa6oTKa файлов и
шлюзы” и пример WIN32 PSP.C в главе 14). Таким образом, отслеживание PSP должно про-
исходить в VxD.
Этот код отслеживания PSP в DOSMGR, судя по всему, объясняет, почему WV86TEST замеча-
ет в Windows 95 больше вызовов, установки PSP, чем в WfW 3.11. С другой стороны, важно пони-
мать, что DOSMGR не переключает PSP для каждого вызова INT 21Ь. Это делается только для тех
вызовов INT 21Ь, которые передаются в реальный режим DOS. Однако это не является обдуман^
ным решением DOSMGR. Если DOSMGR замечает вызов INT 21h, то этот вызов будет передан в
реальный режим DOS. Причина этому проста: IFSMGR перехватывает цепочку перехватчиков V86
INT 21Ь после DOSMGR и поэтому видит вызовы INT 21h до DOSMGR. Когда IFSMGR решает
обработать вызов в защищенном режиме, его обработчик сбрасывает флаг CF до возврата в VMM.
Как объясняется в документации DDK Для Hook_V86_Int_Chain, когда VxD-перехватчик преры-
вания V86 сбрасывает флаг CF, VMM больше не передает прерывание никакому другому VxD:
318
Неофициальная Windows 95
Если процедура перехватчика обслуживает прерывание, она должна сбросить флаг CF для предот-
вращения передачи прерывания следующему обработчику.
Таким образом, когда IFSMGR решает обслужить вызов в защищенном режиме, он не только
предотвращает обработку вызова в реальном режиме DOS, но и в любом VxD, который установил
свой обработчик INT 21Ь до IFSMGR. Фактически, обработка 32BFA вызовов INT 21h в защи-
щенном режиме и является следствием решения IFSMGR не передавать эти вызовы предварительно
загруженным VxD, например DOSMGR и VMM, которые могли бы отразить эти вызовы В ре-
альный режим DOS.
Поскольку IFSMGR отбирает все вызовы INT 21Ь, которые предполагается обслужить в защи-
щенном режиме, DOSMGR выполняет Set PSP только для тех вызовов INT 21Ь, которые пере-
даются в DOS. В этом случае блокирование IFSMGR вызовов INT 21Ь от других VxD продумано
хорошо. В других же случаях могут произойти непредвиденные осложнения.
Например, перехватчик DOSMGR INT 21h также отслеживает DOS-вызовы файлового ввода-
вывода для поддержки возможности FileSysChange в Windows. Если в файле SYSTEM.INI
установлено FileSysChange=On, то DOSMGR отправляет извещения о создании, удалении и т.д.
файлов и каталогов в Windows File Manager или в любую другую программу, которая вызывала
функцию FileCdr (см. Undocumented Windows, гл. 4 и WM_FILESYSCHANGE в гл. 6). Но есть
одна проблема: с включенным 32BFA установка FileSysChange=On не дает эффекта, поскольку
при этом IFSMGR не передает относящиеся к делу вызовы INT 21Ь в VxD (такие, как DOSMGR),
которые вызвали Hook_V86_Int_Chain до него. Порядок VxD в цепочке перехватчиков прерываний
V86 может привести к огромным различиям. (Это похоже на зависимость от порядка загрузки TSR
DOS, что нередко случалось в ранних 80-х годах). То, что IFSMGR вытесняет FileSysChange, явля-
ется одним из непреднамеренных эффектов 32BFA. A 32BFA, в свою очередь, — просто побочный
эффект способности функции Hook_V886_Int_Chain скрывать прерывания от предварительно за-
груженных VxD.
Но вернемся к теме PSP. Windows 95 имеет дополнительный механизм для манипуляций с PSP.
Поскольку средства функций DOS получения и установки PSP (см. Undocumented DOS, 2d ed.,
р. 287-288) настолько просты и переменными DOS легко манипулировать через VxD (см.
CURRDRIV.386 в главе 8), не удивительно, что VMM в Windows 95 имеет функцию
Get_Set_Real_DOS_PSP.
Вход:
АХ = PSP (при установке)
ЕСХ = 0 (Get PSP), 1 (Set PSP)
EBX = дескриптор VM
VMMCall Get_Set_Real_DOS_PSP
Выход:
(EDX - разрушен)
AX = PSP (при получении)
Эта сервисная функция (также недокументированная) активно используется VxD VWIN32.
VWIN32 вызывает сервис VMM Call_When_Task_Switched, передавая ей адрес функции. Когда бы
VMM ни переключал задачи, он вызывает эту функцию VWIN32, переключающую PSP:
CALL_WHEN_TASK_SWITCHED_PROC: ;; VWIN32+936
xor есх,есх
VMMCall Get_Sys_VM_Handle
VMMCall Get_Set_Real_DOS_PSP
mov [esi+56h],ax
mov ax,word ptr [edx+56h]
mov ecx,1
VMMCall Get_Sys_VM_Handle
VMMCall Get_Set_Real_DOS_PSP
VMMCall Contextswitch
Глава 12. Исследование с поллощью программы WV86Test
319
Функция Get_Set_Real_DOS_PSP не манипулирует непосредственно переменной DOS текущего
PSP, которая размещается по смещению 10h в SDA. Вместо этого функция манипулирует копией
данных SDA в VM. При переключении на VM обработчик отказа страницы VMM копирует данные
экземпляра, включая переменную текущего PSP, в нижнюю память DOS. Когда Windows 95 меняет
контекст, она должна сообщить об этом DOS. SDA как раз и была создана для этой цели: постро-
ение многозадачной операционной системы поверх тонкого слоя однозадачной DOS. Как разъяс-
нялось в разделе “Данные экземпляров DOS и SDA” главы 4, SDA DOS и данные экземпляров
Windows идут рука об руку.
VMM дополняет код 32-битового защищенного режима функцией Get_Set_Real_DOS_PSP для
непрямого манипулирования данными DOS. Такие манипуляции данными реального режима DOS
из 32-битового кода VxD — обычное дело в Windows 95. Например, во время инициализации
IFSMGR Windows 95 создает в защищенном режиме указатели на некоторые ключевые внутренние
структуры данных DOS, включая SDA и SysVars (также известную как “список списков”). Это ин-
тересное исследование недокументированного кода DOS заслуживает внимания:
;; фрагмент инициализации IFSMGR Windows 95
;; Использование функции 34h INT 21h {Get InDOS flag) для
;; получения линейного адреса DOS SDA. LIN_SDA повсюду
;; используется в IFSMGR.
50436 mov byte ptr [ebp.Client_AH],34h
5043A mov eax,21h
5043F VMMCall Exec_Int ; Вызов 34h INT 21h (Get InDOS flag)
50445 movzx ecx,word ptr [ebp.Client_ES]
50449 movzx eax,word ptr [ebp.Client_BX]
5044D dec eax ; InDOS ptr - 1 = SDA ptr!
5044E shl ecx,4 ; ECX = InDOS seg « 4 = лин.адр. данных DOS
50451 add eax,ecx ; лин.адр. = (seg « 4) + ofs
50453 mov LIN_SDA,eax ; лин.адр. SDA (no IFSMGR 3908h)
;; Теперь сложить лин.адр. данных DOS с некоторыми смещениями
;; (см. Undocumented DOS, 2d ed., р. 730-733)
;; Заметьте, что SDA находится по DOS + 320h
50458 add dword ptr CURR_SFT_PTR,ecx ; 39DCh = 59Eh SDA+27E
5045E add dword ptr SHARE_NET_PSP_PTR,ecx ; 39E0h = 33Ch SDA+1C
50464 add dword ptr FFIRST_SEARCH_ATTR_PTR,ecx; 39E4h = 56Bh SDA+24B
;; Получить указатель на SysVars (список списков) с
;; помощью недокументированной функции 52h INT 21h
5046А mov byte ptr [ebp.Client_AH],52h
5046E mov eax,21h
50473 VMMCall Exec_Int
50479 movzx ecx,word ptr [ebp.Client_ES]
5047D movzx eax,word ptr [ebp.Client_BX]
50481 shl ecx, 4
50484 add eax,ecx
50486 mov KIN_SYSVARS,eax
;; Получить указатель на счетчик DOS количества системных FCB
5048В movzx ecx.word ptr [eax+1Ah] ; SYSVARS+1Ah = сегм. таблицы FCB
5048F cmp cx.OFFFFh
50493 je short NO_FCB_TAB
50495 movzx eax,word ptr [eax+1Ch] ; SYSVARS+1Ch = смещ. таблицы FCB
50499 shl eax, 4
5049C add eax,ecx ; ЕАХ = лин.адр. сист. таблицы FCB
5049E movzx eax,word ptr [eax+4] ; табл. FCB + 4 = «файлов
504A2 mov DOS_FCB_COUNT,eax
320
Неофициальная Windows 95
IFSMGR использует сервис VMM Exec_Int для вызова функции 34h INT 21h, которая возвра-
щает указатель на флаг InDOS. Эта функция, имеющая большое значение при программировании
TSR, не фыла документирована вплоть до DOS 5.0. Для IFSMGR состояние флага InDOS не
представляет интереса. IFSMGR просто использует тот факт, что флаг InDOS расположен в SDA по
смещению 1. Этот недокументированный способ использования документированной функции (Get
InDOS Pointer — получить указатель InDOS) для доступа к SDA предпочтительнее использования
недокументированной функции 5D06 (Получить SDA), созданной специально для этой цели. Как
это видно из приведенного выше кода, IFSMGR получает указатель реального режима на флаг
InDOS, возвращается на один байт и затем, используя стандартную формулу (линейный адрес =
(сегмент* 16) + смещение), конвертирует полученное значение в линейный 32-битовый указатель
SDA в защищенном режиме.
IFSMGR также берет сегмент, возвращаемый функцией 34h в ES, умножает на 16 и добавляет
полученный линейный адрес к некоторым переменным. Эти переменные уже содержат смещения в
сегменте данных DOS. Например, IFSMGR основывается на том, что SHARE_NET_PSP находится
в сегменте данных DOS по смещению ЗЗСЬ и не заботится о том, что это является смещением ICh в
SDA (которая начинается со смещения 320h в сегменте данных DOS). Поскольку флаг InDOS рас-
положен в сегменте данных DOS, прибавление линейного адреса к этим смещениям дает указатели
защищенного режима на другие внутренние данные DOS.
Дальше IFSMGR получает линейный указатель как на внутреннюю структуру данных DOS
SysVars, так и на DOS-счетчик системных FCB.
Установив LIN_SDA и другие указатели 32-битового защищенного режима на данные реального
режима DOS, IFSMGR может получить доступ к данным в сегменте DOS при первой же необхо-
димости. Эти переменные достаточно широко используются по всему IFSMGR.
Это легко обнаружить с помощью отладчика WINICE. WINICE позволяет вам установить конт-
рольные точки на выполнение кода (используя команду ВРХ), на прерывания (команда BPINT), на
доступ к порту ввода-вывода (команда BPIO) и на поток сообщений Windows WM_XXX (команда
BMSG). Отладчик позволяет вам также установить контрольные точки, которые будут срабатывать
сразу же после того, как произойдет чтение-запись ячейки или области памяти (команды ВРМ и
BPR). WINICE использует отладочные регистры 386-го процессора, так что эти контрольные точки
не замедляют (существенно) работу системы.
В Windows 95 на моей машине функция 34h INT 21h возвращает 00А0:0321, так что SDA распо-
ложена по адресу 00А0:0320 и переменная LIN_SDA IFSMGR таким образом имеет значение (OOAOh
* 10h) + (32lh - 1) = AOOh + 320h =D20h. Для установки контрольной точки на любой доступ к
первым 20h байтам SDA я использовал следующую команду WINICE:
BPR D20 D40 RW
Эта контрольная точка постоянно срабатывает в Windows 95, даже при использовании только
Win32-пpилoжeний (таких, как Explorer) и при отсутствии DOS- или Win 16-приложений (кроме
встроенных TIMER и MSGSRV32, которые являются Winl6-задачами). Например:
Break Due to BPR #0030:00000D22 #0030:00000D28 RW C=01
: ? ecx
00000D20
:u C025B09C
0028:C025B09C MOV BYTE PTR [ECX+03],00
0028:C025B0A0 MOV WORD PTR [ECX+04],0000
0028:C025B0A6 MOV BYTE PTR [ECX+07],00
0028:C025B0AA MOV BYTE PTR [ECX+06],00
0028:C025B0AE MOVSX EAX,WORD PTR [EAX+1A]
За несколько инструкций до контрольной точки IFSMGR загружает в ЕСХ DWRD PTR
[LIN_SDA], Если вы ознакомитесь с содержимым SDA (например, в Undocumented DOS, 2d ed.,
р. 730-733), то увидите, что SDA-смещение lAh в последней строчке дизассемблированного кода со-
держит значение АХ для вызова INT 21h и что смещения 3, 4, 6 и 7 в предыдущих строчках
Глава 12. Исследование с поллощью програллллы WV86Test
321
11 Неофициальная Windows 95
содержат информацию об ошибке DOS. IFSMGR, претендуя на звание DOS защищенного режима,
устанавливает эти значения в 0 для индикации 'отсутствия ошибок:
SDA + 3 BYTE Местоположение ошибки
SDA + 4 WORD Расширенный код последней ошибки
SDA+ 6 BYTE Предлагаемая реакция на последнюю ошибку
SDA+ 7 BYTE Класс последней ошибки
Где-то в IFSMGR есть код для заполнения этих значений SDA информацией об ошибках:
:и С025В07С
0028:С025В07С MOV WORD PTR [ECX+04],OOEA ; расширенный код
0028:С025В082 MOV BYTE PTR [ECX+031,03 ; местоположение
0028:С025В086 MOV BYTE PTR [ECX+07],02 ; класс
0028:С025В08А MOV BYTE PTR [ECX+06],01 ; действия
В качестве другого примера рассмотрим использование IFSMGR LIN_SDA для установки и
сброса флага InDOS:
;;; IFSMGR+5E0
0028:С007363С MOV ЕАХ,[C0076A34] ;; LIN_SDA
0028:00073641 INC BYTE PTR [EAX+01] ;; InD0S++
;;; IFSMGR+7F0
0028:00073840 MOV ЕАХ,[C0076A34] ;; LIN-SDA
0028:00073851 SUB BYTE PTR [EAX+01], 01 ;; InDOS-
0028:00073855 ADC BYTE PTR [EAX+01], 00
Microsoft утверждает, что вам “никогда не придется выполнять код DOS” в Windows 95; но
Microsoft ничего не говорит о том, что вы никогда не будете использовать данные DOS. Мы только
что убедились, что вам придется иметь дело и с ними.
Простой вызов Set PSP из САВ32 обнаруживает, что даже Win32-пpилoжeния в Windows 95 в
конце концов взаимодействуют с DOS. Тем не менее, я надеюсь, на вас.произвела впечатление спо-
собность САВ32 облазить весь мой жесткий диск с одним единственным вызовом INT 21Ь, передан-
ным в реальный режим DOS.
Еще раз обращу ваше внимание — в этой способности нет ничего нового. САВ32 не запустится
под WfW 3.11, но у File Manager есть нечто похожее на средство поиска файлов САВ32. Менеджер
файлов не сможет отыскать строку “foo”, но сможет найти все 840 файлов с расширением *.С в 500
каталогах на моем жестком диске. Когда я проделал это под WfW 3.11 с включенным 32BFA,
WV86TEST зарегистрировал следующее:
Прошло 35 секунд
Вызовы INT 21 h:
2А: 2 20: 2 50: 2
После выполнения той же операции под WfW 3.11 без 32BFA (WIN /D:C) мы можем увидеть,
какие вызовы DOS обрабатываются 32BFA полностью в защищенном режиме. Не удивительно, что
поиск файлов задействует главным образом вызовы функций lAh (Set DTA), 4Eh (Find First) и
4Fh (Find Next):
Прошло 55 секунд
11: 1 1A: 1317 2A: 2 2C: 2 3F: 5 42: 4 47: 1
4E: 988 4F: 12590 50: 2
322
Неофициальная Windows 95
Разница между 35 и 55 секундами с включенным и отключенным 32BFA показывает, что 32BFA
может существенно повысить рейтинг такого средства, как поиск файлов. Даже с кэшем SmartDrv
2 Мбайт, но без 32BFA, та же операция занимает 43 с. (Скорость вашей машины может быть не-
сколько иной.) Обработка всех этих вызовов DOS в защищенном режиме существенно увеличивает
производительность.
Но вообще говоря, это не является честным сравнением с САВ32 из Windows 95, поскольку
диспетчер файлов не ищет в файлах строки. С другой стороны, средство Find File имеется в
WinWord и может использоваться в WfW 3.11 для поиска всех *.С-файлов со строкой “foo” так же,
как я это делал программой САВ32 в Windows 95.
Вот как это выглядит для программы WV86TEST:
Прошло 141 секунд
Вызовы INT 21h:
2А: 857 2С: 857 50: 1 57: 160
Видите? Нет ничего особенного в умении Windows 95 обходиться без DOS в большинстве
операций. Это практически обычное дело, если вы имеете VxD, который обрабатывает вызовы
INT 21Ь. Конечно, много вызовов WinWord получения даты/времени функций 2Ah и 2СЬ и
получения даты/времени/атрибутов файла функции 57Ь отсылается в реальный режим DOS. Но
это является довольно незначительным контактом с DOS. Мы можем увидеть, насколько
незначителен этот контакт, выполнив те же операции под WfW 3.11 без 32BFA (WIN /D:C):
Прошло 208 секунд
Вызовы INT 21h:
1А: 14427 2А: 858 2С: 858 3D: 1689 ЗЕ: 1689 3F: 2013
40: 10 42: 4221 44: 6402 47: 160 4Е: 2123 4F: 12304
50: 1 57: 1849
С включенным 32BFA почти все эти тысячи вызовов INT 21Ь не передаются в реальный режим
DOS, уменьшая таким образом время выполнения поиска на 32% (208 - 141 = 67 / 208 = 32).
Чтобы завершить этот эксперимент, давайте запустим 16-битовое Windows-приложение. под
Windows 95. Честно говоря, результаты довольно странные, и я надеюсь, что это всего лишь следст-
вие недоработанности используемой мною бета-версии Chicago.
Вспомним, что система поиска файлов в WinFile за 35 с нашла все *.С-файлы на моем жестком
диске и при этом использовала только несколько вызовов функций 2Ah, 2Ch и 50h. А вот что прои-
зошло при той же операции в WinFile под Chicago:
Прошло 60 секунд
Вызовы INT 21h:
2А: 2 20:2 4F: 12591 50: 4
Странно! Мы видели, что 32BFA В WfW 3.11 может обрабатывать функцию 4Fh в защищенном
режиме. Почему же Chicago передает эти вызовы Find Next (функции 4Fh) в реальный режим
DOS? Какова бы ни была причина, но конечный результат печален — время выполнения теста
практически равно времёни выполнения такого же теста под WfW 3.11 без 32BFA.
Использование поиска файлов из WinWord приводит в Chicago к подобным результатам. Хотя
обнаружение всех С-файлов, содержащих строку “foo”, заняло 141 секунду в WfW 3.11 без 32BFA
(и 136 секунд при использовании программы САВ32 в Chicago), время выполнения той же операции
WinWord в Chicago — почти такое же, как и под WfW 3.11 без 32BFA:
Прошло 180 секунд
1А: 12306 2А: 860 2С: 860 4F: 12306 50: 9 57: 160
Глава 12. Исследование с поллощью програллллы WV86Test
323
Действительно, есть нечто странное в том, как бета-версия Chicago (май 1994 г.) обрабатывает
функции lAh (Set SDA) и 4Fh (Find Next). И это касается не только Win 16-приложений.
Воспользовавшись V86TEST -VERBOSE -QUERY, я проверил, как ведет себя команда DIR \*.С
/S под Chicago. Проверка жесткого диска заняла 66 секунд, и в DOS было послано 39323 вызова
функции 4Fh. Для сравнения, та же операция под WfW 3.11 с включенным 32BFA заняла только
38 секунд и обошлась без передачи вызовов функции 4Fh в DOS.
Даже то, что Windows 95, как мы уже знаем, передает некоторые вызовы INT 21h в DOS, не
помогает успокоиться — что-то здесь не так. Конечно, функция 4Fh — это странный вызов, по-
скольку DTA выступает в качестве неявного параметра и поскольку вплоть до Windows 95 не было
функции Find Close. (В Windows 95 это будет функция 71Alh INT 21h). Но 32BFA в WfW 3.11
умеет избегать передачи вызовов функции 4Fh в DOS. А в Windows 95 передача всех этих вызовов
в DOS значительно снижает производительность.
Win32-BepcMH FindNextFile —
это функция 714Fh INT 21 h
Нет сомнений, что эта проблема, связанная с функцией Find Next, уже разрешена к тому мо-
менту, когда вы читаете эту книгу. Но все же возникает интересный вопрос: почему подобной поте-
ри производительности не возникает во время поиска на жестком диске с помощью Win32-npMo-
жений? Вспомним, что программа САВ32 выполнила ту же операцию (найти *.С-файлы со строкой
“foo”) за 136 секунд без передачи вызовов функции Find Next в DOS; вспомним также, что у 16-
битового приложения WinWord под Windows 95 это заняло 180 секунд и потребовало выполнения
12306 вызовов Find Next в реальном режиме DOS и почти такого же количества Set DTA.
В чем же секрет 32-битового кода? Да ни в чем! Разница в производительности имеет доста-
точно мало, или вообще ничего, общего с битовой ориентацией приложений. Все дело в API.
Главный цикл поиска файлов программы САВ32 вызывает функции Win32 API: FindFirstFileA,
FindNextFileA и FindClose; окончание А означает, что эти функции работают с ASCII-строками (т.е.
не с Unicode):
#define МАХ_РАТН 260
typedef struct _WIN32_FIND_DATAA {
DWORD dwFileAttributes;
FILETIME ftCreationTime, ftLastAccessTime, ftLastWriteTime;
DWORD nFileSizeHigh, nFileSizeLow;
DWORD dwReservedO, dwReservedl;
CHAR cFileName[MAX_PATH], cAlternateFileName[14];
} WIN32_FIND_DATA, *PWIN32_FIND_DATAA, *LPWIN32_FIND_DATAA;
HANDLE WINAPI FindFirstFileA(LPCSTR IpFileName,
LPWIN32 _FINO_DATAA IpFindFileData);
BOOL WINAPI FindNextFileA(HANDLE hFindFile,
LPWIN32 _FIND_DATAA IpFindFileData);
BOOL WINAPI FindClose(HANDLE hFindFile);
Если вы воспользуетесь отладчиком Soft-ICE/Windows для установки контрольных точек на
функцию FindNextFileA, то сможете убедиться в том, что система поиска файлов постоянно обраща-
ется к этой функции. (В самом деле, САВ32 вызывает SHELL32.DLL, которая, в свою очередь,
вызывает функцию FindNextFileA в KERNEL32.)
Если заглянуть внутрь FindNExtFileA, то можно увидеть, что эта функция состоит из некоторой
проверки параметров н перехода к некоторой внутренней функции:
324
Неофициальная Windows 95
KERNEL32!FindNextFileA
; ... проверка параметров ...
0137:BFF82CE9 JMP BFF93D23
:и bff93d23
0137:BFF93D23 PUSH EBP
0137:BFF93D24 MOV EBP,ESP
0137:BFF93D26 PUSH EBX
0137:BFF93D27 PUSH ESI
0137:BFF93D28 PUSH EDI
0137:BFF93D29 MOV EAX.0000714F ; 714F - звучит знакомо?
0137:BFF93D2E MOV EBX,[EBP+08]
0137:BFF93031 MOV EDI,[EBP+OC]
0137:BFF93D34 XOR ESI,ESI
0137:BFF93D36 CALL BFF71E5F
0137:BFF93D3B JB BFF93D4D
0137:BFF93D3D MOV EAX,00000001
0137:BFF93D42 POP EDI
0137:BFF93D43 POP ESI
0137:BFF93D44 POP EBX
0137:BFF93D45 LEAVE
0137:BFF93D46 RET 08
Теперь очевидно, что функция по адресу BFF71E5Fh должна выполнять всю основную работу
для FindNextFileA. Но эта функция — не что иное, как:
:и bff71e5f
0137:BFF71E5F PUSH ECX
0137:BFF71E60 PUSH EAX
0137:BFF71E61 PUSH 002A0010
0137:BFF71E66 CALL KERNEL32!VxDCallO
0137:BFF71E6B RET
Я не хочу торопиться с разборкой кода VxDCallO 2A0010h, который является довольно важным
и будет обсуждаться подробнее в Главе 14. Однако, если вы вернетесь к коду FindNextFileA, то
можете заметить, что как раз перед вызовом функции VxDCallO 2A0010h, FindNextFileA делает
MOV ЕАХ, 714Fh. Это число должно быть для вас как удар колокола.
Как упоминалось в главе 7, Windows 95 предлагает новые функции INT 2lh для длинных имен
файлов (LFN). Эти новые функции используют АН=71Ь и AL, равный номеру старой функции
прерывания 21Ь, если такой номер существует. Например, функция 39h INT 21Ь — создать каталог,
поэтому функция 7139h INT 21Ь — это новая LFN-функция создания каталога. Точно так же функ-
ция 4Fh — найти следующий файл, так что 714Fh (число, которое, как мы видим,
KERNEL32!FindNextFile помещает в АХ) — это LFN-функция “Find Next”.
Есть также несколько совершенно новых функций — например, 71Alh (Find Close) и 71A6h
(Get File Info By Handle) и уже документированный эквивалент (функция 7160h, Get Full Path и
Get Short Path) для недокументированной ранее функции DOS (60h TRUENAME). Подробное
описание интерфейса функции 71h INT 21h см. в статье “Unconstrained Filenames on the PC!
Introducing Chicago's Protected Mode Fat File System” (Watter Oney, Microsoft Systems Journal,
August 1994).
При изучении функций 71h INT 21h большинство программистов полагают, что это должны
быть DOS-оболочки, которые Microsoft понастроила вокруг функций Win32 API. В файле
MSDOS.DOC фирмы Microsoft говорится, что функции INT 21h “соответствуют операциям, предо-
ставляемым Win32^yHKHHHMH управления файлами”. Может быть, это не так и важно, но взаимо-
отношения между функциями 71h INT 21h и функциями управления файлами Win32 API как раз
обратные: именно Win32^yHKijHH строятся на базе функции 71h INT 21h.
Глава 12. Исследование с поллощью програллллы WV86Test
325
Мы видели минуту назад, что FindNextFileA — не что иное, как проверка параметров, код
обработки ошибок и VxDCallO 2A0010h с AX=714Fh. Как мы увидим в главе 14, VxDCallO 2A0010h
в конце долгого, сложного маршрута приходит к следующему коду в VWIN32 VxD:
С025250В MOV [EDX.Client,ЕАХ].ЕАХ
С0252515 MOV ЕАХ,00000021
С025251А CALL [Exec_PM_Int]
Как вы могли догадаться, VMM-сервис Exec_PM_Int имитирует вызовы INT 21h в защищенном
режиме. Таким образом, FindNextFileA на деле оказывается вызовом функции 714Fh INT 21h в
защищенном режиме.
Я надеюсь, что мои объяснения были достаточно хороши и вы уже сами поняли, почему система
поиска файлов из САВ32 не обращается к коду Find Next реального режима DOS: даже несмотря на
то, что Win32 API управления файлами является всего лишь удобной оболочкой для вызовов INT
21h, реальный режим DOS — даже версия DOS 7 в Windows 95 — не знает, как обрабатывать
функции 71 h.
Когда САВ32 и SHELL32 вызывают FindNextFileA, a FindNextFileA вызывает затем VxDCallO,
а затем VxDCallO обращается к коду в Win32, который вызывает функцию Exec_PM_Int в VMM,
VMM посылает INT 21h в цепочку обработчиков INT 21h защищенного режима, а если вызов в этой
цепочке не будет обработан, то в цепочку обработчиков INT 21h режима V86. VxD IFSMGR уста-
навливает обработчик INT 21Ь в обеих цепочках и непременно заметит вызов функции 71h. IFSMGR
обеспечивает LFN-сервис в Windows 95 и обрабатывает функцию 7 lh в защищенном режиме. Не мо-
жет быть ничего хорошего в том, чтобы IFSMGR передавал функцию 71h в код реального режима
DOS, который ничего не знает о длинных именах файлов.
Win32 API управления файлами является, таким образом, всего лишь оболочкой для сервиса
IFSMGR. Для получения этих сервисных возможностей IFSMGR, KERNEL32.DLL в Windows 95
осуществляет вызов функции 71h INT 21h (а более точно, использует VxDCallO для соответст-
вующего запроса к VWIN32 и VMM). Это как бы новые функции DOS, но они не могут обраба-
тываться в реальном режиме DOS. IFSMGR не только обеспечивает файловый ввод-вывод DOS в
защищенном режиме, он и расширяет интерфейс DOS новыми сервисными функциями.
Это объясняет, почему ни DOS, ни WV86TEST не замечают вызовов INT 21h от САВ32:
IFSMGR — расширение DOS в защищенном режиме — заботится об этих вызовах.
Теперь мы также можем понять, почему WSPY21 не замечает вызовов функции 714Fh INT 21h
от САВ32 со стороны Windows: САВ32 вызывает KERNEL32, который использует VxDCallO для
вызова INT 21h. Для того чтобы увидеть эти вызовы, WSPY21 должна уметь отлавливать
VxDCallO. Хотя это было бы интересным проектом (оставляю его вам), гораздо проще восполь-
зоваться отладчиком Soft-ICE/Windows для установки контрольной точки на VxDCallO.
Однако VxDCallO предназначен не только для осуществления вызовов INT 21h. Как будет
объяснено в главе 14, 2A0010h — просто один из сервисов, которые VxD предоставляют Win32-
приложениям. Контрольная точка на VxDCallO позволит увидеть обращения ко всем прочим Win32-
сервисам (например, 2A002Eh, 2A0030h, 10011Ь и 10013h), которые нас сейчас не интересуют.
Лучше всего установить контрольную точку на код VWIN32, показанный ранее:
C025250B MOV [EDX.Client_EAX].EAX
C0252515 MOV EAX,00000021
C025251A CALL [Exec_PM_Int]
; хорошее место для точки прерывания
Установленная здесь контрольная точка позволит обнаружить все виды активности INT 21h,
которые скрыты для WSPY21 (так как INT 21h исходят от VxD, ниже WSPY21, хотя Win32-
приложения работают на более-менее том же уровне, что и WSPY21) и WV86TEST (так как вызов
не передается в реальный режим DOS, а обрабатывается в IFSMGR в защищенном режиме).
326
Неофициальная Windows 95
Например, пока поисковая система программы Explorer ищет все файлы *.С со строкой “foo”,
несколько вызовов INT 2lh обнаруживаются по адресу C0025250Bh в VWIN32. С помощью таблицы
вы можете отследить, откуда пришли эти вызовы INT 21h.
Функция INT 21h' 3Eh (Закрыть файл) 3Fh (Прочитать файл) 714Eh (LFN Найти первый файл) 714Fh (LFN Найти следующий файл) 716Ch (LFN Создать/Открыть файл) 71 Alh (Завершйть поиск) 71A7h (Время DOS во время файла) Вызывается из KERNEL32: CloseHandle, _Iclose _hread FindFirstFileA FindNextFileA _lopen FindClose DOSDateTimeToFileTime
Я был немного удивлен, что, когда Explorer искал *.С-файлы, он не использовал средства
ввода-вывода файлов отображения памяти для обнаружения строки “foo” (см. главу 14).
Полезно также поставить контрольную точку на IFSMGR-обработчик функции 71h INT 21h,
поскольку это позволит нам проверить не только Win32-программы, но и Win 16- и DOS- програм-
мы, использующие новые функции LFN.
В главе 8, раздел “Получение и установка текущего диска”, я показал, как исследовать таблицу
функций INT 21h в версии IFSMGR из WfW 3.11. Такое же исследование можно проделать и для
версии из Windows 95:
0028:С0073Е9С MOZX ЕСХ,BYTE PTR [EBP+1D]
0028:С0073ЕА0 CMP CL, 72
0028:C0073ECF CALL [C0073CD4+4*ECX]
C:\UNAUTHW\PROTTAB>prottab c0073cd4 72 4 121
C0257E3F i21_004F
C0258164 121_OO71
Чтобы отследить все вызовы функции 71h, мы можем установить контрольную точку по адресу
C0258164h. Но лучшее место для контрольной точки находится чуть ниже, как раз за инструкцией,
в которой обработчик функции 7 lh проверяет номер подфункции в регистре AL:
С025816В MOVZX EAX, BYTE PTR [EBP.Client_AL]
Интересно, что IFSMGR преобразует все вызовы 21/71хх в 21/хх с установкой бита LFN:
C025817A MOVZX EDI,AL
C025818B MOV [EBP.Client.AH],AL ; 21/71XX/BL = 21/XXBL
C025818E MOV AH, [EBP.Client_BL]
C0258191 MOV [EBP.Client_AL],AH
C0258194 MOV CL, AL
C0258197 OR ECX, 40000000 ; установить бит LFN
C02581B8 CMP EDI.OOOOOOAO
C02581BE JB low_func
high_func
Глава 12. Исследование с поллощью програллллы WV86Test
327
С02581С0 CALL [C0257EC3+4*EDI] ; таблица для func >= AOh
C02581C7 JMP done
low_func:
C02581C9 MOVZX EDI,CL
C02581CC CALL [C0073CD4+4*EDI] ; и опять через таблицу
done:
C02581D3 RET
Таким образом, IFSMGR преобразует вызов функции 714Fh от САВ32 в вызов функции 4Fh с
установленным битом, отвечающим за LFN. Обработчик IFSMGR функции 4Fh проверяет этот бит.
Если бит установлен, то IFSMGR обрабатывает вызов в защищенном режиме, и, если бит не уста-
новлен (в текущей реализации), IFSMGR посылает вызов в реальный режим DOS.
Как уже говорилось, контрольная точка на обработчике функции 71h IFSMGR позволит нам
увидеть, какие программы, кроме Win32-npibiK^eHirii, вызывают новые функции LFN. Например,
16-битовая функция _1ореп в KERNEL делает следующее:
KERNEL!_1ореп:
0117:000093Е9 MOV АХ,716С
0117:000093Е9 PUSHF
0117:000093Е9 PUSH CS
0117:000093E9 CALL |7F38 |
Функция по адресу 0017:7F38, которую вызывает _lopen, является основой для старого
DOS3Call API:
:u dos3call
KERNEL!D0S3CALL
0117:000081C3 PUSHF
0117:00008104 PUSH CS____
0117:000081C5 CALL I 7F38 I
0117:000081C8 RETF
Позднее в этой главе мы рассмотрим код WSPY21 и увидим, что программа не только
перехватывает INT 2lh в защищенном режиме, но также использует недокументированную функцию
GetSetKernelDOSProc (см. Undocumented Windows, р. 271-273) для перехвата этой внутренней
функции, используемой DOS3Call. Таким образом, WSPY21 замечает вызовы функций LFN INT
21Ь от Winl6-функций KERNEL, например, _1ореп.
Однако некоторые вызовы функции 71h INT 21h, отловленные с помощью контрольной точки в
обработчике IFSMgr, остаются незамеченными программой WSPY21. Например:
KERNEL!FINDNEXTFILE
0127:00003276 MOV СХ.ОООС
0127:00003279 JMP 33D4
KERNEL!FINDFIRSTFILE
0127:0000327C MOV CX,0010
0127:0000327F JMP 33D4
Это Win 16 thunk-переключатели к Win32 API управления файлами, и они используются,
например, общими диалоговыми окнами Win 16 (COMMDLG). В главе 14 эти thunk-переключатели
будут обсуждаться подробнее, но, чтобы закончить наше исследование функции Find Next в
Windows 95, нам придется бросить беглый взгляд на них уже сейчас.
Как мы видим, Winl6-версии функций FindNextFile и FindFirstFile просто помещают
магическое число в СХ и затем переходят к 0127:33D4. Если рассматривать код, начиная с
0127:33D4, то обнаруживается кусок, который отладчик Soft-ICE/Windows не может правильно
дизассемблировать:
328
Неофициальная Windows 95
0127:000033Е6
0127:000033ЕС
0127:000033ED
[66EA|7E21F7BF
37
0100
JMP BFF7:217E
AAA
ADD [BX+SI],AX
66EA — это 32-битовый код thunk co стороны Winl6; EAh — это код инструкции JMP, a
66h — код, преобразующий инструкцию в 48-битовый дальний переход. Выполнение только одной
этой инструкции осуществляет переключение (thunk) от Winl6 KERNEL к Win32 KERNEL32:
0127:000033Е6 66EA7E21F7BF3701 JMP 0137:BFF7217E
0137:BFF7217E 1Е PUSH DS
0137:BFF7217F 57 PUSH EDI
;... СХ используется для индексирования в таблице
0137:BFF9118A E88F12FEFF CALL KERNEL32!MapSLFix
0137:BFF9118F 50 PUSH EAX
0137:BFF91190 FF731A PUSH DWORD PTR [EBX+1A]
0137:BFF91193 Е88В2В0000 CALL | BFF93D23|
0137:BFF911A1 E89B12FEFF CALL KERNEL32!UnMapSLFixArray
Камнем преткновения является функция BFF93D23h. Как вы можете вспомнить, раньше мы ви-
дели, что функция FindNextFileA состоит из:
KERNEL32!FindNextFileA
; ... проверка параметров ...
0137:BFF82CE9 JMP BFF93D23
Итак, KERNEL!FindNextFile переключается на KERNEL32!FindNextFileA, которая, как мы ви-
дели, в свою очередь обрабатывает функцию 714Fh INT 21h через VxDCallO.
Следовательно, вызовы Explorer функции FindNextFileA не попадают в реальный режим DOS,
поскольку FindNextFileA вызывает функцию 714Fh, а не 4Fh. Здесь не совсем ясно, почему
IFSMGR в Windows 95 в данной реализации передает функцию 4Fh в реальный режим DOS, но,
как бы то ни было, если DOS- или Win 16-программа вызывает функцию 714Fh напрямую либо
через thunk FindNextFile в KERNEL, этот вызов в реальный режим DOS не передается.
DOS- или Win 16-приложения могут использовать LFN по нескольким причинам. Одна из
них — получение доступа к файлам и каталогам с длинными именами. Другая причина состоит в
том, что вызовы новых функций 71h более “чисты”, чем их предшественники. Например, вызовы
LFN-функций FindFirst/Next больше не требуют получения и установки DTA. Как мы видели
раньше, IFSMGR имеет внутренний доступ к DTA, равно как и к другим структурам данных реаль-
ного режима DOS, но теперь, по крайней мере, это использование DTA не маячит перед глазами. .
Производительность — еще одна причина использования LFN-функций. Я надеюсь, что низкая
производительность в тесте с функцией 4Fh INT 21h является следствием недоработанности бета-
версии, а в целом LFN-функции выглядят привлекательными, так как они не могут быть переданы
в реальный режим DOS. (До тех пор, пока какой-нибудь разработчик программного обеспечения не
решит сделать LFN-TSR для DOS 7.)
Чтобы сравнить производительность LFN-фукнции FindFirst/Next со старыми аналогичными
функциями, я написал небольшую DOS-программу, которую можно скомпилировать для использо-
вания либо новых, либо старых функций. Программа MYDIR.C (листинг 12.1) просто перебирает
весь мой диск вызовами функций FindFirst/Next. Если при компиляции воспользоваться опцией
-DLFN, то программа (называемая LFNDIR) будет использовать функции 714Eh, 714Fh и 71Alh.
Если же компилировать без опции -DLFN, то программа (с именем DOSDIR) будет использовать
функции _dos_findfirst и _dos_findnext, которые в свою очередь вызывают функции lAh (Set DTA),
2Fh (Get DTA), 4Eh (Find First) и 4Fh (Find Next).
Глава 12. Исследование с поллощью програллллы WV86Test
329
ЛИСТИНГ 12.1. MYDIR.C
/*
MYDIR.C
bcc -edosdir mydir.c
bcc -DLFN -elfndir.exe mydir.c
Шульман, 1994
•/
«include <stdlib.h>
«include <stdio.h>
«include <string.h>
«include <ctype.h>
«include <io.h>
«include <fcntl.h>
«include <share.h>
«include <dos.h>
«include <time.h>
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef unsigned short HANDLE;
typedef int BOOL;
«ifdef LFN
«define MAX_PATH
260
typedef struct {
DWORD bwLowDateTime, dwHighDateTime;
} FILETIME;
typedef struct {
DWORD dwFileAttributes;
FILETIME ftCreationTime, ftLastAccessTime, ftLastWriteTime;
DWORD nFileSizeHigh, nFileSizeLow;
DWORD dwReservedO, dwReservedl;
BYTE cFileName[MAX_PATH], cAlternateFileName[14];
} WIN32_FIND_DATA;
«define DATETIME_64 0
«define DATETIME_DOS 1
HANDLE FindFirstFile(
char far -Name,
WIN32_FIND_DATA far -Result,
WORD Attributes,
WORD DateTimeFormat)
{ HANDLE Handle;
WORD ConversionCode; // He используется
_asm { push si push di push ds mov ax. 714Eh // Find First File
mov ex, Attributes // атрибуты файла
Ids dx. dword ptr Name // адрес имени для поиска
les di, dword ptr Result // адрес буфера результата
mov si. DateTimeFormat // формат возвращаемых времени и даты
330
Неофициальная Windows 95
int 21h
pop d s
pop di
pop si
jc error
mov [Handle], ax
mov [Conversioncode], ex
}
return Handle;
error:
return 0;
// дескриптор поиска для Find Next, Close
// флаг преобразования из UNICODE в OEM/ANSI
BOOL FindNextFile(
HANDLE Handle,
WIN32_FIND_DATA far «Result,
WORD DateTimeFormat)
{
WORD Conversioncode;
_asm {
push si
push di
push ds
mov ax, 714Fh
mov bx, Handle
les di, dword ptr Result
mov si, DateTimeFormat
int 21h «
pop ds .
pop di
pop si
jc error
mov [ConversionCode], ex
}
return 1;
error;
return 0;
// не используется
// Find Next File
// дескриптор поиска, возвращенный Find First File
// адрес буфера результата
// формат возвращаемых времени и даты
// флаг преобразования из UNICODE в OEM/ANSI
BOOL FindClose(HANDLE Handle)
{
_asm {
mov ax, 71A1h // окончить поиск
mov bx, Handle // дескриптор поиска, возвращенный Find First File
int 21h
jc error
}
return 1;
error:
return 0;
}
«endif
void fail(const char *s) { puts(s); exit(1); }
«ifdef LFN
«define FILEINFO WIN32_FIND_DATA
«else
«define FILEINFO struct find_t
«endif
Глава 12. Исследование с поллощыо програллллы WV86Test
331
void do_filename(int dirpos, const char *dir, FILEINFO *pinfo);
void do_directory(int dirpos, const char *s);
static char dirname[2048] = {0};
static unsigned long ffirst = 0, fnext = 0;
static int print_it = 0;
main(int argc, char *argv[])
{
Clock_t d, c2;
C1 = clock();
do_directory(0, (argc < 2) ? “C:" : argv[1]);
c2 = clock();
printf(“Bbi30Bw Find First: %lu\n", ffirst);
printf(“Вызовы Find Next: %lu\n", fnext);
printf(“Тактов: %lu\n", c2 - c1);
printf(“Вызовов/Такт: %lu\n”, (ffirst + fnext) / (c2 - d));
return 0;
void do_directory(int dirpos, const char *s)
{
struct find_t info;
int prev_dirpos;
char »s2 = (char *) malloc(2048);
if(!s2) Та11(“Недостаточно памяти’’);
if((prev_dirpos = dirpos) == 0)
strcpy(dirname, s);
else
{
strcat(dirname, “\\”);
strcat(dirname, s);
>
dirpos = strlen(dirname);
strcpy(s2, dirname);
strcat(s2, “\\*.*”);
«define ATTRIB (_A_N0RMAL | _A_SUBDIR | _A_RD0NLY)
#i!fdef LFN
{ ;
HANDLE h;
WlN32_FIND_DATA info;
ffirst++;
if((h = FindFirstFile(s2, &info, ATTRIB, DATETIME.DOS)) == 0)
return;
do_filename(dirpos, dirname, &info);
while <FindNextFile(h, &info, DATETIME.DOS))
<i f
: fnext++;
do_filename(dirpos, dirname, &info);
}
FindClose(h);
}
«else
332
Неофициальная Windows 95
ffirst++;
if(_dos_findfirst(s2, ATTRIB, &info) !=0)
return;
do_filename(dirpos, dirname, &info);
while (_dos_findnext(&info) == 0)
{
fnext++;
do_filename(dirpos, dirname, &info);
>
#endif
dirname[prev_dirpos] = '\0';
free(s2);
>
ffifdef LFN
void do_filename(int dirpos, const char «dir, WIN32_FIND_DATA *pinfo)
{
if(pinfo->dwFileAttributes & FA_DIREC)
{ '
if(pinfo->cFiieName[0] !=
do_directory(dirpos, pinfo->cFileName);
>
else if(print_it)
printf(“%s\\%s\n", dir, pinfo->cFileName);
>
#else
void do_filename(int dirpos, const char *dir, struct find_t *pinfo)
{
if(pinfo->attrib & FA_DIREC)
{
if(pinfo->name[0] !=
do_directory(dirpos, pinfo->name);
>
else if(print_it)
printf(“%s\\%s\n", dir, pinfo->name);
>
#endif
Я тестировал с помощью DOSDIR и LFNDIR все стандартные среды. (Функции, используемые
в LFNDIR, доступны только в Windows 95.) Во всех случаях программы регистрировали по 434
вызова Find First (я не создавал новых подкаталогов, пока занимался этими тестами) и от 11311 до
11319 вызовов Find Next (мне пришлось создать несколько новых файлов в процессе тестирования).
В каждой среде я запускал программу дважды, чтобы учесть эффект кэширования.
В табл. 12.2 приведены результаты тестов. В колонке “Тактов” показано время работы (в стан-
дартных тактах по 55 миллисекунд) двух запусков, плюс их совместное время.
Таблица 12.2. Проход по каталогам
Конфигурация Тактов Вызовов/Такт Секунд
Реальный режим DOS 659+654=1313 17 72
SmartDrv 548+513=1061 21 58
WfW 3.11 32BFA 538+82=620 36 34
Chicago DOSDIR 953+409=1362 17 74
Chicago LFNDIR 699+216=915 24 50
Глава 12. Исследование с поллощью програллллы WV86Test
333
Только из дотошности я использовал V86TEST, чтобы выяснить, насколько активно программа
LFNDIR в Windows 95 использует реальный режим DOS. Оказалось, использование было мини-
мальным, это еще раз подтвердило то, что даже программы реального режима DOS не обязательно
требуют реального режима DOS для работы под Windows 95:
VM #2 - 121 вызовов
Вызовов INT 21h:
19: 5 1А: 2 25: 16 29: 6 2А: 1 2С: 1 30: 2 35: 8 38: 2
ЗЕ: 30 40: 10 44: 3 48: 4 49: 2 4А: 2 4В: 2 4С: 2 40: 2
Напротив, DOSDIR в Windows 95 обращалась к DOS довольно часто:
VM #2 - 48879 вызовов
Вызовов INT 21h:
19: 5 1А: 24628 25: 16 29: 6 2А: 1 2С: 1 2F: 12313 30: 2
35: 8 38: 2 ЗЕ: 30 40: 10 44: 3 48: 4 49: 2 4А: 2 4В: 2
4С: 2 4D: 2 4F: 11819
Поскольку программы DOSDIR и LFNDIR — фактически одно и то же, разность между 74
секундами для DOSDIR и 50 секундами для LFNDIR дает некоторое представление об истинной
цене вызовов реального режима DOS. А, возможно, это одна из уЛовок Microsoft, чтобы заставить
программистов использовать Win32 или новый LFN-сервис DOS.
В то же время по результатам теста на первом месте оказывается WfW 3.11: DOSDIR работает
значительно быстрее под WfW 3.11 с 32BFA, чем даже LFNDIR под Chicago. Конечно, Chicago еще
пребывает в бета-версии и конечная версия Windows 95 может значительно улучшить произ-
водительность. Но эта бета-версия говорит нам следующее: идея о “разрыве всех связей Windows 95.
с кодом реального режима DOS” практически бессмысленна, так как разрыв связей с DOS делается
от случая к случаю. Если когда-нибудь самый последний вызов INT 21h, который сейчас
обрабатывается реальным режимом DOS, будет обрабатываться VxD в защищенном режиме, то
будет ли это означать качественное преобразование Windows? — Нет. Настоящее преобразование
произошло несколько лет назад, когда Ральф Лайп (Ralph Lipe), Аарон Рейнольдс (Aaron Renolds)
и другие написали VMM и придали ему такие функции, как Hook_V86_Int_Chain. Все, что вам
нужно знать о взаимоотношениях Windows и DOS, можно прочесть в одном предложении докумен-
тации DDK для Hook_V86_Int_Chain, уже упоминавшемся раньше: “Если процедура перехватчика
обслуживает прерывание, она должна сбросить флаг CF для предотвращения передачи системой
прерывания следующему обработчику”.
Код WV86TEST
Потратив немало времени на изучение результатов WV86TEST, теперь не мешало бы узнать,
как программа эти результаты'получает. Листинг 12.2 представляет исходный код для WV86TEST,
который использует библиотеку WINIO. WINIO позволяет Windows-приложениям использовать С.-
функция вывода в стандартный поток, например printf, и, кроме того, предлагает функции
winio_setmenufunc и winhandler_set, с помощью которых легко создавать меню и обрабатывать
сообщения. Для получения статистики от резидентной DOS-версии V86TEST программа WV86TEST
использует функции real_int86x и шар_геа1 из библиотеки PORT.
WV86TEST имеет файл с расширением .DEF, содержащий следующую команду:
STUB ‘v86test.exe’
334
Неофициальная Windows^
Эго связывает DOS- и Windows-версии в единую исполняемую программу. Больше того,
V86TEST может отлавливать функцию 160Bh (Идентификация TSR) INT 2Fh и заставляет Windows
автоматически запустить WV86TEST. То же самое делает программа COUNTDOS, рассмотренная в
главе 3. Наоборот, если WV86TEST обнаружит, что V86TEST не резидентна (ошибка функции
get_stats), то WV86TEST может использовать функции API Windows ExitWindows (EW_
REST ART WINDOWS) и ExitWindowsExec для того, чтобы дать пользователю возможность пере-
запустить Windows под V86TEST. Этот вопрос я оставляю на изучение вам, если у вас, конечно,
есть свободное время.
Листинг 12.2. WV86TEST
/•
WV86TEST.C -- перехватывает INT 21h и INT 2Fh, подсчитывает вызовы в режиме V86
Использует wv86test.def с командой STUB 'v86test.exe'
Шульман, 1994
bcc -i..\include -L..\lib -W -2 -Р- wv86test.c unauthw.lib
♦/
«include <stdlib.h>
«include «include «include «include «include «include «include «include «include «include typedef <stddef.h> <string.h> <ctype.h> <process.h> <time.h> <dos.h> “windows.h” “winio.h" “wmhandlr.h" “prot.h” unsigned short WORD;
typedef unsigned long DWORD;
«define VM.MAX 8
«define VM_OTHER VM.MAX
«define GET.STATS OxOFFFF
«define SIGNATURE “V86TEST"
«define VXD_MAX 0x100
«define VXD_OTHER VXD_MAX
typedef enum {
N0_WIN,
WIN-INIT.BEGIN, // получен WIN_INIT_NOTIFY
WIN_INIT_DONE, // получен WIN_INIT_COMPLETE
WIN_FINI_BEGIN, // получен WIN_EXIT_BEGIN
WIN_FINI_DONE, // получен WIN_EXIT_NOTIFY
NUM_STATES
} STATE;
static char *state_st.r[NUM_STATES] = {
“Перед запуском Windows",
В процессе инициализации Windows”, // получен WIN_INIT_NOTIFY
Во время работы Windows", // получен WIN_INIT_COMPLETE
При выходе из Windows", // получен WIN_EXIT_BEGIN
После выхода из Windows", // получен WIN_EXIT_NOTIFY
};
Глава 12. Исследований с'Нфмощью программы WV86Test
335
«pragma pack(1)
typedef struct {
char signature[8];
DWORD calls[NUM_STATES], v86_calls[NUM_STATES];
DWORD iopl_count[4];
DWORD vm[VM_MAX+1];
DWORD int21[0x100];
DWORD int2f[0x100], int2f16[0x100], int2f1607[VXD_0THER+1];
time_t start, end;
} STATS;
static STATE state = NO_WIN;
static BOOL display_changes = 0;
static BOOL do_auto_refresh = 0;
static UINT htimer = 0;
static STATS far «fp;
static STATS «stats;
static HMENU gmenu;
void display_results(STATS far »fp2)
{
STATS *fp = stats;
Stats «delta;
DWORD elapsed;
int i;
if(display changes)
{
if(I(delta = malloc(sizeof(STATS))))
fail (“Недостаточно памяти’’);
// Показать изменения; сначала скопируем новую статистику в delta...
_fmemcpy(delta, fp2, sizeof(STATS));
// ...затем вычтем старую статистику, чтобы получить изменения
time(&delta->end);
elapsed = delta->end - fp->end;
delta->calls[WIN_INIT_DONE] -= fp->calls[WIN_INIT_DONE];
delta->v86_calls[WIN_INIT_D0NE] -= fp->v86_calls[WIN_INIT_D0NE];
printf(“Прошло %lu секунд\п”, elapsed);
printf(“%lu вызовов, %lu в режиме V86\n”,
delta->calls[WIN_INIT_DONE], delta->v86_calls[WIN_INlT_D0NE]);
if(elapsed)
printf(“%lu INT 21/2F вызовов/сек.\n”,
delta->calls[WIN_INIT_DONE] / elapsed);
for(i=0; i<4; i++)
{
delta->iopl_count[i]'-= fp->iopl_count[i];
delta->vm[i] -= fp->vm[i];
}
delta->vm[VM_OTHER] -= fp->vm[VM_OTHER];
for(i=0; i<0x100; i++)
{
delta->int21[i] -= fp->int21[i];
delta->int2f[i] -= fp->int2f[i];
delta->int2f16[i] -= fp->int2f16[i];
336 Неофициальная Windows 95
for(i=0; i<=VXD_MAX; i++) // получить также VXD_OTHER
delta->int2f16D7[i] -= fp->int2f1607[i];
fp = delta; // display_results работает c delta
}
else
<
_fmemcpy(fp, fp2. sizeof(STATS));
for(i=NO_WIN; i<NUM_STATES; i++)
printf("%s:\t%lu INT 21/2F вызовов, %lu в режиме V86\n",
state_str[i], fp->calls[i], fp->v86_calls[i]);
printf(“\nBo время работы Windows:\n");
time(&fp->end);
if(!fp->start) time(&fp->start);
if((elapsed = fp->end - fp->start) != 0)
{
printf(“Windows активна %lu секунд\п’’, elapsed);
printf(“%lu INT 21/2F вызовов/сек.\n",
fp->calls[WIN_INIT_DONE] / elapsed);
)
for(i=0; i<4, i++)
if(fp->iopl_count[i])
printf(“IOPL=%d — %lu вызовов\п", i, fp->iopl_count[i]);
for(i=0; i<VM_MAX; i++)
if(fp->vm[i])
printf(“VM #%d -- %lu вызовов\п”, i, fp->vm[i]);
if(fp->vm[VM_OTHER])
printf(“VM > #%d — %lu вызовов\п”, VM_MAX, fp->vm[VM_OTHER]);
«define PRINT_CALLS(s, fp) { \
DWORD *p; \
printf(“\n%s:\n”, (s)); \
for(i=0, p=fp; КОхЮО; i++. p++) \
if(*P) \
printf("%02X: %lu\t", i, *p); \
PRINT_CALLS(“Вызовов INT 21h", fp->int21);
PRINT_CALLS(“\nBbi30B0B INT 2Fh”, fp->int2f);
PRINT_CALLS(“\nBH30B0B INT 2Fh AH=16h”, fp->int2f16);
PRINT_CALLS(“\nBn30B0B INT 2Fh AX=1607h", fp->int2f1607);
if(fp->int2f1607[VXD_0THER])
printf(“VxD>#%04X: %lu\n", VXD_MAX, fp->int2f1607[VXD_0THER]);
printf(“\n\n");
_fmemcpy(stats, fp2, sizeof(STATS));
time(&stats->end);
if(display_changes) free(delta);
}
STATS far *get_stats(void) // вызвать резидентную копию V86TEST
{
union REGS r;
ГлавЬ12. Й&ёледованиё*с помощью программы WV86Test
337
Struct SREGS s;
STATS far *fp;
r.x.ax = GET_STATS;
memset(&s, 0, sizeof(s));
real_int86x(0x2f, &r, &r, &s); // real_int86x находится в PROT.C
if(s.es == D)
return 0;
fp = MK_FP(s.es, r.x.bx);
if((fp = map_real(fp, sizeof(STATS))) == 0) // map_real находится в PROT.C
return 0;
return (_fstrcmp(fp->signature, SIGNATURE) == 0) ? fp : 0;
}
void free_stats(HWND hwnd) { free_mapped_linear(fp); }
/******»**.»**»»***».**»**»*****»**,****»,**»*,,»«»**,»***»*.,,*»»,***,/
/* MENU HANDLERS (cm. MK_MENU в main) */
void refresh(HWND hwnd, int wID)
{
winio_clear(hwnd);
display_results(fp);
}
void clear(HWND hwnd, int wID) { _fmemset(fp, 0, sizeof(STATS)); }
void show_changes(HWND hwnd, int wID)
{ ,
CheckMenuItem(gmenu, 3, display_changes ? MFJJNCHECKED : MF_CHECKED);
display_changes = ! display_changes;
refresh(hwnd, wID);
}
long on_time(HWND hwnd, unsigned message, WORD wParam, LONG IParam)
{
winio_clear(hwnd);
display_results(fp);
void auto refresh(HWND hwnd, int wID)
<
if(do_auto_refresh) // если включено - выключить
{
do_auto_refresh = 0;
KillTimer(hwnd, htimer);
CheckMenuItem(gmenu, 4, MFJJNCHECKED);
}
else // выключено - включить
{
do_auto_refresh = 1;
wmhandler_set(hwnd, WM_TIMER,’on_time);
if(!(htimer = SetTimer(hwnd, 1, 5000, NULL))) // каждые 5 секунд
fail(“Can't create timer”);
CheckMenuItem(gmenu, 4, MF_CHECKED);
}
main(int argc, char *argv[])
{
338 Неофициальная Windows 95
HWND hwnd;
int i;
// только один экземпляр: необходимо поместить обработчик прерывания в DLL
if(__hPrevInst)
fail("WV86TEST уже запущена”);
hwnd = winio_current();
gmenu = CreateMenuO; // глобальное
«define MK_MENU(id, str, func) { \
AppendMenu(gmenu, MF_STRING | MF_ENABLED, id, str); \
winio setmenufunc(hwnd, id, func); \
}
MK_MENU(1, “&Refresh”, refresh);
MK_MENU(2, “&Clear”, clear);
MK_MENU(3, “&Show Changes”, show_changes);
MK_MENU(4, “&Auto-refresh”, auto_refresh);
InsertMenu(winio_hmenumain(hwnd), 1,
MF_STRING | MF_POPUP | MF.BYPOSITION, gmenu, “&Test”);
DrawMenuBar(hwnd);
if(!(fp = get stats()))'
{
// Вместо вызова fail здесь можно вызвать ExitWindowsExec для V86TEST
// и ExitWindows(EW_RESTARTWINDOWS).
1а11(“Невозможно получить статистику V86TEST”);
}
winio_onclose(hwnd, free_stats);
if(!(stats = malloc(sizeof(STATS))))
fail(“Недостаточно памяти”);
display_results(fp);
return O;
)
В программе WV86TEST.C функция main (WINIO позволяет Windows-программам использо-
вать стандартную функцию main вместо непривычной функции WinMain, предлагаемой Windows
SDK) устанавливает дескрипторы меню, использующие макрос MK MENU, который вызывает
AppendMenu, и функцию winio_setmenufunc. Например, MK_MENU (1, “&Refresh”, refresh) озна-
чает, что, когда пользователь выберет пункт “Refresh” из меню WV86TEST, обработчик WM_
COMMAND в WINIO вызовет функцию refresh.
Как уже говорилось, WV86TEST получает указатель для защищенного режима на статистику
V86TEST через функцию real_int86x и map_real из библиотеки PROT. Эти функции, в свою оче-
редь, базируются на функции 0300h INT 31h DPMI (Имитировать прерывание реального режима) и
на функциях Windows API AllocSelector, SetSelectorBase и SetSelectorLimit. WV86TEST использует
функцию winio_onclose для установки обработчика, который непосредственно перед выходом из
программы освобождает селектор, использовавшийся для отображения статистики.
Рабочей лошадкой программы WV86TEST является функция display_results, которая вызыва-
ется каждой функцией работы с меню. Как правило, функция display_results просто копирует ста-
тистику в локальный буфер и отображает статистику на экран с помощью функции printf библио-
теки WINIO. WV86TEST сначала копирует статистику, а не работает с “живой”, принадлежащей
программе V86TEST, так что статистика не изменится, если во время работы функции display
_results будут сгенерированы новые вызовы DOS.
Если пользователь выберет пункт меню “Show Changes”, программа WV86TEST устанавливает пе-
ременную display_changes. Если эта переменная установлена, функция display_results копирует стати-
стику в буфер delta, из которого она вычитает предыдущие данные, и затем отображает этот буфер.
ГлаваА^.'ИбС'Дедование^с помощью программы WV86Test
339
Пункт меню “Auto-refresh” устанавливает таймер с помощью Windows-функции SetTimer.
Каждые 5 секунд (если никто не перепрограммировал таймер) модуль WMHANDLER из WINIO
вызывает функцию on_time, которую WV86TEST устанавливает с помощью wmhandler_set. Эта
функция on_time просто вызывает winio_clear и display_results, обновляя таким образом экран
WV86TEST каждые 5 секунд.
Windows 95 и DOS защищенного режима
Итак, вот что мы уже узнали:
• В Windows 95 не-файловые вызовы INT 21h от Win32-, Winl6- и DOS-приложений пере-
даются в реальный режим DOS. Таким образом Windows 95 не разрывает “все связи с кодом
реального режима DOS”.
• Windows 95 также не разрывает все (и даже многие) связи с данными реального режима
DOS. Windows 95 полагается на такие структуры данных реального режима DOS, как SDA,
PSP и DTA. Однако IFSMGR осуществляет доступ к этим данным непосредственно из защи-
щенного режима.
• Windows 95 существенно полагается на вызовы INT 21h, хотя эти вызовы, как правило, об-
служиваются IFSMGR в 32-битовом защищенном режиме. Хоть утверждение, что Windows 95
“разрывает все связи с кодом реального режима DOS”, неверно, защищенный режим DOS
наверняка присутствует. В Windows 95 вы работаете с версией DOS защищенного режима,
которая использует код и данные реального режима DOS в качестве вспомогательных.
• В большей степени Windows 95 не зависит от реального режима DOS, но то же самое пра-
вильно и для WfW 3.11. Более того, WfW 3.11 менее зависима от DOS, чем Windows 95.
Враждебность WfW 3.11 к реальному режиму DOS вызвала некоторые проблемы, так что
Windows 95 несколько отступила от этой агрессивной позиции.
• Программы, использующие сервисы Win32 API или DOS LFN, могут обходиться без DOS в
гораздо большей степени, чем программы, придерживающиеся старого DOS API. В
дополнение к способности Win32 API управления файлами обходить код реального режима
DOS (но не сервис INT 21h) мы увидим в разделе “Загрузка исполняемых файлов и файлы
отображения памяти” главы 14, что Win32-приложения могут обходиться как без кода
реального режима DOS, так и без вызовов прерывания 21h в защищенном режиме — путем
использования файлов отображения памяти. Но даже Win32-npa/K^eHHH продолжают неяв-
но использовать такие структуры данных реального режима DOS, как SDA, PSP и DTA.
• Winl6-программы и DLL, которые избегают прямого использования прерываний 21h или
DOS3Call и вместо этого используют функции Windows API, например _1ореп, или сервис-
ные функции более высокого уровня, например GetOpenFileName и GetSaveFileName, в
конце концов приходят к использованию новых сервисных функций LFN.
• Обратите внимание, что, пока мы обсуждали взаимоотношения Windows и DOS в целом,
постоянно возникал вопрос о том, как взаимодействуют с DOS Win32-npH/K^einiH. Это, в
свою очередь, приводит нас к вопросу о взаимоотношениях Win32- и Win 16-компонентов
Windows 95, и, в частности, ядра Win32 с ядром Winl6 (KERNEL32.DLL и KRNL386.EXE
соответственно). Эти вопросы будут подробно рассмотрены в следующих двух главах.
340
Неофициальная Windows 95
Глава 13
Thunk! KERNEL32
вызывает KRNL386
Программа WV86TEST из предыдущей главы по сравнению с простенькой V86TEST из гла-
вы 10 для DOS реального режима представляла собой большое усовершенствование (говорю
без лишней скромности). WV86TEST обеспечивает в некотором роде действительное про-
никновение в то, как Windows и Win32-пpилoжeния взаимодействуют с реальным режимом DOS.
Однако, как я отмечал раньше, WV86TEST сама по себе не может многого рассказать вам о тех
вызовах INT 2lh, которые поглощаются в защищенном режиме Windows, и не попадают в реальный
режим DOS. Проблема состоит в том, что WV86TEST не знает, какие вызовы INT 2lh генерируются
в защищенном режиме Windows-приложения или DLL.
Эта глава представляет Windows-программу WSPY21, которая перехватывает INT 21h в защи-
щенном режиме Windows, а не в DOS в режиме V86. Например, рис. 13.1 иллюстрирует, как
WSPY21 регистрирует некоторые вызовы INT 21h из Windows 95 Explorer (который запускает
Windows-приложение с длинным именем файла, содержащимся в каталоге с длинным именем).
Поскольку WSPY21 перехватывает INT 21h со стороны Windows, программа может показывать
вызовы DOS, которые делают Windows-приложения или DLL. В отличие от нее, WV86TEST и
V86TEST пропускают все вызовы INT 21h, которые Windows не передает в DOS реального режима,
и не знают, какая часть вызовов INT 21h прошла незамеченной.
Например, из-за того, что 32-битовый доступ к файлам Windows 3.11 и Windows 95 сам обра-
батывает большинство вызовов INT 21h для доступа к файлам, V86TEST и WV86TEST не смогли
бы увидеть ни одного из следующих случайно выбранных вызовов, зарегистрированных WSPY21:
<WINWORD> (4Е) Find First ‘\\PRDGRESS1\EDIT\IDGREP0R\PP_MTM\*.txt’
<WINWDRD> (4E) Find First ‘\\PR0GRESS1\EDIT\IDGREP0R\DUMMIESV.txt’
<WINWORD> (3D) Open 'C:\WINDOWS\SYSTEM\COMPDBJ.DLL'
<WINWORD> (3D) Open 'C:\WIND0WS\SYSTEM\0LE2.DLL’
<WINWORD> (3D) Open ‘C:\WINDOWS\SYSTEM\STDRAGE.DLL’
<WINWORD> (3D) Open 'C:\WINDOWS\SYSTEM\OLE2DISP.DLL'
<PRDGMAN> (3B) ChDir ‘\MSDFFICE’
<PROGMAN> (3D) Open ‘C:\WIND0WS\SYSTEM\CTL3DV2.DLL’
<PROGMAN> (3D) Open ‘C:\WINDDWS\SYSTEM\MSFFILE.DLL’
<PROGMAN> (3D) Dpen 'C:\WINDOWS\SYSTEM\SDM.DLL'
<PROGMAN> *(4B) Exec ‘C:\MSOFFICE\MSOFFICE.EXE’
<MSDFFICE> (3D) Open ‘C:\WINDOWS\MSDFFICE.INI’
<MSOFFICE> (43) Get/Set File Attr 'C:\WINWORD\WINWORD.EXE’
<MSOFFICE> (43) Get/Set File Attr 'C:\EXCEL\EXCEL.EXE'
<MSDFFICE> (43) Get/Set File Attr ‘C:\POWERPNT\POWERPNT.EXE’
<MSDFFICE> (43) Get/Set File Attr ‘C:\ACCESS\msaccess.exe’
<MSOFFICE> (43) Get/Set File Attr 'C:\WINDOWS\MSMAIL.EXE'
<MSOFFICE> (3D) Open ‘C:\WINDOWS\SYSTEM\AB.DLL’
<MSDFFICE> (3D) Dpen ‘C:\WINDOWS\SYSTEM\FRAMEWRK.DLL’
<MSOFFICE> (3D) Open ‘C:\WINDOWS\SYSTEM\DEMILAYR.DLL’
<MSOFFICE> (3D) Dpen ‘C:\WINDOWS\SYSTEM\MAILMGR.DLL’
<MSOFFICE> (3D) Open ‘C:\WINDOWS\SYSTEM\STORE.DLL’
Глава 13. Thunk! KERNEL32 вызывает KRNL386
341
<MSOFFICE> (3D) Open ’C:\WINDOWS\SYSTEM\OLECLI.DLL’
<MSMAIL> (3D) Open ‘C:\WINDOWS\WGPOMGR.DLL’
<MSMAIL> (3D) Open ’C:\WINDOWS\SYSTEM\VFORMS.DLL’
<MAILSPL> *(4E) Find First ‘C:\WINDOWS\MSMAIL.INI’
<MAILSPL> (3D) Open ‘C:\WINDOWS\SYSTEM\MSSFS.DLL’
<MAILSPL> (3D) Open ‘C:\WINDOWS\SYSTEM\PABNSP.DLL’
<MSOFFICE> (30) Open ‘C:\WINDOWS\SYSTEM\TOOLHELP.DLL' '
<MSOFFICE> (3D) Open ‘C:\WINWORD\WWINTL.DLL’
<MSOFFICE> (3D) Open ‘C:\WIND0WS\SYSTEM\0LE2.DLL’
<MSOFFICE> (3D) Open ‘C:\WIND0WS\SYSTEM\0LE2NLS.DLL’
<EXCEL> (4E) Find First ‘[00020841-0000-0000-0000-000000000046]’
<EXCEL> (3D) Open ‘C:\WINDOWS\SYSTEM\256_1O24.DRV
<EXCEL> (4E) Find First ‘\\PR0GRESS1\EDIT\FD0BISH.XLS’
<EXCEL> (3D) Open ‘C:\WINDOWS\SYSTEM\NETAPI.DLL’
В этой и в следующей главах с помощью WSPY21 будут рассмотрены вызовы INT 21h (кото-
рые, как мы изучили и, вероятно, прочувствовали самим сердцем, вовсе не обязаны соответствовать
активности DOS в реальном режиме), генерируемые некоторыми обычными Windows-приложения-
ми. Мы обратим внимание на то, как именно программное обеспечение Win32 в Windows 95 взаимо-
действует с DOS реального режима.
Как вы поймете при изучении WSPY21.C в конце этой главы, WSPY21 видит вызовы INT 21h,
проходящие через 16-битовое ядро Windows (KRNL386.EXE). Это означает, что WSPY21 не заме-
чает вызовов INT 21h, которые Win32-приложения и DLL осуществляют через механизм VxDCallO,
описанный в разделе “Win32-Bepcmi FindNextFile — это функция 714Fh INT 21h” з главе 12.
И все же WSPY21 достаточно хороша. Как мы увидим в дальнейшем, WSPY21 все-таки обна-
руживает некоторые вызовы INT 21h, берущие начало в Win32-npitTC^eHHHX и DLL. То, что
WSPY21 видит эти вызовы, означает, что они не используют вызову VxDCallO, а проходят через
обработчик INT 2 lh из 16-битового KRNL386. Этот, казалось бы, незначительный факт приведет нас
к важному заключению об архитектуре Windows 95, а именно — ядро Win32 (KERNEL32) вызы-
вает ядро Winl6 (KRNL386), в то время как Microsoft столь энергично утверждает, что этого не
происходит. Мы детально рассмотрим это ложное утверждение в данной главе и выскажем некото-
рые соображения по этому поводу.
WSPY21 не только регистрирует многие вызовы INT 21h в защищенном режиме, которые
V86TEST никогда не увидела бы, но также представляет совершенно иной взгляд на то, как
Windows-приложения взаимодействуют с DOS. Там, где V86TEST и WV86TEST просто могут
показать число вызовов каждой функции INT 21h, WSPY21 может показать каждый отдельный
вызов функции INT 21h вместе с именем вызывающей программы. Например, V86TEST показывает,
что сгенерирована целая куча вызовов функции 50h (Set PSP), тогда как WSPY21 в этой же
ситуации показала бы, что эти вызовы обычно происходят непосредственно до и сразу после
переключения задач. В каких-то будущих версиях WSPY21 можно было бы использовать ToolHelp
NotifyRegister API для отображения переключения задач и других событий. Например:
»
<WINWORD> (42) LseekO 17 000D04f0
<WINWORD> (3F) Read 17 (0D11h), 1168 (0490h)
<PROGMAN> *(59) Get Extended Error Code*
<PROGMAN> (50) Set PSP 6039 (1797h)
<PROGMAN> (1A) Set DTA 1797.0080
<PROGMAN> (3B) ChDir ‘\WIDNOWS’
<PROGMAN> *(19) Get Disk
<PROGMAN> *(47) Get Curr Dir 3 (03h)
<NETDDE> (50) Set PSP 4495 (118Fh)
<NETDDE> (2A) Get Date
<NETDDE> (2C) Get Time
<NETDDE> (2A) Get Date
<WINWORD> (50) Set PSP 10351 (286Fh)
<WINWORD> (2C) Get Time
342
Неофициальная Windows 95
и»
- !1₽ Г «ч.ч1'»* ' ;“Д
эдйШ»» И» 1Ж'(••#»»)• " Я
лей»»» (WJ' Ж#, f I» Вг» Лш»»*а» Э (»Ж) и|
лева»- tecB-fej I* Вт» h»w»«ttrt а (»Ж) я
:ЖЖ»> (Wj ШЛ (•ij 'ttt-tai w» (®Ж) |i|
=<«яи»> (iH !••<«< f#t®w (-м <9ж«й) wi
ae® |в) ылг«« ишш »
Ией»» (Ш лна «й («а»»), «м (шжы «
ксви» («) ««<«« <ме®<. w (веем {И
<еши» *(isj (Лиш еш» ₽» тю (»вчло, 1Я
ЙЙЙЙЙ ('«> :Й* вЙ *•#:#•«• ..-Д
«сжал (то и* (»)-©лй« ‘Шввшвпмв «йм wwi*»*» » И
ЙввЙТ : !«»<SetЙШтШ- «ШЙЙ • : ' :Т- • Л л - ; л < < И
Рис. 13.1. WSPY21 — это Windows-программа, которая наблюдает за вызовами INT 21h из других Windows-
приложений. Здесь WSPY21 наблюдает, как Windows 95 Explorer запускает программу с длинным именем.
Строка, переданная в ЕХЕС, была ‘C:\Unauthorized Windows 95\Windows Binaries\Windows Walker’
Вышеприведенный вывод на первый взгляд, казалось бы, совершенно неинтересен, однако он
позволяет нам много узнать о внутренней структуре Windows 95. Например, следующая строка, по-
казанная WSPY21, надолго привлечет наше внимание:
<САВ32> *(55) (Undoc) Create PSP 7503 (1D4Fh), 256 (0100h)
Почему Win32-npMo«eHHe, да, именно Win32-npMo«eHHe (CAB32 — это Windows 95 Explo-
rer), генерирует вызов DOS Create PSP? И зачем ему проходить весь путь к DOS реального режима
(как мы видели и в предыдущих разделах)? — Рассмотрение даже этой единственной строки из
вывода WSPY21 проливает яркий свет на большинство связей между ядрами Win32 и Winl6 в
Windows 95.
Точно так же, понимание следующей, совершенно “безобидной”, строки из вывода WSPY21
потребует немалых исследований:
<WINHELP> (3F) Read 750 (02eeh), 25958 (6566h)
Как и почему WinHELP, старое Winl 6-приложение, производит чтение с таким дескриптором
файла, как 750, вместо каких-нибудь нормальных 6, 7, 42? В разделе “Win32-flecKpinrropbi файлов
и thunk-переключение” главы 14 мы углубимся в эту непонятную тему и попытаемся пролить свет
на существенно более важные вопросы об архитектуре Windows 95.
Хотя WSPY21 и отслеживает вызовы INT 21h в защищенном режиме, мы собираемся исполь-
зовать ее для наблюдения не только за связью Windows-DOS, но и за связью Win32-Winl6.
Глава 13. Thunk! KERNEL32 вызывает KRNL386
343
Запуск Winl 6-приложений из Explorer
В предыдущей главе мы запускали Winl 6-приложение Calc из Windows 95 Explorer, выполня-
ли некоторые вычисления (даже проверили магическую формулу 3.11-3.10=0.0, — случайную ошиб-
ку, которая появилась в Windows 3.0 и прошла через все реализации, включая Windows NT), пере-
ключились в Help, немного просмотрели и закрыли его, затем закрыли Calc. WV86TEST своим “все-
проникающим” оком, видит это как серию (немногим менее 200) вызовов DOS реального режима:
Прошло 138 секунд
Вызовы INT 21h:
0Е: 44 2А: 3 2С: 14 30: 2 4С: 2 50: 129 55: 2
Для такого же набора операций WSPY21 в Windows 95 регистрирует более 500 вызовов
INT 21h. Большинство из них идет из WINHELP. Когда я получил список от WSPY21, удалил
некоторые детали (такие, как количество прочитанных байтов и смещения в файле), отсортировал
его и использовал программу AWK для подсчета числа одинаковых строк, список вызовов INT 21h
(с количеством вызовов, показанным слева) получился следующим:
43 <WINHELP> *(50) Set PSP 175 (OOAFh)
42 <WINHELP> *(50) Set PSP 7391 (ICDFh)
40 <WINHELP> *(3F) Read 689
31 <WINHELP> *(42) LseekO 5
29 <WINHELP> *(42) LseekO 689
28 <WINHELP> *(3F) Read 5
23 <WINHELP> *(42) LseekO 6
22 <WINHELP> *(3F) Read 6
18 <CALC> *(50) Set PSP 175 (OOAFh)
16 <CALC> *(50) Set PSP 7567 (1D8Fh)
13 <CALC> *(3F) Read 686
11 <CALC> *(42) LseekO 686
Многие из этих вызовов были пропущены WV86TEST по той простой причине, что Windows не
отправляла их в DOS. Вот несколько фрагментов из результатов WSPY21.
<САВ32> *(4В) Ехес ‘C:\WINDOWS\calc.exe’
<САВ32> (50) Set PSP 167 (00A7h)
<САВ32> *(55) (Undoc) Create PSP 7503 (1D4Fh), 256 (0100h)
<CAB32> (1A) Set DTA 00A7:0080
<CAB32> (71) LFN (38) ChDir ‘\WINDOWS’
<CAB32> (50) Set PSP 167 (00A7h)
<CAB32> *(71) LFN (4E) Find First ‘C:\WINDOWS\WIN.INI’
<CAB32> *(71) LFN (A1) Find Close
<CALC> (30) Get DOS Vers
<CALC> *(25) Set Vect 2 (02h)
<CALC> *(25) Set Vect 117 (75h)
<CALC> *(25) Set Vect 62 (3Eh)
<CALC> (50) Set PSP 167 (00A7h)
<CALC> *(4B) Exec ‘C:\WINDOWS\WINHELP.EXE’
<CALC> *(55) (Undoc) Create PSP 7263 (1C5Fh), 256 (0100h)
<CALC> (1A) Set DTA 1D4F:0080
<CALC> (55) (Undoc) Create PSP 7263 (1C5Fh), 256 (0100h)
<CALC> (50) Set PSP 167 (00A7h)
344
Неофициальная Windows 95
<CALC> *(71) LFN (4E) Find First ‘C:\WINDOWS\WIN.INI’
<CALC> *(71) LFN (A1) Fine Close
<CALC> <WINHELP> <WINHELP> <WINHELP> <WINHELP> <WINHELP> (5D) Set PSP 75D3 (1D4Fh) (30) Get DOS Vers (50) Set PSP 167 (D0A7h) (42) LseekO 750 000219d0 (3F) Read 750 (02eeh), 25958 (6566h) (3F) Read 750 (02eeh), 1120 (0460h)
<WINHELP> (1A) Set DTA 1C5F:0080
<WINHELP> (71) LFN (6C) Open/Create ‘C:\WINDOWS\HELP\CALC.HLP’
<WINHELP> (71) LFN (6C) Open/Create 'C:\WINDOWS\CALC.ANN'
<WINHELP> (59) Get Extended Error Info
<WINHELP> (71) LFN (6C) Open/Create ‘C:\WINDOWS\HELP\CALC.GID
<WINHELP> (2F) Get DTA
<WINHELP> (1A) Set DTA 1B07.-353E
<WINHELP> (4E) Find First ‘C:\WINDOW\HELP\CALC.CNT’
<WINHELP> (1A) Set DTA 1C5E:0080
<WINHELP> (71) LFN (6C) Open/Create ‘C:\WINDOWS\SYSTEM\commctrl.dll
<WINHELP> (50) Set PSP 7263 (1C5Fh)
<WINHELP> (42) LseekO 5 00005619
<WINHELP> (3F) Read 5 (0005h), 9 (0D09h)
<WINHELP> (42) LseekO 5 D00D5622
<WINHELP> (3F) Read 5 (0005h), 14 (OOOEh)
. и т.д.
<WINHELP> (00) Exit
<WINHELP> (50) Set PSP 167 (00A7h)
<WINHELP> (3E) Close 750 (02EEh)
<CALC> (00) Exit
<CALC> (50) Set PSP 167 (00A7h)
<CALC> (3E) Close 747 (02EBh)
Прежде чем мы окунемся в специфику этих результатов (а именно, выясним, почему Win32-
приложения, вроде САВ32, вызывают такие функции INT 21h, как ЕХЕС, создание PSP DOS
реального режима, установка текущего PSP и DTA, и вообще ведут себя, как дешевые DOS-про-
граммы), давайте сначала разберемся с самыми важными моментами, которые просматриваются в
выводе WSPY21.
Во-первых, имя, которое WSPY21 показывает при каждом вызове INT 21h, вроде САВ32,
CALC или WINHELP, — это имя модуля, принадлежащее текущей на момент вызова INT 21h за-
даче. WSPY21 извлекает это имя по смещению 0F2h из текущего блока данных задачи (Task Data
Block — TDB); см. Undocumented Windows, гл. 5). WSPY21.C использует примерно следующий
код:
printf(“<%8Fs>”, MK_FP(GetCurrentTask(), 0xf2), 8);
Почему WSPY21 не получает свой собственный дескриптор, когда она вызывает Get
CurrentTask? Потому что GetCurrentTask вызывается из функции обработчика прерывания INT 2 lh,
принадлежащего WSPY21. Когда вызывается обработчик прерывания WSPY21, она не обязательно
будет текущей задачей. Как и другие вызовы Windows API, обработчик прерывания вызывается в
контексте задачи, текущей на момент прерывания. Поскольку WSPY21 достаточно редко обраща-
ется к INT 21h, в выводе обычно будет указана какая-то другая задача, не WSPY21.
Глава 13. Thunk! KERNEL32 вызывает KRNL386
345
При инициализации WSPY21 использует GetCurrentTask для получения собственного иденти-
фикатора задачи, а затем обработчик прерываний будет отфильтровывать любые вызовы INT 21h,
исходящие непосредственно от WSPY21. WSPY21 обычно вызывает INT 21h, только когда сохра-
няет список результатов своей работы в файле. Немного отвлекаясь, замечу, что вы не найдете этого
кода в WSPY21.C (указанного в этой главе), поскольку WINIO автоматически предоставляет всем
WINIO-программы пункт меню File/Save Buffer... Код WINIO, обеспечивающий этот пункт меню,
использует общий диалог GetSaveFileName и затем сохраняет буфер WINIO на диске с помощью
API _lcreat, _lwrite, _lclose. Вызов _lcreat преобразуется в функцию 716Ch прерывания INT 21h,
таким образом WSPY21 (и любая другая программа, использующая WINIO) может сохранять
файлы с длинным именем в Windows 95, даже если это не подразумевалось при создании WINIO.
Этот трюк не сработал бы, если WINIO использовала бы прямой вызов INT 21h или библиотеку С
взамен API _1хххх, (который был недокументированным; см. раздел “Недокументированные функ-
ции файлового ввода-вЬгвода” в первом [1988] издании классической, книги Programming Windows
Чарлза Пицольда).
Видите? Это подталкивает к применению API Windows, даже недокументированных. Однако,
хотя WINIO-приложения могут записывать файлы с длинными именами в Windows 95, они, как и
прочие старые Winl 6-приложения, не могут считывать файлы с длинными именами — и это окон-
чательно запутывает пользователя.
Вернемся к именам задач, выводимым WSPY21. Все Win32-^WK^eHHH имеют Winl6 TDB, так
что прием WSPY21 для получения имени задачи работает и для Win32-пpилoжeний, запускаемых в
Windows 3.x с Win32 или в Windows 95. Вскоре мы узнаем, почему Win32-пpилoжeния имеют
Winl 6 TDB — причина довольно существенна.
Когда WSPY21 в связи с вызовом INT 21h выдает такое имя задачи, как САВ32 или CALC, это
обычно означает не то, что приложение само вызвало INT 21h, а то, что какая-то DLL вызвала INT
21h для текущей задачи. DLL сами по себе задачами не являются (однако интересно, что
KERNEL32.DLL в Windows 95 имеет связанную с ней задачу KERNEL32). Когда какое-либо
приложение вызывает Windows API, DLL обеспечивает то, что API не становится текущей задачей
(как это может быть в системе клиент/сервер). Вместо этого текущая задача как бы “переходит на
другую сторону прилавка” (Гордон Литвин (Gordon Letwin) прекрасно объясняет этот момент в
своей книге Inside OS/2) и запускает код API в DLL.
И еще один, последний, но довольно важный момент в выводе WSPY21: как мы увидим в конце
этой главы, WSPY21 имеет два обработчика прерывания INT 21h. Один из них устанавливается
посредством DPMI-функции Set Protect Mode Interrupt Vector (функция 0205h прерывания INT
31h), второй — с помощью недокументированного Windows API, GetSetKernelDosProc {Undocu-
mented Windows, p. 188, 271-273). Вызов DPMI позволяет WSPY21 перехватить вызов INT 21h в
защищенном режиме до того, как он попадет в Winl6 KERNEL, GetSetKernelDosProc позволяет
WSPY21 вставить обработчик после обработчика Winl 6 KERNEL, но перед первым обработчиком
INT 2 lh VxD защищенного режима.
Все это будет объяснено, с обычными душераздирающими подробностями, ближе к концу гла-
вы. А на данный момент нам достаточно знать, что звездочка (*) в выводе WSPY21 указывает, что
вызов INT 2 lh впервые был замечен в KernelDosProc, где KERNEL обслуживает INT 2 lh из цепочки
VxD-обработчиков INT 21h защищенного режима. Пробел указывает, что вызов был замечен раньше
(или выше, в зависимости от того, как на это посмотреть) в обработчике INT 2 lh из KERNEL. Если
обработчик INT 21h заметил вызов, WSPY21 не будет, его показывать снова в KernelDosProc, если
вы не укажете WSPY21 ключ -SHOWALL.
Позже -SHOWALL покажет нам, что некоторые вызовы функций INT 21h в Windows обходят
DOS реального режима (причем уже многие годы), поскольку их отлавливает KERNEL (а не VxD
вроде IFSMGR). Лучшим примером является функция 4Bh INT 21h (EXEC). Ни реальный режим
DOS, ни даже Windows VxD не могут знать, что делать с ЕХЕС из Winl6-программ, и только об-
работчик INT 21h из KERNEL обрабатывает этот вызов (естественно, преобразуя его в другие вызо-
вы DOS, такие как FileOpen, см. “Win32-flecKpHnropbi файлов и thunk-переключение” в главе 14).
346
Неофициальная Windows 95
САВ32: KERNEL32
использует Winl 6 KERNEL
Результат работы WSPY21, рассмотренный в предыдущем разделе, начинается с САВ32, Win-
dows 95 Explorer/Cabinet. Список показывает, что, когда я нажимаю на пиктограмму Calc, Explo-
rer (или DLL, по его требованию) вызывает функцию 4Bh для запуска CALC.EXE, функцию 55h —
для получения PSP и т.д. Мы знаем еще от WV86TEST, что Windows самостоятельно обрабатывает
вызов функции 4Bh (по причинам, описанным в конце предыдущего раздела). С другой стороны, бла-
годаря WV86TEST нам также известно, что Windows отсылает вызов функции 55h Create PSP в DOS.
Calc является Win 16-приложением. Что случится, если мы используем Win32-npM(^eHHe, та-
кое как САВ32, для запуска другого Win32-npim(^eHHH? Хотя Win32-npM(^eHHe, подобное
САВ32, и не использует функцию 4Bh INT 21h для запуска другого Win32-приложения, WSPY21
по-прежнему увидит вызов Create PSP. Это очень важный момент, и вскоре мы узнаем — почему.
Следующий вывод WSPY21 показывает некоторые из вызовов INT 21h, возникших при запуске
Clock, WinBezMT и FreeCell из Windows 95 Explorer; я дал им какое-то время поработать и затем
закрыл их. Я также использовал FindFile и Control Panel, которые встроены в Explorer.
<САВ32> *(50) Set PSP 167 (00A7h)
<САВ32> (55) (Undoc) Create PSP 7431 (1D07h), 256 (0100h)
<CAB32> *(1A) Set DTA 00A7:0080
<CAB32> *(71) LFN (3B) ChDir /WINDOWS’
<CAB32> *(50) Set PSP 4719 (126Fh)
<CLOCK> *(50) Set PSP 167 (00A7h)
<CLOCK> *(42) LseekO 730 0000038a
<CLOCK> *(3F) Read 730 (02DAh), 217 (00D9h)
<CAB32> *(50) Set PSP 167 (00A7h)
<CAB32> (55) (Undoc) Create PSP 7343 (ICAFh), 256 (010Dh)
<CAB32> *(71) LFN (3B) ChDir /WINDOWS’
<CAB32> *(55) (Undoc) Create PSP 7343 (ICAFh), 256 (0100h)
<CAB32> *(50) Set PSP 4719 (126Fh)
<WINBEZMT> *(50) Set PSP 167 (00A7h)
<WINBEZMT> *(42) LseekO 570 00000631
<WINBEZMT> *(3F) Read 570 (023Ah), 1863 (0747h)
<CLOCK> *(50) Set PSP 167 (00A7h)
<CLOCK> *(42) LseekO 730 000079e0
<CLOCK> *(3F) Read 730 (02dah), 12205 (2FADh)
<CAB32> *(50) Set PSP 167 (00A7h)
<CAB32> (55) (Undoc) Create PSP 7207 (1C27h), 256 (0100h)
<CAB32> *(71) LFN (3B) ChDir ‘\win32app\freecell’
<CAB32> *(55) (Undoc) Create PSP 7207 (1C27h), 256 (0100h)
<CAB32> *(50) Set PSP 4719 (126Fh)
<FREECELL> *(50) Set PSP 167 (00A7h)
<FREECELL> *(42) LseekO 570 00000631
<FREECELL> *(3F) Read 570 (023Ah), 1863 (0747h)
<FREECELL> *(50) Set PSP 7207 (1C27h)
<FREECELL> *(50) Set PSP 7207 (1C27h)
<CAB32> *(44) IOCTL (09) Is Drv Remote? 3 (03h)
<CAB32> *(44) IOCTL (08) Is Drv Removeable? 3 (03h)
<CAB32> *(44) IOCTL (0E) Get Log Drv Map 3 (03h)
<CAB32> *(50) Set PSP 10415 (28afh)
<KERNEL32> *(50) Set PSP 1D415 (28afh)
<KERNEL32> *(00) Exit
Глава 13. Thunk! KERNEL32 вызывает KRNL386
347
<KERNEL32> .(50) Set PSP 167 (00A7h)
<CAB32> *(50) Set PSP 167 (00A7h)
<CAB32> *(42) LseekO 538 00052280
<CAB32> *(3F) Read 538 (021Ah), 4941 (134Dh)
<CAB32> *(3F) Read 538 (021Ah), 368 (0170h)
<CAB32> *(50) Set PSP 4719 (126Fh)
<RUNDLL32> *(50) Set PSP 167 (00A7h)
<RUNDLL32> *(42) LseekO 570 00000631
<RUNDLL32> *(3F) Read 570 (023Ah), 1863 (0747h)
<RUNDLL32> *(71) LFN (3B) ChDir ‘\WINDOWS’
<RUNDLL32> *(2F) Get DTA
<RUNDLL32> *(1A) Set DTA 0BE7:856C
<RUNDLL32> *(4E) Find First ‘C:\WININSTO.400\*.INF'
<RUNDLL32> *(1A) Set DTA 0DA7:0080
<RUNDLL32> *(2F) Get DTA
<RUNDLL32> *(1A) Set DTA 0BE7:856C
<RUNDLL32> *(4E) Find First 'C:\WININST0.400\*.MWD'
<RUNDLL32> *(1A) Set DTA 00A7:0080
<RUNDLL32> *(50) Set PSP 167 (00A7h)
<RUNDLL32> *(42) LseekO 619 00029200
<RUNDLL32> *(3F) Read 619 (026Bh), 256 (0100h)
<RUNDLL32> *(50) Set PSP 11463 (2CC7h)
<KERNEL32> *(50) Set PSP 11463 (2CC7h)
<KERNEL32> *(00) Exit
<KERNEL32> *(50) Set PSP 167 (00A7h)
<CAB32> *(50) Set PSP 167 (00A7h)
<CAB32> (55) (Undoc) Create PSP 11591 (2D47h), 256 (0100h)
<CAB32> *(71) LFN (3B) ChDir ‘\WINDOWS’
<CAB32> *(55) (Undoc) Create PSP 11591 (2D47h), 256 (OlOOh)
<CAB32> *(50) Set PSP 4719 (126Fh)
<RUNDLL32> *(50) Set PSP 167 (00A7h)
<RUNDLL32> *(42) LseekO 570 00000631
<RUNDLL32> *(3F) Read 570 (023Ah), 1863 (0747h)
Здесь приведены только фрагменты (в основном, моменты переключения Windows 95 с одной
задачи на другую) из более чем 1000-строчного списка, полученного при запуске этой группы
Win32-пpилoжeний под Windows 95.
Это может показаться скучным, но я рекомендую просмотреть весь список результатов
WSPY21, особенно самые частые вызовы, а также единичные вызовы Create PSP:
130 <САВ32> *(50) Set PSP 167 (00A7h)
129 <CAB32> *(50) Set PSP 4719 (126Fh)
32 <RUNDLL32> *(50) Set PSP 167 (00A7h)
28 <CAB32> *(44) IOCTL (OE) Get Log Drv Map 3 (03h)
28 <CAB32> *(44) IOCTL (09) Is Drv Remote? 3 (03h)
28 <CAB32> *(44) IOCTL (08) Is Drv Removeable? 3 (03h)
23 <RUNDLL32> *(3F) Read 619
22 <RUNDLL32> *(50) Set PSP 11463 (2CC7h)
22 <RUNDLL32> *(42) LseekO 619
19 <CAB32> *(3F) Read 675
12 <CAB32> *(3F) Read 707
1 <CAB32> (55) (Undoc) Create PSP 7343 (ICAFh). 256 (0100h)
1 <CAB32> (55) (Undoc) Create PSP 7207 (1C27h), 256 (0100h)
1 <CAB32> (55) (Undoc) Create PSP 11591 (2D47h), 256 (0100h)
1 <CAB32> (55) (Undoc) Create PSP 11575 (2D37h), 256 (0100h)
1 <CAB32> (55) (Undoc) Create PSP 10415 (28AFh), 256 (0100h)
1 <CAB32> (55) (Undoc) Create PSP 7431 (1D07h), 256 (0100h)
348
Неофициальная Windows 95
Такие вызовы, как Read и Lseek, обрабатываются в IFSMGR. Они выглядят почти как вызовы
DOS, но при этом не являются вызовами DOS реального режима. Большая часть DOS содержится
сейчас в 32-битовом коде защищенного режима в VxD. С другой стороны, эта большая часть DOS
защищенного режима часто общается с помощником, известным как MS DOS реального режима,
оставленным в 16-битовом режиме V86.
Программа WV86TEST, описанная в главе 12, позволяет нам подслушать диалоги между
Windows и DOS. При работе этой же группы Win32-пpилoжeний под Windows 95 программа
WV86TEST регистрирует следующие вызовы ШТ 21h, которые Windows адресует своему ассистенту —
DOS реального режима:
Прошло 179 секунд
Вызовы INT 21h:
2А: 1542 2С: 1542 30: 7 44: 22 4С: 3 50: 449 51: 1
55: 3 65: 2 ЕА: 1
На самом деле, этот тест не совсем соответствует тому, что я провел с WSPY21. И хотя я
открывал Control Panel в Explorer, я не щелкал ни на одной пиктограмме Control Panel. Как мы
сейчас увидим, щелчок на пиктограмме из Control Panel приводит к загрузке Winl6 DLL. Я хотел
увидеть вызовы DOS реального режима, приходящие из настолько чистой Win32-среды, насколько
это возможно (хотя WV86TEST, Winl6-nporpaMMa, оставалась запущенной). Microsoft и компью-
терная пресса на пару утверждают, будто в Windows 95 есть принципиальная разница между
конфигурацией для запуска смеси Winl 6-, Win32- и DOS-приложений и конфигурацией для
исключительно Win32-пpилoжeний. В этой главе я попытаюсь доказать обратное.
Хотя я и подчеркивал важность того, обрабатываются ли вызовы INT 21h VxD или DOS
реального режима, рецензент этой книги (редактор Microsoft System Journal Джим Финнеган)
заметил, что здесь должна быть высказана еще и такая точка зрения:
Кажется наиболее важным, что независимо от того, действительно ли вызовы INT 21h передаются в
DOS или нет, вызовы Win32 API преобразуются в вызовы прерываний! Меня не волнует, кем
обрабатываются прерывания (DOS или VxD), но тот факт, что ядро Chicago по-прежнему больше
базируется на прерываниях, чем на процедурах, удручает. Вы, наверное, помните, что одной из
отправных точек Microsoft при продаже OS/2 было то, что OS/2 отбрасывала все, базирующееся на
прерываниях. Боюсь, что мы никогда этого не получим...
Отдельно от этого зрелища, где чистые Win32-npWKmeHHH унижаются до обращения к ШТ 2111, —
некоторые из которых, как мы знаем благодаря WV86TEST, проходят весь путь к DOS реального
режима (хотя этот реальный режим контролируется Windows в V86-peжимe), — стоят некоторые
интересные специфические особенности, которые опять же можно обнаружить в выводе WSPY21.
Например, в некоторых местах мы видим, что KERNEL32 производит серию из трех вызовов
DOS:
<KERNEL32> *(50) Set PSP 10415 (28afh)
<KERNEL32> *(00) Exit
<KERNEL32> *(50) Set PSP 167 (00A7h)
Во-первых, как может KERNEL32 выполнять выход через функцию 0 прерывания INT 21h и
быть в состоянии затем сделать вызов Set PSP? KERNEL, как бы, уже попрощался, но затем остал-
ся поговорить с DOS еще немного.
Во-вторых, почему KERNEL32 — основной и самый чистый Win32-MOflynb — работает с PSP и
вызывает устаревшую функцию DOS?
В-третьих, ведь KERNEL32 является динамической библиотекой (DLL) Win32? Как же он
вдруг становится задачей? DLL не являются самостоятельными задачами. Когда задача вызывает
какой-то API, находящийся в DLL, код этого API запускается в контексте вызывающей задачи. Мы
увидим позже (в разделе “Win32 и PSP”), что 0A7h является PSP KERNEL32. Но зачем основа
Windows 95, замечательное новое WIN32 API, прибегает к использованию допотопных структур
данных DOS, вроде PSP?
Глава 13. Thunk! KERNEL32 вызывает KRNL386
349
Если вы когда-либо занимались программированием TSR, ответ на первый вопрос должен быть
вам понятен. Задача KERNEL32 остается действующей после вызова DOS Exit по той простой
причине, что завершается вовсе не KERNEL32. Да, KERNEL вызывает функцию DOS Exit, но пе-
ред этим он вызывает функцию Set PSP, переходя на PSP (28AFh в этом примере), принадлежащий
некоторой другой задаче. Вызов DOS Exit работает для текущего PSP, а из-за обращения к Set
PSP, это не обязательно будет PSP вызывающего модуля.
Другими словами, KERNEL32 завершил какую-то другую задачу. Это стандартная техника при
программировании TSR. Когда пользователь просит деинсталировать TSR, нерезидентная часть TSR
может переключиться на PSP резидентной части и затем завершиться. Когда одна задача навязывает
подобны^ образом выход какой-то другой задаче, DOS освобождает память и дескрипторы файлов
этой другой задачи (см. Undocumented DOS, 2d ed., р. 603, основанную на материалах, предостав-
ленных для первого издания Томом Патерсоном).
В данном случае задача несколько иная: когда заканчивается такое Win32-npmK^einie, как
RUNDLL32, KERNEL32 осуществляет очистку после использования приложения, в частности, этой
Set PSP/Exit/Set PSP-последовательностью.
Это дает ответ и на другие вопросы. Подобная операция очистки запускается как отдельная
ветвь выполнения. KERNEL32.DLL получает дескриптор задачи и PSP, поскольку это необходимо,
чтобы считаться задачей. Отладчик Win-ICE показывает, что KERNEL имеет несколько ветвей:
:thread kernel32
RingOTCB ID Context Ring3TCB Process TaskDB PDB SZ Owner
C0FDE188 0D05 CDFD98C0 81106804 81104250 0097 00A7 32 Kernel32
COFDBCDC 0003 C0FD98C0 81105FC0 81104250 0097 00A7 32 Kernel32
C0FD12CC 0002 C0FD98C0 811052A8 81104250 0097 00A7 32 Kernel32
C4520298 0001 C0FD98C0 81104328 81104250 0097 00A7 32 VM 01
Но кто генерирует последовательность вызовов Set PSP /Exit/Set PSP? WSPY21 видит ее,
поэтому KERNEL32 не использует здесь VxDCallO. Немного порыскав вокруг с помощью Win-ICE,
обнаруживаем, что эти вызовы идут от Winl6 KERNEL (KRNL386) через внутреннюю версию
недокументированного NoHookDOSCall API.
Постойте! Если KERNEL32 является текущей задачей, то как же вступил в действие Win 16
KERNEL? Этого не должно происходить. Microsoft утверждает, что “Модуль KERNEL32 полно-
стью независим от своей 16-битовой версии. Существует некоторая зависимость 16-битовой версии
от 32-битовой, но 32-битовое ядро никогда не вызывает 16-битовое” (.Microsoft System Journal,
May 1994).
Обратная трассировка в отладчике от точки вызова NoHookDOSCall Set PSP/Exit/Set PSP
показывает, что KERNEL32 извлекает дальний 16-битовый selector:offset адрес этого кода Winl6
KERNEL из таблицы двойных слов, затем передает адрес процедуре QT_Thunk из KERNEL32. Мы
рассмотрим QT_Thunk и эту таблицу немного позже (в разделе “От Explorer до Create PSP за
шесть простых шагов”). Сейчас достаточно сказать, что KERNEL32 вызывает не просто процедуру
Winl6 KERNEL, а процедуру Winl6 KERNEL, которая вызывает функции INT 21h.
И, только для самых ярых борцов за чистоту операционных систем, скажу, что функции 0 и
50h прерывания INT 21h передаются в код DOS реального режима. VxD DOSMGR обрабатывает
вызовы функции Set PSP из защищенного режима через сервис VMM_SelectorMapFlat, который
преобразует PSP защищенного режима в адрес параграфа реального режима, и затем передает
вызов в DOS через Begin_Next_V86_Exec и Exec_Int 21h. И DOSMGR обрабатывает вызов функ-
ции 0 (которая уже давно признана устаревшей), преобразуя его в вызов новой функции выхода
4Ch, которая затем снова посылается в DOS через Begin_Next_V86_Exec и Exec_Int 21h. Да,
KERNEL32 действительно использует код реального режима DOS.
Другой интересной изюминкой списка от WSPY21 является то, что мы иногда видим два вызова
Create PSP, разделенных вызовом LFN ChDir:
<САВ32> (55) Undoc) Create PSP 7343 (ICAFh), 256 (OlOOh)
<CAB32> *(71) LFN (3B) ChDir ‘\WINDOWS’
<CAB32> *(55) Undoc) Create PSP 7343 (ICAFh), 256 (OlOOh)
350
Неофициальная Windows 95
Y
По сути дела, здесь только один вызов Create PSP. WSPY21 замечает вызов сначала в своем об-
работчике INT 21h и затем в своей KernelDosProc (выделен звездочкой). Но если мы не используем
опцию -SHOWALL командной строки (а она здесь действительно не использовалась), WSPY21 фильт-
рует вызовы из KernelDosProc, которые соответствуют вызовам, замеченным верхним обработчиком
INT 21h. Но почему этот вызов оказался не отфильтрованным? Потому что второй вызов последо-
вал за первым не сразу: WSPY21 не смогла определить, что они представляют один и тот же вызов.
Каким-то образом вызов LFN ChDir прорывается после того, как Create PSP достиг Win 16
KERNEL, но перед тем, как Create PSP появился в KernelDosProc. Но как? Очень просто: вызов
LFN ChDir вставил обработчик INT 21h из KERNEL. Почему? Потому что сразу после переклю-
чения задач обработчик INT 21h из KERNEL зачастую должен произвести несколько дополни-
тельных вызовов INT 21h не только для установки текущего каталога, но и для установки текущего
диска, PSP и DTA.
Планировщик (scheduler) из Winl6 KERNEL ответствен за логичность DOS-состояния каждой
задачи. Предположим, планировщик дал поработать какой-то Win 16- или Win32 задаче, а затем
переключился на вторую задачу, которая вызывает общий диалог GetOpenFileName, изменивший
текущий каталог и использовавший Find First File и Find Next File для пересчета *.TXT файлов в
этом каталоге. Из-за Find First/Next эта задача, несомненно, изменит текущий PSP и DTA так же,
как и текущий каталог. Возможно, она также изменит и текущий диск. Предположим, что KERNEL
переключится назад на первую задачу; что случится, если она обратится к INT 21h? KERNEL
должен гарантировать, что вызовы DOS из второй задачи не повлияли на первую. Это может потре-
бовать восстановления текущего диска, каталога, PSP и DTA первой задачи.
Отметим, что VMM не помогает в решении ни одной из этих проблем, так как для него систем-
ная VM — это одна большая задача. Это показывает, что характеристика Windows 95 как инте-
грированной системы значит очень мало: нижняя часть Windows практически ничего не знает о том,
что делается в ее верхней части. В Windows 95 планировщик VMM оперирует с ветвями выпол-
нения как с VM, но такие вещи, как текущий диск и каталог, не поддерживаются на уровне ветвей.
Таким образом, Winl6 KERNEL должен брать единый текущий диск, каталог, PSP, DTA и другое
из системной VM, которые предоставляются механизмом экземплярных данных VMM (эти пере-
менные DOS содержатся в SDA и SDS), и разделять их между всеми Winl6- и Win32-3afla4aMH. В
этом и состоит решающая роль Win 16 KERNEL, даже на чистой Win32-cncTeMe.
И последний вопрос по поводу результатов WSPY21: что это за RUNDLL32?
<RUNDLL32> *(2F) Get DTA
<RUNDLL32> *(1A) Set DTA 0BE7:856C
<RUNDLL32> *(4E) Find First ‘C:\WININSTO.400\*.INF’
Я не запускал никакой программы с названием RUNDLL32. Но я щелкал на некоторых пикто-
граммах из Control Panel Windows 95, и это привело к запуску RUNDLL32. Пиктограммы Control
Panel представляют приложения, которые находятся в Winl6 DLL с расширением .CPL (см.
Windows 3.11 Programmer's Reference, Vol. 1 : Overview, Ch. 15). Control Panel встроена в Win-
dows 95 Cabinet/Explorer, который является Win32-HcmwnnieMbiM модулем. Win32 Control Panel
использует RUNDLL32 для загрузки Winl6 .CPL файлов. Как и во многих других случаях,
Windows здесь следует своему “византийскому подходу”. Однако полезно вкратце рассмотреть, что
происходит, когда вы щелкаете на пиктограмме из Control Panel, в частности, New Device, которая
запускает New Device Installation Wizard.
Во-первых, CAB32 запускает RUNDLL32.EXE (которая, как и САВ32, является Win32-
приложением), с чудовищно длинной командной строкой:
C:\WIND0WS\RUNDLL32.EXE shell32,Control_RunDLL \
C:\WINDOWS\SYSTEM\SYSDM.CPL, -602,New Device
RUNDLL32 берет первую часть параметров и передает строку “shell32” в Win32 LoadLibrrary
API. Обратно она получает дескриптор SHELL32.DLL и передает его со следующей частью команд-
ной строки (строка “Control_RunDLL”) в GetProcAddress, динамически связываясь таким образом с
Глава 13. Thunk! KERNEL32 вызывает KRNL386 351
процедурой Control_RunDLL в SHELL32.DLL. Получив указатель на эту процедуру, RUNDLL32
вызывает ее, передавая оставшуюся часть командной строки. Обратите внимание, что RUNDLL32
получила два параметра из командной строки и преобразовала их в нормальный указатель на функ-
цию; ну не чудо ли это — динамическое связывание времени исполнения?
Из полученной части командной строки ControllRunDLL берет следующую строку,
“C:\WINDOWS\SYSTEM\SYSDM.CPL” и передает ее недокументированной функции
KERNEL32, называемой LoadLibraryl6. Как вы могли догадаться из названия, LoadLibraryl6 — это
Win32 API, который загружает Winl6 DLL. То, как это делается, представляет определенный
интерес:
KERNEL32!LoadLibrary16
0137:BFF91D52 MOV CL, 36
0137:BFF91D54 JMP BFF91D60
Позже (в разделе “От Explorer к Create PSP за шесть простых шагов”) мы увидим, что
BFF91D60h — это основная процедура, которая использует значение в CL как индекс в таблице
двойных слов, вроде 011F66CEh, 011F0B5Bh и OllFOOEBh. Вероятно, вы не будете слишком удив-
лены, услышав, что OHFh. в этой конфигурации является одним из сегментов Winl6 KERNEL и что
элемент 36h в этой таблице равен OllFOOEBh; получается так, что 001F:00EB является адресом
Winl6 LoadLibrary API. Другими словами, Win32 LoadLibrary API извлекает дальний selector:offset
адрес Win 16 LoadLibrary API. Этот адрес затем передается функции KERNEL32 под названием
QT_Thunk (см. ниже), которая позволяет 32-битовому коду обращаться к 16-битовому. А я-то ду-
мал, что KERNEL32 никогда не вызывает Winl6 KERNEL.
Когда KERNEL32!LoadLibrary 16 переходит к KERNELILoadLibrary, она передает дескриптор
Winl6 DLL, SYSDM.CPL. Control_RunDLL получает этот дескриптор и строку “CplApplet”
(CplApplet — это главная точка входа для всех приложений Control Panel) и передает их другой
недокументированной функции из KERNEL32 API, GetProcAddresl6. Реализована эта функция ана-
логично LoadLibrary 16, только здесь вместо MOV CL,36 стоит MOV CL,34.
GetProcAddressl6 обращается к GetProcAddress в Win 16 KERNEL, снова используя QT_Thunk,
и возвращает 16-битовый дальний указатель selectonoffset на Winie-функцию. В данном примере
ControlRunDLL получает 16-битовый указатель (selector:offset) на процедуру CplApplet в SYSDM.
На листинге 13.1 представлена консольная программа, демонстрирующая, как Win32-
приложения могут использовать LoadLibrary 16 и GetProcAdressl6:
ЛИСТИНГ 13.1. DYNLINK32.C
/*
DYNLINK32.C
Консольная М1п32-программа — демонстрирует динамическое связывание ^библиотеки Win32
с Win16 посредством недокументированных функций LoadLibrary16, GetProcAddress16
в модуле KERNEL32
Шульман, 1994
*/
«include <stdlib.h>
«include <stdio.h>
«define WIN32_LEAN_AND_MEAN
«include “windows.h”
HINSTANCE (WINAPI *LoadLibrary16)(LPCSTR IpLibFileName);
FARPROC (WINAPI *GetProcAddress16)(HINSTANCE hModule, LPCSTR IpProcName);
void fail(const char *s) { puts(s); exit(1); }
«define GET_PROC(mod, func) GetProcAddress(GetModuleHandle(mod), (func))
352
Неофициальная Windows 95
main(int argc, char *argv[])
{
HINSTANCE mod;
FARPROC fp;
DWORD arg, retval;
if(argc < 3)
ТаЩ“использование: dynlnk32 [имя модуля] [имя функции]”);
«define GET(func, str) \
if(!(func = GET_PR0C(“KERNEL32”, str))) \
fail(“Heao3MO>KHO связаться c" str);
GET(LoadLibrary16, “LoadLibrary16");
GET(GetProcAddress16, “GetProcAddress16");
if(!(mod = LoadLibrary16(argv[1])))
fail(“LoadLibrary16 не сработала”);
printf(“LoadLibrary16(\" %s\ “) ==> 0x%08X\n", argv[1], mod);
if(!(fp = GetProcAddress16(mod, argv[2])))
fail(“GetProcAddress16 не сработала");
printf(“GetProcAddress16(0x%08X,\” %s\ “) ==> 0x%08X\n",
mod, argv[2], fp);
return 0;
}
Вы можете испытать динамическое связывание Win32 с Win 16, набрав имя модуля и функции в
командной строке программы. Например:
C:\UNAUTHW>dynlnk32 kernel getselectorbase
LoadLibrary16(“kernel”) ==> 0x0000012F
GetProcAddress16(0x0000012F, “getselectorbase”) ==> 0x01174BE0
C:\UNAUTHW>dynlnk32 sysdm clpapplet
LoadLibrary16(“sysdm") ==> 0x00002236
GetProcAddress16(0x00002236, “clpapplet”) ==> 0x226F029C
Это, конечно, замечательно, что SHELL32!Control_RunDLL, Win32^yHKijnn, получает указа-
тель selector:offset на функцию в SYSDMICplApplet, Win 16-функцию. Но само по себе обладание
SHELL32 дальним указателем на функцию в SYSDM будет тем же, что телефонный номер без
телефона или адрес без средства доставки. Каким образом такая Win32 DLL, как SHELL32, вы-
зывает такую Winl6 DLL, как SYSDM? Ха, этот вопрос напоминает мне глупую песенку про
“девочку из центра” (Win32) и “мальчика с окраины” (Winl6).
Как эти двое могут быть вместе? Оказывается, очень просто. SHELL32!Control_RunDLL может
вызывать SYSDMICplApplet так же, как и KERNEL32!LoadLibraryl6 вызывает KERNEL!
LoadLibrary, или так же, как KERNEL32!GetProcAdressl6 вызывает KERNEL!GetProcAdress: через
QT Thunk (работу которой детально мы рассмотрим чуть позже). В Windows 95 именно thunk-
переключатели обеспечивают весь этот круговорот.
В добавок к тому, как Win32 Control Panel вызывает Win 16-процедуру CplApplet из .CPL-
файла, заметим, что существование LoadLibraryl6 и GetProcAdress 16 в KERNEL32 достаточно важ-
но. Таким образом мы имеем KERNEL32 API (который, хоть и недокументирован, но основательно
используется системой), чья основная задача — вызывать свои эквиваленты из Winl6 KERNEL.
Фактически способность KERNEL32 обращаться к KERNEL является основой для всех осталь-
ных 32/16-переключений в Windows 95, вроде коммуникации между USER32 и USER и между
GDI32 и GDI, поскольку инициализация этих переключений требует наличия LoadLibraryl6 и
GetProcAddressl6. Контрольные точки, установленные на эти процедуры во время инициализации
Windows 95, помогают выявить следующие динамические связи (основанные на thunk-технологии):
Глава 13. Thunk! KERNEL32 вызывает KRNL386 353
12 НеаЬипиальная Windows 95
Во время инициализации:
LoadLibrary16 GDI.EXE
GetProcAddress16:
GdiThkConnectionDataLS
FT_GdiFThkThkConnectionData
FdThkConnectionDataSL
IcmThkConnectionDataSL
LoadLibrary16 USER.EXE
GetProcAddress16:
UsrThkConnectionDataLS
MsgThkConnectionDataLS
FTJJsrFThkThkConnectionData
FT_UsrF2ThkThkConnectionData
Us r32ThkConnectionDataSL
Запуск Clock:
LoadLibrary16 COMMDLG.DLL
GetProcAddress16 DlgThkConnectionDataLS
Запуск Font Manager:
LoadLibrary16 SYSTHUNK.DLL
GetProcAddress16 FT_LzFThkThkConnectionData
Проверить File Properties:
LoadLibrary16 VER.DLL
GetProcAddress16 FT_VerFThkThkConnectionData
Кроме LoadLibraryl6 и GetProcAddressl6, имеются другие и API из KERNEL32, базирующиеся
на Winl6 KERNEL, такие как FreeLibraryl6, GlobalAllocl6 и GlobalLockl6. В дополнение thunk-
таблица KERNEL32, упоминавшаяся ранее, содержит указатели на многие процедуры из Win 16
KERNEL (KRNL386), имена которых не появляются в бинарных файлах Windows 95. Для того что-
бы увидеть, как активно используются эти thunk-переключатели KERNEL32 -> KRNL386, вы може-
те поместить контрольную точку отладчика в то место кода, где KERNEL32 помещает номер в
регистр CL, являющийся индексом в thunk-таблице:
0137:BFFB7C44 XOR ECX,ЕСХ
0137:BFFB7C46 MOV CL,[EBP-04]
0137:BFFB7C49 MOV EDX,[00030464+4*ECX]
0137:BFFB7C50 MOV EAX.BFF71247
0137:BFFB7C55 JMP
; thunk-номер
; thunk-таблица ‘
; 32-битовое смещение QT_Thunk
Эта контрольная точка срабатывает около 12 раз при запуске такого Win32-npitnK^eHHH, как
Clock, и примерно столько же при выходе из приложения. Число, конечно, может варьироваться для
различных Win32-npmn^einrii, но, во что это обходится, примерно показывает следующая последо-
вательность действий KERNEL32 -> KRNL386, полученная при запуске и выходе из Win32 Clock:
31, 32, 2Е, 36, ЗЕ (хЗ), 1Е (хб), 1С (х5)
Программа Clock появилась на экране
1F
Выполняется Clock (нет переключений К32 -> К16)
Закрыть Clock
1В (хЗ), 2F, 3D (хЗ), 33 (х2), 29 (х2), 30
Здесь thunk с номером 30h — это процедура очистки Set PSP/Exit, рассмотренная выше,
36h — это LoadLibraryl6, a 34h — GetProcAddressl6.
Я особо отмечаю случаи, когда KERNEL32 обращается к KRNL386, только потому, что Micro-
soft утверждала, что подобные переключения отсутствуют. Существует также Много важных случа-
ев, когда KRNL386 обращается к KERNEL32, как и говорила Microsoft. Например, в предыдущей
главе (в разделе “Win32 FindNextFile — это функция 714Fh INT 21h”) мы говорили, что функция
FindNextFile в KERNEL обращается к FindNextFileA в KERNEL32.
354
Неофициальная Windows 95
Впрочем, достаточно возиться с этим единственным списком результатов WSPY21!.. Ясно, что
WSPY21 может предупредить нас о некоторых важных местах Chicago, которые мы не смогли бы
рассмотреть другими способами. Борцы за чистоту операционной системы могут сказать, что
WSPY21 обнаруживает некоторые неприятные стороны Chicago. Я не могу согласиться, что
WSPY21 доказывает, будто Chicago уж очень похожа на лоскутное одеяло или просто неприглядна,
но она, конечно же, показывает швы: те места, где Win32 прошита Winl6 и где Winl6 прошита
DOS реального режима.
Так что же мы выяснили?
• Windows 95 Explorer, Win32-npitnK^eHHe, использует функцию 4Bh (ЕХЕС) INT 21h для за-
пуска Win 16-приложений. Этот вызов не доходит до реального режима DOS, но (как все
вызовы, видимые WSPY21) он вовлекает ядро Winl6. Этот факт достаточно интересен, по-
скольку вовсе не предполагается, что Win32-npitnK^einDi в Windows 95 могут использовать
Winl6 ERNEL.
• Win32-пpилoжeния в Windows 95 прибегают к различным вызовам INT 21h. WSPY21 не
замечает большинства из этих вызовов, так как они проводятся с использованием VxDCallO.
Однако даже вызовы, которые WSPY21 может увидеть, достаточно интересны, так как
показывают, что даже чистая Win32-cpefla Windows 95 обращается к ядру Winl6. Важно и
то, что некоторые из этих вызовов проходят весь путь к DOS реального режима, доказывая
тем самым, что даже полноценная Win32-cpefla в Windows 95 взаимодействует с DOS реаль-
ного режима (которую Windows, однако, запускает в режиме V86).
• Windows 95 Explorer, Win32-пpилoжeниe, использует функцию 51h (Create PSP) преры-
вания INT 21h не только для поддержки Winl 6-приложений, но также и для Win32-
приложений. Мы видим, что эти вызовы также задействуют Win 16 KERNEL. С помощью
WV86TEST мы уже видели, что эти вызовы доходят до DOS. Таким образом, каждое
запускаемое в Windows 95 Win-32-приложение, так же как и Win 16-приложение, получает
PSP реального режима DOS, созданное с использованием функции 55h.
Wtn32nPSF
Используй Win !•> iijxu . ’A D-’Р.’'Р I . < видеть 'ч.? 1„л.г;и- 'ч. 3/
Window, ч> ими-i PSP DOS p«a.;i>!h>-4 i-< mHM:i -И.1 npii i/ылирук ;.»> i... • •
н ticKiHupbii р.11лнч>н’} in: p =;• i> к (ч:>:>-it*' .
Explorer. Скин. WihBi/МГ и нч-.i.’i рл'-мы n« (\ntioi (‘••.мыс
New Device In-tallai ion 'A’;/a:d> шч.гм- «•/-. чиги-ш-. ;ак Rl ’NDl.L'C
Как виджг. КаЖЧи'н Win32 м ;.5ч.1 I'M-ri •ti.nitiHJi PSI* 1 < Г-
Хотя сама «лрскгура PSP ti<- п-ли-.ч. л, ня " Wntd-.-A- 9’>. er . _,i .
исходные данные для Н> ир«-ц« << a i>' >S
В нокаланных рании WSPY?! иы яч.чо •
количссгво вы.яжов. киюрьи ь-;., а-.ии РЧР ч C'.Vl’b : i'i
Большине г во. нч и*1 жт и.[ них, нрвш in ч •- ilt .isa.iaoii A
PSP МЫ ВИДИМ. Ч|И « r.iCKinp НПО. V-i LMj 0А?!| О к:i )U ' - >
[icxibHuio режима и .йог PSP I'lWa.’..» жнл KERNEI.12. ( laiiv. лт..;. • ' 5'’
PSP на ри< 1.’;2 и вы •.видите чш U7D7 прина»! н ж;п. с
режима. DOS нои лоаинию К ц.,.... „•
WINPSP С. как нлклино в .як гвшг И.7: м- ця .tn-
шиольп-ег оч'>ли>чсх\ W»?-,IO
Глава 13. Thunk! KERNEL32 вызывает KRNL386
355
356
Неофициальная Windows 95
kdefine HAP(ptr, bytes) map_real((ptr), (bytes))
«define FREE_HAP(ptr) free_mapped_linear(ptr)
•define GET_REAL(ptr) get_real_addr(ptr)
«pragma pack(1)
typedef struct {
BYTE type:
WORD owner, size;
BYTE unused[3], name[8];
) HCB;
«pragma pack()
WORD get first mcb(void)
{
RMODE_CALL r;
memset(&r, D, sizeof(r));
r.eax = 0x5200;
if(dpmi_rmode intr(0x21, 0, D, &r))
{
// Извлечь сегмент первого HCB из SysVars. Заметьте, что он находится
// по sysvars[-2]. Нельзя вызывать map_real() по sysvars и затем
И вернуться на 2! Необходимо отображать по sysvars-2.
WORD far -trap = (WORD far •) HAP(MK_FP(r.es. (WORD) r.ebx-2). 2);
WORD firstjncb = «tmp;
FREE_HAP(tinp);
return first neb;
>
else
return 0;
void display dos_psp(WORD psp seg, BYTE far *psp, HCB far >mcb)
{
printf(“%04X\t”, psp_seg);
if(_osmajor >= 4)
{
char buf[9];
_fmemcpy(buf, ncb->name, 8);
buf[8] = \D’;
if(-buf)
printf(“\t%-8s", buf);
else
printf(“\t\f);
>
printf(“\t%04X\n”, mcb->size);
void display win psp(WORD prot_psp, char «szHodule, WORD hTask)
{
BYTE far «psp = (BYTE far •) HK_FP(prot_psp, 0);
WORD real_psp = GetSelectorBase(prot_psp) » 4;
WORD flags;
pr intf(“*04X\t»04X\t*-8s\t%04X\t*01X\t".
real_psp, prot_psp, szHodule, hTask,
GetSelectorLimit(prot_psp) +1):
Глава 13. Thunk! KERNEL32 вызывает KRNL386
357
// Флаг WinOldAp в Windows PSP
flags «((WORD far •) &psp[0x48]);
if(IsWinOldApTask(hTask) && (!(flags & 1))) /• на всякий случай */
Га11(“Проблеиа с IsWinOldApTaskl");
if(flags A 1) printf(“DOS”);
// Флаг Win32 в TDB
flags «((WORD far •) MK_FP(hTask, 0x16));
if(flags & 0x10) printf(“WIN32”);
printf(“\n”);
) >
// void fail(char »s) { printf(“»s\n". s); exit(1); ) V
main()
{
TASKENTRY te;
BYTE far *maybe_psp;
HCB far «neb;
WORD mcb_seg, mapped;
BOOL ok;
printf(“PSP DOS (from HCB chaln):\n");
printf(“Real\t \tName \tParas.\n");
// пройтись no цепочке MCB DOS, искать PSP
if(l(mcb_seg get_first_mcb()))
fail(“He ногу получить цепочку НСВГ');
for(;;)
(
meb (MCB far •) MAP(MK_FP(mcb_seg. 0). sizeof(HCB));
maybe_psp « (BYTE far •) MAP(MK_FP(mcb_seg ♦ 1. 0). 512):
if(«((WORD far •) maybe_psp) 0х20С0? // это похоже на PSP?
{
if((mcb_seg + 1) =« mcb->owner) // PSP обычной программы DOS
display dos psp(mcb seg + 1, maybe psp, meb);
)
FREE_MAP(maybe_psp);
if(mcb->type « ’Z)
break; // конец списка
mcb_seg mcb_seg ♦ mcb->size +1; // бежим no списку
FREE MAP(mcb);
)
FREE_HAP(mcb): // освободить последний
mapped « get_mapped();
if(mapped != 0)
printf(“ОШИБКА! Осталось Фи отображенных селекторов!\п”, mapped);
printf("\nPSP Windows (from task list):\n“);
printf(“Real\tProt\tName \tTask\tSize\n”);
// сейчас бежим no списку задач Windows и извлекаем PSP
te.dwSize sizeof(te);
ok TaskFirst(ite);
while(ok)
{
358
Неофициальная Windows 95
Вначале WINPSP проводи! цгррчку :Э( )S Mi’ll в шик i:.ix PSP WINPSP исни.тмус! cnm i
.MpiPLsii к< ' ПЛ !;!• . ! I-. .... '• • .i V- IM’4 P яч -i« вл :i: > ;м«‘«..|
juuiuiicuuuiii режим.» Windows и РО.НПМ) ей кар»;» ;ijpe<nii ргаТ1>ноги режима в
ЧИЧ.Ч UJiK.il.M чр - ‘р.-.ч- lit ‘ »а •,!• !. !• \ I "> "•: и: ',!'.!••• i
PROT (также L'nd- eunu nied Dl)S. rd. p. *31S3S) .{ля хадт. н-ппеишич PSP
Totiillelp дш прохода ш* • .пн к\ .-i.t.i’i Wnubu• l.m ; ...x.v-ii WINPSP B:i.ir.’nci i
(e.TCKlop JHIHIHIU B,4: >10 режим i I-n OhiUilHX1 ". Л,. u .i et»ln\ >TDID. MiCA.I.I-It.I i
ЧТО И» ДСЙППИИ ЛЬИВ PSP. .;;ишг:Я>1 1Ч | P.tnX ,.!’.l *..’.111.1 «ч. »рые MUHIN бЫ.Ь (.Dh 20!’.. u i
ПЫЗЫКН'Г проис <\pv lispkr-. kv’h р-р Он., ч в.>' > -••irpi и- in.; н-ш.н I (retSeh• {пгВаче _|.тч bn
числения M[ki.i вара.раф.г pv.i.ii.W"' p< уям.. ; PSi’ < im- PSP :',.т,кны p.ic4'..i.u;»i:.«>i в
обычной иа.члги. ihus-im;, !iuiHjuiiif,ii>:i i ,< t>. :i 4t<*rR.i < .hi.; |гни.ч должны бьпь .uni'ni!
мымик Нин модуля. i пч 1.-;:.|.ч; i PSP, ;; «ь :t i.’n ня и. 11>«*>.,рч.щни. которую Tof.llkip
хранит о каждой .пиач<-. И, ч-конец. *. и; р-р и’юнгпле» дпа ф кпд л TDH Win* >ЫАр
Поскольку v Window. обнаруживайся в.. *io.i..(;r -ар!. ;ipn« тих D< >S те больше
DOS-op.'>rpa.M'f на деле пк;;.:ыч.1нчя Р. it.d-.л -is <»> p.iM*'.i4n. :ч,>ожнчл и.. WINPSP /Ь-бро {
пожалован, и не гаюн: ж новый мир :i;..ii ра; .л.-лнч Dt'4' > hhoi и pi жима' .;
Кто вызывает INT 21 h?
Мы знаем из предыдущей главы, что Win32-пpилoжeния в Windows 95 зависят от INT 21h даже
больше, чем Microsoft готова признать для Win 16-приложений. Но действительно ли CAB32.EXE
содержит инструкции INT 21h? Нет. Но как тогда запуск Win 16-приложения из Explorer превра-
щается в функцию 4Bh EXEC? И каким образом запуск любого приложения, все равно, Winl6 или
Win32, приводит к созданию такой структуры данных DOS реального режима, как PSP?
Очень важно отметить, что эти “досовские” события не проходят через VxDCallO 2A0010h
21h — механизм, обсуждавшийся в предыдущей главе. Если кто-либо на уровне Win32 обращался
бы к функциям 4Bh или 55h INT 2lh через VxDCallO, WSPY21 не смогла бы этого увидеть. Как
отмечалось раньше, WSPY21 перехватывает INT 2lh на уровне Winl6 KERNEL.
Постойте-ка! Если WSPY21 перехватывает INT 21h на уровне Win 16 KERNEL и если WSPY21
распознает вызовы INT 21h, поступающие, когда активной задачей является Win32-приложение,
значит, KERNEL32 должен обращаться к KRNL386. A Microsoft утверждала, что KERNEL32
никогда не обращается к KRNL386.
Более того. В обоих случаях, когда САВ32 запускает CALC и когда CALC вызывает
WINHELP, мы видим, что задача-предок использует функцию 55h для создания PSP для дочерней
задачи. Вполне логично ожидать этого вызова, когда CALC использует WinExec для запуска
WINHELP, как Мэтт Петрек (Matt Pietrek) показал в своей неподражаемой книге Windows
Internals (р. 229—256):
WinExec -> функция 4B00h INT 21h -> LoadModule -> LMCheckHeader ->
OpenAppEnv -> CreateTask -> BuildPDB -> функция 55h INT 21h
Глава 13. Thunk! KERNEL32 вызывает KRNL386 359
Но откуда приходит этот вызов Create PSP в случае с САВ32? Петрек говорит здесь о Winl6-Bep-
сии-WinExec. WSPY21 видит вызов функции 55h INT 21h из САВ32 даже тогда, когда САВ32 за-
пускает ХУт32-приложение, такое как Clock или WinBezMT. Как (и почему) САВ32 порождает этот
вызов функции 55h? Опять же, здесь не использовался VxDCallO, иначе WSPY21 не увидела бы его.
Ответ находится у нас под носом. Петрек показал, что внутренняя функция Create Task в
KENL386 вызывает другую внутреннюю функцию BuildPDB (PBD — Process Data Block, другое
название PSP), которая вызывает функцию 55h.
Ну конечно! (Редкие аплодисменты.) Какой-то код KERNEL32 должен вызывать Create Task в
KRNL386! Позже мы увидим, как KERNEL32 это делает.
Нам уже известно, что все А¥1п32-приложения имеют соответствующие Winl6 TDB (WSPY21
обращается к ним для показа таких имен, как САВ32). И если бы мы еще этого не знали, было бы
довольно просто это понять при запуске какой-либо диагностической программы под Windows,
которая показывает Win 16-задачи, используя ToolHelp TaskFirst и TaskNext API или какой-либо
метод прохода по задачам, описанный в книге Undocumented Windows. Например, программа
WINWALK Мэтта Петрека содержит функцию TaskWalk (.Undocumented Windows, р. 646-647);
отметим, что здесь используется старый Winl6 ToolHelp API, а не новый ToolHelp32 API:
void TaskWalk(void)
{
TASKENTRY te;
BOOL ok;
I/ использование printf из библиотеки WINIO
printf(“Task list:\n");
printf(“NAME HTASK HMOD HINST PARENT\n”);
te.dwSize = sizeof(te);
ok = TaskFirst(&te);
while ( ok )
{
printf(“%-8s %04X %04X %04X %s\n”,
te.szModule, te.hTask, te.hModule, te.hlnst,
GetModuleNameFromHandle(te.hTaskParent));
// GetModuleNameFromHandle использует ModuleFindHandle, TaskFindHandle
ok = TaskNext(&te);
}
}
При запуске CAB32, Clock, WinBezMT и Win32-Bepc™ FreeCell, но без Winl6-npHZK^einrii, за
исключением самого WinWalk, данный код произведет следующий вывод в Windows 95:
Task list:
NAME HTASK HMOD HINST PARENT
FREECELL 1B9E 1E06 1B9E
WINBEZMT 1CA6 1CFE 1CA6
CLOCK 1E26 1E3E 1E26
BATMETER 1F1E 1F06 1F1E
CAB32 1276 125E 1276
TIMER 130F 132F 12E6 MSGSRV32
MSGSRV32 13EF 13FF 13CE KERNEL32
KERNEL32 0097 010F 0097
WINWALK 1B87 1B67 1C66 CAB32
Код проходит только список Winl6-зaдaч. Он ничего не знает о Windows 95 или о Win32-
процессах, однако он видит имя каждого Win32-nponecca. Таким образом, каждый Win32-nponecc
имеет соответствующий Winl6 TDB. (В Windows Internals, р. 226, отмечается, что флаг 10h в сло-
360
Неофициальная Windows 95
ве по смещению 16h в TDB указывает на ХУт32-задачу. WINPSP.С, показанная выше, обращалась
к нему, так же как и программа WALKWIN.С, которую мы рассмотрим немного позже.)
С другой стороны, важно отметить, что Winl6 TDB для Win32-зaдaч немного отличаются от
аналогичных структур для Win 16-задач (которые, в свою очередь, отличаются по формату от TDB
Windows 3.x; см. следующий раздел). Например, дескриптор задачи-предка в Win32 TDB по сме-
щению 22h содержит 0 (обратите внимание, что WINWALK не в состоянии найти ни одного предка
Win32-задачи).
Существует другое ключевое отличие между Win32- и Winl6-зaдaчaми: хотя TDB Win32-зaдaчи
имеет селектор защищенного режима для PSP по смещению 60h, так же как и TDB для Win 16-
задачи, сам PSP (в отличие от селектора) не находится по смещению 210h в TDB, как это делается
в Windows 95 в TDB Winl6-зaдaчи. (В Windows 3.x этот PSP находится по смещению 100h в
TDB.) Поскольку TDB Winl6-зaдaчи содержит (и рассматривается как расширение) PSP DOS
реального режима, весь TDB должен находиться в стандартной памяти. Снова, как это показал
Петрек в Windows Internals (р. 254):
// Распределить память под TDB ниже 1М байта, чтобы он был доступен DOS
// для файлового ввода-вывода.
TDB = GloablDosAlloc(tdb_alloc_size);
Для сравнения здесь приводятся типичные значения TDB и PSP для Win32- и Win 16-задач,
запущенных под Windows 95:
TDB_________ PSP
Селектор База Размер Селектор База Размер
Win 16 1BDF 00030Е00 320 1АВ7 00031010* 110
Win32 1ВС6 800Е0В20 210 1B1F 00031120 120
* База Win16 PSP = база Win16 TDB + 210h
Хотя TDB Win32-зaдaчи не располагается в обычной памяти, ассоциированный с ним PSP
находится именно там. Это важно. Если вы установите контрольную точку на процедуру Global
DosAlloc в Winl6 KERNEL и запустите Win32-npH7K^einie под Windows 95, она сработает. Память
под PSP для каждой Win32-зaдaчи распределяется с помощью GlobalDosAlloc API из Winl6. Таким
образом, ядро Win32 из Windows 95 действительно обращается не только к DOS реального режима,
но и к ядру Win 16.
В Win32 API вы запускаете приложение с помощью CreateProcess. Вы также можете использо-
вать Win32-Bepcino WinExec, что вызовет внутреннюю версию CreateProcess. Раз мы выяснили, что каж-
дый Win32-nponecc имеет Winl6 TDB, вполне логично, что Create Process должен вызывать каким-то
образом CreateTask в KRNL386 не только для Win 16-приложений, но и для Win32-npnuK^einrii.
Мы увидим позже, что CreateProcess API в KERNEL32 действительно создает TDB через вызов
внутренней функции CreateTask из KRNL386. И это вполне обоснованно. Удивляет здесь только то,
что Microsoft утверждала, будто такие вещи в Chicago невозможны. Действительно, как же еще мо-
жет Win32-nponecc получать Winl6 TDB (который, тем не менее, несколько отличается от TDB,
получаемого Winl6-зaдaчeй и от TDB в Windows 3.x)?
Где в Windows 95 текущий каталог?
Windows 95 имеет еще одно важное отличие в структурах TDB для Winl6- и Win32-зaдaч.
В Windows 3.x TDB содержит текущий диск и каталог в 68-байтовом буфере по смещению 66h
и помещает PSP по смещению 100h (см. Undocumented Windows, р. 366-368).
В Windows 95, хотя текущий диск остался по смещению 66h, однако текущий каталог сдвинут
на смещение 100h. Как мы видели, PSP, который располагался по смещению 100h, сдвинулся на
Глава 13. Thunk! KERNEL32 вызывает KRNL386
361
смещение 210h (для Win 16-задач) или в отдельный сегмент стандартной памяти(для^1п32-задач).
Это перемещение текущего каталога из 67-байтового буфера по смещению 67h (помните, что байт по
смещению 66h — текущий диск) в 272(110Ь)-байтовый буфер по смещению 100h было проделано —
как вы, возможно, догадались — из-за использования длинных имен файлов.
Winl6 KERNEL должен хранить текущий каталог и диск для каждой задачи. Это еще один
пример проблем, как отмечалось в главе 12, связанных с заметным разграничением верхнего и
нижнего слоев (уровней) в Windows. С точки зрения IFSMGR, существует только один текущий
каталог для каждой виртуальной машины (VM). DOSMGR аналогично создает DOS-переменную
текущего диска для каждой виртуальной машины. Но в системной VM работает сразу несколько за-
дач, каждая из которых может иметь свои текущий диск и текущий каталог.
VxD на самом деле ничего не знают о том, что происходит внутри системной VM. Все
различные Win 16- и Win32-пpилoжeния выглядят как одно большое приложение, называемое
KRNL386.EXE. Как описано в главах 6 и 7, это разделение между верхним и нижним слоями Win-
dows означает, что слой VMM/VxD может выполнять нечто другое, чем графический интерфейс
Windows, и, наоборот, Windows GUI может работать на чем-то другом, отличном от слоя
VMM/VxD. Слабо связанные системы часто оказываются предпочтительнее “интегрированных”
систем с сильной связью.
Однако выгоды такой модульности могут перевешиваться удвоением усилий, которых это
требует. Ядро в системной VM зачастую вводит в заблуждение нижние уровни Windows, заставляя
их поверить, что запущено единственное приложение — которое просто очень любит частенько
переключать текущие PSP, DTA, диск и каталог!
Со временем верхние и нижние уровни Windows станут более интегрированными. VxD будут
больше знать о задачах Window’s и станут главным направлением в программировании под Win-
dows. Сервис ApplyTime и сервисы динамического связывания времени исполнения, вроде
_SHELL_CallAtApplyTime, _SHELL_LoadLibrary, _SHELL_GetProcAddress и _SHELL_CallDll,
предоставляемые SHELL VxD в Windows 95, являются важным шагом в этом направлении. Во
время так называемого ApplyTime (времени приложения) VxD может осуществлять вызовы
Windows API. VxD VWIN32 в Windows 95 также является шагом к большей интеграции между
Windows-приложениями и Windows VxD.
В Windows 95 VxD знают о ветвях выполнения столько же, сколько о виртуальных машинах,
и, как мы видели еще в главе 1, VxD могут использовать Thread Local Storage (TLS). Но текущие
диск и каталог не предназначены для поддержки на уровне ветвей. Поэтому единственный текущий
каталог, который IFSMGR предоставляет системной VM, и переменная текущегб диска, созданная
DOSMGR, должны быть размножены среди всех Winl6- и Win32-3aAa4 скорее на уровне ядра,
нежели на уровне VxD. Когда происходит переключение задач, KERNEL также должен пере-
ключить текущие диск и каталог. Следовательно, имеет смысл хранить эти данные в своем собст-
венном TDB каждой задачи.
Пока что в Windows 95 все это остается без изменений. Win32-зaдaчи отчасти нуждаются в
Win 16 TDB для хранения своих текущих диска и каталога. Отличие Windows 95 состоит в том, что
длинные имена файлов и каталог не позволяют продолжать использовать буфер текущего каталога
по смещению 67h в TDB. Windows 95 хранит длинное имя текущего каталога в том месте, где
Windows 3.x хранит PSP. Байт текущего диска по-прежнему хранится по смещению 66h.
Почему TDB для Win 16-задач нуждается в дополнительном пространстве для текущего ката-
лога? Потому что Win 16-приложение может вызывать функцию 71h INT 21h из числа LFN-функ-
ций. Вдобавок, Winl6 KERNEL (KRNL386) содержит такие функции, как SetCurrentDirecoty и
GetCurrentDirectory, которые вызывают их эквиваленты в KERNEL32 (которые затем развора-
чиваются и вызывают функции, 19h и 1747h INT 21h для GetCurrentDirectory и OEh и 713Bh для
SetCurrentDirectory ).
Как отмечалось, Winl6-,zpajioni в Windows 95 могут иногда вызывать LFN-функции для Winl6-
приложений даже без уведомления самих приложений. (Пользователи просто заметят, что они получи-
ли возможность сохранять длинные имена файлов и каталогов.) Это относится только к вызовам ус-
тановки (Set). Замена же вызовов приложений получения (Get) на новые, которые могут возвращать бо-
льше данных (более длинное имя файла или каталога), может привести к переполнению их буферов.
362
Неофициальная Windows 95
Здесь существенно, что даже \¥1п32-задачи хранят свои текущие диск и каталог в Win 16 TDB.
Это легко можно продемонстрировать с помощью небольшого консольного Win32-npnuu^einiH
CHGDIR.C, показанного в листинге 13.3.
Листинг 13.3. CHGDIR.C
// CHGDIR.C — Ш1п32-приложение
// Показывает, что М1п32-задачи хранят диск/каталог в Win16 TDB
«include <stdlib.h>
«include <stdio.h>
«define WIN32_LEAN_AND_MEAN
«include “windows.h”
DWORD (WINAPI *VxDCall)(DWORD srvc, DWORD eax, DWORD ecx);
«define GET_PROC(mod, func) GetProcAddress(GetModuleHandle(mod), (func))
«define VWIN32_INT21_CALL 0x2AD010
«define VWIN32_INT31_CALL Dx2AD029
«define DosCalKeax, ecx) VxDCall(VWIN32_INT21_CALL, (eax), (ecx))
«define DPMICalKeax, ecx) VxDCall(VWIN32_INT31_CALL, (eax), (ecx))
char curdir[MAX_PATH], buf[MAX_PATH], bufupr[MAX_PATH];
void fail(const char *s) { puts(s); exit(1); }
main()
{
HANDLE id;
BYTE *tdb_drv, *tdb_curdir, *tdb_base;
WORD tdb, psp;
// Id ветви == ТНСВ 3-го уровня (Thread Control Block)
id = GetCurrentThreadldO;
printf(“Thread ID: %081Xh\n”, id);
// Win16 TDB - no смещению 1Ch в ТНСВ 3-го уровня
// Я даже удивлен тем, насколько это просто!
tdb = -((WORD •) (((BYTE •) id) + Qxlc));
printf(“TDB: %04Xh\n", tdb);
// Используется динамическое связывание времени выполнения,
// чтобы по именам получить указатели на функции
if((VxDCall = GET PR0C(“KERNEL32", “VxDCallO”)))
{
// Получение базового линейного адреса TDB с помощью функции 6
// прерывания INT 31h DPMI (Получить базу селектора).
// Хоть мы и в 32-битовом приложении, нам доступен 16-битовый DPMI-сервис,
// поскольку мы вызываем DPMI косвенно.
_asm mov bx, tdb
DPMICal1(0x0006, 0);
_asm mov word ptr tdb_base+2, ex
_asm mdv word ptr tdb_base, dx
printf(“TDB base: %081xh\n", tdb.base);
// Вызов функции 62h прерывания INT 21h (Получить PSP)
DosCall(0x62D0, 0);
_asm mov psp, bx
printf(“PSP: %04Xh\n\n", psp);
// На всякий случай: TDB[6Dh] == PSP?
Глава 13. Thunk! KERNEL32 вызывает KRNL386 363
if(*((WORD *) (tdb.base + 0x60)) != psp)
fail(“TDB и PSP не соответствуют друг другу!");
// Текущий диск по смещению 66h в TDB,
// Текущий каталог по смещению 100h.
// Замечание: Текущий диск/каталог относятся к каждой задаче, а не ветви.
tdb_curdir = tdb_base + 0x100;
tdb drv = tdb_base + 0x66;
)
else
printf(“Предупреждение: Нет доступа к VxDCallO\n\n”);
for(;;)
{
if(GetCurrentDirectory(MAX_PATH, curdir) == 0)
printf(“неверный>");
else
printf(“[%s]”, curdir);
gets(buf);
if((buf[O] == \0) || (buf[0] == \n))
continue;
strcpy(bufupr, buf);
strupr(bufupr);
«define MATCH(cmd) (strncmp(bufupr, (cmd), sizeof(cmd)-l) == 0)
if(MATCH(“EXIT”))
break;
else if(MATCH(“CD”))
{
if(!SetCurrentDirectory(&buf[3]))
printf(“CD failed\n”);
else if(tdb curdir)
{
printf(“TDB+66h: %02Xh\n”, »tdb_drv);
printf(“TDB+100h: \'%s\'\n", tdb_curdir);
}
}
else if(MATCH(“MD’’))
{
if(!CreateDirectory(&buf[3], 0))
printf(“MD failed\n”);
)
else
WinExec(buf, SW_NORMAL);
)
printf(“done\n’’);
return 0;
}
Как вы можете видеть, программа CHGDIR — немногим больше, чем цикл, который позволяет
пользователю вводить несколько команд: MD (Создать каталог), CD (Изменить каталог), EXIT или
команду, которая будет передана в WinExec. В приглашении выводится (в квадратных скобках)
текущие диск и каталог, возвращаемые функцией GetCurrentDirectory Win32 API. Перед входом в
цикл программа делает несколько странных вещей. Например, CHGDIR вызывает функцию
GetCurrentThreadld Win32 API и рассматривает полученный ID как блок управления ветви (Thread
Control Block — ТНСВ) уровня 3; По крайней мере в бета-версии Chicago, на которой основана эта
книга, слово по смещению ICh в ТНСВ уровня 3 — это Winl6 TDB. (Конечно, что-то или даже все
это еще может измениться в конечной реализации Chicago.) Win32-пpилoжeния могут разымено-
вывать свой собственный идентификатор ветви (32-битовый ближний указатель, например
810FE91Ch) и получать указатель на свой TDB без всяких проблем. Обратите внимание, что, так
364
Неофициальная Windows 95
как CHGDIR — 32-битовая программа, она не нуждается в дальних указателях для доступа к этим
внешним данным:
WORD tdb = ‘((WORD*) (((BYTE*)GetCurrentThreadId()) + OxIC));
Однако селектор своего собственного TDB не очень-то помогает Win32-nporpaMMe. Программе
CHGDIR нужен 32-битовый линейный адрес своего собственного TDB. В Win32-nporpaMMe этот 32-
битовый линейный адрес (например, 800E0A80h) может использоваться как ближний указатель, при
условии, что линейный адрес отображается в ваше адресное пространство. Функция GetSelectorBase
Win 16 API не поддерживается в Win32 API, так что мы должны использввать что-то другое. DPMI
предлагает функцию (6h INT 31h, GetSegmentBase), которая, получая селектор, возвращает линей-
ный базовый адрес:
BYTE *tdb_drv, »tdb_curdir, *tdb_base;
_asm mov bx. tdb
DPMICall(0x0006, 0); // получить базу селектора
_asm mov word ptr tdb_base+2, ex
_asm mov word ptr tdb_base, dx
DPMICall — это просто оболочка вокруг недокументированной сервисной функции VxDCall,
предоставляемой KERNEL32 (см. “Win32-дескрипторы файлов и thunk-переключение” в главе 14).
Программа CHGDIR использует GetProcAddress для получения указателя на VxDCall (чье
настоящее имя в KERNEL32 — VxDCallO):
// Используется динамическое связывание времени выполнения, •
// чтобы по именам получить указатели на функции
«define GET_PROC(mod, func) GetProcAddress(GetModuleHandle(mod), (func))
«define VWIN32_INT31_CALL 0x2A0029
«define DPMICall(eax, ecx) VxDCall(VWIN32_INT31_CALL, (eax), (ecx))
DWORD (WINAPI *VxDCall)(DWORD srve, DWORD eax, DWORD ecx);
VxDCall = GET_PR0C(“KERNEL32”, “V/DCallO”);
Использовав DPMICall, CHGDIR может легко получить 32-битовый линейный указатель на
свой собственный TDB. Только для уверенности CHGDIR вызывает функцию 62h INT 21h (снова с
VxDCall, на этот раз используя Win32-cepBHC 2A0010h) для получения PSP и сравнивает получен-
ный указатель со словом по смещению 60h в структуре, которая, как мы надеемся, является TDB.
Как объяснялось раньше, по смещению 60h в TDB содержится указатель защищенного режима на
PSP задачи. Это так же верно для Win32-зaдaч, как и для Win 16-задач:
WORD psp;
DosCall(0x6200, 0); // Get Current PSP
_asm mov psp, bx
assert(*((WORD*)(tbd_base + 0x60)) == psp);
И наконец, перед выполнением своего цикла чтения-выполнения-вывода, CHGDIR создает
указатель со смещением 100h в TDB, где хранится текущий каталог, и со смещением 66h, где
хранится текущий диск:
tdb_curdir = tdb_base + 0x100;
tdb_drv = tdb_base + 0x66;
Внутри цикла, когда бы пользователь ни выполнил команду CD (Установить текущий каталог),
программа CHGDIR выводит содержимое TDB+66h и TDB+100h:
SetCu гrentDi rectory(&buf[ 3]);
printf(“TDB+66h; %02Xh\n”, *tdb_drv);
printf(“TDB+100h: \'%s\’\n”. *tdb_curdir);
Глава 13. Thunk! KERNEL32 вызывает KRNL386 .365
Вот пример вывода программы CHGDIR:
Thread ID: 810FF300h
TDB: 1CA6h
TDB base: 800E0CA0h
PSP: 1CEFh
[C:\UNAUTHW\TEST] md This is a long directory name
[C:\UNAUTHW\TEST] cd This is a long directory name
TDB+66h: 82h *
TDB+100h: ‘\UNAUTHW\TEST\This is a long directory name’
[C:\UNAUTHW\TEST\This is a long directory name] md This is another long \
directory name
[C:\UNAUTHW\TEST\This is a long directory name] cd This is another long \
directory name
TDB+66h: 82h
TDB+100h: ‘\UNAUTHW\TEST\This is a long directory name\This is another \
long directory name'
[C:\UNAUTHW\TEST\This is a long directory name\This is another long \
directory name] cd \
TDB+66h: 82h
TDB+100h: ‘\’
[C:\] cd unauthw\test
TDB+66h: 82h
TDB+100h: ‘\unauthw\test’
[C:\unauthw\test] cd h:\
TDB+66h: 87h
TDB+IOOh: ‘\’ . ‘
[h:\] cd c:
TDB+66h: 82h
TDB+100h: '\'
Действительно, TDB+66h содержит текущий диск (в форме, описанной в обсуждавшейся рань-
ше внутренней функции SaveState KERNEL) и TDB+IOOh содержит текущий каталог.
Обратите внимание, что функции API SetCurrentDirectory и GetCurrentDirectory манипулируют
не только каталогами, но и дисками. В API Win32 нет отдельных функций SetDrive и SetVolume.
Если вы хотите поменять диск, то вызываете SetCurrentDirectory. (Обратите внимание на CD Н: и
CD С: в результатах CHGDIR; между прочим, в COMMAND.COM Windows 95 это не проходит.)
Функция SetCurrentDirectory также поддерживает UNC-описания дисков типа \ \имя сер-
вера\имя ресурса.
Отсутствие специальной функции изменения диска приводит к одному необычному эффекту,
который можно заметить в конце результатов CHGDIR: если вы находитесь в каталоге
C:\unauthw\test (обратите внимание, что имена не чувствительны к регистру, хотя и сохраняют
его) и вызываете функцию SetCurrentDirectory (Н:), а затем SetCurrentDirectory (С:), то вы
попадете не в C:\unauthw\test, а в С:\. Из одного этого примера становится ясно, что Windows
95 не имеет встроенного понятия нескольких текущих каталогов на разных дисках. MS DOS, напро-
тив, дает каждому диску свой собственный текущий каталог, содержащийся в структуре текущего
каталога (CDS, см. Undocumented DOS, 2d ed., р.163-167, 443-449).
Чтобы выяснить, как CHGDIR выглядит для DOS реального режима, я запустил программу
вместе с V86TEST. Было зарегистрировано много разнообразных вызовов INT 21h. Чтобы
366
Неофициальная Windows 95
разобраться со всеми этими вызовами INT 21h, показанными программой V86TEST, я написал
консольное ЭД1п32-приложение, NOTHING.EXE, которое представляет из себя всего лишь:
main() { return 0; }
Затем я запустил эту программку под V86TEST и сравнил вывод V86TEST из CHGDIR с
выводом V86TEST из NOTHING.EXE. Оказалось, что CHGDIR ответственна только за три типа
вызовов INT 21h:
06: 43550 40: 140 62: 1
Большое количество вызовов функции 06h (Ввод-вывод на консоль) представляет время, когда
программа CHGDIR внутри С-функции gets ожидает ввода с клавиатуры. (Хм-м, почему колесо
крутится? Почему не блокировать VM до тех пор, пока что-то не введут?) 32-биговая библиотека
Microsoft реализует функцию gets через вызов ReadFile. Чтение с консоли генерирует обращение
VxDCall к VxD VCOND (драйверу виртуальной консоли). На самом деле, вызов функции 06h гене-
рируется в режиме V86 программой CONAGENT.EXE, которая в свою очередь загружается
VMM32, когда вы запускаете консольное Win32-npnuK^einie.
Вызовы функции 40h (Запись) исходят от программы CHGDIR из функций printf вывода в
стандартный поток вывода. А вызовы функции 62h (Установить PSP) — от VxDCall CHGDIR
(0х2А0010, 0x6200, 0).
Кроме того факта, что Win32-пpилoжeния (хоть они и работают в символьном режиме) ис-
пользуют для ввода DOS реального режима (помните, что Windows 95 якобы обходится без DOS
реального режима!?), важно также то, что V86TEST — а значит, и DOS реального режима — не за-
мечают вызовов получения и установки текущего каталога и диска. Эти Win32 API могут обра-
щаться к ядру Winl6 чаще, чем это хотела бы представить Microsoft, однако мы, по крайней мере,
видим, что они не обращаются к DOS реального режима, хотя и используют функции 7 th INT 2 th.
Что думает программа WSPY21 об этом консольном Win32-пpилoжeнии? Если консольное
Win32-npii«^eHHe вызывает FreeConsole для отсоединения от текущей консоли (обычно, окна
DOS) и вызывает AllocConsole для создания отдельной консоли (в Windows 95 эти консоли —
виртуальные машины), WSPY21 заметит, как Win32-npH/K^eHHe вызывает функцию 4Bh для за-
пуска CONAGENT.EXE. Но в случае с CHGDIR WSPY21 не замечает ничего особенного.
Фактически, WSP21 показывает одни и те же результаты и для CHGDIR, и для NOTHING:
<WINOLDAP> *(50) Set PSP 167 (00A7h)
<WINOLDAP> (55) (Undoc) Create PSP 7407 (ICEFh), 256 (OlOOh)
<WINOLDAP> *(1A) Set DTA 1E57:0080
<WINOLDAP> «(71) LFN (3B) ChDir ‘\WINDOWS’
<WINOLDAP> *(50) Set PSP 7767 (1E57h)
<KERNEL32> *(50) Set PSP 7407 (ICEFh)
<KERNEL32> *(00) Exit
<KERNEL32> *(50) Set PSP 167 (00A7h)
Обратите внимание, что PSP, созданный WINOLDAP (здесь это ICEFh), соответствует PSP,
отображаемому CHGDIR сразу после запуска.
Но почему Win32-nponeccbi нуждаются в Winl6 TDB?
С одной стороны, Win32-пpилoжeния требуют Win 16 TDB для работы с существующей си-
стемой сообщений Winl6 в USER. Много внимания уделялось тому, что Windows 3.x имеет
единственную очередь ввода, разделяемую всеми задачами, — поэтому зависшая задача блокирует
все остальные задачи, и тому, что в Windows 95 каждая ветвь имеет свою собственную очередь
ввода. Microsoft называет это десинхронизацией ввода. Однако один момент не был достаточно
рассмотрен: система сообщений в Windows все еще основывается на Win 16.
Мэтт Петрек указал на это в Microsoft Systems Journal (“Investigating the Hybrid Windowing
and Messaging Architecture of Chicago”, September 1994, p. 17):
Глава 13. Thunk! KERNEL32 вызывает KRNL386
367
I Хотя оконная процедура для окна 32-битовой программы написана на 32-битовом коде, существующие
16-битовые приложения... ожидают, что любое окно, хоть 16-, хоть 32-битовое, работает так же, как и в
Windows 3.x. Рассмотрим, например, создание оконных подклассов... Если бы Chicago хранила в
структуре WND 32-битовый линейный адрес, все быстро бы запуталось. Для предотвращения таких
ситуаций Chicago идет на все, чтобы окна вели себя как 16-битовые.
Одной из таких мер является то, чтобы дать каждой Win32-3iuia4e Winl 6 TDB с Winl 6-
очередью.
Более того, каждая Win32 WndProc имеет соответствующую Winl6 WndProc. С точки зрения
оконной системы USER, все окна получаются 16-биговыми, с 16-битовыми WndProc и очередью
сообщений. Вы легко можете убедиться в этом с помощью любой Win 16-программы, которая
проходит список окон. Например, WALK WIN. С из листинга 13.4 — простая WINIO-программа,
использующая GetWindow для прохода по списку окон:
ЛИСТИНГ 13.4. WALKWIN. С
/*
WALKWIN.С
Windows-приложение, которое пробегает список окон и показывает их параметры.
Шульман, 1994
*/
«include <stdlib.h>
«include <string.h>
«include <dos.h>
«include “windows.h”
«include “toolhelp.h”
«include “winio.h"
void print hwnd(HWND hwnd, int level)
{
char taskname[9];
char wndproc_owner[9];
GLOBALENTRY *pge;
HANDLE htask, htaskq;
void far ‘wndproc;
char *wndtext, *classname;
int flag32;
int i;
if(!(wndtext = (char *) malloc(128)))
fail(“Недостаточно памяти”);
if(!(classname = (char *) malloc(128)))
fail(“Недостаточно памяти”);
। htask = GetWindowTask(hwnd);
GetWindowText(hwnd, wndtext, 128);
wndproc = GetClassLong(hwnd, GCL_WNDPROC);
GetClassName(hwnd, classname, 128);
flag32 = ‘((WORD far *) MK_FP(htask, 0x16)) & 0x10;
htaskq = ‘((WORD far *) MK_FP(htask, 0x20));
_fmemcpy(taskname, MK_FP(htask, 0xF2), 8);
taskname[8] = '\0';
wndproc_owner[0] = '\0';
if(!(pge = (GLOBALENTRY *) malloc(sizeof(GLOBALENTRY))))
fail(“Hefl0CTaT04H0 памяти’’);
pge->dwSize = sizeof(GLOBALENTRY);
368
Неофициальная Windows 95
if(GlobalEntryHandle(pge, FP SEG(wndproc)))
{
MODULEENTRY »pme;
if(!(pme = (MODULEENTRY *) malloc(sizeof(MODULEENTRY))))
Та11("Недостаточно памяти”);
pme->dwSize = sizeof(MODULEENTRY);
if(ModuleFindHandle(pme, pge->hOwner))
strcpy(wndproc_owner, pme->szModule);
free(pme);
)
free(pge);
for(i=0; Klevel; i++)
printf(“ ”);
printf(“96s 96s 96O4Xh 96O4Xh 9604Xh 96Fp (96s) \'96s\ ",
flag32 ? “32": “16",
taskname, hwnd, htask, htaskq,
wndproc, wndproc_owner, classname);
if(wndtext && «wndtext)
printf(“\" %s\ “ ”, wndtext);
printf(“\n” );
free(classname);
free(wndtext);
void walkwin(HWND hwnd, int level)
{
if(hwnd == 0)
return;
hwnd = GetWindow(hwnd, GW_HWNDFIRST);
while(hwnd)
{
print_hwnd(hwnd, level);
walkwin(GetWindow(hwnd, GW_CHILD), level+1);
hwnd = GetWindow(hwnd, GW_HWNDNEXT);
)
if (level •== 0)
printf(“\n");
main(int argc, char *argv[])
{
HWND hwnd;
if(argc < 2)
hwnd = GetWindow(GetDesktopWindow(), GW_CHILD);
else
sscanf(argv[1], “%04X", &hwnd);
winio_setpaint(___hMainWnd, FALSE);
#if 1
walkwin(hwnd, 0);
#else
walkwin(GetWindow(GetDesktopWindow(), GW_CHILD), 0);
#endif
winio_setpaint(___hMainWnd, TRUE);
return 0;
}
Эта Win 16-программа может легко найти дескриптор окна, TDB (из которого она может из-
влечь имя задачи и адрес очереди сообщений), WndProc, имя класса и заголовок окна не только для
Глава 13. Thunk! KERNEL32 вызывает KRNL386 369
других Win 16-приложений, но и для каждого запущенного \У1п32-приложения. Вот небольшая
часть результатов WALKWIN при запущенных Win32-nporpaMMax Clock, WinBezMT, FreeCell
(вместе с самой WALKWIN):
32 САВ32 OODOh 1276h 207Fh 167F:0022 (KERNEL) 'tooltips_class32’
32 CAB32 00C4h 1276h 207Fh 167F:0022 (KERNEL) ‘tooltips_class32’
32 CAB32 00B4h 1276h 207Fh 167F:0114 (KERNEL) ‘Shell_TrayWnd'
32 САВ32 00B8h 1276h 207Fh 079F:3E61 (USER) ‘Button’
32 CAB32 OOBCh 1276h 207Fh 167F:01AE (KERNEL) ‘TrayNotifyWnd’
32 CAB32 OOCOh 1276h 207Fh 167F:01C4 (KERNEL) ‘TrayClockWClass’
32 CAB32 00C8h 1276h 207Fh 167F:01DA (KERNEL) 'MSTaskSwWClass’
32 CAB32 OOCCh 1276h 207Fh 167F:007A (KERNEL) ‘SysTabControl32’
16 MSGSRV32 0088h 13EFh 1677h 0797:06A2 (USER) ‘#32768’
16 MSGSRV32 0084h 13EFh 1677h 0777:0005 (USER) ‘#32771’
16 WALKWIN 05D0h 2787h 272Fh 2847:2B1B (WALKWIN) ‘winio_wcmain’ “WALKWIN”
16 16 WINOLDAP 045Ch 1E07h 1CFFh 1227:0069 (COMMCTRL) ‘tooltips_class’
WINOLDAP 0450h 1E07h 1CFFh 1EB7.0B07 (WINOLDAP) ‘tty’ “COMMAND"
32 FREECELL 0128h 1B46h IBIFh 167F:05FA (KERNEL) 'FreeWClass' “FreeCell Game #5755”
32 WINBEZMT 014Ch 1A56h 1B4Fh 167F.0702 (KERNEL) ‘winbezmt’ “32-bit Multi-Threaded WinBez"
32 WINBEZMT 0150h 1A56h 1B4Fh 07E7:0000 (USER) ‘MDIClient’
32 WINBEZMT 0228h 1A56h 1B4Fh 167F:0718 (KERNEL) ‘ThreadClass’ “Thread Window 1"
32 WINBEZMT 0224h 1A56h 1B4Fh 167F:0718 (KERNEL) ‘ThreadClass’ “Thread Window 2”
32 CAB32 015Ch 1276h 207Fh 079F:3E70 (USER) ‘#32770’ "Find: All Files"
32 CAB32 0184h 1276h 207Fh 079F:3E70 (USER) ‘#32770’ “Name & Location"
32 CAB32 0188h 1276h 207Fh 079F.3E66 (USER) ‘Static’ “&Named:”
32 CLOCK 0120h 1CAEh 1C6Fh 167F:04F2 (KERNEL) ‘Clock’ “Clock”
32 BATMETER 00D8h 1F9Eh 1FA7h 167F:02F8 (KERNEL) ‘BatteryMeter.Main’ "Battery Meter"
32 BATMETER OODCh 1F9Eh 1FA7h 079F.3E61 (USER) ‘Button’ “Power Status”
32 CAB32 00D4h 1276h 207Fh 167F:016C (KERNEL) 'ProxyTarget’
32 CAB32 OOBOh 1276h 207Fh 167F:016C (KERNEL) ‘ProxyTarget’
32 CAB32 OOAOh 1276h 207Fh 17D7:503E (DDEML) ‘DMGFrame’
32 CAB32 009Ch 1276h 207Fh 17D7:5104 (DDEML) ‘DMGClass’
32 CAB32 OllOh 1276h 207Fh 17D7:52EA (DDEML) ‘DMGHoldingClass’
32 CAB32 0094h I276h 207Fh 167F:00FE (KERNEL) 'OTTimerClass’
16 MSGSRV32 0090h 13EFh 1677h 1307:0458 (MSGSRV32) ‘Windows 32-bit VxD Message Server’
16 TIMER 008Ch 130Fh 12CFh 1377:0332 (MMSYSTEM) ‘#42’
32 CAB32 0098h 1276h 207Fh 167F:012A (KERNEL) ‘Progman’ “Program Manager"
32 CAB32 00A4h 1276h 207Fh 167F:0156 (KERNEL) ‘SHELLDLL.DefView’
32 CAB32 00A8h 1276П 207Fh 167F:004E (KERNEL) ‘SysListView32’
32 CAB32 OOACh 1276h 207Fh 167F:0064 (KERNEL) ‘SysHeader32’
WALKWIN использует Win32-4>nar по смещению 16h в TDB, чтобы определить, принадлежит
окно Win 16- или Win32-пpилoжeнию. Кроме флага (указывающего 16 или 32 в начале каждой
строки) и вашего собственного знания, является ли конкретное приложение Winl6- или Win32-
приложением, нет другого способа узнать разрядность приложения из прочих пунктов вывода про-
граммы WALKWIN. Вот в чем дело: с точки зрения оконной системы Windows 95, Win32-npiuio-
жения выглядят очень похоже на Winl6-npiuM^eHHH, вплоть до использования ими таких структур
KERNEL, как TDB и очереди сообщений.
Для получения 100%-ной уверенности в том, что все окна Wm32-пpилoжeний в самом деле име-
ют Win 16 WndProc, WALKWIN использует ToolHelp-интерфейс для получения имени владельца
сегмента, в котором находится каждая WndProc. Как видно из вывода WALKWIN, WndProc для
встроенных Win32-KnaccoB окон, таких как Button, MDI-Client и Static, расположенных в Winl 6-
модуле USER, и WndProc для Win32-OKOH, специфичных для приложений, принадлежат Winl 6
KERNEL.
370
Неофициальная Windows 95
\У1п32-приложения САВ32, WinBezMT, Clock, FreeCell вызывают Win32-Bepcnro RegisterCiass
из USER.DLL и все устанавливают 32-битовые WndProc. Но сообщения WMXXX в Windows 95
всегда идут в Winl6 WndProc. Эти 1б-битовые WndProc передают управление 32-битовым
WndProc, установленным Win32-npHnoaceiniHMM. (Этот механизм передачи сообщений объяснялся в
сентябре 1994 г. в статье Мэтта Петрека (Matt Pietrek) в Microsoft Systems Journal.) Точно так же
функции и8ЕВ32-интерфейса, например GetMessage, переключаются к своим двойникам из USER.
Все это выглядит очень похоже на схему, использовавшуюся в Win32s. Если вы проследите за
передачей сообщений в Windows 95, от прибытия сообщения WMXXX к заместителю Winl6
WndProc до доставки сообщения к Win32 WndProc, то сможете обнаружить вызов функции из
KERNEL32 с именем W32S_BackTo32. Функция с тем же именем имеется в Win32s-MO,nyne
W32SKRNL.DLL. Запуск программы WALKWIN совместно с некоторыми Win32-пpилoжeниями
под Win32s обнаруживает тот же основной подход, который мы наблюдали в Windows 95: в случае
встроенных классов окон заместители Win 16 WndProc принадлежат USER, Winl6-3aMecTnre.nH для
Win32 WndProc, специфичных для приложений, принадлежат WIN32S16. Например:
32 WINBEZMT 256Ch 1D67h 1D2Fh 1F07:0042 (WIN32S16) ‘winbezmt’ “32-bit Multi-Threaded WinBez"
32 WINBEZMT 25C0h 1D67h 1D2Fh 06FF:1062 (USER) ’MDIClient’
32 CLOCK 2468h 1DFFh 1DD7h 1F07.002C (WIN32S16) ‘Clock’ “Clock - 9/9/94"
32 FREECELL 1ED8h 125Fh 1167h 074F:0429 (USER) ‘#32770’ “About FreeCell"
32 FREECELL 22E0h 125Fh 1167h 074F:2313 (USER) ‘Static’ “Memory:"
32 FREECELL 2328h 125Fh 1167h 074F:2313 (USER) ‘Static’ “8,048 KB Free"
32 FREECELL 2370h 125Fh 1167h 074F.2313 (USER) ’Static’ “System Resources."
32 FREECELL 23B8h 125Fh 1167h 074F:2313 (USER) ‘Static’ “85% Free"
32 FREECELL 2400h 125Fh 1167h 074F:1A12 (USER) ‘Button’ “OK"
32 FREECELL 1E98h 125Fh 1167h 1F07:0016 (WIN32S16) ‘FreeWClass’ "FreeCell Game #7260"
Несмотря на такие важные изменения, как десинхронизация ввода, система сообщений в Win-
dows 95 остается 16-битовой, почти как в Windows 3.x. Схема поддержки Win32-coo6meHHft выгля-
дит почти такой же, как и в Win32s. Win32-пpилoжeния участвуют в обмене сообщениями Win-
dows 95 благодаря своим Win 16-заместителям.
Итак, система сообщений Win 16 USER целиком зависит от структур данных Win 16 KERNEL,
особенно от очереди сообщений. Как отмечалось в книге Undocumented Windows (р. 380), иногда
трудно определить, где заканчивается USER и начинается KERNEL:
Поскольку сообщения реально посылаются задаче, а не к окну, получается, что очередь задач сама по
себе есть структура данных KERNEL, а не USER. С другой стороны, многие процедуры внутри USER
имеют достаточно интимные знания о структуре очереди задач; к ним относятся, например,
ReplyMessageO, InSendMessageO, GetMessageTimeO, GetMessagePosO, PostQuitMessageO...
На самом деле трудно решить, является ли очередь задач структурой данных KERNEL или USER. Дей-
ствительно, она разделяется между двумя модулями и фактически . склеивает KERNEL и USER.
Функции GetTaskQueue и SetTaskQueue находятся в KERNEL, но сама очередь задач создается функ-
цией InitApp в USER.
Сопоставьте эти два факта — то, что Win32-CHCTeMa сообщений зависит от поддержки Winl6-
сообщений и что Win 16-сообщения зависят от структур данных Win 16 KERNEL, — и станет ясно,
что поддержка Win32 в Windows 95 должна основываться на Win 16 KERNEL. Идея о том, что
USER32 обращается к USER, но KERNEL32 це обращается к KRNL386 — абсурдна. В самом деле,
в Windows 95 с запущенными только Win32-пpилoжeниями контрольная точка на доступ к данным
(BPR в отладчике Soft-ICE/Windows), помещенная на TDB или очереди задач, будет постоянно
срабатывать.
Ну и что? Win32-пpилoжeния нуждаются в Win 16 TDB для совместимости с системой сообще-
ний Windows. И в любом случае, Windows 95 Нуждается в Winl6 KERNEL для запуска Winl6-npmio-
жений. Что плохого в том, что KERNEL32 иногда использует код, уже существующий в KRNL386?
Ничего в этом нет плохого, за исключением того, что Microsoft заявляет, что в отличие от
USER32, который практически постоянно обращается к Winl6 USER, и в отличие от GDI32,
Глава 13. Thunk! KERNEL32 вызывает KRNL386
371
который часто обращается к Win 16 GDI, KERNEL32 предположительно никогда не вызывает
KRNL386:
Большая часть кода в USER32 — не более чем прослойка, которая принимает 32-битовые вызовы и
передает их на выполнение 16-битовым аналогам... Это разумный путь использования проверенного
кода — в конце концов, 16-битовый API все равно должен присутствовать для сохранения
совместимости. GDI32 предлагает значительное улучшение производительности. Следовательно, 32-
битовый GDI обрабатывает значительное количество вызовов API непосредственно.
Модуль KERNEL32 совершенно не зависит от своей 16-битовой версии. Случаются обращения с 16-
битовой на 32-битовую сторону, но 32-битовый KERNEL никогда не вызывает 16-битовый код. Этого и
следовало ожидать, так как большая часть кода (например, распределение памяти и управление ветвями
выполнения) значительно изменилась.
— Адриан Кинг, “Memory Management, the Win32 Subsystem, and Internal Synchronization in Chicago”,
Microsoft Systems Journal, May 1994, p. 58.
Утверждения, что KERNEL32 “совершенно не зависит” от 16-битового KRNL386 и что “32-
битовый KERNEL никогда не вызывает 16-битовый код” просто неверны, в чем мы вскоре и убе-
димся. Так же как и в случаях с другими подобными “никогда”, касавшимися Windows и DOS и
обсуждавшимися в предыдущих главах, — например, “если вы запускаете только Windows-
приложения, вам никогда не придется выполнять код DOS” — на ум приходят несколько строк из
HMS Pinafore Гильберта и Саливана. Капитан корабля поет, что он никогда не ругается, и хор
спрашивает у него, правда ли это.
Хор: Что, никогда?
Капитан: Да, никогда.
Хор: Что, никогда?
Капитан: Ну, иногда.
К сожалению, компьютерная пресса часто представляет несколько другой хор: когда Microsoft
говорит о каких-либо компромиссах в Windows 95, например о Winl6Lock (см. главу 14), вся прес-
са сходит с ума. Однако, если Microsoft сообщает прессе что-либо, что приятно слышать, например,
что исчезла значительная часть 16-битового кода, не делается почти никаких попыток проверить это.
Компьютерная пресса иногда похожа на хор попугаев, перепутавших, кому подражать.
Вместо гонений на Microsoft из-за необходимых компромиссов, как в случае с Winl6Lock, и
последующей искренней веры в то, что реальный режим DOS исчез и что ядро Win32 не
полагается на ядро Win 16, было бы намного лучше вовсе не строить догадок об архитектурных
компромиссах Microsoft, но и не верить компании, заявляющей, что она устранила такие
необходимые компромиссы.
Компьютерная пресса эхом повторяет заявления Microsoft о полной независимости ядра Win32
от ядра Winl6. В качестве примера в статье “A Revaling Look at the Architecture” (.Windows
Magazine, July 1994, p. 186) представлена диаграмма, показывающая, что в Chicago работают сле-
дующие вызовы: USER32 —> 16, GDI32 <-> 16, но KERNEL32 <— 16, и утверждающая, что “все ком-
поненты ядра Chicago (управление задачами, управление памятью, файловая система) улучшены с
помощью использования 32-битового защищенного режима”.
Никто, кто пытался независимо проверить заявления Microsoft, не стал бы утверждать то же
самое. Даже без дизассемблирования кода, просто используя доступные средства отладки —
наподобие Soft-ICE/Windows и WinScope, можно показать, например, что управление задачами
Windows 95 требует как кода 16-битового защищенного режима, так и некоторого кода реального
режима. Как только вы увидите, что каждый Win32-npoiiecc имеет Win 16 TDB и PSP реального
режима DOS, все встает на свои места. KERNEL поворачивается в обе стороны (16 <-> 32): опре-
деленно KRNL386 вызывает KERNEL32, но и KERNEL32 обращается к KRNL386 (как мы только
что убедились; с вызовами LoadLibraryl6 и GetProcAddressl6 мы подробнее разберем чуть позже).
Да и нет разумной причины, почему это должно быть не так. Позже мы обсудим не-очень-разумные
причины, чтобы утверждать, что это не так.
Точка зрения Кинга о “проверенном коде” в USER также очень важна. Эта точка зрения обос-
нованна, даже если вы не верите, что все ошибки в этом коде были исправлены. (У меня была
'» ”—— ............ ............................. -..............................—.-----------
372 Неофициальная Windows 95
возможность взглянуть на исходный код USER, что привело меня просто в ужас.) Вот как об этом
говорит Мэтт Петрек:
USER.EXE — это то, что часто называют “унаследованным кодом”. Он был модифицирован, слегка
подлатан и прочее примерно за неделю... есть, без сомнения, некоторые странности внутри USER.EXE,
которые приложениям придется воспринимать как нормальное поведение. Похоже, ни один человек
просто не может хранить рабочую модель USER и все предположения о ней и ее причуды в своей
голове. Если бы код USER перенести полностью в 32-битовый код, то существующие приложения не
смогли бы работать.
— Мэтт Петрек, “Investigating the Hybrid Windowing and Messaging Architecture Chicago”, Microsoft Systems
Journal, September 1994, p. 15-16.
Другими словами, хорошо изученный вами черт предпочтительнее ангела, которого вы никогда
не встречали. Код USER был основательно обкатан на протяжении ряда лет миллионами поль-
зователей во всем мире. Это (а не предположительно независимая оценка доверчивой, льстящей и
обманывающейся компьютерной прессы) — подлинная проверка всему. Не думаю, что якобы “под-
линные” операционные системы, за которые ратуют борцы за чистоту операционных систем, могут
выдержать такую рыночную толчею.
Но есть еще один вопрос, о котором я пока не говорил: если что-то применимо к USER, то
почему же не к KERNEL? Адриан Кинг говорит, что предположительная независимость Win 16
KERNEL от KERNEL32 — это то, “чего и следовало ожидать”, но мне совершенно так не кажется.
Мы уже знаем, что Win 16 USER обрабатывает сообщения WM_XXX для Win32-пpилoжeний и что
система сообщений USER сильно зависит от управления задачами KERNEL. Поскольку Windows 95
использует Win 16 для обработки сообщений WM_XXX, естественно ожидать, что Win32 будет
нуждаться в Win 16 KERNEL по крайней мере для создания каждому Win32-nponeccy USER-
совместимых очереди задачи и TDB. Принимая, что такие функции API USER32, как GetMessage,
обращаются к их эквивалентам из Win 16 USER и что управление очередью сообщений по-прежнему
находится под ответственностью Win 16 USER, логически заключаем, что Win32 KERNEL32 должен
использовать некоторые сервисные функции из Winl6 KERNEL.
От Explorer до Create PSP
за шесть простых шагов
Ну что ж, если вас не убедили мои логические умозаключения, рассмотрим конкретный пример
того, как KERNEL32 в Windows 95 на самом деле использует некоторый сервис из Win 16 KERNEL.
Вспомним следующую важную строку из вывода WSPY21, которая появляется всегда, когда
САВ32 запускает приложение, даже просто другое Win32-пpилoжeниe:
<САВ32> *(55) (Undoc) Create PSP 7503 (1D4Fh), 256 (0100h)
Каким образом or щелчка мыши на пиктограмме приложения в Explorer мы приходим к гене-
рации вызова функции 55h INT 21h, который передается в реальный режим DOS? Оказывается,
путь лежит через ядро Winl6:
• Когда вы выбираете приложение для запуска из Explorer, САВ32 вызывает ShellExecuteEx
API в SHELL32. Этот API в свою очередь вызывает Win32-Bepcnio WinExec в KERNEL32.
Win32-пpилoжeния используют CreateProcess, а не WinExec, так что WinExec здесь является
как бы слоем совместимости вокруг CreateProcess.
• Просматривая CreateProcess (на самом деле, CreateProcessА), мы в конце концов обнару-
живаем следующий кусок кода:
0137:BFF91A67 MOV CL,31
0137:BFF91A69 PUSH EBP
0137:BFF91A6A MOV EBP,ESP
; thunk It
Глава 13. Thunk! KERNEL32 вызывает KRNL386
373
0137:BFF91A6C PUSH ECX
0137:BFF91A6D SUB ESP.+3C
0137:BFF91A70 PUSH WORD PTR [EBP+08] ; 0A7h
0137:BFF91A74 PUSH DWORD PTR [EBP+OC] ; 810FD9A4h
0137:BFF91A77 PUSH WORD PTR [EBP+10] ; 1E97h
0137:BFF91A7B CALL [BFF9181A] ; BFFB7C44h
0137:BFF91A81 MOVZX EAX, AX
0137:BFF91A84 LEAVE
0137:BFF91A85 RET oooc
• Трассируя CALL [BFF9181A], мы получим следующее:
0137:BFFB7C44 X0R ЕСХ, ЕСХ
0137:BFFB7C46 MOV CL,[EBP-04] ;31h, thunk#
0137:BFFB7C49 MOV EDX, [00030464+4*ECX] ;0117653Fh (см. ниже)
0137:BFFB7C50 MOV EAX.BFF71247
0137: BFFB7C55 JMP EAX
Заметим, что код использует 31h в CL как индекс в таблице двойных слов. Через пару ми-
нут мы посмотрим на эту таблицу пристальнее. В настоящий момент ячейка 31h в таблице
содержит значение 0117653Fh, и код загружает это значение в регистр EDX.
• Затем мы переходим к BFF71247h, что, как и следовало ожидать, является процедурой
QT_Thunk в KERNEL32:
KERNEL32!QT_Thunk
0137:BFF71247 TEST BYTE PTR FS:[0000001C],01
0137:BFF7124F JE BFF71320
0137:BFF71255 POP DWORD PTR [EBP-24]
0137:BFF71258 PUSH DWORD PTR [BFFB7CD8]
0137:BFF7125E PUSH EDX ;; Win16 адрес вызова
0137:BFF712B4 RETF
• Когда QT_Thunk выполняет RETF, мы неожиданно оказываемся уже не в KERNEL32. Вмес-
то этого, QT_Thunk “возвращается” по (фальшивому) адресу возврата, который был занесен
в стек. Обратите внимание на инструкцию PUSH EDX, которая находится вверху
QT_Thunk. Вспомним, что EDX содержит величину 0117653Fh, выбранную из ячейки 31h в
таблице двойных слов по 30464b. Выполнение RETF в конце QT_Thunk перебрасывает нас
по адресу segment:offset в EDX, который был помещен в стек:
0117:0000653F PUSH BP
0117:00006540 MOV BP,SP
0117:00006542 PUSH WORD PTR [BP+OC]
0117:0000.6545 PUSH DWORD PTR [BP+08]
0117:00006549 PUSH WORD PTR [BP+06]
Сегмент 0117h принадлежит ядру Win 16. Таким образом, благодаря волшебству QT_Thunk
и таблице 30464h, KERNEL32 вызывает некоторый код в ядре Winl6. (Здесь нет никакого
волшебства, QT_Thunk переходит в часть 16-битового кода, заталкивая полный 48-битовый
адрес в стек и затем “возвращаясь” по нему.)
• Если мы проследим за этим кодом KERNEL, то в конце концов наткнемся на вызов функции
55h INT 2 lb:
011F: 0000411Е MOV
011F:00004121 MOV
011F:00004124 MOV
011F:00004126 INT
DX,[BP+08]
SI,[BP+06]
AH, 55
21
374
Неофициальная Windows 95
Так где же мы находимся в KERNEL? Вспомним нашу предыдущую дискуссию, основанную на
книге Windows Internals Мэтта Петрека, о внутренней функции CreateTask в KERN386, вызыва-
ющей BuildPDB, которая, в свою очередь, вызывает функцию 55h INT 21h. Мы находимся где-то в
CreateTask, KERNEL32 thunk-переключатель #31h предоставляет Win32-KOfly, вроде CreateProcess,
возможность вызвать 16-битовую функцию CreateTask из KERNEL.
Эти невинно выглядящие команды MOV АН, 55h и INT 21 едва ли будут концом путешествия.
Путешествие в действительности только начинается. Поскольку KERNEL вызывает INT 21h в защи-
щенном режиме, вызов пойдет по цепочке INT 21h защищенного режима. Обработчик KERNEL пре-
рывания INT 21h обычно находится первым в этой цепочке. KERNEL передает вызов ниже, тому,
что мы назвали KernelDosProc, обычно это VxD, который был предыдущим владельцем вектора
прерывания INT 21h. Если никто в защищенном режиме не обработал вызов функции 55h INT 21h
(и обычно так оно и случается), то вызов INT 2lh посылается к цепочке перехватчиков прерывания
INT 21h режима V86 (где его увидят такие VxD, как IFSMGR и DOSMGR) и наконец к действи-
тельной V86-neno4Ke прерывания INT 21h, где различные драйверы устройств обнаружат вызов INT
21h до того, как он дойдет до DOS, которая создаст PSP.
Путешествие выглядит следующим образом:
Щелчок на пиктограмме в САВ32 ->
SHELL32!ShellExecuteEx ->
KERNEL32!WinExec ->
внутренняя KERNEL32!CreateProcess ->
thunk #31h ->
KERNEL32!QT_Thunk ->
внутренняя KRNL386!CreateTask ->
внутренняя BuildPDB ->
функция 55h прерывания INT 21h ->
цепочка INT'21h защищенного режима (KERNEL -> KernelDosPros -> и т.д.) ->
цепочка перехватчиков INT 21h V86 (IFSMgr -> DOSMGR -> и т.д.) ->
цепочка INT 21h режима V86 (V86TEST -> IFSHLP.SYS -> и т.д.) ->
DOS реального режима
Это дает быстрый ответ на поставленный раньше вопрос, каким образом щелчок на пикто-
грамме приложения в Explorer приводит к вызову Create PSP в DOS реального режима. Возвра-
щенное значение функции 55h INT 21h должно проделать обратный путь к CreateTask, а из
CreatTask — в вызвавший ее KERNEL32.
Мы только что рассмотрели один thunk-переключатель, #31, посредством которого KERNEL32
заставляет KRNL386 (кроме всего прочего) вызвать функцию 55h INT 21h. Имеет смысл посмотреть
и некоторые другие процедуры в thunk-таблице со смещением 30464 в системной VM. Мы можем
просмотреть дамп всей таблицы с помощью утилиты PROTDUMP:
C:\UNAUTHW\PROTDUMP>protdump #1 30464 -dword 0x100
С4430464 С4430474 С4430484 С4430494 011F66CE 011F0B5B 011F4396 01272364 0127258В 0127253В 01272527 01272513 01170105 01177544 011765АВ 011703D0 01270525 01270233 0127017F 01270155
С44304А4 С44304В4 011775FD 011775D6 0117764В 01177624 011F04CD 011F041D 011F0317 011F048D
С44304С4 011F0387 011F045B 011F0355 011F03DF
C44304D4 011F02BD 011F03AD 011F023D 011700F1
С44304Е4 C44304F4 01170144 01170175 01170158 011700D4 01170119 011700В8 011F01EF 011F0074
С4430504 01270533 011F4062 0117470С 01176580
С4430514 С4430524 011760А7 0117607В 0117605А 0117604В 01176553 0117653F 01176568 01174956
Глава 13. Thunk! KERNEL32 вызывает KRNL386
С4430534 | 01176432 011F0018 011F00EB 01170190
C4430544 I 01170189 0117022C 01170218 01271331
C4430554 I 01271430 01271073 01271CE6 01271D95
; ... точно не знаю, где таблица заканчивается
Каждый из этих шестидесяти (или около того) элементов представляет thunk-переключатель из
KERNEL32 в KRNL386 (а вы знаете, что этого, предположительно, никогда не случается). Однако
далеко не каждый thunk в этой таблице обязательно должен использоваться в Windows 95, некото-
рые из них могут быть просто удалены или не использоваться в коммерческой версии Windows 95.
Чтобы узнать, что делают некоторые из этих KERNEL32 —> KRNL386 thunk-переключателей и есть
ли среди ' них еще те, которые являются полезными или важными (кроме LoadLibraryl6,
GetProcAddressl6 и CreateTask, мы их только что исследовали), иногда проще рассматривать код
KERNEL32, который использует thunk, чем копаться в коде KRNL386, чей адрес стоит в вышеука-
занной таблице. Чего-чего? Ладно, вот что я имею в виду:
KERNEL32!FindAtomA
0137:BFF825E9 PUSH EDI
;;; проверка параметров
0137:BFF91D4E MOV CL,06
0137:BFF91D50 JMP BFF91D60
KERNEL32!LoadLibrary16
0137:BFF91D52 MOV CL, 36
0137: BFF91D54 JMP BFF91D60
KERNEL32!DeleteAtom
0137:BFF91D7F MOV CL, 05
0137:BFF91D81 JMP BFF91D9D
KERNEL32!GlobalFree16
0137:BFF91D8B MOV CL,23
0137:BFF91D8D JMP BFF91D9D
KERNEL32! GlobalDeleteAtom
0137:BFF91D97 MOV CL,11
0137:BFF91D99 JMP BFF91D9D
KERNEL32!InitAtomTable
0137:BFF91DE6 MOV CL,03
0137:BFF91DE8 JMP BFF91DEC
Все это имеет смысл. Например, LoadLibraryl6 — thunk #36; элемент #36 в таблице находится
по адресу 30464+36*4=3053Ch, который указывает на 011F:00EB. Это адрес LoadLibrary API в ядре
Winl6. Аналогично, FindAtomA — thunk #6; элемент #6 в таблице указывает на 0127:2527. Это
адрес процедуры в KERNEL, которая вызывает FindAtom.
Короче говоря, во всех этих случаях KERNEL32 вызывает KRNL386. Таблица, которую мы
тестируем, обеспечивает KERNEL32 16-битовыми дальними указателями на различные процедуры в
KRNL386, и KERNEL32 использует QT_Thunk для вызова этих процедур. Трудно поверить, что
кто-нибудь станет отрицать, что KERNEL32 обращается к KRNL386. Только что мы рассмотрели
именно тот механизм, который позволяет это делать.
Подобный же механизм позволяет Winl 6-коду в KRNL386 обращаться к Win32-KOfly в
KERNEL32. Например, как я отмечал раньше, KRNL386 содержит такие функции, как
SetCurrentDirectory и GetCurrentDirectory, которые переходят к эквивалентным функциям в
KERNEL32.
Нет абсолютно ничего плохого, если KERNEL32 необходимо вызывать Winl6 KERNEL. Micro-
soft, конечно, известно, что KERNEL32 обращается к Winl6 KERNEL, и то, что это логически сле-
376
Неофициальная Windows 95
дует (кроме всего прочего) из комбинации зависимостей USER32 от Winl6 USER и Winl6 USER от
Winl6 KERNEL. Так к чему такие настойчивые заявления о том, что KERNEL32 не нуждается в
Winl6 KERNEL?
Одна из причин, я думаю, психологическая. Microsoft славится как новаторская маркетинговая
организация, которая умеет фантастически успешно использовать любые возможности. Но мало кто
в индустрии программного обеспечения считает компанию новаторской в смысле технологий.
Microsoft страдает комплексом неполноценности по поводу своих наиболее удачных операционных
системы MS DOS и Windows. Годами компания пытается показать, что она также может построить
то, что в определенных кругах называется операционной системой “для настоящих мужчин” и что
прочие могут просто оценить как “синдром второй системы”. Превратно истолкованное, это может
звучать, будто Билл Гейтс продолжает вести себя подобно Стиву Джобсу.
Microsoft производит удивительно удачные операционные системы, которые становятся де
факто стандартами на рынке персональных компьютеров, но этого кажется мало. Microsoft хочет не
только того, чтобы все думали, что ее продукты господствуют среди персональных компьютеров, но
что эти продукты представляют некий критерий чистоты операционных систем.
Отсюда и проистекают, по крайней мере некоторые, преувеличенные заявления о независимости
Windows 95 от кода старой доброй DOS и Windows. Равно как и не принимающее протестов
заявление фирмы Microsoft, что “Chicago не является Win32s!” (имеющее в виду презираемые, но
отлично работающие дополнения, которые позволяют Windows 3.1х выполнять некоторые Win32-
программы, см. главу 14). Пока Microsoft пытается отстаивать версию, будто она создала новую
технологию, я чувствую, что с все возрастающей надежностью могу утверждать, что это не так.
Но, кроме психологических мотивов, здесь есть и практическая проблема. С одной стороны,
много дезинформации об архитектуре Windows 95 проистекает из естественного желания фирмы
Microsoft (что детально обсуждалось в главе 1) представить Windows 95 как интегрированную, и
следовательно ни в чем не уступающую, альтернативу высокоинтегрированному Apple Macintosh.
Другая причина, вероятно, менее важная, объясняется странными взаимоотношениями Microsoft
с компьютерной прессой. Та же самая компьютерная пресса, которая даже не пыталась независимо
проверить все заявления Microsoft, любит насмехаться над относительно глупыми поступками Mic-
rosoft. В случае Windows 95 все сторонники чистоты операционной системы выражают недо-
вольство, что Windows 95 недостаточно похожа на Windows NT.
Следующие цитаты не являются безупречными примерами, так как их автор — великолепный
журналист, чьи статьи частенько попадают в цель, но они характерны нападками на Microsoft по
поводу того, что Chicago просто не является NT:
Поддержка Winl6 в Chicago является потенциальной ахиллесовой пятой...
Если вы используете Chicago только для того, чтобы запустить прикладные программы Win32, она
будет более похожа на ‘NT lite'...
Chicago будет вести себя подобно Windows 3.x с незначительными усовершенствованиями, когда запус-
кает DOS и Winl6-приложения...
Может это и хорошо, но Chicago не является чистокровной 32-битовой операционной системой, какой
является NT. Вместо этого она представляет собой 16- и 32-битовый гибрид. На декабрьской конфе-
ренции, на которой Microsoft распространяла свой второй вспомогательный комплект разработчика,
Chicago произвела большее впечатление на тех программистов, которые еще не работали с NT, чем на
уже знакомых с NT.
— Jon Udell, "Chicago: An Ambitious Compromise”, Byte, March 1994, p. 23.
Заметим, что, критикуя достаточно разумные планы фирмы Microsoft не моделировать Win-
dows 95 по принципу тусклой операционной системы NT, статья Удела полностью поддерживает
пропагандируемые Microsoft отличия между Windows 95, управляющей исключительно Win32-npn-
ложениями или параллельно с DOS и Win 16-приложениями. Внушается, что существует качествен-
ное различие между этими двумя средами.
В другой статье (“The Fix Is In for Chicago”, Byte, September 1994, p. 193) Удел заявляет, что
Chicago, управляющая Win32-пpилoжeниями, может оказаться намного более устойчивой, чем
Chicago или Windows 3.x, управляющие в основном DOS и Win 16-приложениями. Хотя похоже,
что он ссылается здесь в основном на способность Windows 95 предоставлять каждой Win32-
Глава 13. Thunk! KERNEL32 вызывает KRNL386
377
программе свое собственное адресное пространство, я думаю, что не мешало бы понять, что система
Windows 95, по крайней мере в нынешней конструкции, никогда не сможет выполнять только
Win32-npHno»ceHHH, — хотя бы потому, что MSGSRV32, Winl6-3afla4a, ввегда запущена. Если вы
возьмете Windows 95, выполняющую только Win32-npMo»ceHHH, а затем запустите Win ^прило-
жение, то качественно новых изменений не произойдет.
В качестве примера ниже я представил список всех загруженных модулей Windows 95, в
которой запущен только Explorer. Я выбросил файлы шрифтов (они являются модулями) из
следующего списка, который был получен с помощью команды MOD в WinICE:
:mod
hMod PEHeader > Module Name .EXE File Name
010F KERNEL C:\WIND0WS\SYSTEM\KRNL386.EXE
024F WSSYS c:\ws_chi\wssys.drv
0297 SYSTEM C:\WINDOWS\SYSTEM\system.d rv
0167 KEYBOARD C:\WINDOWS\SYSTEM\keyboard.d rv
0257 MODS C:\WINDOWS\SYSTEM\mous.d rv
029F DISPLAY C:\WINDOWS\SYSTEM\vga.drv
02FF SOUND C:\WINDOWS\SYSTEM\sound.d ry
038F COMM C:\WINDOWS\SYSTEM\comm.d rv
03EF GDI C:\WINDDWS\SYSTEM\gdi.exe
075F USER C:\WINDOWS\SYSTEM\user.exe
17E7 DDEML C:\WINDOWS\SYSTEM\DDEML.DLL
13FF MSGSRV32 C:\WIND0WS\SYSTEM\MSGSRV32.EXE
13A7 MMSYSTEM C:\WINDOWS\SYSTEM\mmsystem.dll
132F TIMER C:\WINDOWS\SYSTEM\mmtask.tsk
13AF POWER C:\WINDOWS\SYSTEM\powe r.d rv
1317 SHELL C:\WINDOWS\SYSTEM\SHELL.DLL
123F COMMCTRL C:\WINDOWS\SYSTEM\commctrl.dll
206F SHELL16 C:\WIND0WS\SYSTEM\shell16.dll
1E77 WIN87EM C:\WINDDWS\SYSTEM\WIN87EM.DLL
1DE7 COMMDLG C:\WINDOWS\SYSTEM\COMMDLG.DLL
018E 013F: BFF70080, KERNEL32 C:\WIND0WS\SYSTEM\KERNEL32.DLL
0606 013F:810F8564 GDI32 C;\WIND0WS\SYSTEM\GDI32.DLL
06DE 013F:810F8820 ADVAPI32 C:\WIND0WS\SYSTEM\ADVAPI32.DLL
071E 013F:810F8A8C ICM32 C:\WIND0WS\SYSTEM\ICM32.DLL
1686 013F:810F8D3C USER32 C:\WIND0WS\SYSTEM\USER32.DLL
131E 013F:810F9A80 WINMM C:\WINDOWS\SYSTEM\WINMM.DLL
12AE 013F:810FA55C MPR C:\WINDOWS\SYSTEM\MPR.DLL
12F6 013F:810FA878 MSPWL32 C:\WIND0WS\SYSTEM\MSPWL32.DLL
1296 013F:810FAB18 NETLIB32 C:\WIND0WS\SYSTEM\NETLIB32.DLL
125E 013F:810FAF08 CAB32 C:\WIND0WS\CAB32.EXE
1256 013F:810FB8F8 C0MCTL32 C:\WIND0WS\C0MCTL32.DLL
124E 013F:810FBB34 SHELL32 C:\WIND0WS\SHELL32.DLL
1F5E 013F:810FC71C LINKINFO C:\WINDOWS\SYSTEM\LINKINFO.DLL
1F06 013F:810FD264 BATMETER C:\WINDOWS\BATMETER.EXE
1F0E 013F:810FD60C CRTDLL C:\WINDOWS\SYSTEM\CRTDLL.DLL
Таково “устойчивое состояние” Windows 95, по крайней мере для текущей реализации. Заме-
чено большое количество модулей, для которых поле PEHeader пусто; это Winl6 DLL.
Затем я запустил три Win32-пpилoжeния: Glock, WinBezMT и Win32-Bepcnio FreeCell. Появи-
лись следующие дополнительные модули Win32:
27CE 013F:81103908 CLOCK C:\WINDOWS\CLOCK.EXE
27D6 013F:81104164 C0MDLG32 C:\WIND0WS\SYSTEM\C0MDLG32.DLL
26C6 013F:811043E0 WINBEZMT C:\WINDOWS\WINBEZMT.EXE
232E 013F:81100294 MSNET32 C:\WIND0WS\SYSTEM\MSNET32.DLL
1C3E 013F:811052D8 FREECELL C:\WIN32APP\FREECELL\FREECELL.EXE
ОВСЕ 013F:81105D04 CARDS C:\WIN32APP\FREECELL\CARDS.DLL
27DE 013F:81105F40 SHELL32 C:\WIND0WS\SYSTEM\SHELL32.DLL
378
Неофициальная Windows 95
Затем я запустил WSPY21 — приложение Winl 6. Но дополнительных модулей, кроме самого
WSPY21, не появилось:
276F WSPY21 С:\UNAUTHW\BINW\WSPY21.EXE
Таким образом, для работы этой программы новые Win 16-модули не нужны. Наконец, я за-
пустил отладчик WinScope. При этом загрузились три модуля WinScope и появились три Win 16
DLL — WIN87EM, TOOLHELP и COMMDLG, которые не показывались, пока мы запускали толь-
ко Win32-пpилoжeния.
1EBF WINSCOPE С:\ws_ch i\winscope.exe
1Е77 . WSCD C:\ws_chi\WSCD.DLL
1Е57 WSMISC C:\ws_chi\WSMISC.DLL
1E2F WIN87EM C:\WIND0WS\SYSTEM\WIN87EM.DLL
1E4F TOOLHELP C:\WINDOWS\SYSTEM\TOOLHELP. DLL
0A8F COMMDLG C:\WINDOWS\SYSTEM\COMMDLG.DLL
Особенно интересен мне был результат загрузки WinScope, потому что другой тест для исполь-
зования чистого 32-битового кода я сделал с помощью этого отладчика. При работе стандартного
набора Win32-пpилoжeний, о которых упоминалось уже бесчисленное множество раз в этой главе:
Clok, Explorer, FreeCell и WinBezMT, — без Winie-приложений, кроме самого WinScope, я полу-
чил перехваченные WinScope вызовы от всех DLL ко всем API в Win 16 KERNEL DLL. После двух
или трех минут этого эксперимента я отсортировал результат WinScope и с помощью программы
AWK подсчитал количество вызовов каждой функции Win 16 KERNEL:
143367 total calls to KERNEL'
02422 GlobalUnlock 02278 GlobalLock
01263 Istrlen 01167 LocalAlloc
01091 LocalFree 00823 GetCurrentTask
00747 PrestoChangoSelector 00424 GetExpWinVer
00371 LocalUnlock 00371 LocalLock
00294 IsBadReadPtr 00250 WaitEvent
00250 PostEvent 00250 GetExePtr
00247 hmemcpy 00241 FindAtom
00198 GlobalAlloc 00188 GlobalFree
00155 OldYield 00144 LockResource
00141 LoadResource 00141 FindResource
00132 Istrcpyn 00126 . GetAtomName
00108 IsBadHugeReadPtr 00081 FreeResource
00078 LocalSize 00056 Istrcpy
00052 GlobalReAlloc 00050 GetAppCompatFlags
00046 AddAtom 00040 IsBadStringPtr
00037 IsBadCodePtr 00032 NoHookDOSCall
Значение этого списка состоит в том, что не было запущено ни одного Winl6-npmK^einiH, кро-
ме WinScope, но за две или три минуты было свыше 14000 вызовов Winl6 KERNEL API. Предпо-
лагая, что WinScope не сильно искажает эти результат, мы получаем, что в целом мнение о чистоте
Windows 95, запускающей только Win32-пpилoжeния, является практически бессмысленным.
Легко проверить, кем делается это огромное количество вызовов — такими Win 16-приложе-
ниями, как WinScope, или нет. Я использовал WinICE (который работает за пределами Windows),
чтобы поставить контрольные точки на некоторых функциях Win 16 KERNEL, которые WinScope
обнаружила в получистой конфигурации Win32. Затем я выгрузил WinScope, и стало быть ни одна
Winl6-nporpaMMa не была загружена. Я не мог избавиться от TIMER и MSGSRV32, Winl6-зaдaч,
но они встроены в Windows 95. Таким образом получилась настолько чистая Win32-кoнфигypaция, на-
сколько это возможно для текущей реализации Windows 95. Вот как выглядит эта конфигурация:
Глава 13. Thunk! KERNEL32 вызывает KRNL386
379
: task
TaskName SS:SP StackTop StackBot StackLow TaskDB hQueue Events
WINBEZMT * 0000:0000 00737000 00740000 26E6 26C7 0000
CLOCK 0000:0000 00737000 00740000 279E 2777 0000
BATMETER 0000:0000 00737000 00740000 1F1E 1EF7 0000
CAB32 0000:0000 00756000 00760000 1276 207F 0000
TIMER 12E7:1F88 00B2 201C 201C 130F 12CF 0000
MSGSRV32 13CF:327E 00E2 3314 30C0 13EF 1677 0000
KERNEL32 012F:1218 000348B0 000448AF 0097 1677 0000
:thread
Ring 10 Context Ring3TCB Process TaskDB POB SZ Owner
C0FE55F0 0014 C0FD48E8 810FFC6C 810FF188 26E6 26DF 32 WINBEZMT
C0FE53A0 0013 C0F048E8 810FFA2C 810FF188 26E6 260F 32 WINBEZMT
C0FE5150 0012 C0F048E8 810FF7EC 810FF188 26E6 260F 32 WINBEZMT
C0FD5194 0011 C0F048E8 810FF52C 810FF188 26E6 260F 32 * WINBEZMT
C0FD3E30 0010 C0FE02F4 810FE7D8 810FE438 279E 27A7 32 * CLOCK
C0FD0140 000D C0F0CB9C 810F0960 810FB30C 1276 126F 32 CAB32
COF00074 oooc C0FE011C 810FD024 810FCC74 1F1F 1F2F 32 * BATMETER
C0FDF9F4 0008 C0F0CB9C 810FC224 810FB30C 1276 126F 32 CAB32
C0F0E21C 0007 C0F0CB9C 810FB6B8 810EB30C 1276 126E 32 * CAB32
C0F0C8B4 0006 C0FE8B1C 810FA26C 810F9F18 130F 1307 16 TIMER
C0F0BE74 0005 C0FE8B1C 810F9820 810F726C 0097 OOAF 32 KERNEL32
C0FDBAC8 0004 C0FE8B1C 810F95E0 810F928C 13EF 13E7 16 * MSGSRV32
C0FD916C 0003 C0FE8B1C 810F8FDC 810F726C 0097 OOAF 32 KERNEL32
C0F00E14 0002 C0FE8B1C 810F82C4 810F726C 0097 OOAF 32 KERNEL32
C4520298 0001 C0FE8B1C 810F7344 810F726C 0097 OOAF 32 VM 01
C0FE57F4 0018 C0F0CB9C 811002B0 810FB30C 1276 126F 32 CAB32
C0FD53D8 0016 C0F0CB9C 811000E4 810FB30C 1276 126F 32 CAB32
В этой конфигурации нет других 16-битовых задач или ветвей, кроме TIMER и MSGRV32, и я
поместил контрольные точки в некоторые вызовы Win 16 KERNEL, которые обнаружила WinScope.
Оказывается, WinScope несколько исказила результаты теста. Например, многие (хотя и не
все) вызовы GetCurrentTask просто исчезают, когда не работает WinScope. Эти вызовы приходят из
ToolHelp, который присутствовал только потому, что его требовала WinScope.
Однако большинство вызовов Winl6 KERNEL, зарегистрированных WinScope, осталось, хотя
Win 16-программ уже не было. Несколько примеров:
• Все эти вызовы PrestoChangoSelector (см. Undocumented Windows, р. 39-40, 343-346) име-
ют место независимо от того, запущены Win 16-приложения или нет. PrestoChangoSelector
может преобразовать селектор кода в селектор данных и наоборот. Она используется при
реализации самоизменяющегося кода или выполняемых данных. Модуль BITBLT драйвера
дисплея вызывает PrestoChangoSelector, чтобы компилировать битовые пересылки “на лету”
(см. образец такого кода в Windows DDK, например, \DISPLAY\4PLANE\
\BITBLT\BITBLTASM).
• GetExePtr вызывается из FindResource и LoadResource, которые вызываются из Loadstring,
которая, в свою очередь, вызывается через QT_Thunk из USER32!LoadStringA. Loadstring
постоянно вызывается в Windows 95, возможно, чтобы создавать эти смышленые строки
“Hint:” внизу экрана. Во всяком случае постоянно вызывается Winl6^yHKiniH GetExePtr,
которая почти по любому типу глобального дескриптора находит модуль, к которому отно-
сится этот дескриптор (см. Windows Internals, р. 474—476: “GetExePtrO является одной из
моих любимых недокументированных функций”).
• Все вызовы NoBookDosCalls приходят из функции InquireSystem из SYSTEM.DRV
(см. Undocumented Windows, р. 339-340, 608-609). Она в свою очередь вызывается
GetDriveType API в KERNEL.
380
Неофициальная Windows 95
Мы можем продолжать так до бесконечности, проверяя каждый вызов Win 16 KERNEL,
который появляется в среде Windows 95 “исключительно с Win32-nporpaMMaMH”, но идея понятна:
даже в чистом виде существует много обращений к ядру Winl6 KERNEL. Это совершенно логично,
учитывая тот факт, что Win 16-версии GDI и USER вызывают KERNEL.
Все больше и больше кажется, что заявление Microsoft о том, будто 32-битовое ядро KERNEL
никогда не обращается к 16-битовому ядру, является не только обманом, но и бессмыслицей.
Задумайтесь на секундочку: даже если вы приняли заявление Microsoft (которое, как вы убедились,
оказалось неправдой), что KERNEL32 никогда непосредственно не вызывает KRNL386, Microsoft
сказала, что USER32 вызывает USER и GDI32 вызывает GDI. Исследования с помощью Microsoft
EXEHDR или EXE Quick View из Windows 95 Explorer показывают, что и USER, и GDI вызывают
KERNEL. Например, они во многом полагаются на GlobalUnlock, LocalAlloc и LocalFree (сервисы
управления памятью). Таким образом, даже когда работают только Win32-пpилoжeния, Winl6
KERNEL постоянно используется, поскольку USER32 и GDI32 вызывают USER и GDI, которые в
свою очередь используют KERNEL.
Итак, даже если KERNEL32 непосредственно не обращается к Win 16 KERNEL, Win 16 KERNEL
остается решающей частью Windows 95, даже если вы запускаете только Win32-приложения.
Утверждая, что USER32 -> USER и GDI32 -> GDI, но по крайней мере KERNEL32 ! ->
KRNL386, Microsoft, очевидно, пытается выиграть в глазах знатоков операционных систем
(“armchair OS designers”, как называет их Удел), которые верят, что есть что-то предпочтительное в
чистой 32-битовой операционной системе.
Конечно, разработчики из Microsoft знают, что не может быть ничего хорошего в совершенно
новой операционной системе, которая не полагается на проверенный и надежный (хотя и не
лишенный ошибок) базовый код DOS или Winl6. И, конечно, Microsoft знает, что многие заяв-
ления, касающиеся Windows 95 — что она устраняет реальный режим DOS, что ядро Win32 не
полагается на ядро Winl6, что управление исключительно Win32-nporpaMMaMH существенно
отличается от управления смесью программ DOS, Winl6 и Win32 — являются просто ложными.
Но, возможно, Microsoft может все это подчистить: “То, что не доделано в Windows 95, мы вос-
полним в Windows 96”. Действительно, каждая новая версия Windows все меньше зависит от реаль-
ного режима DOS. По этому пути Microsoft может пойти и в случае с зависимостью от Winl6-кода.
Но, если однажды последняя капля 16-битового кода или кода реального режима исчезнет из
Windows (мы, наконец, сможем создавать PSP в защищенном режиме и хранить их в дополни-
тельной памяти), явится ли это действительно важным преобразованием? Нет. Настоящее преобра-
зование произошло в мае 1990, когда появился расширенный режим Windows 3.0.
После решающих преобразований 1990 года Windows по-прежнему базировалась на расшири-
теле DOS защищенного режима. Вспомним, что подобное изменение произошло в 1987-1988 годах с
Windows/386 2.x (которая сформировала базис для большей части VMM). Гораздо важнее, чем
новая технология для Windows 3.x, было то, что продажа Windows значительно возросла в 1990 го-
ду. С другой стороны, этого никогда бы не произошло, если бы Windows не включала расширитель
DOS защищенного режима, который обеспечил объем памяти, достаточный для того, чтобы Win-
dows стала действительно полезной.
С массовой продажей все стало возможным. Здесь просматривается интересная параллель с
историческими спорами вокруг индустриальной революции: была ли это “волна машин” или более
важным был взрыв в продажах, потреблении, росте населения и размерах рынка?
Когда Удел говорит: “Chicago произвела большее впечатление на тех программистов, которые
еще не работали с NT, чем на уже знакомых с NT”, я хотел бы знать, что думают программисты,
уже знакомые с NT. Что производит впечатление в Windows, так это не какие-то ее конкретные
технические возможности, а ее инсталяционная база. Если вы стремитесь к техническому совер-
шенству,то, я уверен, легко сможете переплюнуть Windows. Если же вы хотите разрабатывать при-
ложения, которые будут использовать миллионы людей, то Windows послужит лучшим примером.
Другими словами, посредственная технология, но с огромной инсталяционной базой, лучше
совершенной технологии, но без достаточной инсталяционной базы.
Windows 95 будет опережать NT, по крайней мере, по объему продаж. Microsoft знает, что это —
главная черта Windows 95. Все самые крутые возможности не имеют значения без широкого при-
Глава 13. Thunk! KERNEL32 вызывает KRNL386
381
сутствия на рынке настольных систем, необходимого признания операционной системы удачной.
Как говорится в рекламе StarKist: “Нам не нужен тунец с хорошим вкусом, нам нужно, чтобы тунец
был хорош на вкус”.
Зависимость Windows от DOS и Win 16-кода не является “ахиллесовой пятой”. Наоборот, изба-
, вившись от реального режима и кода Winl6, можно одержать пиррову победу. Windows могла бы до-
стичь состояния 32-битового блаженства и потерять большое число покупателей. В фильме “Поле грез”
все это неправильно: вы можете что-то построить, но оно не обязательно кому-нибудь понадобится.
KOAWSPY21
Эта моя длинная тирада, как вы можете вспомнить, была вызвана несколькими строками вывода
WSPY21. Вначале я предполагал использовать WSPY21 для иллюстрации связи DOS-Windows, но
это обернулось возможностью пролить свет на более важный (а сейчас, конечно, более интересный)
вопрос о связи Win32-Winl6. Как мы увидим, WSPY21 также проясняет некоторые особенности
обработчика прерывания INT21h защищенного режима, который находится внутри Win 16 KERNEL.
Итак, давайте посмотрим на код, который создал эти несколько строк вывода, на которые мы
потратили так много времени. На рис. 13.3 показано дерево вызовов для WSPY21.
main
INIT_REQUBUF
GetCurrentTask
get_vect
set_vect (установить IntHandler)
_dpmi_set_pmode_vect (INT 31h AX=0205h)
_dpmi_get_pmode_vect (INT 31h AX=0204h)
GetSetKernelOosProc (установить IntHandler2)
display_ints
winio_openwindows
wmhandler_yield (GetMessage, т.д.)
read data
IS_REQUEST
BEGIN_CRIT_SEC
fmemcpy, fstrncpy
END_CRIT_SEC
SET_NEXT
display_dos_int
printf
IntHandler
GetCurrentTask
write_data
BUFFER FULL
BEGIN_CRIT_SEC
_fmemcpy, _fctrncpy
GetTaskName
verr
sei size (GetSelectorLimit)
MAYBE_COPY
verr
sei size
SE<NEXT
ENO_CRIT_SEC
PostMessage
_chain_intr
IntHandler2
GetCurrentTask
SAME_CALL
write_data (см. выше)
_chain_intr
Рис. 13.3. Дерево вызовов для WSPY21
382 Неофициальная Windows 95
Как видно из рис 13.3, программа состоит из трех полунезависимых частей:
• Функция main устанавливает обработчики прерываний и вызывает display_ints, которая вы-
зывает read_data для извлечения данных из циклического буфера. (WSPY21 является прило-
жением WINIO и поэтому начинается с main, а не с WinMain.) Если отслеживается не INT
21h (вы можете определить другое прерывание в командной строке WSPY21; например, INT
31h для DMPI также представляет интерес) или обнаруживается неизвестный вызов INT
21h, WSPY21 просто выводит содержимое всех регистров.
• IntHandler является перехватчиком INT 21h. При каждом обнаруженном вызове INT 21h
IntHandler вызывает write_data для помещения блока информации в тот же самый буфер, из
которого display_ints читает. Эта информация содержит имя текущей задачи во время
прерывания INT21h; обработчик прерывания в WSPY21 работает как часть этой задачи.
(Как я объяснил выше, по этой причине GetCurrentTask не получает дескриптор задачи
WSPY21.) IntHandler посылает сообщение WM_NULL окну WSPY21, так что из функции
wmhandler_yield (которая вызывает GetMessage) произойдет возврат и display_ints сможет
отобразить следующий вызов INT21h. IntHandler передает вызов прерывания предыдущему
обработчику, который обычно является обработчиком прерывания INT2 lh KERNEL.
• IntHandler2 почти такой же, как IntHandler, исключая то, что он устанавливается с помощью
GetSetKemelDosProc, а потому отлавливает INT 2lh на более низком уровне, чем IntHandler.
IntHandler2 использует макрос SAME_CALL, чтобы определить, что IntHandler уже видел
вызов и поэтому нет необходимости отображать его снова (если только вы не запустили
WSPY21 -SHOWALL, см. ниже).
Листинг 13.5 показывает код программы WSPY21. Эта программа основана на WISPY (I Spy
for Windows), программе из Undocumented Windows (p. 180-188). Самое важное различие заклю-
чается в том, что WISPY использовала только GetSetKemelDosProc, чтобы установить единст-
венный обработчик прерывания INT21h, a WSPY21 устанавливает два обработчика INT21h. WISPY,
следовательно, упускает все такие вызовы, как функция 4Bh(Exec), которая обрабатывается в
KERNEL, но WSPY21 видит эти вызовы. Важен тот факт, что KERNEL может поглощать или изме-
нить некоторые вызовы INT 21h в защищенном режиме, и мы это вскоре увидим.
ЛИСТИНГ 13.5. WSPY21.C
/‘
WSPY21.C -- Windows-приложение контролирует вызовы INT 21h
Шульман, 1994
Ьсс -I..\include -L..\lib -W -2 -Р- wspy2l.c unauthw.lib
*/
ttinclude <stdlib.h>
ttinclude <dos.h>
ttinclude <string.h>
ttinclude “windows.h"
ttinclude “wmhandlr.h”
ttinclude “winio.h"
ttpragma pack(l)
typedef struct {
ttifdef _BORLANDC__
WORD bp,di,si,ds,es,dx,cx,bx,ax;
ttelse
WORD es,ds,di,si,bp,sp,bx,dx,ex,ax; /* порядок такой же, как в PUSHA */
ttendif
WORD ip,cs,flags;
Глава 13. Thunk! KERNEL32 вызывает KRNL386
383
} REG.PARAMS;
«define BUF.SIZE 512
«define MAX.STR 40
«define MAX.REQU (BUF_SIZE-1)
«define SET_NEXT() { if (next==MAX_REQU) next=0; else next++; }
typedef struct {
int type;
REG.PARAMS r;
char taskname[9];
char dsdx[MAX.STR], dssi[MAX_STR];
int flag;
} REQUEST, FAR * LPREQUEST;
«define REQU.FREE
f /* блок запроса не используется */
«define REQU.USE 'u' /* используется */
«define BEGIN_CRIT_SEC() _asm cli
«define ENO_CRIT_SEC() _asm sti
«define IS_REQUEST(buf) (buf[next],type != REQU.FREE)
«define BUFFER_FULL(buf) IS_REQUEST(buf)
«ifndef MK.FP
«define MK_FP(a,b) ((void far *)(((DWORD)(a) « 16) | (b)))
«endif
«define COPY_DSDX 1
«define COPY.DSSI 2
«define CHECK_AL 3
«define FIRST.FUNC 0x3b
«define LAST.FUNC 0xa1
// Команды для WRT.REQUEST, как обрабатывать
// каждую из функций прерывания INT 21h
static char copyflags[LAST_FUNC - FIRST.FUNC] = {
/*ЗЬ*/ COPY.DSDX, COPY.DSDX, COPY.DSDX,
/*3e*/ 0, 0, 0, 0, 0,
/*43*/ COPY.DSDX,
/*44*/ 0, 0, 0, 0, 0, 0, 0,
/*4b*/ COPY.DSDX,
/*4c*/ 0, 0,
/*4e*/ COPY.DSDX,
/*4f*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*5a*/ COPY.DSDX, COPY.DSDX,
/*5c*/ 0, 0, 0, 0,
/*60*/ COPY.DSSI,
/*61*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*6c*/ COPY.DSSI,
/*6d*/ 0, 0, 0, 0,
/*71*/ CHECK.AL,
/*72*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*80*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*90*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*aO*/ COPY.DSDX,
};
«define sel.size(sel) (GetSelectorLimit(sel)+1)
384
Неофициальная Windows 95
WORO verr(WORD short sei)
{
_asm {
mov ax, 1
verr word ptr sei
je short okay
dec ax
}
okay:;
static REQUEST far *requ;
char far *GetTaskName(HANDLE hTask)
{
static char none[2] = “ ” ;
if(verr(hTask) && (sel_size(hTask) > (0xf2+8)))
return (char far *) MK_FP(hTask, 0xf2);
else
return none;
int WRT_REQUEST(REQUEST far *buf, REG_PARAMS far *pr, HANDLE task, int flag)
{
static int next = 0;
unsigned char ah;
void far *fp;
if(BUFFER,FULL(buf))
return 0;
BEGIN_CRIT_SEC();
_fstrncpy(&buf[next],taskname, GetTaskName(task), 8);
buf[next],taskname[8] = '\0';
_fmemcpy(&buf[next].r, pr, sizeof(REG_PARAMS));
buf[next],type = REQU.USE;
#if 1
«define MAYBE_COPY(str, r1, r2) { \
_fmemset(str, 0, MAX.STR); \
if(verr(r1) && (sel_size(r1) > r2)) \
_fmemcpy(str, MK_FP(r1, r2), min(MAX STR-1, sel_size(r1)-r2)); \
)
«else
// IsBadStringPtr приводит к возникновению нарушения общей защиты
// (General Protection Fault), которое отлавливается KRNL386.
// Но то же самое в Win32s приводит к исключительной ситуации
// отказа страницы (Page Fault)!
«define MAYBE_COPY(str, И, r2) { \
_fmemset(str, 0, MAX_STR); \
fp = MK_FP(r1, r2); \
if(!IsBadStringPtr(fp, 256)) \
_fstrncpy(str, fp, min(_fstrlen(fp), MAX_STR-1)); \
}
«endif
ah = pr->ax » 8;
if(ah >= FIRST_FUNC && ah <= LAST.FUNC)
{
ah -= FIRST.FUNC;
13 Неофициальная Windows 95
Глава 13. Thunk! KERNEL32 вызывает KRNL386
385
top:
if(copyflags[ah] == C0PY_DSDX)
MAYBE_COPY(&buf[next],dsdx, pr->ds, pr->dx) //;
else if(copyflags[ah] == C0PY_DSSI)
MAYBE_COPY(&buf[next].dssi, pr->ds, pr->si) //;
else if(copyflags[ah] == CHECK_AL)
{
ah = (pr->ax & Oxff) - FIRST_FUNC;
if(copyflags[ah] != CHECK_AL)
goto top;
)
)
buf[next],flag = flag;
SET_NEXT();
END_CRIT_SEC();
PostMessage(__hMainWnd, WM_NULL, 0, 0); // чтобы не останавливаться!
return 1;
int RO_REQUEST(REQUEST far *buf, REG_PARAMS *pr, LPSTR ptaskname,
LPSTR Ipdsdx, LPSTR Ipdssi, int far *pflag)
{
static int next = 0;
if(!IS_REQUEST(buf))
return 0;
BEGIN_CRIT_SEC();
_fmemcpy(pr, &buf[next], r, sizeof(REG_PARAMS));
_fmemcpy(ptaskname, &buf[next],taskname, 9);
_fmemcpy(Ipdsdx, &buf[next],dsdx, MAX_STR);
_fmemcpy(lpdssi, &buf[next].dssi, MAX_STR);
«pflag = buf[next],flag;
buf[next].type = REQU_FREE;
ENO_CRIT_SEC();
SET_NEXT();
return 1;
REQUEST far *INIT_REQUBUF(void)
{
LPREQUEST buf, p;
int i;
if(!(buf =
(LPREQUEST) GlobalLock(GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
BUF_SIZE * sizeof(REQUEST))))
)
return 0;
for(i=BUF_SIZE, p=buf; i—; p++)
p->type = REQU.FREE;
return buf;
FARPROC (FAR PASCAL *GetSetKernelDosProc)(FARPROC DosProc) = 0;
typedef void (-interrupt _far *INTRFUNC)();
i
INTRFUNC _dpmi_get_pmode_vect(int intno)
{
INTRFUNC iv;
_asm { <
386
Неофициальная Windows 95
mov ax, 0204h
mov bl, byte ptr intno
int 31h
jc error i
mov word ptr iv+2, ex
mov word ptr iv, dx
}
return iv;
error:
return (INTRFUNC) 0;
}
void _dpmi_set pmode_vect(int intno, INTRFUNC iv)
{
_asm {
mov ax, 0205h
mov bl, byte ptr intno
mov ex, word ptr iv+2
mov dx, word ptr iv
int 31h
}
// asm jc error
}
#define get_vect(intno) _dpmi_get_pmode_vect(intno)
int set_vect(WORD intno, INTRFUNC handler)
{
// Функция 25 Int 21h не используется, поскольку обработчик Int 21h из KERNEL
'// не позволяет устанавливать INT Ibh, ich, 21h, 24h. Поэтому используем DPMI.
// Однако можно было бы использовать недокументированную NoHookDOSCall,
// которая обходит большую часть специальной обработки KERNEL.
_dpmi_set_pmode_vect(intno, handler);
return (_dpmi get pmode vect(intno) == handler);
} "
void .interrupt _far IntHandler(REG_PARAMS r);
void .interrupt .far IntHandler2(REG_PARAMS r);
static DWORD calls = 0, lost = 0, missed = 0, already = 0, mine = 0;
static HANDLE wspy21_task;
static int intno = 0x21; // INT 21h по умолчанию
static INTRFUNC old = 0;
static BOOL enabled = FALSE;
static FARPROC old.dos = (FARPROC) 0;
void on_close(HWND hwnd) .
{
int i;
winio_warn(FALSE, “W.SPY21",
“%081u\tnepexBa4eHo\n”
“%081и\1уже было отловлено в первом обработчике\п"
“%081и\1отобрано в первом обработчике\п"
“%081и\1утеряно при переполнении буфера\п"
“%081и\1утеряны, когда наблюдатель был отключен\п"
“%081и\1были мои собственные вызовы\п",
calls, already, (intno == 0x21) ? calls - already : 0,
lost, missed, mine);
Глава 13. Thunk! KERNEL32 вызывает KRNL386
387
if(!set_vect(intno, old))
winio_warn(FALSE, “WSPY21”,
“Невозможно восстановить Int %02Xh", intno);
if((intno == 0x21) && old_dos)
GetSetKernelOosProc(old_dos);
void display_int(REG PARAMS *pr)
{
printf(“AX=%04x BX=%04x CX=%04x DX=%04x DS=%04x SI=%04x 0I'=%04x"
“CS:IP=%04x:%04xh\n",
pr->ax, pr->bx, pr->cx, pr->dx,
pr->ds, pf->si, pr->di, pr->cs, pr->ip);
«define PRINT_STR(s)
«define PRINT_2_STR(s1, s2)
«define PRINT_STR_WORD(s, w)
«define PRINT_2_W0RD(s, w1, w2)
«define PRINT_STR_BYTE(s, b)
«define PRINT_STR_FP(s, fp)
printf(“%s\n”, (s))
printf(“%s \'%s\'\n”, (s1), (s2))
printf(“%s %u (%04Xh)\n“, (s), (w), (w))
printf(“%s %u (%04Xh), %u (%04Xh)\n”, \
(s), (w1), (w1), (w2), (w2))
printf(“%s %u (%02Xh)\n”, (s), (b), (b))
printf(“%s %Fp\n”, (s), (fp))
«ifdef SHOW_IOCTL
void print_ioctl(REG_PARAMS *pr)
{
int al = pr->ax & OxFF;
int bl = pr->bx & OxFF;
printf(“IOCTL (%02X)", al);
switch(al)
{
case 0x00: PRINT_STR_WORD(“Get Device Data", pr->bx); break;
case 0x01: PRINT_STR_WORO(“Set Device Data", pr->bx); break;
case 0x02: PRINT_STR_WORD(“Get Char Dev Data”, pr->bx); break;
case 0x03: PRINT_STR_WORD(“Set Char Dev Data", pr->bx); break;
case 0x04: PRINT_STR_BYTE(“Get Block Dev Data”, bl); break;
case 0x05: PRINT_STR_BYTE(“Set Block Dev Data”, bl); break;
case 0x06: PRINT_STR_WORD(“Check Input Status”, pr->bx); break;
case 0x07: PRINT_STR_WORD(“Check Output Status", pr->bx); break;
case 0x08: PRINT_STR_BYTE(“Is Drv Removeable?”, bl); break;
case 0x09: PRINT_STR_BYTE(“Is Drv Remote?", bl); break;
case OxOA: PRINT_STR_WORD(“Is File/Oev Remote?”, pr->bx); break;
case OxOC: PRINT_STR_BYTE(“Char Minor Code", pr->cx & OxFF); break;
case OxOD:
printf(“(%02X)”, pr->cx & OxFF);
switch(pr->cx & OxFF)
{
case 0x40: PRINT_STR_WORD(“Set Dev Params", pr->bx); break;
case 0x41: PRINT_STR_WORD(“Write Track”, pr->bx); break;
case 0x42: PRINT_STR_WORD(“Format Track", pr->bx); break;
case 0x46: PRINT_STR_WORD(“Set Media ID", pr->bx); break;
case 0x47: PRINT_STR_WORD(“Set Access Flag", pr->bx); break;
case 0x49: PRINT_STR_WORD(“Eject", pr->bx); break;
case 0x4A: PRINT_STR_WORD(“Lock Logical Vol”, pr->bx); break;
case 0x4B: PRINT_STR_WORD(“Lock Phys Vol", pr->bx); break;
case 0x60: PRINT_STR_WORD(“Get Dev Params”, pr->bx); break;
case 0x61: PRINT_STR_WORD(“Read Track”, pr->bx); break;
case 0x62: PRINT_'STR_WORD(“Verify Track”, pr->bx); break;
case 0x66: PRINT_STR_W0R3(“Get Media ID", pr->bx); break;
case 0x67: PRINT_STR_WORD(“Get Access Flag”, pr->bx); break;
388
Неофициальная Windows
case 0x68: PRINT_STR_W0R0(“Sense Media Flag”, pr->bx); break;
case 0x6A: PRINT_STR_W0R0("Unlock Logical Vol”, pr->bx); break;
case 0x6B: PRINT_STR_WORO(“Unlock Phys Vol", pr->bx); break;
case 0x6C: PRINT_STR_BYTE(“Write Poll", bl); break;
case 0x6D: PRINT_STR_WORO(“Enum Open Files”, pr->bx); break;
case 0x6E: PRINT_STR_WORO(“Find Swap File", pr->bx); break;
default: printf(“ \n” ); break;
}
break;
case OxOE: PRINT_STR_BYTE(“Get Log Drv Map”, bl); break;
case OxOF: PRINT_STR_BYTE(“Set Log Orv Map”, bl); break;
default: printf(“\n"); break;
}
}
#endif
void display_dos_int(REG_PARAMS *pr, char *dsdx, char *dssi)
{
int ah;
top:
ah = pr->ax » 8;
printf(“(%02X)”, ah);
switch(ah) // ICantBelievelUsedASwitchStatement
{
case 0x00: PRINT_STR(“Exit”); break;
case OxOE: PRINT_STR_BYTE(“Set Disk”, pr->dx & OxFF); break;
case 0x19: PRINT_STR(“Get Disk”); break;
case 0x1A: PRINT_STR_FP(“Set OTA”, MK_FP(pr->ds, pr->dx)); break;
case 0x25: PRINT_STR_BYTE(“Set Vect", pr->ax & OxFF); break;
case 0x2A: PRINT_STR(“Get Date”); break;
case 0x2C: PRINT_STR(“Get Time"); break;
case 0x2F: PRINT_STR(“Get OTA”); break;
case 0x30; PRINT_STR(“Get DOS Vers"); break;
case 0x32: PRINT_STR_BYTE(“Get OPB", pr->dx & OxFF); break;
case 0x35: PRINT_STR_BYTE(“Get Vect", pr->ax & OxFF); break;
case 0x36: PRINT_STR_BYTE(“Get Disk Space", pr->dx & OxFF); break;
case 0x3В: PRINT_2_STR(“Ch0ir", dsdx); break;
case ОхЗС: PRINT_2_STR(“Create”, dsdx); break;
case 0x30: PRINT_2_STR(“0pen”, dsdx); break;
case ОхЗЕ: PRINT_STR_WORD(“Close”, pr->bx); break;
case 0x3F: PRINT_2_W0R0(“Read", pr->bx, pr->cx); break;
case 0x40: PRINT_2_W0R0(“Write”, pr->bx, pr->cx); break;
case 0x41: PRINT_2_STR(“0elete", dsdx); break;
case 0x42: printf(“Lseek%d %u %04x%04x\n”,
pr->ax & OxFF, pr->bx, pr->cx, pr->dx);
break;
case 0x43: PRINT_2_STR(“Get/Set File Attr", dsdx); break;
case 0x44: print_ioctl(pr); break;
case 0x47: PRINT_STR_BYTE(“Get Curr Dir”, pr->dx & Oxff); break;
case 0x4b: PRINT_2_STR(“Exec", dsdx); break;
case 0x4c: PRINT_STR_BYTE(“Exit”, pr->ax & Oxff); break;
case 0x4e: PRINT_2_STR(“Find First", dsdx); break;
case 0x4f: PRINT_STR(“Find Next”); break;
case 0x50: PRINT_STR_WORO(“Set PSP", pr->bx); break;
case 0x51:
case 0x62: PRINT_STR(“Get PSP"); break;
case 0x55: PRINT_2_W0R0(“(Undoc) Create PSP”, pr->dx, pr->si); break;
case 0x57: PRINT_STR_WORO(“Get/Set File Date/Time”, pr->bx); break;
case 0x59: PRINT_STR(“Get Extended Error Info"); break;
case 0x5A: PRINT_2_STR(“Create Temp File”, dsdx); break;
Глава 13. Thunk! KERNEL32 вызывает KRNL386
389
case 0x58: PRINT_2_STR(“Create New File", dsdx); break;
case 0x60: PRINT_2_STR(“Truename'’, dssi); break;
case 0x65: PRINT_STR_BYTE(“International”, pr->ax & OxFF); break;
case 0x68: PRINT_STR_WORD("Commit", pr->bx); break;
case 0x6C: PRINT_2_STR(“ pen/Create”, dssi); break;
case 0x71: printf(“LFN"); pr->ax «= 8; goto top;
case OxAO: PRINT_2_STR(“Get Volume Info”, dsdx); break;
case 0xA1: PRINT^STRC'Find Close"); break;
default: display_int(pr);
)
void display ints(void)
{
REG.PARAMS r;
static char modname[16];
static char dsdx[MAX_STR];
static char dssi[MAX_STR];
int flag;
// Этот цикл ищет, извлекает и отображает сообщения, сохраненные
// в очереди обработчиком прерывания. Единственный способ завершить
// функцию - закрыть окно
while(winio openwindowsO)
{
wmhandler_yield();
while(RD_REQUEST(requ, &r, &modname, &dsdx, &dssi, &flag))
{
// Можно добавить опцию для отображения прерываний от
// выбранного модуля.
printf(“<%s> %с", modname, flag ? ' ' : '*');
if(intno == 0x21)
display_dos_int(&r, dsdx, dssi);
else // Можно сделать форматированный вывод других прерываний
display_int(&r);
)
)
main(int argc, char *argv[])
{
char buf[128J;
char num[8];
if(__hPrevInst)
fail(“WSPY21 должна быть запущена только однажды");
winio_setbufsize(___hMainWnd, (WORD) 32768, TRUE);
if(!(requ = INIT_REQUBUF()))
fail("INIT_REQUBUF отказал!");
winio.about(“WSPY21");
winio_onclose(___hMainWnd, (DESTROY.FUNC) on.close);
wspy21_task = GetCurrentTaskO;
if(argc < 2) intno = 0x21;
else sscanf(argv[1], “%02X”, &intno);
old = get_vect(intno);
if(!set_vect(intno, (INTRFUNC) IntHandler))
fail(“SetVect не сработал!”);
390
Неофициальная Windows 95
if((intno == 0x21) && (GetSetKernelDosProc = GetProcAddress(
GetModuleHandle(“KERNEL"), “GETSETKERNELDOSPROC"))
)
, old.dos = GetSetKernelDosProc((FARPROC) IntHandler2);
sprintf(buf, “Наблюдатель прерываний Windows: INT %02Xh", intno);
winio_settitle(___hMainWnd, buf);
enabled = TRUE;
display_ints();
return 0;
)
static REG.PARAMS prev.r = (0) ;
static HANDLE prev.task = 0;
static int was.prev = 0;
// CS:IP может отличаться
«define SAME_CALL(r1, r2) \
(((r1)->es == (r2)->es) && ((r1)->ds == (r2)->ds) && \
((r1)->di == (r2)->di) && ((r1)->si == (r2)->si) && \
((r1)->bx == (r2)->bx) && ((r1)->dx == (r2)->dx) && \
((r1)->cx == (r2)->cx) && ((r1)->ax == (r2)->ax))
void interrupt _far IntHandler(REG_PARAMS r)
{
HANDLE task = GetCurrentTask();
prev.r = r;
prev.task = task;
was.prev = 1;
if(task != wspy21_task) /» не показывать мои собственные вызовы */
if(enabled)
<
if(!WRT_REQUEST(requ, &r, task, 0))
lost++; /* переполнение буфера */
calls++;
)
else
missed++;
chain intr(old);
)
void interrupt _far IntHandler2(REG_PARAMS r)
<
HANDLE task = GetCurrentTask();
if(was_prev— && SAME CALL(&r, &prev r))
<
already++;
goto done; /* это мы уже видели в обработчике прерываний */
)
if(task == wspy21 task) /» мои собственные прерывания не показывать */
<
mine++;
goto done;
Глава 13. Thunk! KERNEL32 вызывает KRNL386
391
if(enabled)
{
if(!WRT_REQUEST(requ, &r, task, 1))
lost++; /* переполнение буфера */
calls++;
)
else
missed++;
done:
_chain_intr((INTRFUNC) old_dos);
)
Самое главное, что, в отличие от V86TEST, WSPY21 видит вызовы INT21h, сгенерированные в
защищенном режиме программами Windows в системной VM. WSPY21 не видит вызовов INT21h из
окон DOS и тех, что были сгенерированы на уровне VxD и затем посланы в режим V86 (см.
программу WLOG212F, обсуждаемую в главе 1).
Чтобы установить обработчик вызовов INT 21h, сгенерированных в защищенном режиме, про-
грамма, работающая под таким расширителем DOS, как Windows, могла бы нормально вызвать ту
же функцию DOS, которую вызвала бы программа реального режима для перехвата INT 21Ь
реального режима: Set Interrupt Vector (Функция 25h прерывания INT 21h). Целью расширителя
DOS является обеспечение привычного интерфейса INT 21h в защищенном режиме и выполнение
всех мер, чтобы DOS казалась операционной системой защищенного режима. (Хотя, возможно,
слово казалась уже не подходит, см. врезку “Расширитель DOS: все еще серьезный претендент?”)
; Расширитель DOS: все еще серьезный претендент?
I ||Н .[< .">14. ; •:!'<= Hb: НИ-Н : : ь... i'.i
ИМИ я.:»1 i К >S ill- ’.1 : :'Л 1<-
| таблицу в Excel или делаете что-либо подобное в любой нрогоаммс Windows в запдашенном
’ . t-. i-p-'i иля ч>: DI • ; !'•’ 1. ‘ ! ;; . ?_ .
-' 1 ’.1-Н< ;н lii’M.j . I > », f : - - . I < rfspa?-..! к II « о " . • TIT s.i: i - 21 •’ I , i -'
' < pc.lif i <• ii,in> f- и > чи< V, :. ....
- • :.s!ip«4 < i i!-ч И! ч я ! \ i 'Л
, -'sc.r i . |. i. I s'A 1-n. -I” : s’
: i.T? . ?,|is si I9’-H > ч!-s !> л< нм.;! s ..nil tip;;- 'i ь :
: nntsiU. n-a.-.i:-. Г.!.:п-s hl I; .11,1,-51, •: .: ... . ь .- s
1 a up, I-IJU ,p ;i|s-in.i4-t ! H •. ... a . ! !•
; ‘.Siniliir ill’. !H 'di.'Hl-A', si-. li;- :
I защищенном режиме, представьте, что она может сделать с прерываниями (даже с
j :чпчрын;н1цлчи Г)( »S. «чч-.н i\ i 2Sи .< ы 1\ Г :-• ..'-’a-i:-----
: т-л-ич;-. Hsinpi-ii- !. П1Ч\ iss.si-?: Wnuin-л ,
! .I’:1 .-(>! I I I h р;и{чч« H.iM-s ‘ ;• ьтт p j . - -< . ;•.:
392
Неофициальная Windows 95
_asm mov ah, 48h ; функция 48h DOS: выделить память
_asm mov bx, DFFFEh ; OFFFEh • 16 « 1048544 байт
_asm int 2lh ; "DOS-вызов" не обязательно вызывает DOS!
_asm jc alloc_error ; установленный CF указывает на ошибку
_asm mov segment, ax ; сохранить адрес распределенной памяти
alloc_error:
Считается, что Win 16-приложения распределяют память не вызовом функции 48Ь пре-
рывания INT 21Ь, а с помощью функций Windows API, таких как GlobalAlloc или LocalAlloc.
Поверьте мне: вышеприведенный код может быть помещен вовнутрь программы Win 16, и,
более того, он сработает, выделив OFFFEh параграфов (1048554 байт) памяти.
Что в этом странного? Если вы мысленно вернетесь во времена DOS реального режима,
то вспомните, что запрос достаточно большого количества параграфов, такого как OFFFEh или
OFFFFh, однозначно приводил к отказу. Вызов функции 48h прерывания INT21h с ВХ, содер-
жащим “недопустимо большое” число параграфов, был стандартным способом определения
размера самого большого свободного блока памяти. Даже при наличии мегабайтов памяти
DOS реального режима могла выделить значительно меньше мегабайта так называемой стан-
дартной памяти.
Но функция 48h INT 21h не ведет себя таким образом, когда вызывается из Winl 6-прило-
жения. Для демонстрации этого в листинге 13.6 приведена маленькая Windows-программа
DOSMEM.C, которая вызывает функцию DOS выделения памяти.
ЛИСТИНГ 13.6. DOSMEM.C
/• DOSMEM.C •/
«include <stdlib.h>
«Include <stdio.h>
«include “windows, h”
«define MK_FP(seg,ofs) ((void far *)(((DWORD)(seg) « 16) | (ofs)))
int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance.
LPSTR IpszCmdLine, int nCmdShow)
Г
char buf[80];
DWORD huge «fp, huge -fp2;
DWORD limit, size, 1;
WORD segment, avail:
/• вызвать функцию DOS выделения памяти •/
_asm mov ah, 48h
_asm mov bx, OFFFEh
_asm int 21h
_asm jc alloc_error
_asm mov segment, ax
fp = (DWORD huge *) MK_FP(segment, 0);
/• узнать у Windows, какого размера блок:
limit = смешение последнего доступного байта •/
limit = GetSelectorLimit(segment);
/• запись в каждый байт выделенной области •/
size = limit / sizeof(DWORD);
for(i=D, fp2=fp; 1 <= size; i++, fp2++)
*fp2 = 1;
Глава 13. Thunk! KERNEL32 вызывает KRNL386
393
sprintf(buf. “Allocated %lu bytes at %Fp“, limit + 1, fp);
MessageBox(0, buf, “GOSHEN" . HB_OK);
/• не освобожда ь блок до тех пор. пока пользователь не нажмет ОК •/
_asm mov ah. 49h
_asm mov es, segment
_asm Int 2lh
_asm jc free_error
return 0;
free.error.
NessageBox(0, “Ошибка: невозможно освободить блок!”, “DOSNEM", МВ_ОК);
return 1;
alloc_error.
_asm mov avail, bx
sprintf(buf, “Доступно только %04Xh параграфов”, avail):
MessageBox(0, buf, “DOSNEN", NB_0K);
return 1;
На рис. 13.4 показано, что DOSMEM может выделить около мегабайта памяти с по-
мощью единственного вызова функции 48h INT 2th. DOSMEM также освобождает память с
помощью функции 49h INT 21h, когда вы нажмете кнопку ОК. В результате, при запуске
нескольких копий, DOSMEM выделяет несколько мегабайтов одновремешю
Рис. 13.4. Вы можете выделить мегабайты памяти с помощью функции 48h INT 21h
394
Неофициальная Windows 95
Эти результаты показывают, что INT 21h в защищенном режиме Windows ведет себя не
так, как в реальном. Мы уже знаем из рассмотрения цепочки обработчиков INT 21h V86 в
предыдущих главах, что этот интерфейс DOS претерпевает в Windows радикальные изме-
нения. Эти изменения особенно ощутимы, когда INT 2lh выдается в защищенном режиме. В
нашем примере функция 48h не только распределяет более миллиона байтов памяти за раз, но
и возвращает селектор защищенного режима на только что распределенный блок памяти.
Но откуда взялась эта наивная концепция, что “Windows обрабатывает INT 2th,
обращаясь к DOS”, в которую продолжают верить многие программисты? Ведь функция 48h
INT 21h не может выделить так много памяти. Она не возвращает селекторы защищенного
режима. И она не может распределять дополнительную память за пределами одного мега-
байта, что, как мы видим, происходит на рис. 13.4. Ведь было выделено намного больше
одного мегабайта, следовательно, какая-то часть распределенной памяти должна находиться в
дополнительной. Расширитель DOS из Windows не может передавать этот вызов в DOS.
Никакие переводы из защищенного режима в реальный не заставят DOS распределить
мегабайты в непосредственно доступной дополнительной памяти.
Я говорю “в непосредственно доступной” дополнительной памяти потому, что программы
DOS могут выделять кучу памяти через интерфейс XMS и EMS Однако взгляните на
фрагмент кода в DOSMEM.C, следующий за комментарием “запись в каждый байт
выделенной памяти”. DOSMEM может немедленно использовать память (*fp2). Но этого
нельзя было бы сделать с памятью, выделенной через XMS или EMS. Эти интерфейсы
требуют, чтобы память вначале была отображена с помощью таких функций, как Move
Extended Memory Block (функция OBh XMS) или Map Extended Memory Page (функция 44h
из EMS). Как видно из листинга 13.6, DOSMEM не делает ничего подобного, она просто
использует память.
Если программе Windows необходимо выделить обычную память и получить сегмент
реального режима, она не может использовать функцию 48h прерывания INT21h. Вместо этого
она должна использовать специальную функцию, наподобие GlobalDOSAlloc из Windows API
или Allocate DOS Memory Block из DMP1 (функция OlOOh INT 31h).
Необычное поведение функции 48h INT21h в защищенном режиме является хорошим
примером того, что вкладывается в термин “расширитель DOS”. Расширитель DOS яв-
ляется средой, которая, подобно Windows, обеспечивает сервис прерывания INT 2th в
защищенном режиме. Функция 25h устанавливает вектор прерывания в защищенном ре-
жиме, функция 48h распределяет дополнительную память и возвращает селектор за-
щищенного режима и так далее.
Обычно концепция расширителя DOS (если это можно так назвать) имеет следующее
объяснение: говорят, что расширитель DOS делает так, как будто MS DOS — операционная
система защищенного режима; другими словами, он создает такую иллюзию.
Например, рассмотрим функцию 3Fh (Read File) прерывания INT21h. В реальном ре-
жиме эта функция предполагает, что пара регистров DS:DX указывает на буфер, в который
DOS будет помещать данные из указанного файла, указатель DS:DX является адресом
реального режима, а буфер находится в стандартной памяти. Но под расширителем DOS за-
щищенного режима DS:DX должен быть адресом защищенного режима, и буфер может нахо-
диться в дополнительной памяти. В общем, расширитель DOS мог бы реализовать версию
функции 3Fh защищенного режима путем выделения буфера в стандартной памяти,
переключения в реальный (или V86) режим и повторного вызова функции 3Fh с указателем в
DS:DX на буфер в стандартной памяти. При возврате из DOS расширитель DOS скопировал
бы данные из буфера в стандартной памяти в исходный буфер, указанный приложением.
Подобный сценарий, в котором расширитель DOS переводит запросы защищенного режи-
ма прерывания INT21h в термины реального режима, снова вызывает прерывание INT21h в
реальном режиме или в режиме V86 и затем транслирует любое возвращенное значение, явля-
ется основанием ^ля неправильной концепции “Windows должна вызывать DOS”.
Глава 13. Thunk! KERNEL32 вызывает KRNL386
395
Конечно, такой расширитель DOS, как Windows, в основном обрабатывает многие функции
прерывания INT 2 th именно описанным способом. Расширитель DOS заставляет DOS реаль-
ного режима думать, что ее вызвала обычная программа реального режима, а программы
защищенного режима — думать, что DOS является операционной системой защищенного
режима. Другими словами, расширитель DOS просто обманщик.
А как же с примерами функций 25Ь и 48h? Как мы видели, чтобы обеспечить прием-
лемую реализацию функций 25h и 48h в защищенном режиме, расширитель DOS не может
передавать вызов в DOS. Он должен сам обрабатывать эти вызовы полностью в защищенном
режиме, поскольку никакой обман не заставит DOS реального режима установить векторы
прерывания защищенного режима или распределить дополнительную память.
В этом случае со стороны расширителя DOS нет никакого обмана. Если он обрабатывает
вызов прерывания INT 2 th полностью в защищенном режиме, не вызывая DOS реального ре-
жима, то расширитель DOS работает как настоящая операционная система защищенного
режима, по крайней мере, для этой функции.
Если со временем такой расширитель DOS, как Windows, начинает обрабатывать все
большее число вызовов INT 21h в защищенном режиме без обращения к DOS, тогда он все
меньше становится расширителем DOS, а все больше — операционной системой защищенного
режима, которая обеспечивает хорошо известный интерфейс прерывания INT 2 th. Заметьте
однако, как постепенно может происходить такое преобразование: с каждой новой версией
расширителя DOS авторы могут брать несколько функций и, вместо передачи их обработки в
DOS, обрабатывать их полностью в защищенном режиме. Мы получим плавный переход от
иллюзии системы защищенного режима к действительной DOS защищенного режима.
Хм-м,.. уж очень похоже на то, что многие годы происходит с Windows.
Поскольку программы реального режима перехватывают векторы прерывания реального режи-
ма с помощью функции 25h, для расширителя DOS имеет смысл обеспечить функцию 25h INT 2th
защищенного режима, которая перехватывает векторы прерывания защищенного режима. Windows
обеспечивает такую функцию 25h в защищенном режиме. Однако WSPY21 перехватывает INT 2 th
не с помощью функции 25h INT 21h, а с помощью DPMI-функции 0205h INT 31h (Set Protected-
Mode Interrupt Vector). Нс обращайте внимание на выражение Protected Mode, которое присут-
ствует в названии функции DPMI и отсутствует в названии функции DOS. В защищенном режиме
под Windows функция DOS устанавливает вектор прерывания защищенного режима точно так же,
как это делает фу1 кция DPMI.
Так почему же используется функция DPMI? Потому что ядро Win 16 KERNEL содержит
обработчик прерывания INT 21h защищенного режима, который обеспечивает специальную обра-
ботку для нескольких функций DOS, одной из которых является функция 25h. Как мы вскоре уви-
дим, KERNEL игнорирует любую попытку перехватить некоторые прерывания, в частности INT 2th!
Итак, программы Windows не могут использовать INT21h AX=252th для перехвата INT21h.
Решением является использование функции DPMI.
Обработчик INT 21 h из KERNEL
и KernelDosProc
Кроме перехватчика INT 2th, установленного через DPMI, WSPY21 имеет второй обработчик,
установленный с помощью GetSetKernelDosProc, о которой я упоминал несколько раз. Зачем два
обработчика? Уже понятно, что вы должны перехватить INT 2th через DPMI, а не обычным путем,
зачем еще GetSetKernelDosProc? Затем, что многие Windows-программы и DLL используют DOS
без вызова INT 2th, а второй перехватчик, установленный с помощью GetSetKernelDosProc, будет
видеть эти вызовы.
396
Неофициальная Windows 95
Возьмем, к примеру, Dos3Call API (пожалуйста!). В Windows SDK говорится, что если вам
необходимо сделать вызов DOS, делайте это с помощью функции Dos3Call. Dos3Call не является
оболочкой для INT 21h; она не генерирует настоящий вызов INT 21h, который бы увидел
обработчик INT 21h WSPY21. Вместо этого, как вы видели в предыдущей главе, Dos3Call является
оболочкой для обработчика INT 21h из KERNEL:
KERNEL!DOS3CALL
0117:000081СЗ PUSHF ;; поместить в стек флаги для имитации прерывания
0117:00008104 PUSH OS ;; фальшивый дальний вызов: /FARCALLTRANS
0117:00008105 CALL 7F38 ;; вызвать обработчик INT 21h KERNEL
0117:00008108 RETF
Существует также много мест, где KERNEL вызывает обработчик прерывания INT 21h непо-
средственно, без использования Dos3Call. Пример этого был также показан в предшествующей
главе, в коде для _1ореп:
KERNEL!_1ореп:
0117:000093Е9 MOV AX.716C ;; LFN Open/Create
0117:000093ЕС PUSHF
0117:000093ED PUSH CS
0117:000093ЕЕ CALL 7F38 ;; вызвать обработчик INT 21h KERNEL
Недокументированная функция NoHookDosCall (см. Undocumented Windows, р. 339-340)
предоставляет другой способ, с помощью которого Windows может вызывать DOS без INT 21h.
IntHaAdler2 из WSPY21, установленный с помощью GetSetKernelDosProc, также увидит эти вызовы
NoHookDosCalls.
Но что значит по hook (без перехвата)? Почему какая-то часть Windows использует NoHook
DosCall, а не Dos3Call? И если обработчик, установленный GetSetKernelDosProc, будет видеть эти
не-INT 21h вызовы DOS, такие как Dos3Call и NoHookDosCall, почему WSPY21 нужен обработчик
на более высоком уровне? Почему не просто использовать GetSetKernelDosProc?
Я говорил, что WSPY21 базируется на программе WISPY из книги Undocumented Windows, но
WSPY21 устанавливает два обработчика, в то время как WISPY — только один, используя функ-
цию GetSetKernelDosProc. В результате WISPY не увидит такие важные вызовы, как функция 4Bh
(EXEC). Важно не то, что вы поймете, как работают WSPY21 и WISPY (даже и не пытайтесь это
понять!), а то, что проблема WISPY и ее решение в WSPY21 выявляют некоторые важные аспекты
Windows. К тому же, перехват INT 21h из Windows-приложений становится вечной темой, по
крайней мере на страницах журналов для программистов (см. колонку Windows Q&A Пола Бония
(Paul Bonneau) в Windows/DOS Developer’s Journal, April 1994, June 1994, September 1994).
Даже до появления защищенного режима Windows в версии 3.0 обработчик INT 21h из
KERNEL обрабатывал некоторые вызовы из Windows-приложений без передачи их в DOS.
Наилучший пример этого — функция 4Bh (EXEC):
<PROGMAN> (4В) Exec 'write.exe’
DOS не умеет запускать такие Windows-программы (NE-файлы), как WRITE.EXE, поэтому
ядро KERNEL не может передать функцию 4Bh в DOS. Когда функция вызвана из Windows-
приложения или DLL (например, функция WinExec в KERNEL), KERNEL заменяет ее на вызов
LoadModule (см. Windows Internets, р. 229-231). Даже при вызове WinExec для программ
реального режима DOS KERNEL все равно не может передать функцию 4Bh в DOS реального
режима, поскольку DOS, к сожалению, не умеет запускать DOS-приложения в окне DOS.
Если вы используете функцию 4Bh в реальном режиме, пытаясь запустить программу Windows,
то получите сообщение: “This program must be run under Microsoft Windows”. DOS выполняет
только заглушку реального режима, которая находится в NE-файле, но может делать все, что ей за-
хочется (даже загружать Windows); однако обычно она просто выводит это сообщение и завер-
шается. В Windows 95 вы можете (наконец-то!) запускать Windows-программы из окна DOS, но это
делается путем перехвата функции 4Bh прерывания INT 21h, проверкой того, запускаете ли вы
Глава 13. Thunk! KERNEL32 вызывает KRNL386
397
Win 16-приложение, и если да, то преобразованием вызова функции 4Bh в вызов функции WinExec.
Следовательно, DOS реального режима действительно не знает, как запускать программы Windows.
(Это означает, что Windows сама требует загрузчика, чтобы стартовать из DOS, см. Windows
Internals, р. 10-13.)
Кроме того, функция 4Bh прерывания INT21h может даже запускать PIF-файлы:
<PROGMAN> (48) Exec 'C:\WINDDUS\EDIT.PIF'
Довольно просто убедить вас в том, что нет способа, которым Windows может передать этот вы-
зов DOS реального режима; DOS не знает, как запускать PIF-файлы.
В качестве другого примера приведем, что KERNEL поглощает большинство вызовов функций
OEh (Set Drive) и 19h (Get Drive) прерывания INT 21h из Windows-приложений. Причина простая:
каждая Windows-программа имеет свои собственные текущий диск и каталог, хранящиеся в TDB,
ассоциированном с ее PSP. Вы можете иметь одну Windows-программу, находящуюся в C:\FOO, и
другую (или ту же самую) в D:\BAR. Каждая VM в Windows также имеет свои собственные
текущие диск и каталог, но это осуществляется через данные экземпляров (см. главу 4) и, как было
отмечено раньше в этой главе, не решает проблем для программ, работающих в одной системной
VM. Системная VM имеет один экземпляр структуры текущего каталога DOS (CDS). И поскольку
KERNEL разделяет эту единственную CDS среди нескольких Windows-программ, он должен
обрабатывать DOS-вызовы Set Drive и Get Drive из Windows-программ, манипулируя TDB.
Специфическая обработка KERNEL некоторых вызовов INT 21h также объясняет, почему
WSPY21 не может перехватить INT 21h, используя функцию установки вектора прерывания DOS
(функция 25h INT 21h). Как упоминалось раньше, KERNEL отвергает любую попытку использовать
функцию 25h DOS, чтобы изменить четыре вектора прерывания. Это INT IBh (<Ctrl+Break>), ICh
(Timer), 21h (DOS) и 24h (Critical error). Заметьте, что среди них есть INT 21h. Это означает, что
программы Windows не могут использовать функцию 25h INT 21h для перехвата INT 2 lh.
KERNEL также по-особому обрабатывает несколько прерываний, которые требуют обработки
TDB для каждой задачи (см. Undocumented Windows, р. 365-366):
Прерывание
Описание
ШТ 0
ШТ 2
ШТ 4
ШТ 6
ШТ 7
INT ЗЕЬ
ШТ 75h
Деление на нуль
Немаскируемое прерывание (NMI)
Прерывание по переполнению (INTO)
Неверный код операции
Сопроцессор занят
Используется эмулятором операций с плавающей точкой
Ошибка сопроцессора
WSPY21 имеет опцию -SHOWALL, показывающую вызовы INT 21h, которые обрабатываются
внутри KERNEL. Опция -SHOWALL говорит обработчику KernelDosProc WSPY21 не отфильт-
ровывать вызовы, о которых уже сообщил обработчик INT 21h верхнего уровня. Выводя инфор-
мацию обо всех вызовах, зарегистрированных обработчиком INT 21h и увиденных обработчиком
KernelDosProc (их WSPY21 отмечает звездочкой), опция -SHOWALL позволяет довольно легко
выделить те вызовы, которые были обработаны в KERNEL. Например:
< SH> (19) Get Disk
< SH> (47) Get Cur Dir 3 (03h)
< SH> *(1A) Set DTA 2E07:0080
< SH> *(47) Get Cur Dir 3 (03h)
Здесь обработчик прерывания INT 21h KERNEL увидел вызов функции 19h INT 21h (Get
Disk), и он был последним, кто видел этот вызов. KERNEL обработал его самостоятельно. Далее,
после того, как обработчик INT 21h KERNEL увидел вызов функции 47h (Get Current Directory),
398
Неофициальная Windows 95
он сгенерировал вызов функции lAh (Set DTA) перед отправкой вызова функции 47h в
KernelDosProc. KERNEL не только поглощает некоторые прерывания INT 21h, но может также
выдавать некоторые дополнительные вызовы INT 21h после переключения задач, чтобы гаран-
тировать, что текущая задача имеет правильные DTA, PSP, текущие диск и каталог (как мы видели
раньше в этой главе):
<SH> *(47) Get Cur Dir 8 (08h)
<CLOCK> (71) LFN (4E) Find First ‘C:\WINDOWS\clock.ini‘
<CLOCK> *(1A) Set DTA OOAF:OO8O
<CLOCK> *(0E) Set Disk 2 (02h)
<CLOCK> *(71) LFN (3B) ChDir ‘\BORLANDC\BIN‘
<CLOCK> *(71) LFN (4E) Find First ‘C:\WINDOWS\clock. ini'
Сразу после переключения с задачи SH на CLOCK обработчик INT 21h KERNEL получил вы-
зов функции 714Eh (LFN Find First). Перед передачей этого вызова в KernelDosProc KERNEL
должен вызвать функции lAh (Set DTA), OEh (Set Disk) и 713Bh (LFN Change Directory).
Итак, вы видите, что KERNEL поглощает некоторые вызовы INT 21h (а иногда генерирует
дополнительные вызовы вместо них) и не передает эти вызовы не только DOS реального режима,
но даже и всем VxD, которые перехватили INT 21h в защищенном режиме. Чтобы увидеть вызовы,
которые обрабатывает KERNEL, вам нужен обработчик INT 21h, который находится перед
KERNEL.
Кроме того, я не думаю, что читателей сильно волнуют превратности поведения WISPY и
WSPY21. Но вопрос, как увидеть вызовы прерывания INT 21h, прежде чем их обработает KERNEL,
является важным, так как показывает, что даже до Windows 95, даже до 32-битового файлового
доступа в WfW 3.11, до введения защищенного режима в версии 3.0, одним словом, даже в дни вер-
сии-2.х, Windows уже делала многое из интерфейса прерывания INT 21h.
Итак, WSPY21 увидела вызов прерывания INT 2lh и передала его в KERNEL. KERNEL выпол-
нил некоторую специальную обработку прерывания INT 21h и теперь передает вызов (если это не
один из вызовов INT 21h, которые KERNEL обрабатывает самостоятельно) в DOS реального
режима. Впрочем, не совсем так. Можно даже сказать, совсем не так! KERNEL передаст вызов
предыдущему (т.е. установленному до него) обработчику прерывания INT 21h, который в свою
очередь может опять передать вызов предыдущему обработчику, а может и не передавать, это — уж
как ему захочется.
Обработчик прерывания INT 2lh в KERNEL вызывает предыдущий обработчик INT 2lh с Помо-
щью недокументированной функции NoHookDosCall. (Наконец-то, мы вернулись к NoHook
DosCall!) Чтобы обойти обработку, которую KERNEL выполняет для определенных вызовов DOS,
прочие части KERNEL и некоторые DLL, такие как SYSTEM.DRV, используют NoHookDosCall.
Возьмем к примеру последовательность из трех вызовов IOCTL, которые часто выводятся
программой WSPY21. Эти вызовы приходят от функции InquireSystem из SYSTEM.DRV (см.
Undocumented Windows, р. 609), которая в свою очередь вызывается GetDriveType API из
KERNEL:
<САВ32> *(44) IOCTL (09) Is Drv Remote? 3 (03h)
<CAB32> *(44) IOCTL (08) Is Drv Removeable? 3 (03h)
<CAB32> *(44) IOCTL (0E) Get LOg Drv Map 3 (03h)
Такое использование NohookDosCall не будет видно тем Windows-программам, которые пере-
хватили только INT 21h. Даже имя функции, NoHookDosCall (дословно — неперехватываемый
вызов DOS), подтверждает это.
NoHookDosCall не обходит абсолютно всю обработку KERNEL. По-прежнему, например,
должны проверяться вызовы функций OEh и 19h (Get/Set Disk). Однако NoHookDosCall очень
простая и быстро вызывает предыдущий обработчик INT 21h защищенного режима, указатель
на который KERNEL сохранил при начальной загрузке по смещению lAh в одном из своих
кодовых сегментов:
0117:0008148 CALL FAR CS:[001А]
Глава 13. Thunk! KERNEL32 вызывает KRNL386
399
Однако интересно, кто владел INT 21Ь защищенного режима перед запуском KERNEL? Веро-
ятно какой-то VxD. (VxD SHELL обычно является последним статистически загружаемым VxD, он
загружает KERNEL.) Но давайте посмотрим, куда передается управление, когда NoHookDosCall
вызывает предыдущий обработчик:
:dd 117:1а
003В;0396
:u ЗЬ:396
0038:00000396 INT 30
Каждый INT 21b. защищенного режима в Windows, если не поглощается KERNEL, будет, в
свою очередь, генерировать INT ЗОЬ! Этот INT ЗОЬ является всем тем, что относится к Kernel
DosProc (ну не совсем, но мы вернемся к этому, когда будем обсуждать функцию GetSet
KemelDosProc).
Используя такую утилиту, как IDTMAP, можно увидеть, что около десяти других прерываний
защищенного режима через шлюзы ловушки также приводят в этот же самый селектор ООЗВЬ.
(Между прочим, шлюз ловушки — это то же самое, что и шлюз прерывания, только он оставляет
прерывания разрешенными; если вы сильно заинтересовались этим, посмотрите документацию
Windows DDK для Set_PM_Int_Type.)
C:\UNAUTHW\IDTMAP>idtmap I find “003B:"
0010 INTR 0028:C0003DF4 (3) TRAP16 0038:00000342 (3) PMCB
0013 INTR 0028:C0003E0C (3) TRAP.16 0038:00000324 (3) PMCB
0015 INTR 0028:C0003E1C (3) TRAP16 0038:00000326 (3) PMCB
001С INTR 0028:C0003E54 (3) TRAP16 0038:00000328 (3) PMCB
0021 INTR 0028:C0003E7C (3) TRAP16 0038:00000396 (3) PMCB
0025 INTR 0028:C0003E9C (3) TRAP16 0038:00000390 (3) PMCB
0026 INTR 0028:C0003EA4 (3) TRAP16 0038:00000392 (3) PMCB
002F INTR 0028:C0003EEC (3) TRAP16 0038:00000388 (3) PMCB
0031 INTR 0028:C0003EFC (3) TRAP16 0038:00000210 (3) PMCB
0033 INTR 0028:C0003E0C (3) TRAP16 0038:00000320 (3) PMCB
Каждое из этих прерываний защищенного режима генерирует INT ЗОЬ. В действительности сег-
мент ОЗВЬ не содержит ничего, кроме инструкций INT ЗОЬ. Как было вкратце замечено в дискуссии
о программе WINBP в главе 8, INT ЗОЬ является обратным вызовом защищенного режима для пере-
хода с защищенного режима уровня 3 в 32-битовый защищенный режим уровня 0. VxD пере-
хватывают прерывания защищенного режима путем передачи адреса 32-битового обработчика уров-
ня 0 функции Allocate_PM_Callback из VMM и затем передачей полученного адреса обратного
вызова функции Set_PM_Int_Vector.
В Windows 3.0 вы не увидели бы INT ЗОЬ. Этот сегмент был заполнен командами HLT. Все
правильно: каждое прерывание INT 21Ь защищенного режима генерировало HLT. Поначалу это зву-
чит странно: каждое прерывание INT 21Ь будет останавливать процессор? Но в руководстве по
микропроцессору Intel сказано, что в защищенном режиме HLT является привилегированной коман-
дой, которая вызывает ошибку GP, если текущий уровень привилегий не 0. В Windows 3.0 про-
граммы защищенного режима работают на уровне привилегий 1, поэтому HLT не останавливала
работу процессора, а просто приводила к возникновению ошибки GP.
Итак, каждое прерывание INT 21Ь из Windows-программы должно генерировать ошибку GP?
Это звучит не лучше, чем прекращение работы процессора. Однако, несмотря на столь зловещее
звучание, ошибка GP, на самом деле, является только еще одним прерыванием (INT ODh), которое
может быть перехвачено VMM или VxD. Таким образом, выполнение HLT программой в защи-
щенном режиме под управлением Windows 3.0 приводит к переходу с уровня 1 на уровень 0. Наце-
лив вектор прерывания защищенного режима на команду HLT, вызывающая программа (например,
Windows-программа, которая вызывает INT 21Ь) может переходить на уровень 0.
То, что Windows 3.1 и более поздние версии используют INT ЗОЬ в той же ситуации, в которой
Windows 3.0 использует HLT, говорит нам, что здесь есть нечто интересное, Это использование
400
Неофициальная Windows 95
команд HLT в защищенном режиме похоже на использование команд ARPL в режиме V86, что об-
суждалось в главе 8. В действительности эти две схемы похожи. ARPL используется для обратных
вызовов и контрольных точек V86. Windows 3.0 использует HLT обратных вызовов в защищенном
режиме; Windows 3.1 и более поздние версии — INT 30h. Как говорится в руководстве WINICE:
“Переход на INT 30h не только повышает производительность, но и несколько упрощает очень
сложный обработчик ошибки GP”.
Итак, куда же ведет INT 30h? Мы можем выяснить это, запустив IDTMAP 30:
0030 INTR 0028:С0001А2С (3) same
Этот адрес (28:С0001А2С) находится в Windows VMM. Таким образом, INT 30h является дру-
гим примером того, что Microsoft иногда (не совсем точно) называет thunk. Выполняя INT 30h,
программа пользовательского уровня (уровня 3) защищенного режима попадает в VMM на
привилегированный уровень 0 32-битового защищенного режима. Обработчик прерывания INT 30h
из VMM использует адрес, из которого пришел вызов INT 30h (например, ЗВ:396), чтобы
определить обработчик для этого обратного вызова защищенного режима (т.е. адрес, который был
передан AIIocate_PM_Callback). Это очень похоже на то, как VMM использует ARPL в обратных
вызовах V86.
VMM вызовет самый последний установленный обработчик INT 21h защищенного режима. В
Windows 95 им обычно является IFSMGR. Если IFSMGR не заинтересуется этим вызовом INT 21h,
он вызовет предыдущий обработчик в цепочке, которым обычно является DOSMGR. DOSMGR
является расширителем DOS. Он обрабатывает большинство вызовов INT 2lh защищенного режима,
используя маленькие сценарии (script), которые он передает V86MMGR_Xlat_API. В большинстве
случаев V86MMGR отражает вызовы дальше в режим V86, используя Begin_Nest_V86 и Exec_Int
21h. (Этот процесс более детально описан в Undocumented DOS, 2d ed., р. 122-128.)
Когда говорят, что V86MMGR отражает вызов INT 21h в режим V86, используя
Begin_Next_V86 и Exec_Int 21h, нужно понимать, что это не совсем так! Exec_Int посылает (или
отражает) вызовы в цепочку перехватчиков прерывания, которая состоит из 32-битового кода
защищенного режима, установленного VxD с помощью Hook_V86_Int_Chain. Windows отразит
вызов в режим V86, только если его не обработает VxD. Более того, даже если вызов INT 21h
отражается в режим V86, как мы знаем из предыдущей главы, Windows использует DOS
реального режима в качестве ассистента, и вызовы, сделанные DOS в режиме V86, часто прыгают
обратно на уровень VMM и VxD (например, благодаря встроенным контрольным точкам V86).
Как отмечалось раньше, KernelDosProc — не более чем INT ЗОИ, за исключением того, что
такая программа, как WSPY21, может заменить KernelDosProc своей процедурой. Windows-прило-
жения могут управлять адресом предыдущего обработчика прерывания с помощью другой недо-
кументированной функции GetSetKernelDosProc В книге Undocumented Windows (р. 188, 271-273)
эта функция обсуждается, но, к сожалению, не говорится, что GetSetKernelDosProc манипулирует
предыдущим обработчиком прерывания INT 21h, с которым связывается обработчик прерывания
INT 21h из KERNEL, и что, используя GetSetKernelDosProc, можно установить обработчик INT 21h
непосредственно перед KERNEL:
;;; код для GetSetKernelDosProc
mov ах, [bp+6] ; взять параметр DWORD
xchd ах, [001А] ; обменять с DWORD по 0117:001А
mov dx, [bp+8]
xchcj dx, [001c]
GetSetKernelDosProc позволяет вам вставить обработчик прерывания INT 21h перед KERNEL,
но после любого VxD, который перехватывает INT 21h. Это похоже на интерфейс функции 13h из
INT 2Fh, обсуждавшийся в главе 8, где было сказано, что хорошо было бы иметь похожую
функцию 21h INT 2Fh, роль которой сейчас играет IFSHLP. GetSetKernelDosProc играет
аналогичную роль для INT 21h в защищенном режиме.
Глава 13. Thunk! KERNEL32 вызывает KRNL386
401
Мы видим, что имя KemelDosProc нисколько не соответствует действительности, так как обра-
ботчик ме расположен в KERNEL. Это просто обработчик, с которым связывается обработчик INT
21h из KERNEL (через NoHookDosCall).
Чтобы увидеть все возможные INT 21h защищенного режима, WSPY21 использует GetSet
KernelDosProc. Пока работает WSPY21, KERNEL передает непоглощенные вызовы INT 21h
WSPY21, которая, в свою очередь, передает их обратному вызову защищенного режима. В то же
время WSPY21 перехватывает INT 21h (используя DPMI, а не DOS, по причинам, объясненным
раньше), так что WSPY21 увидит вызовы INT'21h до KERNEL. WSPY21, таким образом, пере-
плетается с обработчиком INT 21 h из KERNEL, несколько напоминая сэндвич.
Кроме исследования INT 21h в KERNEL с целью показать, что с самого начала Windows
должна была обходить некоторые функции DOS, в этой главе большей частью рассматривались
связи ядра Win32 с ядром Winl6 и с DOS. В этой главе обсуждались такие аспекты ядра Win32,
как thunk-переключатели и Winl6Lock, впрочем без особых деталей. Следующая глава, заклю-
чительная, подробнее затронет многочисленные аспекты ядра Win32, включая thunk-переключатели,
файлы отображения памяти, недокументированные VxDCall API и Win32 сервисы.
402
Неофициальная Windows 95
Глава 14
Clock: смесь 32-битового
и 16-битового кодов
В этой последней части мы сосредоточимся на одной маленькой, во многих отношениях не за-
служивающей внимания программе, поставляемой вместе с Windows, — Clock. Поскольку Micro-
soft поставляет Winl6- и Win32-BepcHH, а обе эти версии выполняются и в Windows 3.x, и в
Windows 95, Clock удобна для исследования разнообразных аспектов взаимосвязи Winl6 и Win32:
Версия Clock
Winl6
Win32
Windows 3.1х
16/16: 32BFA
32/16: Win32s;
CALL FWORD PTR
Windows 95
16/32: Win32-4ecKpHrrropbi файлов
32/32: загрузка EXE с отображением памяти;
Winl6Lock
Мы начнем с того, что рассмотрим, как Windows-приложения вызывают и обходят MS DOS. А
закончим обсуждением разнообразных вопросов, имеющих отношение к смешиванию Win32- и
Winie-кода: CALL FWORD PTR, QT_Thunk, Win32-cepBHCbi, Winl6Lock, _EnterSysLevel,
сходству между Win32s и Windows 95 и многому другому.
16/16: Winl 6 Clock под WfW 3.11
Под WfW 3.11 с 32BFA я запустил Clock, поставляемую с Windows, дал ей немного поработать
и закрыл ее. При включенной опции Show Changes WV86TEST показала следующие вызовы DOS,
зарегистрированные резидентной DOS-версией программы V86TEST, загруженной перед Windows:
Прошло 139 секунд
Вызовы INT 21h:
2А: 402 2С: 2201 4С:1 50: 15 55: 2
Это дает нам точный список некоторых характерных вызовов INT 21h, посылаемых в MS DOS,
даже при включенном 32-битовом доступе к файлам (32BFA):
• функции 2Ah и 2Ch получают дату и время. Всякий раз, когда Win 16-версия программы
Clock получает сообщение WM_TIMER, она вызывает эти функции. WV86TEST показывает,
что эти вызовы передаются в DOS. Вызовов функции 2Ch больше, чем вызовов функции
2Ah, поскольку Win 16-версия программы Clock производит большее число вызовов функции
2Ch при старте. Если же вы выполните в WV86TEST команды Show Changes и Refresh,
вызовов 2Ah будет в точности столько же, сколько и вызовов 2Ch — примерно в три раза
больше числа прошедших секунд;
• функция 4Ch завершает текущую программу DOS. Стандартный Windows-код начальной
загрузки вызывает эту функцию после возврата из WinMain. Действительно, приложения
Глава 14. Clock: смесь 32-битового и 16-битового кодов
403
• Windows заканчиваются DOS-функцией выхода. Это написано в документации по Mic-
rosoft Windows 3.1 SDK (Programmer’s Reference, Vol. 1: Overview, Ch. 22. “Windows
Application Startup”);.
• функция 50h устанавливает текущий PSP, функция 55h создает PSP. Осуществляемое Win-
dows переключение между приложениями обычно также сопровождается переключением PSP.
Поэтому можно ли утверждать, что Windows-приложения представляют собой лишь “раз-
украшенные” DOS-приложения защищенного режима? Использование программой Clock функций
DOS для получения даты и времени, выход из Windows-программ с помощью функции 4Ch и
зависимость от PSP, по-видимому, подтверждают это мнение.
В то же время заметим, что при каждом запуске программе Clock нужно узнать, какие имен-
но — аналоговые или цифровые — часы вы предпочитаете. Если вам больше нравится цифровой
вывод, программе нужно знать выбранный вами шрифт. Чтобы получить эту информацию, Clock
читает CLOCK.INI. Это, в свою очередь, вовлекает вызовы не только INT 21h для открытия, записи
и закрытия CLOCK.INI, но и Find First, а также Get и Set DTA. Как показывает WV86TEST, DOS
не видит ни одной из этих операций файлового ввода-вывода. Даже когда я изменил установки
Clock с аналоговой на цифровую, перелистал список шрифтов, выбрал новый шрифт и вышел из
соответствующего меню (в результате чего сохранились изменения в CLOCK.INI), результаты,
зарегистрированные V86TEST и отображенные WV86TEST, Не очень изменились:
Прошло 56 секунд
Вызовы INT 21h:
2А: 139 2С: 788 4С:1 50: 63 55: 1 62: 23
За время работы по выбору нового шрифта и записи измененных установок в CLOCK.INI все,
что DOS видела дополнительно, это только несколько вызовов Get PSP (функция 62h).
Конечно, из-за работы 32BFA большинство вызовов DOS никогда не передается в DOS. В от-
личие от этих семи вызовов INT 21h, которые для Clock заметила WV86TEST, ниже приводится
лишь малая часть того, что зарегистрировала WSPY21:
<PROGMAN> *(4В) Ехес ‘C:\WFW311\CL0CK.EXE’
<PROGMAN> (50) Set PSP 183 (00B7h)
<PROGMAN> (3D) Open ‘C:\WFW311\CL0CK.EXE’
<CLOCK> *(25) Set Vect 0 (OOh)
<CLOCK> *(2F) Get DTA
<CLOCK> (50) Set PSP 4287 (lOBFh)
<CLOCK> (1A) Set DTA 10BF:0080
<CLOCK> *(4E) Find First ‘C:\WFW311\WIN.INI’
<CLOCK> (3D) Open ‘C:\WFW311\CL0CK.INI’
<CLOCK> (44) IOCTL (09) Is Drv Remote? 3 (03h)
<CLOCK> (44) IOCTL (08) Is Drv Removeable? 3 (03h)
<CLOCK> (44) IOCTL (0E) Get Log Drv Map 3 (03h)
<CLOCK> (57) Get/Set File Date/Time 5 (0005h)
<CLOCK> (42) Lseek2 5 OOOOOOOOh
<CLOCK> (42) LseekO 5 OOOOOOOOh
<CL0CK> (3F) Read 5 (0005h), 89 (0059h)
<CLOCK> (3E) Close 5 (0005h)
<CLOCK> *(2C) Get Time
<CLOCK> (50) Set PSP 4287 (10BFh)
<CLOCK> (2C) Get Time
<CLOCK> *(2A) Get Date
; ... множество вызовов 2A, 2C ...
<CLOCK> *(2F) Get DTA
404
Неофициальная Windows 95
<CLOCK> (1A) Set DTA 10BF:0080
<CLOCK> (3D) Open ‘C:\WFW311\SYSTEM\TIMES.TTF’
<CLOCK> (3F) Read 5 (0005h), 28 (DOICh)
; ... просмотр шрифтов ...
<CLOCK> (3D) Open ‘C:\WFW311\CLOCK.INI’
<CLOCK> (57) Get/Set File Date/Time 5 (0005h)
<CLOCK> (40) Write 5 (0005h), 87 (0057h)
<CLOCK> (40) Write 5 (0005h), 0(0000h)
<QL0CK> (3E) Close 5 (0005h)
Сличив выводы WSPY21 и WV86TEST, несложно увидеть, что целые серии DOS-функций,
включая lAh (Set DTA), 2Fh (Get DTA), 3Dh (Open File), 3Eh (Close File), 3Fh (Read File), 42h
(Lseek) и 44h (IOCTL), генерируются CLOCK, но никогда не передаются в DOS.
С одной стороны, Windows-приложения подобны DOS-программам тем, что используют интер-
фейс DOS, причем намного интенсивнее, чем предполагают большинство программистов, Даже в Win-
dows 95 им требуется код DOS реального режима. (Вспомним документацию Microsoft, цитируемую в
главе 8: “по умолчанию все прерывания INT 21h, за исключением файловых, передаются вниз”.)
С другой стороны, Windows-приложения требуют кода DOS реального режима намного мень-
ше, чем кажется на первый взгляд. Когда вы видите в приложении вызов INT 21h или DOS3Call,
нечего даже и думать, что это действительно вызов DOS. Это может просто оказаться более удоб-
ным способом вызова VxD.
После всего прочитанного в этой книге тот факт, что Windows и использует, и обходит
MS DOS, уже не должен быть для вас новостью. Итак, обратимся к Win32-BepcHH программы
Clock, поставляемой с Windows 95. По сравнению с Winl6, сходства и различия будут
потрясающими.
32/16: Clock из Windows 95 под Win32s
Можно взять Win32-пpилoжeниe Clock из Windows 95 и запустить под WfW 3.11, используя
Win32s. Правильно, оно работает не совсем как следует (только цифровые часы), но сам факт того,
что эта Win32-nporpaMMa работает под Windows 3.1, показывает, что Win32s, возможно, нечто
большее, чем мы полагали раньше, a Windows 95 представляет менее радикальные изменения, чем
нам говорили.
Вот что показывает WV86TEST для Win32-BepcHH Clock под WfW 3.11 с Win32s:
Прошло 177 секунд
Вызовы INT 21h:
2А: 1482 2С: 1482 30: 2 4С: 1 50: 146 51: 58 55: 2 62: 6
Вызовы INT 2Fh:
11: 2
Заметим, что вызовов для определения даты (функция 2Ah) столько же, сколько и для времени
(функция 2Ch). Выполняемая под Win32s или Win95 Win32-BepcHH Clock производит около 8 пар
(1482/177) вызовов Get Date/Time в секунду. Учитывая, что даже старая 386SX/20 может
выполнить около 2 млн. команд в секунду, выполнение последовательности из нескольких тысяч
операций по 8 раз в секунду отнимает в худшем случае только 1% производительности. С другой
стороны, это только одна операция, накопление других подобных последовательностей может стать
серьезной проблемой для производительности Windows.
В любом случае, WV86TEST показывает, что Win32-BepcHH Clock произвела в точности столько
же вызовов DOS, сколько мы раньше видели у Winl6-BepcHH. В то же время WSPY21 заметила
совершенно другой пример вызовов INT21h:
Глава 14. Clock: смесь 32-битового и 16-битового кодов
405
<WINFILE> *(4В) Exec ‘C:\WINDOWS\CLOCK.EXE’
<WINFILE> (50) Set PSP 175 (OOAFh)
<WINFILE> (3D) Open ‘C:\WFW311\SYSTEM\W32SYS.DLL’
<WINFILE> (3D) Open ‘C:\WFW311\SYSTEM\WIN32S\WIN32S.EXE’
<WINFILE> (3D) Open 'C:\WFW311\SYSTEM\WIN32S16.DLL’
<WINFILE> (3D) Open ‘C:\WFW311\SYSTEM\D0EML.DLL’
<WINFILE> (3D) Open ‘C:\WFW311\SYSTEM\0LECLI.DLL’
<WINFILE> (3D) Open ‘C:\WFW311\SYSTEM\0LESVR.DLL'
<WINFILE> (47) Get Curr Dir 3 (03h)
<W32SXXXX> (50) Set PSP 175 (OOAFh)
<W32SXXXX> (42) LseekO 10 OOOIdaOO
<W32SXXXX> (3F) Read 10 (OOOAh), 2687 (0A7Fh)
<W32SXXXX> *(3D) Open ‘C:\WFW311\SYSTEM\win32s\w32skrnl.dll
<W32SXXXX> *(3D) Open ‘C:\WINDOWS\CLOCK.EXE’
<W32SXXXX> *(3D) Open ‘C:\WFW311\SYSTEM\win32s\comdlg32.dll
<W32SXXXX> *(3D) Open ‘C:\WFW311\SYSTEM\win32s\KERNEL32.dll
<W32SXXXX> *(3D) Open ‘C:\WFW311\SYSTEM\win32s\USER32.dll’
<W32SXXXX> *(3D) Open ‘C:\WFW311\SYSTEM\win32s\GDI32.dll’
<W32SXXXX> *(3D) Open ‘C:\WFW311\SYSTEM\win32s\NTDLL.dll’
<W32SXXXX> *(3D) Open ‘C:\WFW311\SYSTEM\win32s\SHELL32.dll’
<W32SXXXX> (3D) Open ‘C:\WFW311\SYSTEM\USER.EXE’
<W32SXXXX> (3F) Read 6 (0006h), 6755 (1A63h)
<CLOCK> *(2F) Get DTA
<CLOCK> (50) Set PSP 6799 (1A8Fh)
<CL0CK> (2F) Get DTA
<CLOCK> *(1A) Set DTA 31C7:8B8E
<CL0CK> *(4E) Find First ‘C:\WFW311\WIN.INI’
<CL0CK> *(1A) Set DTA 1A8F:0080
<CL0CK> (3D) Open ‘C:\WFW311\CL0CK.INI’
<CLOCK> *(2A) Get Date
<CLOCK> (50) Set PSP 6799 (1A8Fh)
<CLOCK> (2A) Get Date
<CLOCK> *(2C) Get Time
<CLOCK> (2A) Get Date
<CLOCK> *(2C) Get Time
Чтобы запустить '\У1п32-приложение, Windows 3.1x должна загрузить подсистему Win32s. Этот
процесс описан в моей статье о Win32s в Microsoft System Journal (April 1993), (см. раздел “How
Does It Work”, p. 24-29) и в книге Windows Internals Мэтта Петрека, (p. 244, 292-293).
Коротко говоря, Win32s загружается так: когда вы выбираете программу для запуска под Win-
dows 3.x (например, щелкнув на пиктограмме в Program Manager), оболочка, используемая вами,
вызовет функцию API WinExec, которая в свою очередь вызывает LoadModule. LoadModule возвра-
щает ошибку с определенным кодом, показывающим, что указанный модуль являлся портативным ис
полняемым (Portable Executable, РЕ) Win32s, а не Winie-исполняемым (New Executable, NE) фай-
лом. Видя этот код ошибки, WinExec вызывает функцию ЕхесРЕ из KRNL.386. Да, в Windows 3.1 бы-
ли изначально заложены знания о Win32 РЕ-файлах; Win32s не была чем-то, что придумали потом.
Это очень важно, так как, если бы Win32s была “интегрирована” в Windows 3.1, могло бы
неожиданно показаться, что Windows 95 — это не новая версия Windows, а улучшенная версия
Win32s. Подобная точка зрения вызывает решительный протест программистов Windows 95, ра-
406 Неофициальная Windows 95
ботающих в Microsoft: “Chicago — это не Win32s”. Эта фраза часто повторялась, например, на кон-
ференции пользователей Win32s в Диснейленде в 1993 г., где утверждалось, что, в противополож-
ность Win32s, Windows 95 “интегрирована”, “бесшовна” и вообще — хороша во всех отношениях.
Но Microsoft протестует чересчур громко. Когда Win32s впервые появилась в конце 1992 г.,
Microsoft пела иную песню: “Win32s и Windows 3.1 тесно связаны. Win32s является настоящим
системным расширением для кода операционной системы Windows 3.1” (статья Microsoft
KnowledgeBase, “Coordinations between Windows 3.1 and Win32s”, 1992). Это ниточка для вызова
маленького перехватчика в WinExec обеспечивает “тесную связь”, но это все та же ниточка, которая
присутствует в каждой новой версии программного обеспечения Microsoft. Новое программное обес-
печение всегда “интегрировано” , “бесшовно” и “тесно связано”, и вдруг оказывается, что предшест-
венники таковыми не являлись.
Возможно, мы однажды услышим, что Windows 2000 действительно интегрирована, a Windows 95
нет. Я предсказываю, что в декабре 1998 г. на конференции пользователей (которая, по моим даль-
нейшим предсказаниям, состоится в Coney Island Cyclone на Astroland Amusement Park (см. Micro-
soft System Journal, Jule 1994 г.), разработчики Microsoft скажут собравшимся 20000 ISV (пи-
шущим на Visual Basic “примочки” для Microsoft Office): “Windows 2000 — это вам не Windows 95!”
Возвращаясь к действительности (и выводу WSPY21), посмотрим, как ЕхесРЕ загружает
W32SYS.DLL (Winl6 DLL), которая, в свою очередь, загружает WIN32S.EXE. Это маленькая
Win 16-программа действует как “доверенное лицо” PE-файла в 16-битовом мире. Другая Win 16
DLL, файл WIN32S16.DLL, содержит 16-битовую часть ядра Win32s. В дополнение к коду, кото-
рый может загружать PE-файлы в память под Windows 3.1, настраивать ссылки и т.д., WIN32S16
также содержит 16-битовую часть кода, позволяющую программам Win32 API выполняться поверх
существующих 16-битовых DLL из Windows 3.1. WIN32S 16 загружает W32SKRNL.DLL, которая
является уже Win32 DLL.
Загрузив базовую порцию 16/32-связи Win32s, WIN32S16 может теперь загружать любой ис-
полняемый Win32-4>aibi (здесь CLOCK.EXE) й любую Win32 DLL, которую она использует (здесь
COMDLG32, KERNEL32, USER32, GDI32, NTDLL и SHELL32). Например, KERNEL32.DLL со-
держит функции GetSystemTime и GetLocalTime, которые Clock нужно вызвать при получении со-
общения WM_TIMER.
Вывод WV86TEST показывает, что DOS не видит почти ничего из этих ужасных действий,
связанных с загрузкой всей подсистемы Win32s. В сочетании с 32BFA и Win32s WfW 3.11 уже на-
чинает приобретать вид 32-битовой операционной системы, работающей в защищенном режиме,
сильно похожей на Windows 95. Действительно (и об этом мы еще будем говорить подробно в конце
этой главы) Windows 95 очень похожа на “улучшенную” Win32s, и, определенно, не является “NT
Lite”, как очень хотели бы думать некоторые компьютерные издания.
WV86TEST видит вызовы функций 2Ah и 2Ch из Win32-BepcHH Clock. Вспомним, однако, что
это CLOCK.EXE, поставляемая с Windows 95. Такому файлу нежелательно содержать прямые вы-
зовы INT 21h. На самом деле это не то что нежелательно, а просто невозможно: помещение _asm int
21h в Win32-иcпoлняeмый файл вызывает ошибку GP как в Win32s, так и в Windows 95. Каким же
образом WV86TEST замечает вызовы функций 2Ah и 2Ch INT 2lh от Clock?
Как уже отмечалось, обработчик WM_TIMER из Clock вызывает функции GetSystemTime и
GetLocalTime из Win32 API, экспортируемые из KERNEL32.DLL. Если воспользоваться командой
BMSG (Остановка по сообщению) в Win-ICE для наблюдения сообщений WM_TIMER,
поступающих в Clock, и при каждом останове трассировать (<F8>) код, то мы сразу попадем в
следующий блок кода из KERNEL32:
MOV AH,2Ah
CALL 80BDC3E4h
MOV AH,2Ch
CALL 80BDC3E4h
Числа 2Ah и 2Ch, записываемые в регистр АН, оказываются вполне знакомыми: это номера
функций Get Date и Get Time INT 21h. Дело в том, что функция с адресом 80BDC3E4h выполняет
Глава 14. Clock: смесь 32-битового и 16-битового кодов
407
INT 21h для Win32-npmro>KeHHft. Пройдя через несколько уровней кода, вы найдете следующую ин-
струкцию в W32SKRNL.DLL:
2197:80В927ВЗ FF1DF4D0B980 CALL FWORD PTR [80B9D0F4]
Странная на вид инструкция FWORD PTR ссылается на дальний указатель. В 32-битовом коде
дальний указатель имеет вид 16:32 и длину 48 бит (16 бит для селектора и 32 — для смещения).
FWORD PTR показывает, что процессор будет использовать 48-битовый адрес (16-битовый селек-
тор и 32-битовое смещение). Позже мы увидим, что Windows 95 использует точно такую же инст-
рукцию, когда 32-битовому коду нужно вызвать 16-битовый. Windows NT использует тот же меха-
низм как часть своей подсистемы WOW (Windows on Windows) для исполнения 16-битовых-прило-
жений. Великолепное обсуждение этих вопросов можно найти в статье Дж. Финнегана о WOW в
Microsoft System Journal, June 1994, особенно во врезке о CALL FWORD PTR (р. 34-35).
В нашем примере по инструкции CALL FWORD PTR шесть байтов по адресу 80B9D0F4h будут
интерпретироваться как 16:32 адрес для вызова:
:dw 80b9d0f4
2197: 80B9D0F4 02С5 0000 1137 ...
Другими словами, при выполнении CALL FWORD PTR [80B9D0F4] будет произведен дальний
вызов по адресу 1137:000002С5. В этой конфигурации селектор 1137h содержит 16-битовый код,
принадлежащий WIN32S 16:
:Idt 1137
1137 Code16 Base=80B3A620 Lim=0000057F DPL=3 P RE
:heap 1137
Han./Sei Address Length Owner Type Seg/Rsrc
1136 80B3A620 00000580 WIN32S16 Code 03
Таким образом, при выполнении инструкции CALL FWORD PTR 32-битовая часть Win32s
вызывает 16-битовую часть. Трассируя с помощью отладчика отдельно взятую инструкцию
W32SKRNL, вы неожиданно обнаружите, что просматриваете 16-битовый код WIN32S 16. Код по
адресу 1137:02С5 генерирует нашу любимую команду INT 2 lh для 32-битового кода.
Thunking: смешение 16- и 32-битового кода
j с помощью CALI FWORD PTR
! 1 i.i. ! \V( »1П ’ !': k f. <•:<. : I } i ‘ Oh - - :>'» v. . ,t.i > ’’2
! r’dl I < •..« му и- гл: < It' MiiO'-i't . . . .1. It ; <;'!»
i (nil'll •/кН'!: > ;n !/i'-t ... n !6 i. fei.-
i ' b : ч i • t,t : i- Л ; i» ». I • ’• : • = i''- 11 : Ц . t 1 , 't. ! 5 i
i ни -lit 1 ц. . нчи :? . i . •. ' . .r- ра.м.ма
i к»111иi.i.'i it!-.:.’:- ’ 1 v, Н;: t j'ik . • .',•••
I сгроке программы, нгнользуя также указанные значения дли LAX, ЕЗХ, 1л. л и LDX.
ДЯЖЖВОдаВШЯШЖйЖОВВвйИййЖ
Ifincluce
| в inc luce
408 Неофициальная Windows 95
void fail(const char *s) { puts(s); exit(1); }
«pragma pack(1)
typedef struct {
unsigned long ofs;
unsigned short seg;
} fword;
int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance,
LPSTR IpCmdLine, int nCmdShow)
{
// Microsoft C code per MSJ, May 1991, pp. 135-6
«define argc __argc
«define argv __argv
extern int ___argc;
extern char *•__argv.
char buf[80];
fword addr;
unsigned reax, rebx, recx. redx;
int i;
sscanf(argv[1], “%04X:%081X", Saddr.seg. Saddr.ofs);
sscanf(argv[2], “%081X/W81X/%081X/%081X",
&reax, &rebx, &recx, &redx);
sprintf(buf, “Calling %04X:%081X %081X/%081X/%081X/%081X".
addr.seg, addr.ofs, reax, rebx, recx, redx);
MessageBox(0, buf, “FWORD", MB_0K);
for(i=0: К1000; i++)
{
_asm mov eax, reax
_asm mov ebx, rebx
_asm mov ecx, recx
_asm mov edx. redx
_asm call fword ptr [addr]
_asm mov reax, eax
_asm mov rebx. ebx
_asm mov recx, ecx
_asm mov redx, edx
}
sprintf(buf, “Returned %081X/%081X/%081X/%081X".
reax, rebx, recx, redx);
MessageBox(0, buf. “FWORD". MB_OK);
return 0;
)
INTSERV.C (листинг 14.2) — это Win 16-программа, которая содержит три функции, вы-
зывающие прерывания INT 21h (DOS), INT 2Fh (мультиплексное прерывание) и INT 31h
(DPMI). INTSERV выводит на экран адреса трех функций, так что вы можете использовать
их в командной строке FWORD.
Глава 14. Clock: смесь 32-битового и 16-битового кодов 409
Листинг 14.2. INTSERV.C
/• л
INTSERV.C
bcc -W -3 -В intserv.c
Пульман, 1994 ,
•/
include <stdlib.h>
•include <stdio.h>
•include <dos.h>
•define NOGDI
•include “windows h“
static char dumy, // что-нибудь для того, чтобы узнать сегмент
// статические переменные только один вызов одновременно
•define SERVER(calIs, intno) { \
static char stack[1024]; \
static unsigned stack_seg: \
static unsigned prev_seg. \
static unsigned long prev_ofs; \
static unsigned prev_ds. \
_asm { \
push ax; \
push bx; \
mov ax, ds; \
mov bx, seg dumy; \
mov ds, bx; \
mov stack_seg, bx; \
mov prev_ds, ax; \
pop bx; \
pop ax; \
mov prev_seg, ss. \
mov dword ptr prev_ofs. esp; \
mov ss, stack_seg; \
mov sp, offset stack; \
add sp. 512; \
} \
calls++; \
_asm { \
int intno; \
mov ss, prev seg; \
mov esp, dword ptr prev_ofs; \
mov ds. prev_ds; \
db 66h; \
retf; \
) \
}
unsigned long calls21 = 0;
unsigned long calls2F 0;
unsigned long calls31 » 0;
void server21(void) ( SERVER(calls21, 0x21); )
void server2F(void) ( SERVER(calls2F, 0x2F); }
void server31(void) ( SERVER(calls3l, 0x31); )
410 Неофициальная Windows 95
int PASCAL WinMain(HANDLE hlnstance. HANDLE hprev21Instance,
LPSTR IpszOndLine. int nCndShow)
(
char buf[120],
int len;
len = sprintf(buf, “INT 21h server < %Fp\n" , (void far •) server21):
len ♦= sprintf(buf+len, "INT server 2Fh * Vp\n", (void far •) server2F);
len ♦= sprintf(buf+len, “INT server 31h e %Fp“, (void far •) server3l);
HessageBox(0. buf, “INTSERV . HB_0K)
// серверы активны до тех пор. пока не будет нажата клавиша ОК
len = sprintf(buf, "%lu calls to INT 21h server\n‘‘, calls2l):
len += sprintf(buf+len, "%lu calls to INT 2Fh server\n". calls2F)
len ♦= sprintf(buf+len, “»lu calls t INT 3ih server", calls3D
MessageBox(0. buf. “INTSERV, NB_0K)
return 0;
>
На рис. 14.1 показан пример совместной работы двух программ: 32-битовой FWORD и
16-битовой INTSERV. INTSERV выводит окно сообщения, указывающее, что ее сервер INT
21Ь находится по 1A0F:0170, INT 2Fh — по 1AOF:O1AE, a INT 31h — no 1AOF:O1EC Я хотел,
чтобы Win32-nporpaMMa FWORD получила свой PSP с помощью функции 62h (Get PSP)
прерывания INT 21h, поэтому я запустил следующую команду:
FWORD 1A0F 0170 6200/0/0/0
FWORD генерирует 100 вызовов, поэтому их легко заметить в выводе WSPY21 WSPY21
определенно показывает, что FWORD делает вызовы Get PSP. И, наконец, FWORD вывела
на экран возвращенные значения регистров ЕАХ, ЕВХ, ЕСХ и EDX с EBX=01D07h. PSP
FWORD - lB07h.
Рис. 14.1. 32-битовая программа FWORD в 16-бвтовая INTSERV демонстрвруют смешвванве
кода в Win32s в Windows 95
Глава 14. Clock: смесь 32-битового и 16-битового кодов
411
FWORD и INTSERV работают как в Win32s, так и в Windows 95. Из листингов 14.1 и 14.2
видно, что код достаточно прост. Единственная сложность существует в INTSERV, где прово-
дится временное переключение на 16-битовый стек для обслуживания запроса 32-битовой выз-
ывающей программы, например FWORD. INTSERV на входе должна запоминать 32-битовый
указатель стека вызывающей программы и восстанавливать его перед возвращением. Из-за ре-
ализации INTSERV.C код получился нереентерабельным, поддерживающим одновременно толь-
ко одну вызывающую программу. С другой стороны, прерывания, по которым обращается
INTSERV, сами по себе не являются реентерабельными, поэтому все, что я сделал в INSERV, —
создал свой собственный флаг InDOS.
Самое интересное в INTSERV.C расположено внутри макроса SERVER. Например,
функция, которая обрабатывает вызовы INT 2 lh от \\4п32-приложсн1П1, — не что иное, как
SERVER(calls21, 0x21). Это расширяется в следующий ассемблерный код:
push ax ;; сохранить AX и BX
pusn bx mov ax,ds ;; сохранить DS
mov bx,seg dummy ;; SEG DUMMY - это INTSERV DS
mov dS.bx ;; установить наи DS
mov stack_seg,bx mov prev_ds,ax pop bx ;; восстановить AX и BX
pop ax mov prev_seg.ss сохранить указатель стека
mov dword ptr prev_ofs.esp mov ss,stack_seg установить новый стек
mov sp,offset stack add sp.512 Inc dword ptr calls2l увеличить счетчик
int 21h выполнить!
mov ss,prev_seg mov esp.dword ptr prev_ofs ; восстановить указатель стека
mov ds,prev_ds ;; восстановить DS
db 66h ret ;; 66 RETF: 48-битовый дальний возврат
Заметим, что 16-битовая INTSERV должна постоянно запоминать и восстанавливать все
48 битов 32-битового указателя стека вызывающей программы (SS:ESP, а не SS:SP) и возвра-
щаться в 32-битовый код вызывающей программы с 48-битовым (16.32) дальним адресом воз-
врата. Кроме этих операций со стеком, вызов 16-битового кода из 32-битового достаточно
надежен. В действительности, его можно сделать даже более надежным, поскольку Джим
Финнеган, редактор этой книги, заметил, что “Согласно Intel, вы не обязаны заниматься по-
добными глупостями со стеком. Если вы оперируете В битом DS и ваш стек не превысил
64 Кбайт, то все будет в порядке”. В-бит определяет размер указателя стека, 32-битовый
регистр ESP или 16-битовый регистр SP, при неявных ссылках на стек (см. главу “Mixing 16-
Bit and 32-Bit Code" в i486 Microprocessor Programmer's Reference Manual фирмы Intel).
Хотя Win32s (и, как мы позже увидим, Windows 95) использует CALL FWORD PTR,
чтобы позволить приложениям Win32 вызвать INT 21Ь, это не единственный доступный
механизм. Посмотрите на конструкцию DB 66h RETF в только что показанном 16-битовом ко-
де, которая осуществляет возврат по 48-битовому дальнему указателю к 32-битовой вызыва-
ющей программе. Поместив адрес в стек, участок кода может воспользоваться RETF для воз-
врата к другому участку кода, который никогда его не вызывал. Таким образом, способность
возвращаться из 16-битового кода в 32-битовый код — тоже форма thunk-переключения.
Забегая немного вперед, скажем, что KERNEL32 в Windows 95 использует RETF в своей под-
программе QTThunk, которая вызывается Win32^ynKiuiHMH в USER32 и GDI32, вызываю-
щими старые Win 16 DLL. И хотя Microsoft отрицает, что KERNEL32 обращается к По-
битовому ядру, KERNEL32 тоже использует QT Thunk.
412 Неофициальная Windows 95
•S Л .: J' A' ' .i.s ’ • :' ' : •- r ?;’. s JЧ> I \ Г. .11 !..:•.:. . ;11. : • >p ! ’: " L '.‘i л ..iiii <M’ :
-t.4 6l’. WM .ы ii.i‘ili -. ....”: l) ’<> '.‘г: : ’:. к ' '•: г1-’<i--i--.. !»• । •':• i « :yt-..»«;:« • i
: ИИИИИИв
Н('рШ>:>.г: . : :' ' М1Ч.'- 1 (::;• .••,4,:.t' lit i : >.--•*: i.isi f: . : < Т.Н”. V. : 3.' .. X s И I
Windows lj> i "-it ..... <; i- s'>- >: ' .-•..•: ( г’.С’Ч m ‘?iinpi4 ?.',н mi. i;-n 'iiomiuix B’lii.’icit I
i <<> tiopoHbl p . ij'.iOHHif-.‘.и-. 4i“.i v\ ;•.’I’>-><!., 1НИ.Ч1 ipy,'!:-> и.-;н ч;ич !• n: и-. > <м<..<.нп :it-p< HvC’l И ч i
; Win32. n< < 'Л n.3. ny.i.iiшо ниях hs,:.::> Mr:>6\< .lint:.. Miru-n!: ».мби.'ьч >
ihoh принципы ’iiiiTniM L‘б«1УШ'1<| и б!.;и> енхб.шкоианп hi-ckhti.kii '
thunk ниirpiju i'n («в К i ivku.w нин i-i :i. ip<i p.i i .и'игыч .••-‘я ;»ц.< 4.i
• l.’mvef.a! I'h'ink 32. <« W.sltv! H:< - . 'Mh !«•*:’ .:>! 32 Li: Г. dr i
Yotir Лрр”.-.t'. !:<4-- n;!i '.h<- W.4.32' 5 ’inn ivd Ti.j
' ИИВ1!!11в«И1ИИ1^^
• G« ill rii I'iti::;i- и .4 I ч ' i *. ч Гл л . \i и: <; !<i m > ч Hit t «чi i1 * n<
Window*. X’; I >W I..-.:-..,.-1 <• v;. >. ;!il ’-mt». i
• Thunk ( ini!',':< f ; ‘A .mi-,'.. •• .......... i!1 \KMl- I X 1 - W::-i; ..i - ‘ч '•’! H.. :i j-thi: :ч i
r.»p. T НГХК.1 '.Г. i:: '.r::4.1- . i.l :s .' . i ......... ;•' ь>:и t . . : Ы-. J ! < :.;.••• i
ЯЫ( .• • .Krf ,.. -..n ' . i.n':;.':' ' iv.i <i>|u..i -.J.i !
il’HHMt ;•!> 1 ill:?:: I- < . 1’1 .. i: |i.t ;.i I < ip.i :
>i!l ;;•! <1 XSA: ;. j , I > : i'' (>,(!> |<>H ill”
Ht.i.Kti’-.i !. . M:• . 1».i;. .1!'.i.. ... i! . 1.1 »•-.« t- I: : : . '•.i-. 'I’. :i :.’Я ;
фпрчои H‘1 1-1 .'Hi .1 1 k!'i
Hpn UIKIIH nil .(.'.p.' <>' il*« -Ч-..I t.li; i f> >.: •Hl-: . :;..!< ' .1
i 'it.< 11't ПЧ1..1- Hi ;n члг: = :|'ЬИ. !'•• : к. Г. : ii .4 ,.'4lll,-i i‘.4i :. I .;;i:i l”ii.: i 5 I'.VOl.'i) :
i 1ЧК JMP PAil -.1 Iti’.I I
Раньше было показано, что реализация GetLocalTime в Win32s вызывает код Winl6, который,
в свою очередь, вызывает функции DOS GetDate и GetTime прерывания INT 21h. Вот заморочка,
правда? Хорошо, что Windows 95 — полноценная операционная система защищенного режима, не
требующая DOS для получения даты и времени. Что ж, давайте посмотрим...
32/32: Clock в Windows 95
Запустив версию программы в Win 16- и Win32-eepcHH Clock под WfW 3.11, давайте возьмем
Win32-eepcino Clock и запустим ее под Windows 95. (Позже мы рассмотрим работу и Win 16-версии
Clock под Windows 95.) Во время работы Clock программа WV86TEST регистрировала следующие
вызовы DOS:
Прошло 50 секунд
Вызовы NT 21h:
2А: 451 2С: 451 4С: 1 50: 23 55: 1
Снова вызовы функций 2Ah и 2Ch. Естественно, WSPY21 показывает, что Clock делает
намного больше вызовов, включая несколько новых функций работы с длинными именами (INT 21h
функция 71h):
<СЮСК> *(71) LFN (4Е) Find First ‘C:\WINDOWS\clock.ini’
<CLOCK> *(71) LFN (A1) Find Close
<CLOCK> *(71) LFN (60) Truename ‘C:\WINDOWS\CLOCK.EXE’
Глава 14. Clock: смесь 32-битового и 16-битового кодов
413
Но есть и нечто более интересное в выводе WSPY21 для Win32-eepcHH Clock под Windows 95:
нет вызовов функций 2Ah (Get Date) и 2Ch (Get Time)!
Мы только что видели, что WV86TEST под Windows 95 видит вызовы функций 2Ah и 2Ch во
время работы программы Clock. Как же может DOS видеть эти вызовы, если перехватчик INT 21h
верхнего уровня в WSPY21 их не видит? Откуда приходят эти вызовы?
Раньше отмечалось, что всякий раз, когда Win32-eepcwi Clock получает сообщение
WM_TIMER, она вызывает Win32^i>yHKmno GetLocalTime из KERNEL32.DLL. (Между прочим,
фраза “Win32-BepcHH Clock получает сообщение WM_TIMER” скрывает устрашающее количество
различных сложностей; в главе 13 кратко обсуждается, как Win32-nporpaMMbi получают сообщения
WM_XXX в Windows 95.)
В Win-ICE вы можете установить контрольную точку (BMSG) для WM_TIMER в программе
Clock. Когда она сработает, можно пройтись по коду, нажимая <F8>. После “груды” кода (включая
вызов KERNEL32!W32S_BackTo32, которая навевает воспоминания о Win32s), вы в конце концов
попадете в то место, где Clock вызывает GetLocalTime. Трассируя код этой функции в
KERNEL32.DLL, вы, наконец, получите следующий фрагмент кода, удивительно напоминающий
реализацию GetLocalTime в Win32s:
0137:BFF71186 MOV АН.2А
0137:BFF71188 CALL BFF71E5F
0137: BFF7119C MOV АН.2С
0137:BFF7119E CALL BFF71E5F
Функция по адресу BFF71E5Fh в KERNEL32 должна выполнять вызовы INT 21Ь для
приложений Win32. Исследовав эти функции в Win-ICE, получим:
:и bff71e5f
0137:BFF71E5F PUSH ЕСХ
0137:BFF71E60 PUSH ЕАХ
0137: BFF71E61 PUSH 002А00Ю
0137:BFF71E66 CALL KERNEL32!VxDCallO
0137:BFF71E6B RET
Из главы 13 известно, что VxDCallO 2A0010h вызывает INT 21h для Wip32-приложений, но мы
не заглядывали внутрь кода, чтобы увидеть его работу. Давайте сделаем это сейчас:
:и vxdcallO
KERNEL32!VxDCallO
0137:BFF71F10 POP DWORD PTR [ESP]
0137:BFF71F13 CALL FWORD PTR CS:[BFFB5004]
И снова CALL FWORD PTR! Microsoft много протестовала, что “Chicago — это не Win32s!”
Хотя есть много различий между Windows 95 и Win32s (и хотя группа Chicago в Microsoft, быть
может, не использовала большую часть кода, написанного группой Win32s в Microsoft Israel, тем не
менее, в Microsoft не любят изобретать колесо заново), у этих двух сред есть и много общего.
Помните, что CALL FWORD PTR использует 16:32 (48-битовый) адрес. Проверив шесть байтов
по BFFB5004, получим:
:dw bffb5004
0137:BFFB50D4 D3B6 0000 003В
Таким образом, функция VxDCall в KERNEL32 — не более чем причудливый способ вызова
кода по адресу 033В:03В6. Это в свою очередь:
003В:000003В6 INT 30
414
Неофициальная Windows 95
Увидеть INT ЗОЬ после всех наших продолжительных изысканий — это что-то вроде того, если
бы вам сказали, что смысл Жизни, Вселенной и вообще Всего — 42. Какой смысл в замене INT 21Ь
на INT ЗОЬ? Что такое INT ЗОЬ?
Вспомним из главы 8, что INT ЗОЬ — это обратный вызов защищенного режима. VxD генери-
руют эти обратные вызовы защищенного режима с помощью Allocate_PM_Call_Back, которая обес-
печивается VMM и документирована в Windows DDK. INT ЗОЬ — это переходник, позволяющий
приложениям защищенного режима с уровня 3 переходить на код уровня 0 в VMM и VxD. Выпол-
нение INT ЗОЬ приводит к переходу на обработчик обратного вызова VMM, который, основываясь
на смещении в сегменте ООЗВЬ, из которого поступил INT ЗОЬ (в данном примере — ОЗВбЬ), перей-
дет на соответствующий обратный вызов защищенного режима.
На рис. 14.2 изображено то, о чем мы уже давно говорим: VxDCallO — недокументированная
функция, экспортируемая KERNEL32.DLL. VxDCallO ожидает номер Win32-cepBHca (например,
2A0010h) VxD и любых значений для ЕАХ и ЕСХ в стеке (см. ниже в этой главе программу
WIN32PSP.C). 2А0010Ь указывает VxD с идентификатором #002Ah, Win32-cepBHc — #0010Ь.
Обработчик обратного вызова защищенного режима декодирует этот запрос. VxD 2АЬ — это
VWIN32 и обработчик вызовет его сервис #10.
Рис. 14.2. Как Win32-программа Clock получает текущую дату и время, часть 1. Получив
сообщение WM_TIMER, программа Clock вызывает Win32 функцию GetLocalTime, которая
выполняет VxDCallO 2A0010h с AH=2Ah и 2Ch. VxDCallO приводит к выполнению обратного
вызова защищенного режима, который отлавливается VMM (часть 2 см. на рис. 14.3 )
Глава 14. Clock: смесь 32-битового и 16-битового кодов
415
Win32-cepBHc #10 в VWIN32 выдаст INT 21h для Win32-npHJioxeHHil, вызывая Exec_PM_Int с
параметром 21h. На рис. 14.3 изображена следующая часть диаграммы.
Exec_PM_Int — это оболочка для других вызовов VMM:
Set_PM_Exec_Module
Begin_Use_Locked_PM_Stack
Exec_Int
End_Nest_Exec
A Exec_Int, в свою очередь, эквивалентна Simulate_Int, за которой следует Resume_Exec.
В основном вызов Exec_PM_Int посылает имитированное прерывание в цепочку прерываний
защищенного режима. Таким образом, механизм VxDCallO 2A0010h подобен коду
FWORD/INTSERV, приведенному в листингах 14.1 и 14.2.
Рис. 14.3. Как Win32-nporpaMMa Clock получает текущую дату
и время, часть 2. Обратный вызов, выполняющийся внизу рис.
14.2, ответствен за дешифровку запроса Ш1п32-сервиса. Сервис
2А0010Ь обеспечивается VxD VWIN32 и использует
Exec_PM_INT в VMM для вызовов INT 21h Шш32-программам
(часть 3 см. на рис. 14.4)
Если никакой обработчик прерывания в защищенном режиме не поглощает вызов, то управ-
ление придет к стандартному обработчику защищенного режима, который отразит вызов в цепочку
У86-перехватчиков прерывания (т.е. цепочку VxD-обработчиков прерываний, установленных с
помощью Hook_V86_Int_Chain). Если не окажется VxD (такого, как IFSMGR), который обра-
батывает имитированное прерывание V86, то стандартный обработчик VMM передаст вызов уже в
настоящую цепочку V86 прерывания (т.е. цепочку обработчиков прерывания реального режима,
которые Windows выполняет в режиме V86). Тут важно понимать разницу между цепочкой
прерывания V86 (обработчики прерывания реального режима, включая DOS, драйверы устройств и
TSR) и тем, что я называю цепочкой перехватчиков прерывания V86 (обработчики защищенного
режима, установленные VxD).
Если ни один VxD не поглощает INT 21h, имитированный в VWIN32 с помощью Exec_PM_Int,
вызов INT 2lh отсылается к DOS и V86TEST видит вызов, хотя WSPY21 его не видит. (WSPY21 не
видит вызова, так как он зародился не посредством INT 2lh, Dos3Call или NoHookDosCall, которые
в основном отлавливаются WSPY21.)
На рис. 14.4 представлена следующая часть нашего сложного пути, которым проходит запрос
даты и времени от Win32-программы.
416
Неофициальная Windows 95
Рис. 14.4. Как Win32-nporpaMMa Clock получает дату и время, часть 3. Сервис
Exec Pm lnt в VMM посылает имитированные вызовы функций 2АЬ и 2СЬ
прерывания INT 21Ь по цепочке прерывания защищенного режима. Если никакой
обработчик INT 21Ь защищенного режима не обслужил вызов, он затем посылается в
цепочку перехватчиков прерывания INT 21h VxD. Если и здесь вызов не будет
обслужен, он будет отправлен по цепочке прерывания V86 и, в конце концов, попадет
в MS DOS. Рис. 14.5 должен объяснить, что происходит с вызовами внутри DOS
Рисунки с 14.2 по 14.14 раскрывают гораздо более сложный и интересный механизм, чем
банальный INT 21h, однако DOS до сих пор видит вызовы функций 2Ah или 2Ch INT 21h, как и
это показано в выводе WV86TEST, с которого начиналось это изложение.
Действительно ли Windows 95 обращается
прямо к DOS? И что же она там делает?
К этому моменту у вас, должно быть, появился некоторый скепсис и беспокойство относительно
того, действительно ли Windows 95 обращается к MS DOS для выполнения таких функций Win32
API, как GetLocalTime.
Во-первых, действительно ли из того, что V86TEST видела обращения Windows к функциям
2Ah и 2СЬ прерывания INT 21h, следует то, что DOS тоже видит эти обращения? Несомненно,
Глава 14. Clock: сллесь 32-битового и 16-битового кодов
417
14 Неофициальная Windows 95
V86TEST передает все обращения предыдущему обработчику прерывания (см. V86TEST.C в главе
10); Но мы знаем из предыдущих глав, что IFSHLP.SYS располагается между V86TEST и DOS и
что одна из основных обязанностей IFSHLP состоит в том, чтобы перенаправлять вызовы DOS в
IFSMGR (см. “Роль IFSHLP.SYS и У86-обработчиков завершения вызова” в главе 8). Итак,
вполне возможно, что V86TEST видит эти вызовы, a DOS — нет. Я уделю внимание этой
проблеме позже.
Во-вторых, этим мы могли бы взволновать знаменитого физика Вернера Гейзенберга. Как одно
из пояснений открытого им принципа неопределенности (гласящего, что вы не можете одновременно
с определенной степенью точности указать положение и момент субагомарной частицы), Гейзенберг
изобрел “мысленный эксперимент” с использованием воображаемого гамма-лучевого микроскопа.
При использовании этого микроскопа Гейзенберга процесс наблюдения воздействует на частицу и
изменяет ее положение или момент, которые она имела бы в ненаблюдаемом состоянии. И чем
лучше разрешающая способность микроскопа, чем больше проводится Наблюдений, тем больше они
влияют на предмет.
Это прекрасная аналогия (но не более того) с вычислительными процессами: используя один
элемент программного обеспечения для изучения другого элемента, с которым он взаимодействует,
опасайтесь построить микроскоп Гейзенберга, который изменяет поведение объекта наблюдения.
Итак, является ли V86TEST микроскопом Гейзенберга? Изменяет ли простой акт загрузки
V86TEST перед Windows способ взаимодействия Windows с DOS? Может ли присутствие V86TEST
спровоцировать Windows посылать обращения к функциям 2Ah и 2Ch INT 21h прямо в DOS? А
если V86TEST отсутствует, то делает ли это Windows?
Это довольно важные вопросы. Microsoft долго утверждала, что Chicago полностью обходила
бы DOS, если бы в системе не было перехватчиков INT 21h (предположительно, отличных от самой
DOS). Присутствие даже единственной TSR, перехватывающей INT 21h, по-видимому, должно
вынуждать Chicago посылать все вызовы DOS по цепочке INT 21h. Итак, возможно, V86TEST и
есть такой TSR, и, возможно, без V86TEST Windows вела бы себя совершенно иначе?
Существует несколько способов это проверить. Во-первых, заметим, что это противоречит дру-
гим утверждениям Microsoft, высказанных goyim (то бишь “для массового потребителя”, на идиш),
в которых Microsoft в явной форме утверждала, что Windows будет передавать все вызовы INT 21h,
не касающиеся файлового ввода-вывода. Еще раз обратимся к документу Microsoft, озаглавленному
“Chicago File .System Features”, процитированному в главе 8: “По умолчанию все прерывания INT
21h, за исключением файловых, передаются вниз”.
В этой книге я наверняка уже цитировал это высказывание несколько раз — всякий раз напо-
миная вам, что DOS сама является перехватчиком INT 21h — и даже не пытался эксперимен-
тировать. Этим тезисом Microsoft объявила, что Windows 95 — включая Win32-npmio>KeHHH, выпол-
няющиеся под Windows 95, — обращается к DOS для таких не-файловых сервисов, как получение
и установка даты и времени. Но описание в документации взаимодействия Windows с перехват-
чиками INT 21h не слишком вас вдохновит (меня так точно) до тех пор, пока вы не проведете
эксперименты, чтобы понять, о чем же говорится в документации. Этот тезис вступает в резкое про-
тиворечие с пропагандистским лозунгом Microsoft “Windows 95 не нужна DOS”, поэтому боль-
шинство читателей, возможно, сосредоточатся на части “за исключением файлового INT 21 API”.
Windows 95 действительно привносит во взаимоотношения с DOS только то, что и WfW3.ll — 32-
битовый доступ к файлам.
В любом случае, утверждение Microsoft согласуется с нашим выводом, что Windows 95, подоб-
но Win32s, реализует некоторые функции Win32 API путем (хотя и окольным) вызова кода
MS DOS реального режима (который Windows выполняет в режиме V86).
Во-вторых, сравнивая вывод WV86TEST и WSPY21, мы видели, что многие обращения DOS не
передаются вниз, даже когда загрузилась V86TEST. Это тоже согласуется с заявлением Microsoft о
файловой системе Chicago: “Вызовы файлового API INT 21 действительно передаются перехват-
чикам из VM (локальным), но не глобальным (AUTOEXEC.BAT)”. V86TEST является глобальным
обработчиком INT 21h. Мы видели, что Windows не передает ей никаких файловых вызовов INT
21h. В таком случае кажется вполне правдоподобным предположение, что некоторые из вызовов,
прошедших вниз, прошли бы и без V86TEST.
418
Неофициальная Windows 95
В-третьих, работая без V86TEST и поместив контрольную точку отладчика в коде DOS ре-
ального режима, мы можем убедиться, что вызовы INT 21h, замеченные V86TEST, например функ-
ции 2Ah и 2Ch, действительно передаются в DOS даже при отсутствии V86TEST. На это стоит
потратить время не только ради доказательства, что V86TEST — это не микроскоп Гейзенберга
(было бы о чем волноваться!), но потому, что это помогает принять критическую точку зрения отно-
сительно взаимодействия DOS и Windows.
Я запустил Chicago без V86TEST и из окна DOS запустил программу INTCHAIN, используя ее
для обнаружения DOS-обработчика INT 21h. Начиная с момента, когда INTCHAIN выполняет
инструкцию INT, полученную из командной строки (например “21/2А00” для функции 2Ah
INT 21h), до тех пор, пока прерывание не возвратится к следующей строке кода за командой INT,
INTCHAIN шаг за шагом проходит по цепочке прерываний, отображая ключевые адреса, если
изменяется сегмент или встречается другая инструкция INT. Я добавил комментарии к следующему
выводу INTCHAIN:
C:\UNAUTHW>intchain 21/2а00 ;; функция 2Ah INT 21h (Get Date)
Tracing INT 21 AX=2A00
494 instructions
Skipped over 5 INT
0337:04А8 IFSSHLPS ;; IFSHLP.SYS
01EF:0023 D:
0498:1956 DBLSYSHS ;; DBLSPACE.BIN
OOAO:OFAC DOS ;; остатки в нижней памяти, когда DOS в НМА (DOS=HIGH)
FE9E:4249 HMA ;; DOS-обработчик INT 21h в НМА
FE9E:4339 HMA INT 2Ah AX=8200h ;; Конец критической секции 0-7
FE9E:5354 HMA INT 2Ah AX=8002h ;; Начало критической секции 2
0070:0166 10 ;; Процедура Strategy драйвера устройства
FE9E:8979 HMA
0070:00EE 10 ;; Процедура Interrupt драйвера CLOCKS
FFFF:0040 HMA
FFFF:08DA HMA INT 1Ah AX=0000h ;; CLOCKS вызывает 1А/00 (Get system time)
FE9E:8985 HMA
FE9E:5383 HMA INT 2Ah AX=8102h ;; Конец критической секции 2
Позже мы более детально изучим эту цепочку DOS —> CLOCKS —> INT lAh. Сейчас же нам
нужно знать только одно: FE9E:4249 — адрес обработчика INT 21h DOS. Мы можем использовать
Win-ICE, чтобы установить контрольную точку на этом адресе режима V86 (ВРХ &FE9E:4249).
Вполне понятно, что контрольная точка срабатывает, показывая, что Windows действительно
обращается прямо к DOS. Этим дается отрицательный ответ на первый вопрос, относительно того,
что, быть может, IFSHLP перенаправляет все вызовы INT 21h в IFSMGR. Действительно, почему
он должен это делать? IFSMGR обрабатывает только около сорока различных функций INT 21h.
При отсутствии других VxD, подобных моим CURRDRIV.386 и INTVECT.386 из главы 8, которые
обрабатывают INT 21h, остальные шестьдесят или около того не так часто используемых и менее
“дорогостоящих” функций INT 21h должны отсылаться в DOS. Контрольная точка Win-ICE в DOS-
обработчике INT 21h показывает, что так оно и есть.
Итак, теперь мы знаем, что Windows 95 действительно обращается к DOS, даже в отсутствие
обработчика INT 21h, подобного V86TEST. Мы могли бы оставить это как есть, трактуя DOS как
монолитный черный ящик. Но вместо того чтобы молча довольствоваться неопределенным
утверждением “Windows обращается к DOS”, давайте все-таки всмотримся (на языке Microsoft,
“пробуравим”) DOS-обработчик INT 21h. Разобравшись с происходящим внутри DOS при
обращении Windows к функции 2Ah INT 21h, мы увидим, почему так важно помнить, что Windows
выполняет DOS в режиме V86, а не в реальном режиме, и почему это подобно выполнению кода
DOS реального режима в защищенном режиме.
Если вы протрассируете DOS-обработчик INT 21h, вас, вероятно, удивит нечто подобное
следующему:
Глава 14. Clock: смесь 32-битового и 16-битового кодов
419
FE9E:000042F1 FE9E:000042F3 MOV SHL BL, AH BX,1
FE9E:00004354 MOV BX,CS:[BX+3FE1]
FE9E:00004359 XCHG BX,SS:[05EA]
FE9E:0000435Е MOV DS,SS:[05EC]
FE9E:00004363 CALL SS:[05EA]
;; BX = 2 * номер функции INT 21h
;; индекс в таблице
;; вызов обработчика функции INT 21h
Этот решающий кусочек кода с душераздирающими подробностями объясняется в главе
“Disassembling DOS” книги Undocumented DOS (2d ed., p. 291-297). Коротко говоря, регистр ВХ
хранит номер функции INT 21h (например, 2Ah), умноженный на два. Это число используется для
индексирования в таблице, расположенной здесь по смещению 3FElh в кодовом сегменте DOS. Эта
таблица хранит указатели для обработчиков DOS каждой функции INT 21h. Предшествующий код
выбирает и вызывает этот указатель функции. С помощью такой утилиты, как FTAB (версия
PROTTAB для реального режима), можно получить дамп этой таблицы и локализовать обработчики
для Get/Set Date/Time функций INT 21h:
C:\UNAUTHW>ftab fe9e:3fel 73 121 2 ;; Показать 73h слов в FE9E:3FEL
FE9E:48B8
FE9E:48D5
FE9E:48F5
FE9E:4906
121.2A
121.2B
121.2C
121.2D
;; Получить дату
;; Установить дату
;; Получить Время
;; Установить Время
Теперь мы можем поместить контрольную точку на обработчик функции 2Ah INT 21h. по
FE9E:48B8 и снова запустить Clock. Если контрольная точка срабатывает, значит Windows 95
обращается к DOS. Приведу часть трассировки Win-ICE, которую я сохранил в файле, пропустил
через утилиту VXDNAME, а затем сопроводил обычными комментариями, в духе Талмуда:
Break Due to ВРХ &FE9E:0D0048B8 С=01
FE9E:000D48B8 PUSH FE9E:000048B9 POP SS вызвать код DOS, который обрабатывает 21/2A DS
; Подготовить пакет запросов драйверу устройства. Функция 4 (читать), управляемая
; драйвером. Буфер указывает на 6-байтовую структуру CLOCKS
FE9E:00008980 CALL 007D OOOOODEE CALL FAR SS:[0378] ;; DOS вызывает драйвер CLOCKS 01AF ;; это процедура Interrupt CLOCKS
D070:000001BF JMP FFFF 00000040 PUSHA FAR CS:[013A] ;; перейти в HMA (DOS=HIGH)
FFFF:000008D8 SUB . FFFF:000008DA INT AH, AH ;; CLOCKS вызывает функцию 0 INT 1Ah (Get Sys Time) 1A
Итак, Windows 95 обращается к DOS. В свою очередь, DOS-обработчик функции 2Ah вызывает
процедуру Interrupt драйвера устройства CLOCKS. Тот, в свою очередь (предполагается, что вы не
используете какой-то новый драйвер CLOCKS), получает текущие дату и время для DOS вызовом
функции О INT lAh ROM BIOS (Получить системное время).
Почему CLOCKS не обращается непосредственно к области данных BIOS, которая содержит
счетчик импульсов по адресу 46Ch (0040:006С) и флаг полуночи (к сожалению, для тех, чьи
компьютеры работают несколько дней подряд, это действительно флаг, а не счетчик!) по 470h
(0040:0070)? Исходный текст CLOCKS, включенный Microsoft в DOS 5.0 OEM Adaptation Kit
(OAK), содержит следующий комментарий:
; 11/26/91 NSM М101: Иногда под Windows теряется дата. Чтобы решить эту проблему,
; вместо обращения no 40:6ch вызывается Int 1а, и если произошел сбой, то
; текущие время и дата обновляются из CMOS.
420
Неофициальная Windows 95
Это взято из исходного текста PTIME.ASM — версии CLOCKS для управления питанием в
POWER.EXE. Но стандартная версия CLOCKS в MSCHAR.ASM тоже вызывает ROM BIOS
INT lah для получения времени, видимо, из тех же самых соображений.
Итак, CLOCKS, по крайней мере, думает, что он вызывает ROM BIOS. На самом же деле,
поскольку мы работаем в режиме V86 под Windows, все векторы прерываний идут через IDT (см.
главу 9). Элемент IDT для INT lAh установлен на VMM. Итак, когда в DOS драйвер устройства
CLOCKS обращается к INT lAh, на самом деле он вызывает VMM. Windows может обращаться
прямо к DOS в режиме V86 для обработки функции 2Ah прерывания INT 21h, но DOS (сама того
не зная) снова обращается к Windows для обслуживания функции "О INT lAh. Если в Soft-ICE вы
нажмете <F8> (trace) для “пробуравливания” сквозь инструкции INT lAh, то увидите тому под-
тверждение. От INT lAh внутри DOS-драйвера устройства CLOCKS вы внезапно переходите в 32-
битовый защищенный режим к следующей инструкции внутри VMM:
FFFF:000008DA INT 1А ;;; VMM перехватывает INT 1Ah через IDT
;;; INT переводит вас из режима V86 DOS непосредственно в
;;; 32-битовый защищенный режим VMM!
0028:VMM+2E44 CALL VMM+000 ;;; адрес возврата - VMM+2E49
При получении этого DOS-вызова INT lAh VMM проводит общую начальную предобработку и
затем переходит к цепочке перехватчиков прерывания V86:
0028:VMM+000 CLD ; ;; общий обработчик INT в VMM
0028:VMM+001 PUSHAD
0028:VMM+002 MOV EAX,[ESP+20] ; ;; адрес возврата (2E49h)
0028:VMM+006 SUB EAX,VMM+2D79 ; ;; отнять базу обработчиков INT
0028:VMM+00B SHR EAX,03 ; ;; (адрес - база)/8 = номер прерывания (1Ah)
0028:VMM+00E MOV EBP, ESP ; ;; создать структуру регистров клиента
0028:VMM+010 MOV BX,0030
0028:VMM+014 MOV DS.BX ; ;; взять сегмент данных VMM
0028:VMM+016 MOV ES.BX
0028:VMM+018 MOV EDI,[VMM+F670] ;; EDI = дескриптор текущей ветви выполнения
0028:-VMM+01E XCHG ESP,[EDI+48] ;; переключиться на стек ветви
0028:VMM+021 MOV EBX,[VMM+F6E4] ;; ЕВХ = дескриптор текущей VM
0028:VMM+027 STI ;; разрешить прерывания
0028:VMM+028 PUSH VMM+2E0 ;; поместить в стек адрес возврата (см. ниже)
0028:VMM+02D JMP [VMM+D228+4*EAX] ;: перейти к цепочке перехватчиков INT 1Ah V86
0028:C0FCE1BC CALL VTD+1C0 ;; цепочка перехватчиков прерывания V86
VxD устанавливают свои перехватчики в цепочку перехватчиков прерывания V86, естественно,
с помощью Hook_V86_Int_Chain. В процессе инициализации Windows виртуальное устройство
таймера (VTD) вызывает Hook_V86_Int_Chain с ЕАХ=1АН и ESI=VTD+lCOh. Итак, получив INT
lAh в режиме V86, VMM вызывает процедуру по адресу VTD+lCOh:
0028:VMM+02D JMP [VMM+D228+4*EAX] ;;; перейти к цепочке перехватчиков INT 1Ah V86
Q028:C0FCE1BC CALL VT0+1C0 ;;; цепочка перехватчиков прерывания V86
Таким образом, обращение к функции О INT 1АЬ, из самой глубины драйвера устройства
CLOCKS, завершается в 32-битовом обработчике защищенного режима прерывания INT lAh
устройства виртуального таймера Windows:
0028:VT0+1C0 CMP BYTE PTR [EBP.Client
0028:VTD+1C4 JB VTD+1C8
0028:VTD+1C8 CMP BYTE PTR [EBP.Client
0028:VTD+1CC JA VTD+1F8
0028:VTD+1CE JB VTD+1E2
0028:VTD+1E2 MOV EAX,[VTD+3A4]
0028:VTD+1E7 MOV [EBP.Client_DX], AX
0028:VTD+1EB SHR EAX,10
.АН], 10
;;; функция INT 1Ah < 10h? Да
.АЙ],01
; функция INT 1Ah > 1? Нет
; функция INT 1Ah < 1? Да
; обработчик функции 0 INT 1Ah
; возвратить СХ:0Х=импульсов с полуночи
Глава 14. Clock: смесь 32-битового и 16-битового кодов
421
0028: VTD+1EE MOV [EBP.Client_CX], AX
0028:VTD+1F2 MOV- BYTE PTR [EBP.Client_AL], 00 ;;; флаг полуночи = О
0028:VTD+1F6 CLC ;;; обработано - дальше не передавать
0028:VTD+1F7 RET
Версия VTD в Windows 95 обрабатывает вызов INT lAh полностью в 32-битовом защищенном
режиме, загружая структуру регистров клиента из переменной (VTD+3A4h), которая хранит теку-
щее время в импульсах с полуночи.
Каким образом VTD поддерживает этот счетчик импульсов? Переменная устанавливается обра-
ботчиком функции 1 INT lAh (Set Time) VTD, которая вызывается из функций DOS 2Bh (Set
Date) и 2Dh (Set Time). Однако VTD не может полагаться на эти функции для обновления счет-
чика импульсов, поскольку они вызываются только тогда, когда пользователь изменяет дату или
время (например, в Control Panel).
Как упоминалось раньше, область данных BIOS содержит счетчик импульсов по 46Ch
(0040:006С) и флаг полуночи по 470h (0040:0070). Вы можете подумать, что VTD мог бы под-
держивать свои собственные счетчик импульсов и флаг полуночи с помощью чтения области данных
BIOS. Однако VTD читает эти значения только один раз в процессе инициализации. Фактически
отношение между VTD и BIOS строится иным путем. Пока Windows работает, VTD сам отвечает за
модификацию данных BIOS (благодаря его перехватчику V86 INT ICh).
Итак, откуда же берутся счетчик импульсов и флаг полуночи VTD? В процессе инициализации
VTD вызывает VPICD Virtualize lRQ виртуального контроллера программируемых прерываний
(Programmable Interrupt Controller — PIC) для регистрации HW_Int_Proc для IRQ 0. Запрос на
прерывание 0 является аппаратным прерыванием таймера, обычно связанным с INT 8. VPICD
перемещает IRQ на диапазон с INT 50h по 5FH, поэтому под Windows действительное аппаратное
прерывание таймера вызывается как INT 50h. Приблизительно 18,2 раза в секунду VPICD получает
одно из этих прерываний таймера.
Среди всего прочего VPICD вызывает VTD-процедуру Hw_Int_Proc. Hw_Int_Proc увеличивает
счетчик импульсов VTD, устанавливает флаг полуночи (VTD поддерживает флаг полуночи, а не
счетчик) и т.д. Одна из его прочих обязанностей состоит в том, чтобы вызывать сервис VMM
Update_System_Clock, который может приводить к срабатыванию приоритетного переключателя за-
дач, если VM или подзадача израсходовала свой квант времени.
Таким образом, VTD не только обрабатывает вызов INT lAh из CLOCKS полностью в защи-
щенном режиме, но и (совместно с VPICD) управляет аппаратным прерыванием таймера. Это слу-
жит примером более общего положения: VxD теперь играют роль, аналогичную той, которую
выполняла только ROM BIOS. (См. программу HOOKINT в главе И.)
И будет небольшим преувеличением сказать, что VxD теперь и есть ROM BIOS! И так же как
исходные тексты BIOS IBM были основой для явления, характеризуемого как революция персо-
нальных компьютеров, формирование промышленной стандартной архитектуры PC или же как
превращение PC в товар, по-видимому, полные листинги подсистем VMM и VxD могли бы стать
основой для новой промышленной стандартной архитектуры. Я бы сказал, что VxD уже утвер-
дились в качестве промышленного стандарта де факто на архитектуру. К сожалению, эта де факто
архитектура недостаточно широко понимается.
Более широкое понимание VMM и VxD могло бы проложить путь радикальным усовершен-
ствованиям в архитектуре PC. Первая возможность состоит в полной замене BIOS 32-битовыми
VxD защищенного режима и затем в размещении всего слоя VMM/VxD в ROM так, чтобы он
присутствовал с момента загрузки машины. Вот это будет действительно интегрированный Windows-
РС. Ощутимые выгоды такого размещения, однако, менее ясны.
Во всяком случае, мы видели, что, даже когда Windows посылает вызовы непосредственно в
реальный режим DOS, они могут попадать обратно к VxD. Итак, Windows отражает все вызовы
функций 2Ah и 2Ch в DOS. На рис. 14.5 показано, как DOS вызывает CLOCKS, a CLOCKS
обращается к функции 0 INT lAh, которая обрабатывается полностью внутри VTD.
422
Неофициальная Windows^
Рис. 14.5 отвечает на вопросы, с которых начинался этот раздел. Да, Windows действительно
вызывает DOS. Однако DOS, которую Windows выполняет в режиме V86, существенно отличается
от обычной. Когда Windows обращается к DOS, последняя может (не подозревая о том) обратиться
опять к Windows.
Рис. 14.5. Как Win32-nporpaMMa
Clock получает дату и время,
часть 4. Windows посылает вы-
зовы функций 2АЬ и 2Ch INT
21h в DOS, но, когда DOS-драй-
вер устройства CLOCKS вызыва-
ет INT lAh, этот вызов отлавли-
вается обратно в Windows. VMM
посылает вызов INT 1АЬ вирту-
альному устройству таймера
(VTD).
Что происходит, когда VTD возвращается из своего обработчика функции О INT lAh? VTD был
вызван из VMM и поэтому возвращается в VMM. VMM проверяет CF, который VTD сбрасывает
для обозначения того, что VMM не должен передавать вызов INT lAh другим обработчикам:
0028:COFCE1C1 MOV ЕАХ, 0000001A ;; обратно в цепочку прерывания V86
D028:C0FCE1C6 MOV EBX, [VMM+F6E4] ;; EBX = дескриптор текущей VM
0028:C0FCE1CC JB VMM+1424 ; ; JB=JC. CF сброшен, значит INT обработан
0028:C0FCE1D2 RET ; Все!
В документации DDK по Hook_V86_Int_Chain говорится, что “Если процедура обслуживает
прерывание, то она должна очищать флаг CF для предотвращения передачи прерывания следующей
процедуре”. Это вежливое замечание имеет огромный подтекст. Сбрасывая CF, обработчик преры-
вания V86 из VxD предотвращает не только вызов другого VxD, но и любого обработчика INT lAh
реального режима!
Позвольте мне повторить: любой обработчик INT lAh реального режима никогда не увидит это-
го вызова INT lAh. Такова оборотная сторона тезиса о том, что VTD управляет вызовами полностью
в защищенном режиме.
Полностью означает, что ваша TSR, ожидающая по каким-либо причинам вызов функции О INT
lAh, никогда его не дождется. Это также означает, что многие диагностические средства DOS
(например, Manifest фирмы Quarterdeck) безнадежно устарели. Если вы запустите одно из этих
инструментальных средств, оно сообщит, где обрабатывается INT 1АЬ:
INT 1А: System Timer
F000:FE6E System ROM
Однако это совершенно неверно. Когда DOS вызывает INT lAh, вызов попадает в VMM, кото-
рый передает его VTD, который опять возвращает его VMM и тот (как мы увидим ниже) исполь-
зует IRETD для возвращения в DOS. Вызов никогда не попадает к F000:FE6E. Инструментальные
средства такого типа, как Manifest (или программа INTVECT) получают этот адрес F000:FE6E с
Глава 14. Clock: смесь 32-битового и 16-битового кодов
423
помощью вызова функции 25h INT 21h или из IVT. Но это больше не будет удачным способом
нахождения места, где обрабатываются прерывания. Фактически они не годятся уже с тех пор, как
в мае 1990 г. Microsoft представила расширенный режим Windows 3.0. С тех пор VxD играют
основную роль системной ROM.
Если вам слишком трудно в это поверить, поэкспериментируйте с программой HOOKINT из
главы 11. Например, в старой доброй DOS можно загрузить HOOKINT и выдать команды DATE и
TIME, предоставляемые DOS. Если вы измените дату и время, HOOKINT покажет около 20
обращений к функции 0 INT lAh. Теперь выпрлните ту же операцию под расширенным режимом
Windows, даже под расширенным режимом Window’s 3.0 1990 г. HOOKINT не увидит вызовов INT
lAh, и ROM BIOS тоже. Где они обрабатываются? В VTD, конечно!
Как только VTD обработал вызов INT lAh и возвратился в VMM, последний готов вернуться
туда, где INT lAh был вызван изначально (в данном случае, драйвер устройства CLOCKS):
0028:VMM+2E0 MOV EBX,[VMM+F6E4]
0028:VMM+2E6 MOV EDI,[VMM+F670]
0028:VMM+2EC CLI
0028:VMM+2ED XOR ЕАХ, EAX
0028:VMM+2EF CMP EAX,[VMM+DC88]
0028:VMM+2F5 JNZ VMM+304
0028:VMM+2F7 TEST BYTE PTR [EBX],20
0028:VMM+2FA JNZ VMM+358
0028:VMM+2FC XCHG ESP,[EOI+48]
0028:VMM+2FF POPAD
0028:VMM+300 ADD ESP,+04
0028:VMM+303 IRETD
FFFF:08DC TEST BYTE PTR [0008],80
; получить дескриптор текущей VM
; получить дескриптор текущей ветви
; запретить прерывания
; есть ожидающие события?
; нет
; статус VM & РМ_Ехес?
; нет. VM в режиме V86.
; переключить стек обратно
;;; возвратиться в режим V86
;;; обратно в CLOCKS после INT 1Ah
Вот самые важные две строки:
0028:VMM+2EF СМР ЕАХ,[VMM+0C88]
0028:VMM+2F5 JNZ VMM+304
;;; есть ожидающие события?
;;; нет
Непосредственно перед возвратом (в данном случае, в драйвер CLOCKS, который, не подозре-
вая этого, вызвал VMM выдачей INT lAh в режиме V86) VMM всегда проверяет, есть ли какие-
либо ожидающие события. Эти события не следует путать с событиями, получаемыми Windows-при-
ложениями в форме сообщений WM XXX, а также с событиями, которые получают VxD в форме
сообщений управления системой. События являются функциями, вызов которых VxD запрашивают
у VMM, когда это безопасно для последнего. Это как раз безопасно делать перед тем, как VMM
возвратится в вызвавшую его VM. Если есть ожидающие события, VMM обслуживает их перед
возвратом к VM.
Заметим, что события, которые VMM обрабатывает перед возвратом к VM, не имеют никакого
отношения к тому, что делала VM, когда вызывала VMM. Например, сервис VPICD_Set_Int_Re-
quest, который отражает аппаратные прерывания в VM, делает это, используя Schedule_Glo-
bal_Event и Schedule_VM_Event. При каждом нажатии на клавишу, каждом импульсе таймера или
поступлении чего-либо в последовательный порт не исключено, что VxD, обслуживающий аппа-
ратное обеспечение, вызовет VPICD_Set_Int_Request, который в свою очередь планирует событие.
Таким образом, много чего может случиться в этом цикле обработки событий, и было бы не-
правильно думать, что VMM всегда возвращается в ту же самую VM, которая вызвала его. В
случае с CLOCKS и INT lAh VMM мог обслужить много событий и идти обратно различными
путями, вовлекая множество переключений VM или ветвей выполнения, прежде чем возвратиться в
CLOCKS из INT lAh. Любой вызов в VMM — даже невольный, подобный INT или ошибке GP,
вызванной обращением к перехваченному порту I/O, или отказом страницы, вызванным вирту-
альной памятью, — является для VMM потенциальным местом для переключения на другую VM
(или, в Windows 95, к другой ветви выполнения). Может пройти много времени, прежде чем VMM
вернется к команде, следующей за невинно выглядящим INT lAh.
424
...Неофициальная Windows-25
. Магия Call_Priority_VM_Event________________________________________________________
События раскрывают важный факт о Windows: VMM является нереентерабельной опера-
ционной системой. А поскольку VMM нереентерабельна, VxD, которые вызываются асин-
хронно (например, из-за аппаратного прерывания), могут использовать только малую часть
сервиса VMM. Однако среди этих нескольких всегда доступных реентерабельных вызовов
есть Schedule_Global_Event, Schedule_VM_Event и Schedule_Thread_Event. VxD, который
вызывается асинхронно, ио нуждается в обращении к некоторому нереентерабельному сервису
VMM, может планировать события. Затем событие, когда будет вызвано, сможет использовать
сервис. Прямо перед возвратом в вызванную VM VMM вызывает каждую функцию в списке
событий. Эти функции, в свою очередь, могут вызывать любой сервис VMM (включая, воз-
можно, Schedule_XXX_Event, так что VMM, перед возвратом в VM, может довольно долгое
время оставаться в своем цикле обслуживания событий).
Документация DDK по поводу событий внушает, что они полезны только в программах
обслуживания прерываний. Но роль событий в Windows ие ограничивается обработкой ап-
паратных прерываний. Это становится ясно, если вы тщательно прочтете документацию DDK
по Call_Priority_VM_Event:
Call_Priority_VM_Event либо вызывает процедуру обратного вызова немедленно, либо планирует
приоритетное событие для заданной виртуальной машины. Этот сервис планирует событие, если
виртуальное устройство обрабатывает аппаратное прерывание, прервавшее VMM, или же текущая
виртуальная машина не является указанной, или же параметр Flags указывает значение
PEF_Always_Sched. Во всех прочих случаях сервис вызывает процедуру обратного вызова и воз-
вращается без планирования события.
Обратим внимание на ситуации, при которых Call_Priority_VM_Event планирует событие
(вместо того, чтобы немедленно выполнить вызов): не только при обработке VxD аппаратных
прерываний, но и если текушря VM не является указанной в запросе. Иными словами, VxD
можно вызвать в контексте одной VM и спланировать произвольный код для выполнения в
контексте некоторой другой VM. Это обеспечивает полный базис для связи между VM в
Windows. Дополнительно к Call_Priority_VM_Event и Schedule_VM_Event из Windows 3.x в
Windows 95 добавлена функция Schedule_Thread_Event.
События, в частности сервис Call_Priority_VM_Event, предоставляемый VMM, можно
использовать во многих ситуациях, требующих связи между процессами:
• VNETBIOS: интерфейс INT 5Ch NetBIOS включает опцию выдачи сетевых команд “без
ожидания”. Например, вместо ожидания завершения отправки пакета данных по сети,
NetBIOS может возвращать управление немедленно. Затем можно опрашивать байт завер-
шения в NCB, однако лучше устанавливать почтовую процедуру. NetBIOS вызовет почто-
вую процедуру по завершении команды. Тем временем можно пойти и заняться другими
делами. Сервер, который использует NetBIOS, может, например, таким способом управ-
лять многочисленными клиентами. VNETBIOS должен выполнять эту работу в многозадач-
ной среде Windows, где VM, выдавшая вызов NetBIOS “без ожидания”, может больше не
быть текущей VM при завершении выполнения команды, когда необходимо вызывать
почтовую процедуру. Где же решение? VNETBIOS использует Call_Priority_VM_Event для
планирования события, чтобы вызвать его в контексте оригинальной VM, в которой была
выдана команда “без ожидания”. Это событие, в свою очередь, вызывает почтовую про-
цедуру. (VNETBIOS исчезает вместе с NDIS 3.0, но реализация почтовых процедур
NetBIOS служит все же хорошим примером Call_Priority_VM_Event.)
• Многие программисты удивлены странной на вид функцией 1685h INT 2Fh (Switch VMs
and Callback), которая предоставляется Windows. Для использования этой функции следу-
ет указать идентификатор VM в ВХ и дальнюю V86-npone;jypy в ES:DI. (Вы можете ука-
зать, что прерывания разрешаются в VM или что VM не должна находиться в критической
Глава 14. Clock: смесь 32-битового и 16-битового кодов 425
секции). Когда происходит переключение на целевую VM, VMM обратится к вашей про-
цедуре. DOS-программа, выполняемая под Windows, может использовать это как быстрый
и “грязный" способ вызова дальней процедуры в другой VM. К сожалению, функция не
имеет поддержки защищенного режима и не может использоваться для вызова кода защи-
щенного режима. Вы также не можете передавать какие-либо параметры вызываемой функ-
ции! Но это все произвольные ограничения, так как функция 1685h INT 2Fh является
ничем иным, как Call_Priority_VM_Event для указанной процедуры. Когда целевая VM
активна и имеет определенный вами статус, процедура выполняет вложенный У86-вызов
(Begln_Nest_V86_Exec, BulId_Int_Stack_Frame, Resume_Exec, End_Nest_Exec) функции,
определенной вами в ES:DL
• Можно построить и лучшую функцию “Switch VMs and Callback". В качестве превосход-
ного примера см. PIPE VxD в статье Томаса Олсена (Thomas Olsen) “Making Windows and
DOS Programs Talk”, (Windows/DOS Developer's Journal, May 1992). Канал обмена
Олсена использует Call_Priority_VM_Event.
• VxD SHELL дает DOS-приложениям практический доступ к буферу обмена (Clipboard)
Windows через функцию 17h INT 2Fh. Например, функция 1701h эквивалентна вызову
Windows API OpenClipboard, функция 1703h — SetClipboardData и функция 1705h —
GetCIipboardData. Каким образом SHELL организует для вас эффективную возможность
удаленных вызовов Windows API из окна DOS? Естественно, с помощью Call Prio-
rity_VM_Event. SHELL планирует событие, которое во время работы системной VM ис-
пользует SHELL Event для передачи запросов к буферу обмена в WINOA386
(WinOldAp), который действует в качестве RPC-сервера буфера обмена.
• В Windows 95, VxD SHELL обеспечивает широкие возможности так называемые сервисы
Арру time (название, как полагают, обыгрывает слова application и happy), которые по-
зволяют другим VxD вызывать Windows API. Эти VxD могут затем передавать Windows
API сервис приложениям DOS. Например, когда вы набираете имя исполняемого Windows-
модуля в окне DOS под Windows 95, вместо старого сообщения “This program requires
Microsoft Windows”, Windows 95 запустит приложение Windows — какая превосходная
идея! Хотя именно такое поведение ожидалось от Windows давным давно, все же инте-
ресно, каким образом Windows 95 превращает функцию 4Bh INT 21 h из DOS-программы в
WinExec? Очень просто: эта функция планируется с помощью _SHELL_CallAtAppyTime.
SHELL вызывает эту функцию в безопасный для обращения к Windows API момент, т.е.
когда системная VM является текущей. В период “apply time" функция использует другие
сервисы, вроде SHELLPostMessage, _SHELL_ShellExecute, SHELLCallDll,
_SHELL LoadLibrary и _SHELL_GetProcAddress, чтобы загрузить любой указанный поль-
зователем исполняемый Windows-модуль (и, факультативно, чтобы ждать его завершения).
Но каким образом _SHELL_CallAtAppyTime организует вызов функции в момент готов-
ности системной VM? С помощью VMM сервиса Call_Restricted_Event, который в его
текущей реализации идентичен Call_Priority_VM_Event.
• Задолго до того как в Microsoft сообразили, что указание имени Windows-приложения в
командной строке DOS может означать, что пользователь хотел бы его запустить,
несколько независимых фирм создали расширения Windows, которые могли превращают
ЕХЕС для приложения Windows из окна DOS в обращение системной VM к WinExec.
Самое известное из этих расширений, возможно, Phar Lap FrontRunner. С помощью
Call_Priority_VM_Event Phar Lap превращает функцию 4Bh INT 21h в окне DOS для
Windows-приложения в обращение системной VM к WinExec.
Все это очень интересно, но что Call_Priority_VM_Event должна делать с циклом обра-
ботки событий, о котором упоминалось в связи с возвратом VMM к CLOCKS? Вот что: если
426
Неофициальная Windows 95
\ .И i : m i г.ы.пчио вроде
i "Ski’.Ii \ M i-. ! ,х,н.г чнуь> \ M, >H !»1 H’SVBICft VM,
i < all M E'-tlM SO 11 i*> ; H.,-; la ь-чг L’ ".'ГЧ Г.'1И!КП!.1 rortbHW- VM. Это
I Ci/ifiliHl- при и;; i:'t '.'НИИ V M'-? :l.» !iv.':CB\n> VM. ВОЗМОЖНО, В
; < HM ;s! i >.:K<4i :i- C p-J’I1: i чу'Л'чн!,-л i-iKH^ !hh'i НВДЖОГЯ MHOIO-
: p.-. if. .i • -. a . • • •. .• .. . 5 (y sc'i tf п<,-лн px>
JiBlMiMeieiiiiiiilieiiliBlffiiiiie
Wln32-AecKpnnTopbi файлов
и thunk-переключение
Мы уже использовали WSPY21 для проверки трех из четырех возможных вариантов сочетания
исполняемого модуля и среды: 16/16 (Winl6 Clock под WfW 311), 32/16 (Win32 Clock под WfW
311 с Win32s) и 32/32 (Win32 Clock под Chicago). Запуск Winl6 Clock под Chicago (16/32)
завершает эту таблицу.
Когда WSPY21 используется для тестирования Winl6 Clock в Windows 95, одна вещь из
вывода WSPY21 сразу бросается в глаза. Посмотрите на дескрипторы файлов, которые передаются
функциям DOS Read и Lseek:
<САВ32> *(4В) Ехес ‘C:\wfw311\clock.exe’
<САВ32> (3F) Read 574 (023Eh), 64 (0040h)
<САВ32> (42) LseekO 574 00000400
<САВ32> (3F) Read 574 (023Eh), 64 (0040h)
<САВ32> (3F) Read 574 (023Eh), 277 (0115h)
<САВ32> (42) LseekO 574 000005a0
<САВ32> (3F) Read 574 (023Eh), 13888 (3640h)
<САВ32> (50) Set PSP 4831 (12DFh)
<CLOCK> (50) Set PSP 175 (OOAFh)
<CLOCK> (42) LseekO 574 00003f30
<CLOCK> (3F) Read 574 (023Eh), 240 (OOFOh)
<CLOCK> (50) Set PSP 2735 (OAAFh)
<CLOCK> (ЗЕ) Close 574 (023Eh)
Что значит дескриптор файла 574? Хотя это может быть и настоящий дескриптор файла DOS,
трудно представить, что может быть таким большим при обычных обстоятельствах. В DOS реаль-
ного режима дескрипторы файлов представляют собой индексы в структуре данных под названием
таблица рабочих файлов (Job File Table — JFT, см. Undocumented DOS, 2d ed., p. 472-474), кото-
рая отдельно хранится для каждой задачи. PSP-задача содержит (по смещению 34h) дальний указ-
атель на свою JFT. Размер JFT указан в слове по смещению 32h в PSP. Достоверный дескриптор
файла с номером 574 требует, чтобы JFT содержала по крайней мере 575 элементов (прону-
мерованных с 0 до 574).
Хотя такая разбухшая JFT может быть создана функцией 67h прерывания INT 21h (Set Handle
Count) или манипулированием указателями в PSP {Undocumented DOS, 2d ed., р. 485-488), ни
один из PSP, выведенных WSPY21, не имеет такой громадной JFT. Это демонстрируется в
следующем разделе, где также приводится краткий пример Toolhelp32 и обсуждаются некоторые
приемы программирования в Win32 с использованием VxDCall.
ГлаваД4ДЛорк: смесь 32-битового и 16-битового кодов
427
Чтение PSP из WIN32
ТН32 из листинга 14.3 является простым консольным ^Уш32-приложением, использующим
Process32First и Process32Next API из Microsoft Toolhelp32 API (TLHELP32.H) для перечисления
всех Win32-npoueccoB. Для каждого процесса ТН32 показывает адреса PSP и размер JFT.
Листинг 14.3. ТН32.С
/*
ТН32.С
*/
«include <stdlib.h>
«include <stdio.h>
«define WIN32_LEAN_AND_MEAN
«include “windows.h”
«include “tlhelp32.h”
void fail(const char *s) { puts(s); exit(1); }
main()
{
PROCESSENTRY32 pe32;
MODULEENTRY me32;
BOOL ok, ok2;
HANDLE snap;
char *name;
DWORD (WINAPI *VxDCal10)(DWORD srvc, DWORD eax, DWORD ecx);
HANDLE k32 = GetModuleHandle(“KERNEL32’’);
«define GET(func) func = GetProcAddress(k32, «func)
GET(VxDCallO);
«define VWIN32_INT31_CALL 0x2A0029
«define DPMICah(eax, ecx) VxDCallO(VWIN32_INT31_CALL, (eax), (ecx))
if(!(snap = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0)))
fail(“Can’t create Toolhelp32 snapshot");
printf("Process Module Parent PSP(«files) «Threads\n’’);
printf(“-----------------------------------------\n’’);
// проход по списку процессов
pe32.dwSize = sizeof(pe32);
for(ok = Process32First(snap, &pe32); ok; ok = Process32Next(snap, &pe32))
{
// PSP находится по смещению 28h в структуре процесса
WORD psp = *((WORO*) (pe32.th32ProcesSID + 0x28));
DWORD psp_base, jft;
WORD max;
_asm mov bx, psp
DPMICall(0x0006, 0);
_asm moy word ptr psp_base+2, ex
_asm mov word ptr psp_base, dx
max = *((WORD*) (psp_base + 0x32));
jft = *((DWORO*) (psp_base + 0x34));
// проход по списку модулей в поисках этого
name = “ " ;
me32.dwSize = sizeof(me32);
428
Неофициальная Windows 95
for(ok2 = Module32First(snap, &me32);
ok2;
ok = Module32Next(snap, &me32))
if(me3‘2.th32ModuleID == pe32.th32ModuleID)
{
if(!(name = me32.szModule))
name = me32.szExePath;
break;
}
printf("%081X %081X %081X %04X (%d)\t%d\t%s\n”,
pe32.th32ProcessID,
pe32.th32ModuleID,
pe32.th32ParentProcess!D,
(DWORD) psp, (DWORD) max,
pe32.cntThreads,
name);
}
CloseHandle(snap);
}
Для каждого процесса Toolhelp32 заполняет структуру PROCESSENTRY32, содержащую дес-
криптор процесса, дескриптор процесса-предка, дескриптор его модуля и т.д. Есть подобные функ-
ции Toolhelp32 для перечисления всех ветвей выполнения и модулей, а также для прохода по
Win32-Ky4aM памяти. Поскольку Win32-nporpaMMbi работают под Windows 95 с приоритетной
(вытесняющей) многозадачностью, система может изменить свое состояние во время просмотра этих
списков; по этой причине функция CreateToolhelp32Snapshot должна быть вызвана до прочих
функций Toolhelp32. Структура PROCESSENTRY32 не является внутренней структурой процесса
Windows 95. Вместо этого, она является структурой, которая содержит копии различных элементов
структуры внутреннего процесса, которой, по мнению Microsoft, для вас вполне достаточно. К
сожалению, структура не включает PSP, который сопровождает процесс. Однако дескриптор про-
цесса, помещенный в структуру PROCESSENTRY32, можно использовать как 32-битовый указатель
на текущую внутреннюю структуру процесса в Windows 95, которая, по крайней мере в бета-версии,
по смещению 28h содержит селектор защищенного режима для PSP процесса.
Этот селектор PSP защищенного режима не может сразу использоваться в такой Win32-npo-
грамме, как ТН32. Чтобы получить доступ к PSP, ТН32 требуется 32-битовый линейный адрес PSP.
Получив селектор, базовый адрес можно получить с помощью DPMI-функции 6 INT 31h (Get Selec-
tor Base Address). Win32-nporpaMMbi не могут вызывать DPMI непосредственно. Однако VWIN32
VxD предоставляет Win32-cepBHc (2A0029h), который вызывает DPMI INT 31h. Программы Win32
могут обращаться к этим Win32-cepBHcaM, вызывая экспортируемый KERNEL32 VxDCallO API.
Этот API не упоминается в файлах описаний Chicago или библиотеках, но GetProcAddress API
позволяет легко динамически компоновать VxDCallO во время выполнения.
Связавшись динамически с VxDCallO, ТН32 может использовать эту функцию для вызова
Win32*cepBHca 2A0029h, который, в свою очередь, используется для обращения к функции 6 из
DPMI. ТН32 находит селектор PSP по смещению 28h в структуре процесса и передает этот селектор
DPMI-функции 6; DPMI возвращает базовый адрес PSP. С этим базовым адресом ТН32 может про-
должать исследование PSP для определения размера его JFT (что, как вы помните, и было целью
этого упражнения).
Поскольку ТН32 является 32-битовой программой с плоской (flat) моделью памяти, она может
непосредственно использовать базовый адрес PSP как ближний указатель. Win32-nponecc может
использовать ближний указатель для доступа к памяти в 4 Гбайтовом адресном пространстве, если
память отображена в адресное пространство процесса. Дальний указатель не только не нужен в
большинстве случаев, но даже недопустим.
Размер JFT хранится в слове по смещению 32h в PSP. Указатель JFT хранится в двойном слове
по смещению 34h. Имея базовый адрес PSP, найти размер его JFT очень просто:
WORD jft_size = * ((WORD)* (psp based + 0x32);
Глава 14. Clock: смесь 32-битового и 16-битового кодов
429
На рис. 14.6 представлен результат вывода программы ТН32. Заметим, что ни один из Win32-
процессов не имеет DOS JFT достаточного большого размера для использования дескриптора файла
вроде 574. Самая большая JFT, принадлежащая процессу 81100268b (PSP 00A7h), позволяет
разместить только 128 дескрипторов фалов DOS.
Рис. 14.6. ТН32 и WIN32PSP являются \¥т32-программами, которые показывают PSP и JFT, ассоцииро-
ванные с Win32-nponeccaMH (ТН32 — текстовое консольное \У1п32-приложение)
Кроме программы ТН32 на рис. 14.6 также показаны два экземпляра программы WIN32PSP,
код которой приведен в листинге 14.4. Это Win32-nporpaMMa, которая обращается к своему собст-
венному PSP. В дополнение к сервису Win32 2A0029h для вызова DPMI, WIN32PSP также ис-
пользует сервис Win32 2A0010h для вызова DOS (или по крайней мере для обращения к INT 21h,
что как мы знаем, не одно и то же).
Листинг 14.4. WIN32PSP.С
/*
WIN32PSP.С
М1п32-приложение производит доступ к своему собственному PSP
реального режима DOS
Использует VxDCallO для вызова INT 21h (DOS) и INT 31h (DPMI)
Шульман, 1994
*/
einclude <stdlib.h>
#include <stdio.h>
430
Неофициальная Windows 95
edefine WIN32_LEAN_AND_MEAN
«include “windows.h"
«define MSG(s) MessageBox(0, s, “WIN32PSP", MB.OK)
void fail(const char *s) { MSG(s); exit(1); }
DWORD (WINAPI *VxOCall)(DWORD srvc, DWORD eax, DWORD ecx);
«define GET_PROC(mod, func) GetProcAddress(GetModuleHandle(mod), (func))
DWORD lsl(W0RD sei)
{
if(lsel) return 0;
_asm Isl eax, sei
// результат в ЕАХ
}
int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance,
LPSTR IpszCmdLine, int nCmdShow)
{
char buf[512];
MEMORY_BASIC_INFORMATION info;
DWORD base;
unsigned short psp;
int len;
// инициализация
if(!(VxDCall = GET_PR0C(“KERNEL32", “VxDCallO’’)))
fail(“Cannot link to VxDCall’’);
«define VWIN32_INT21_CALL 0x2A0010
«define VWIN32_INT31_CALL 0x2A0029
«define DosCall(eax, ecx) VxDCall(VWIN32_INT21_CALL, (eax), (ecx))
«define DPMICall(eax, ecx) VxDCall(VWIN32_INT31_CALL, (eax), (ecx))
// Вызов функции 62h DOS-прерывания INT 21h (Получить PSP)
DosCall(0x6200, 0);
_asm mov psp, bx
// возвращает селектор PSP
len = sprintf(buf, “Win32 app’s PSP (prot mode): %04Xh\n”, psp);
// Передать PSP защищ. режима функции 6 DPMI INT 31h 6
// (Получить базу селектора)
_asm mov bx, psp
0PMICall(0x0006, 0);
_asm mov base+2, ex
_asm mov base, dx
len += sprintf(buf+len, “PSP base address: %081Xh\n”, base);
// Передать PSP защищ. режима инструкции LSL (Загрузить границу селектора)
len += sprintf(buf+len, “PSP length: %04Xh bytes\n", Isl(psp) + 1);
// PSP для Win32-npилoжeния имеет размер 120h байт, а не lOOh!
MSG(buf);
return 0;
}
WIN32PSP и TH32 больше представляют интерес из-за исходного текста, чем из-за своего выво-
да. Хотя Toolhelp32 API и имеет некоторые ограничения (см. Мэтт Петрек, “Investigating the
Глава 14. Clock: смесь 32-битового и 16-битового кодов
431
Hybrid Windowing and Messaging Architecture of Chicago”, Microsoft Systems Journal, September
1994, p. 28—29), он все равно является определенным достижением, поскольку Microsoft раньше
вообще не собиралась обеспечивать никакой Toolhelp-поддержки для Win32-пpилoжeний в Chicago.
Функция VxDCallO, обеспечиваемая KERNEL32, является ценным API. Хотя нет никакой до-
кументации по • Win32-cepBHcaM, обеспечиваемым VxD и доступным Win32-пpилoжeниям через
VxDCallO, некоторые из них отыскать довольно легко.
Программа VXDLIST показывает количество Win32-cepBHCoB, обеспечиваемых каждым VxD, и
выделяет их из списка “нормальных” VxD-сервисов восклицательным знаком:
C:\UNAUTHW\BIN>vxdlist | find "!”
VMM 4.00 0001 h C000EC28 CD00230E C0002A11 C0002A11* 377 ! 39
REBOOT 2.00 0009h C0074094 C0074C64 C0259750* 0 ! 2
VWIN32 1.02 002Ah C0063234 C006228C C0239FB0* 21 ! 65
VCOMM 1.00 002Bh C0063D44 C0063A94 C023D588 C023D588* 35 ! 27
VCONO 1.00 0038h C0073B10 C0073ACC C0252C3C* C0252D46* 2 ! 52
Вывод VXDLIST показывает около 175 Win32-cepBHcoB. Чтобы детальнее узнать об этих функ-
циях, запустите VXDLIST -VERBOSE. Некоторые из них, такие как 2A0010h и 2A0029h, исполь-
зуемые в ТН32 и WIN32PSP, являются шлюзами для доступа к другим функциональным возмож-
ностям (в данном случае, целые DOS INT 21h и DPMI INT 31h интерфейсы). С течением времени
эти Win32-сервисы, возможно, получат известность в среде разработчиков для Windows 95, как это
произошло с недокументированными функциями DOS в 80-х годах.
Использование УМп32-дескрипторов файлов
Итак, JFT для САВ32 и CLOCK по умолчанию имеют только двадцать элементов. Тем не менее
они вызывали функции Read и Lseek прерывания INT 21h с дескриптором файла 574. Каким обра-
зом они это делали?
Чтобы ответить на этот вопрос, нам надо понять, как вызовы Lseek и Read нижнего уровня, вы-
веденные ранее WSPY21, представляются в терминах Windows-операций верхнего уровня.
Win 16 CLOCK.EXE не содержит действительных вызовов Read и Lseek INT 21h, которые мы
видим в выводе WSPY21. Вместо этого Clock вызывает функции Windows API, что, в свою
очередь, генерирует обращение к INT 21h. Как показала утилита Borland TDUMP, параметры
операций Lseek (смещение 3F30h) и Read( FOh байтов) соответствуют элементу ресурса в самом
CLOCK.EXE:
;;; Отрывок из вывода WSPY21
<CLOCK> (42) LseekO 574 00003F30
<CLOCK> (3F) Read 574 (023Eh), 240 (OOFOh)
;;; Отрывок из TDUMP \WFW311\CL0CK.EXE
type: DATA
Identifier: CLOCK
offset: 03F30h length: OOFOh
Раз OFOh (240) без остатка делится на 60, что в свою очередь соответствует числу позиций стре-
лок часов, похоже, что определяемый пользователем ресурс хранит предварительно вычисленные
значения синусов или косинусов. Во всяком случае, Lseek/Read операции, зарегистрированные
WSPY21, являются отражением на нижнем уровне манипулирования ресурсом со стороны Clock.
Clock вызывает KERNEL API такого типа, как FindResource и LoadResource, и эти API, в конечном
счете, преобразуются в вызовы INT 21h, наблюдаемые WSPY21. Загрузка ресурса — один из основ-
ных видов взаимодействия (хотя и косвенного) Windows-приложений с DOS. Загружая ресурсы,
Clock читает из своего собственного исполняемого файла. Дескриптор файла 574, таким образом,
непосредственно соответствует CLOCK.EXE.
432
Неофициальная Windows 95
WSPY21 также показала, что после запуска CLOCK.EXE, но до того, как CLOCK стала
текущей задачей, Chicago Cabinet (САВ32) осуществляла чтение из того же самого файла. Запуск
исполняемого модуля включает чтение в память по крайней мере одного исполняемого файла. То,
что для САВ32 выглядит как высокоуровневый вызов WinExec, для WSPY21 на нижнем уровне
выглядит как обращение к INT 21h. Естественно, мы можем сопоставить обращения INT 21h со сто-
роны САВ32 с форматом CLOCK.EXE, который можно получить с помощью TDUMP. Например,
то, что WSPY21 видит как очередную операцию Lseek/Read, TDUMP показывает как чтение
САВ32 загружаемой части CLOCK.EXE:
;;; Отрывок из вывода WSPY21 после EXEC CLOCK.EXE
<САВ32> (42) LseekO 574 000005а0.
<САВ32> (3F) Read 574 (023eh), 13888 (3640h)
;;; Отрывок из TDUMP CLOCK.EXE
Start of Gangload Area 05A0h
Length of Gangload Area 3640h
Вместо чтения сегментов исполняемого файла по одному, область групповой загрузки (gang-
load) позволяет группировать и загружать несколько сегментов одновременно. Хорошее разъяснение
области групповой загрузки (которая в документации Windows 3.1 SDK называлась областью быст-
рой загрузки — fast-load) содержится в Windows Internals (р. 243 , 248). Мэтт Петрек показывает,
что внутренняя функция LoadExeHeader в Win 16 KERNEL использует функцию _hread для чтения
области групповой загрузки.
САВ32 не работает непосредственно на таком низком уровне, как чтение области групповой
загрузки. То, что для WSPY21 выглядит как Lseek/Read INT 21h этой части файла, для САВ32
только крошечная часть (о которой она ничего не знает) в намного большей, значительно более
высокоуровневой операции запуска приложения.
Всякий раз, когда вы используете оболочку Windows 95 для запуска программы, САВ32 вызы-
вает ShellExecuteEx API из SHELL32.DLL. Этот API в свою очередь (среди всего прочего) вы-
зывает Win32 WinExec API из KERNEL32.DLL. Если KERNEL32!WinExec должен запускать
исполняемый Winl6-мoдyль, он “переключается” (через QT_Thunk, с CL=26h) в Winl6 WinExec
API из KRNL386.EXE. Мы опять видим, что заявление Microsoft о том, что KERNEL32 никогда не
обращается к KRNL386 не только неверно, но и вовсе бессмысленно: каким же еще образом Win32
WinExec может запускать исполняемые Win 16-модули?
Мы можем вспомнить историю из Windows Internals (р. 229-231): Winie-версия WinExec
вызывает функцию 4Bh INT 21h (EXEC), что и было показано в верхней части вывода WSPY21.
Обработчик KRNL386 функции 4Bh INT 21h вызывает LoadModule, который обращается к внутрен-
ней функции под названием LoadExeHeader. Эта функция среди много другого использует _hread
для чтения области групповой загрузки.
Итак, вызовы INT 21h, увиденные WSPY21, представляют загрузку оболочкой Windows 95
CLOCK.EXE, который в свою очередь загружает ресурсы. Большой дескриптор файла — 574, пред-
ставляет открытый файл CLOCK.EXE. Этот файловый номер превышает размер JFT оболочки,
поэтому кто-то другой, а не DOS должен иметь отношение к вызовам INT 21h, которые Winl6 API
типа LoadModule и LoadResource вызывают для этого большого дескриптора файла.
Итак, кто же этот другой? Файл IFSMGR.INC, включенный в бета-версию DDK для Chicago,
кратко описывает сервис под названием IFSMGR_Win32MapExtendedHandleToSFT, что звучит так,
как будто он может что-то делать с этими большими дескрипторами файлов. VCOND (виртуальная
консоль — Virtual CON Device) использует этот сервис IFSMGR при запуске консольных
(текстовых) Win32-пpилoжeний и переназначении их потоков stdout или stdin:
;** IFSMgr_Win32MapExtendedHandleToSFT - отобразить расширенный дескриптор в SFT
; Этот сервис распределяет свободную SFT и отображает расширенный дескриптор в эту
; SFT. Он возвращает индекс в SFT. Этот API обеспечивает перенаправление для
; Ы1п32-приложений, когда они запускают DOS-приложения. Этот сервис ДОЛЖЕН вызываться
; в контексте той DOS VM, в которой предполагается использовать SFT
Глава 14. Clock: смесь 32-битового и 16-битового кодов 433
15 Неофициальная Windows 95
Другая функция, которая, быть может, также относится к делу, Win32HandleToDosFileHandle,
экспортируется из KERNEL32.DLL. Этот API не упоминается в WINBASE.Н, но к нему достаточно
легко получить доступ тем же самым способом, каким ТН32 и WIN32PSP получают доступ к
VxDCallO: с помощью динамической компоновки времени выполнения, GetProcAddress. В листинге
14.5 показана маленькая Win32-nporpaMMa, которая вызывает этот API в цикле для всех прием-
лемых Win32-flecKpinrropOB файлов, пытаясь обнаружить, какой из них соответствует дескриптору
файла DOS.
ЛИСТИНГ 14.5. W32HAND.C
// w32hand.c
«include <stdlib.h>
«include <stdio.h>
«define WIN32_LEAN_AND_MEAN
«include "windows.h”
void faiKconst char *s) { puts(s); exit(1); }
unsigned short (WINAPI *Win32HandleToDosFileHandle)(HANDLE h);
HANDLE (WINAPI *0osFileHandleToWin32Handle)(unsigned short h);
«define GET_PROC(mod, func) GetProcAddress(GetModuleHandle(mod), (func))
main(int argc. char *argv[])
{
unsigned short fu;
int fh;
Win32HandleToDosFileHandle = GET_PR0C(“KERNEL32",
“Win32HandleToDbsFileHandle”);
if(!Win32HandleToDosFileHandle)
fail(“Нельзя связаться c Win32HandleToDosFileHandle");
printf(“Win32\t\tD0S\n”
-\t\t—\n’’);
for(fh"=D; fh<Ox1OO; fh++)
if(fu = Win32HandleToDosFileHandle(fh))
printf(“%0u (%OXh)\t%Ou (%08Xh)\n", fh, fh, fu, fu);
return 0;
>
Если вы запустите W32HAND и перенаправите ее вывод и (не использующийся) ввод, то про-
грамма выводит что-то, что очень похожее на большие дескрипторы файлов, какие были показаны в
выводе WSPY21:
C:\UNAUTHW\BIN32>w32hand > w32hand.log < tmp.tmp
С:\UNAUTHW\BIN32>type w32hand.log
Win32 DOS
2 (2h) 713 (000D02C9h)
3 (3h> 714 (000002cah)
Получается, что большие дескрипторы файлов, наблюдаемые WSPY21, — это своего рода “пус-
тышки”, DOS-эквиваленты Win32^ecKpjnrropoB файлов. Когда Windows 95 запускает Win 16-
приложение, она должна использовать ^ш32-дескрипторы файлов. Для WSPY21 это выглядит как
большие дескрипторы файлов DOS.
Но как был создан Win32-flecKpHrrrop файла для Win 16-приложения? Давайте возвратимся к
Win 16-версии WinExec, которую Win32-eepCHH WinExec вызывает для запуска Winl6-MonyKH.
434 Неофициальная Windows 95
‘Ч
t - <• X, , .? *> AVtt JEJ • S> •*« ’ '
WlnExec/TsaK уже-ЙЫло сказано, вызывает функцию 4Bh прерывания INT 21h, обработчик которой
в Winl6KERNEL вызывает LoadModule.
’ _ - Мэйт Петрек баять может стать нашим гидом, по крайней мере в рассмотрении этого вопроса.
. Как показано В книге- Windows Internals (р. 241), LoadModule зависит от большого количества
вспомогательных функций, малая часть из которых приведена в следующем дереве вызовов:
LoadModule
LMAlreadyLoaded - если модуль еще не загружен...
LMLoadExeFile
MyOpenFile
LoadExeHeader - загружает заголовок NE
Jiread - прочитать область групповой загрузки (рассматривалось раньше)
Определив с помощью LMALreadyLoaded, что указанный модуль еще не загружен (в нашем
' Примере, CLOCK), LoadModule вызывает внутреннюю процедуру LMLoadExeFile. Мэтт показал,
что при этом используется другая внутренняя процедура, MyOpenFile, для открытия исполняемого
файла Win 16-программы, подобной CLOCK.EXE. MyOpenFile — это оболочка для документиро-
ванного Openpile API.
В Windows Internals описывается Windows 3.1. Все несколько отличается, когда речь идет о за-
грузке Win 16-программы в Windows 95 (мы увидим в следующем разделе, что при загрузке Win32-
программ в Windows 95 все вообще иначе). Вместо вызова OpenFile MyOpenFile обращается к
следующей, простенькой на первый взгляд, процедуре:
0127:00003368 MOV СХ, 00В0
0127:0000336В JMP 33D4
Хотя это часть ядра Winl6, по адресу 0127:33D4 содержится 32-битовый код и 48-битовый
дальний JMP (код операции — 66h EAh):
0127:00003304 MOV AX, 013F
0127:00003307 MOV ES, AX
0127:00003309 MOVZX ECX, CX
0127:00003300 MOV EDX,ES:[ECX+BFF90FF8] ; использовать CX (BOh) как индекс
0127:000033Е6 JMP 0137:BFF7217E ; 48-битовый дальний переход
Как только задача выполняет 48-битовый дальний переход, она покидает ядро Winl6 и
попадает в ядро Win32. Еще один переходник (thunk)! Раньше в этой главе мы уже видели, что
KERNEL32 использует CALL FWORD PTR для перехода в KRNL386. Сейчас мы видим, что
KRNL386 использует 48-битовые переходы для переключения на KERNEL32. Как можно заметить,
значение в СХ (здесь B0h) используется как индекс в таблице двойных слов, из которой выбирается
конкретная процедура KERNEL32, к которой хочет обратиться KRNL386.
В конечном счете код KERNEL32, который вызывается MyOpenFile в KRNL386, делает нечто
подобное тому, что мы рассматривали раньше в “32/32: Windows 95 Clock”:
0137:BFF7110D MOV EAX, 0000716C ; ; AX=716Ch (LFN Open/Create File)
0137:BFF711E2 CALL BFF71ESF ; ; выполнить INT 21h через VWIN32 2A0010
0137:BFF71ESF PUSH ECX
0137:BFF71E60 PUSH EAX
0137:BFF71E61 PUSH 002A0010 ; ; вызвать VWIN32-cepBnc #10h
ai37:BFF71E66 .CALL BFF71F0C ; ; процедура VxDCall ‘
VxOCall! - ; ожидает в стеке Win32 svc#
0137:BFF71FOC MOV EAX, [ESP+04]
0137:BFF71F10 POP DWORD PTR [ESP]
O137:BFF71F13 CALL FWORD PTR CS:[BFFB5004] ; ;; переходник
003B:000003B6 INT 30 ; ; обратный вызов защищенного режима
0028:C0001A2C SUB ESP,+04 ; ; сейчас в обработчике обратного вызова VMM
435
. ......... ............... -----------------------------
Глава Т4. Clock: смесь 32-битового и 16-битового кодов
>- ..------------- . £:. S ....................
KERNEL32 использует VxDCallO API для обращения к Win32-cepBHcy #2A0010h, который пре-
доставляется VxD VWIN32. Как мы видели раньше, этот сервис по требованию Win32 выдает обра-
щение к INT 21h. В этом случае KERNEL32 хочет вызывать функцию 716Ch INT 21h. Как показано
на рис. 14.3 и 14.4, VWIN32 Win32-cepBHc 2A00010h использует Exec_PM_Int сервис VMM, кото-
рый в свою очередь использует Set_PM_Exec_Mode, Begin_Use_Locked_PM_Stack и Exec_Int.
Итак, САВ32 может эффективно обращаться к KRNL386, который переключается на
KERNEL32, который, в свою очередь, обращается к обратному вызову защищенного режима, а тот
приводит нас на уровень 0, где приключения только начинаются. Возможно, следующее дерево вы-
зовов поможет прояснить ситуацию. Поскольку это дерево не помещалось на ширину страницы, я
начинал с левого края каждый раз, когда встречалось основное изменение уровня (например, thunk-
переходник):
САВ32
SHELL32!ShellExecuteEx
KERNEL32!WinExec
QT_Thunk #26
KERNELIWinExec
WinExec
функция 4Bh INT 21 h
LoadModule
LMLoadExeFile
MyOpenFile
KERNEL32 thunk-переходник #BOh
48-битовый дапьний переход к KERNEL32
KERNEL32 thunk-процедура #BOh
VxDCall 2A0010h EAX=716Ch (LFN Extended Open/Create File)
обратный вызов защищенного режима (INT 30h)
обработчик INT 30h VMM
перейти на обработчик обратного вызова Win32-cepenca
перейти на Win32 сервис 2A0010h
VMM Exec_PM_Int 21h
Set_PM_Exec_Mode
' Begin_Use_Locked_PM_Stack
Exec_Int
Simulate_Int
Resume_Exec
... в конце концов попадаем в обработчик INT 21h IFSMgr
End_Nest_Exec
Это похоже на какой-то запутанный способ вызова INT 2lh в защищенном режиме. Но в конеч-
ном счете IFSMGR открывает файл CLOCK.EXE и большой дескриптор файла проходит обратный
путь к LoadModule. Кроме использования дескриптора файла для чтения заголовка исполняемого
модуля, области групповой загрузки и т.д., LoadModule также помещает дескриптор файла в кэш.
Впоследствии LoadResource и другие API, получающие доступ к находящемуся в памяти модулю,
вызовут внутреннюю процедуру под названием GetCachedFileHandle (Windows Internals, р. 233),
которая, получив номер модуля, возвращает соответствующий дескриптор файла из кэша.
Мы интересовались, каким образом Winie-версия Clock может успешно вызывать функции
DOS Read и Lseek с дескрипторами файлов типа 574, превышающими размер ее JFT. Теперь, зная,
что эти большие дескрипторы файлов поступают от IFSMGR, мы должны заглянуть внутрь
IFSMGR, чтобы разобраться, что происходит, когда такой API, как LoadResource, вызывает DOS
функцию (Lseek или Read) для одного из этих дескрипторов.
Даже после беглого взгляда на IFSMGR ясно, что 200h — тот рубеж, который Microsoft вы-
брала между дескрипторами файлов DOS и расширенными дескрипторами файлов. Всюду в
IFSMGR (включая версию, реализованную в WfW 3.11) имеются многочисленные сравнения
436
Неофициальная Windows 95
дескриптора файла с 200h, вычитания 200h из дескриптора файла и т.д. Код 32BFA предполагает,
что все дескрипторы файлов >= 200h ссылаются на отдельную таблицу файлов IFSMGR. Этот
можно увидеть из следующего фрагмента трассировки Soft-ICE/Windows функции 3Fh INT 21h
чтения по расширенному дескриптору файла:
IFSMgr2+7A60 IFSMgГ2+7А63 MOV CMP ESI,[EBP+OC] ESI,00000200 ; дескриптор файла ; нормальный дескриптор?
IFSMgr2+7A69 MOV ECX,[EBP+08]
IFSMgr2+7A6C JL IFSMgr2+7A80
;;; С этого места IFSMgr работает с дескрипторами >= 200h
IFSMgr2+7A6E MOV [ECX+06],SI ;; ; дескриптор файла
IFSMgr2+661 MOVZX EAX,WORD PTR [EDX+06] ;; ; дескриптор файла
IFSMgr2+665 SUB EAX,00000200 ;; ; минус 200h
IFSMgr2+66A JB IFSMgr2+6AE
IFSMgr2+66C MOV EBX,EAX
IFSMgr2+66E SHR EBX,08 ;; определить, в какой таблице
IFSMgr2+671 MOV EBX,[IFSMgr+3B18+4*EBX] ;; ; таблица таблиц
IFSMgr2+678 OR EBX,EBX
IFSMgr2+67A JZ IFSMgГ2+6АЕ
IFSMgr2+67C XOR AH, AH
IFSMgr2+67E LEA EBX,[EBX+8*EAX] ;; ; каждый элемент - 8 байт
IFSMgr2+681 MOV EAX,[EBX]
Эту проверку на 200h очень просто взломать. Джефф Чапелл исследовал эту возможность в
WfW 3.11, где IFSMGR предполагает, что все дескрипторы >= 200h создавались с помощью
ServerDosCall. Чапелл написал маленькую тестовую программу (приведенную в листинге 14.6),
которая использует функцию Dup (DOS-функция 45h) для создания более 200h дескрипторов
файлов. Эта программа, возможно, надуманна, но вполне законна и использует только документи-
рованные функции DOS. Она работает прекрасно в DOS, но с 32BFA (WfW 3.11 или Chicago)
выводит сообщение: “Invalid handle error while reading file with handle > = 0200h”.
ЛИСТИНГ 14.6. FILETEST.ASM
comment $
FILETEST.ASM
Geoff Chapell (geoffc@cix.compulink.co.uk)
8 March 1994
ML FILETEST.ASM
LINK FILETEST
EXEHDR /MAX:O FILETEST.EXE
MODEL SMALL, FARSTACK
.STACK .DATA 1D0h
config db “c:\config.sys"
buffer db ?
no_problem db ODh, OAh,
“Just to confirm everything worked OK", ODh, OAh,
something_wrong db ODh, OAh, “This shouldn’t happen. Something’s gone” “wrong with the test rig.’’, ODh, OAh,
something_very_wrong db ODh, OAh, “Invalid handle error while reading file" “with handle >= 0200h.”, ODh, OAh, “$’’
Глава 14. Clock: смесь 32-битового и 16-битового кодов
437
CODE
.$тмт*
; Использовать функция 67ti DOS для увеличения количества дескрипторов файлов
; программы более, чем 0200b
mov bx,0400h
mov ah,67h
int 21h
jc no_increase
; Открыть файл (левой). Здесь открывается файл CONFIG.SYS в корневом
; каталоге диска С:
mov dx,OFFSET config
mov ax,3000h
int 21h
jc no_open
mov bx, ax
; Создать дубликат дескриптора файла 0200h раз. В конце концов мы получим
; дескриптор >= 0200b
mov ex,0200h
в®: mov ah,45h
int 21h
jc no_dup
loop
; Питаться читать, используя последний дескриптор. Результат может отличаться от
; того, если бы мы просто открыли файл и прочитали (поскольку дескриптор уже >= 0020h)
cmp ах,0200ft
jbe not_enough
mov bx,ax
mov dx,OFFSET buffer
mov cx,0001h
1 mov ah,3Fh
int 21h
jne ok
cmp ax,0006h
jz invalid_handle
no_increase:
no_open:
no_dup:
not_enough:
mov dx,OFFSET something_wrong
mov ah,09h
int 21h
jmp done ,
invalid_ftandle:
mov dx,OFFSET something_very_wrong
mov ah,09h
in't 21h
jmp done
ok:
mov dx,OFFSET no_problem
mov ah,09h
int ' 21h
done:
.EXIT OOh
END
438
Неофициальная Windows 95
Если вы запустите эту программу вне Windows или же под Windows 3.1, то получите сооб-
щение, что все работает нормально (“Just to confirm everything worked OK”). Но если диск С:
управляется 32BFA, вы получите следующее сообщение: “Invalid handle error while reading file with
handle >= 0200h”. Эта ошибка проявляется не только в бета-версии Chicago (что было бы еще как-
то понятно), но и в коммерческих версиях WfW 3.11.
Настоящий обход DOS:
загрузка исполняемого модуля
и файлы отображения памяти
А
Невзирая на использование Win32-flecKpinrropoB файлов, что гарантирует обход файлового
ввода-вывода DOS при загрузке исполняемых файлов или их ресурсов, Win 16-программы и DLL
загружаются вполне нормально под Windows 95.
С исполняемыми модулями Win32 дело обстоит совсем иначе. Win32-nporpaMMbi и DLL
используют портативный формат исполняемого модуля (Portable Executable — РЕ). Когда Windows
95 загружает Win32-мoдyль в адресное пространство процесса, она использует отображенные в
память файлы (memory-mapped, file). В отличие от всего того, что мы до сих пор видели в этой
книге, файлы, отображенные в память, не являются оболочкой для слоя обходных маневров для
вызовов INT 21h. Отображенные в память файлы действительно обходят DOS.
Файл, отображенный в память, — это файл, который действительно отображен в память. Хоро-
шо, это не очень полезное и понятное объяснение. Основная идея состоит в том, что вы получаете
доступ к файлу не с использованием вызовов чтения, записи и перемещения файлового указателя, а
с использованием операций чтения и записи в память (программисты на языке BASIC использовали
бы в этом случае операции реек и роке). Для программы файл, отображенный в память, выглядит
как массив.
Чтобы попытаться сделать эту простую, но необычную идею более конкретной, взглянем на
листинг 14.7, где приводится пример маленькой консольной Win32-nporpaMMbi — MAPFILE. Эта
программа просто распечатывает содержимое файла, указанного в командной строке.
ЛИСТИНГ 14.7. MAPFILE. С
/*
MAPFILE. С
Шульман, 1994
*/
ftinclude <stdlib.h>
ftinclude <stdio.h>
ftinclude “windows.h"
void fail(const char *s) { puts(s); exit(1); }
main(int argc, char *argv[])
{
DWORD size;
unsigned char *p, *p2;
HANDLE f, f2;
int i;
// Открыть файл;
// Да, в Win32 вы открываете файл с помощью CreateFile(OPEN_EXEISTING)
if((f = CreateFile(argv[1], GENERIC_READ,
Г Айва 14. Clock: смесь 32-битового и 16-битового кодов 439
.. . ... .... . . .Г . . . ....................
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
fail(“CreateFile failed");
size = GetFileSize(f, NULL);
// Получить дескриптор отображенного в память файла
if((f2 = CreateFileMapping(f, NULL, PAGE.READONLY, 0, size, NULL)) == NULL)
fail(“CreateFileMapping failed”);
// Отобразить в память весь файл, получить указатель
if((p = MapView0fFile(f2, FILE_MAP_READ, 0, 0, 0)) == NULL)
fail(“MdpViewOfFile failed");
// теперь файловый ввод-вывод (индикатор работы диска светится)!
for(i = 0, р2 = р; i < size; i++, р2++)
putchar(*p2);
CloseHandle(f2);
CloseHandle(f);
}
Хотя три вызова Win32 API, CreateFile, CreateFileMapping и MapViewOfFile занимают боль-
шую часть MAPFILE и хотя глава об отображенных в память файлах в стандартной книге о про-
граммировании под Win32, естественно, сосредоточилась бы на этих трех вызовах, я хочу привлечь
ваше внимание к чрезвычайной курьезности реализации MAPFILE как программы, которая выводит
на экран содержимое файла: ведь в ней совершенно отсутствуют вызовы чтения!
Однако, уверяю вас, программа действительного выводит на экран содержимое файла, указан-
ного в командной строке. Каким образом она выводит файл на экран, если не читает его? А вот каким:
for(i=0, р2=р; i < size; i++, р2++)
putchar(*p2);
Доступ по указателю р2 (*р2) осуществляет чтение из файла. Вместо явного чтения файла мы
используем доступ по указателю. Это и есть сущность отображенных в память файлов.
Можно было бы предположить, что это всего лишь трюк, поскольку р2 вначале равнялся р, а в
MAPFILE р получен из MapViewOfFile API. Если мы ничего не знаем об этой Win32^yHKinni,
покажется очевидным, что она читает целый файл в память и возвращает указатель на первый байт
в буфере памяти.
Этот дешевый трюк, однако, не является принципом работы MapViewOfFile. Когда
MapViewOfFile возвращается, никакого файлового ввода-вывода еще не было. В MAPFILE весь
файловый ввод-вывод осуществляется внутри выражения *р2.
Как это работает? Как Windows 95 “догадывается” предварительно прочесть часть файла, когда
Win32-nporpaMMa читает или пишет по определенному указателю? Она догадывается об этом так
же, как о необходимости подгрузки страниц виртуальной памяти из файла подкачки.
Допустим, программа имеет указатель р на блок памяти, который был отгружен в файл под-
качки виртуальной памяти. Когда программа читает или пишет в эту память (*р или p[i]), возни-
кает исключительная ситуация “отказ страницы”. Операционная система с виртуальной памятью,
вроде Windows, устанавливает обработчик ситуации “отказ страницы” (на микропроцессорах Intel
просто INT OEh). Когда возникает одна из этих ошибок при попытке программы обратиться к
(“потребовать”) памяти, которую операционная система отгрузила на диск, операционная система
подгружает ее обратно с диска, отгрузив перед этим что-нибудь другое для высвобождения места, и
перезапускает команду, которая привела к ошибке.
Отображенные в память файлы работают аналогично: отображенный в память файл — тот же
самый файл подкачки виртуальной памяти. Но не огранивая вас единственным файлом подкачки,
выбранным операционной системой, таким как WIN386.SWP, функции CreateFileMapping и
MapViewOfFile позволяют использовать любые файлы как файлы подкачки виртуальной памяти.
440
Неофициальная Windows 95
Если MAPFILE использует индексацию массива вместо указателей, выражение *р2 заменяется
на p[i], и в переводе на 32-битовый ассемблер получилось бы что-то вроде следующего:
;;; с = р[1]
0137:00401240 MOV EAX,_p
0137:0040124F MOV ECX,_i
0137:00401252 MOV AL,[EAX+ECX] ;; ; здесь происходит отказ
0137:00401255 AND EAX,OOOOOOFF ;; ; однобайтовый символ
Заметим, что команда MOV AL, [ЕАХ+ЕСХ] превратилась в интерфейс чтения байта по смеще-
нию ECX (i) в файле/массиве ЕАХ (р). Подобным же образом MOV [ЕАХ+ЕСХ], AL пишет в
файл (если MAPFILE открыла и отобразила файл не в режиме “только для чтения”).
Однако как это работает на самом деле? Я говорил, что Windows 95 использует файлы, отобра-
женные в память, для загрузки исполняемых Win32-MO/Q^neft. Когда САВ32 загружает Win32-
версию Clock, фрагменты программы как-то читаются с диска с использованием выражений, подоб-
ных *р и p[i]. Как это все транслируется в файловый ввод-вывод, который все же где-то должен
происходить?
Как отмечалось раньше, операции чтения или записи для отсутствующей страницы памяти
вызывают ошибку INT OEh. Чтобы увидеть, где обрабатывается эта ошибка в Windows 95, я
запустил программу FAULTHKS. FAULTHKS — это всего лишь оболочка для сервиса
Get_Fault_Hook_Addr, который обеспечивается VMM. Для вызова этого сервиса FAULTHKS.C
использовала некоторый стандартный VxD.
С:\U NAUTHW\BIN>faulthks
INT V86 PM VMM
OE C00077B0 C00077B0 C00077B0
Обработчик INT OEh будет одним и тем же для V86 режима, защищенного режима и VMM —
не важно, в каком режиме находится машина в момент INT OEh, ошибка всегда будет обработана по
адресу С00077В0. Краткая проверка этого обработчика показывает, что прежде всего он читает
регистр процессора CR2. Как объясняется в несчетном количестве книг по архитектуре Intel, CR2
является регистром адреса отказавшей страницы: он содержит линейный адрес самой последней
ошибки INT OEh.
Если теперь протрассировать INT OEh, которое возникло при доступе к отображенному в память
файлу в Windows 95, то вы найдете кое-что интересное: из глубины VMM обработчика ошибки
отказа страницы мы внезапно попадаем в KERNEL32.DLL.
Довольно неожиданно встретить этакое, чтобы в ядре операционной системы, в обработчике
ошибки отказа страницы, вызывалась такая высокоуровневая DLL, как KERNEL32. Но именно так
и работают отображенные в память файлы: поскольку эти файлы в действительности всего лишь
выбранные пользователем файлы подкачки виртуальной памяти, должен быть и менеджер памяти
для этих файлов. Этот менеджер памяти является частью KERNEL32.DLL, которая обеспечивает
Win32 API для отображенных в память файлов. VMM вызывает KERNEL32, так как KERNEL32
устанавливает свой менеджер отображенных в память файлов, через сервис VMM _PagerRegister.
KERNEL32 не может непосредственно обратиться к этой функции VMM, но VMM обеспечивает
Win32-cepBHC, 10003h, который вызывает _PagerRegister. Это один из Win32-cepBHCOB управления
памятью, которые обеспечивает VMM:
Win32 service table © VMM+ED20 (39 services)
OOOIOOOOh (0)
00010001h (3)
00010002h (5)
v00010003h (3)
“ 00010004h (1)
00010005h (2)
00010006h (1)
© VMM10+1C8h
© VMM6+1188h
@ VMM6+119Dh
@ VMM5+165Ch
@ VMM5+1671h
@ VMM5+1686h
@ VMM7+000h
_PageReserve
_PageCommit
_PageDecommit
_PagerRegister
_PagerQuery
_PagerDeregister
_ContextCreate
Глава 14. Clock: смесь 32-битового и 16-битового кодов
441
00010007b (0) • VWf7+015h
00010008b (1) • VMM7+02Ab
00010009b (4) 0 VMH7+03Fh
_ContextDestroy
_PageAttach
_PageFlush
Следовательно, отображенные в память файлы отличаются от обычной страничной виртуальной
памяти тем, что являются, по сути, инсталируемым менеджером памяти. Если вы следили за разви-
тием операционных систем в течение последних десяти лет, то, вероятно, помните, что это понятие
устанавливаемого менеджера памяти сильно перекликается с концепцией микроядра, впервые пред-
ставленной в операционной системе Mach, в которой многие раньше привилегированные модули
операционной системы вынесены на уровень пользовательского прикладного программного обес-
печения. Действительно, интерфейс _PagerRegister в VMM Windows 95 подобен тому, что в Mach и
OSF/1 называется внешним управлением памятью. Есть две превосходные книги на эту тему (обе
они полезны для понимания отображенных в память файлов в Windows 95): “Design of the OSF/1
Operating System” фирмы Open Software Foundation и “Programming under Mach” авторов Boykin,
Kirschen, Langerman и LoVerso.
Разработчик Mach, Ричард Рашид (Richard Rashid), сейчас возглавляет Microsoft Research в
широко рекламируемой Advanced Technology Group (ATG) (Business Week, March 21 и June 27,
1994). И не столь важно, что идея вынесения автономных модулей операционной системы на уро-
вень приложений на первый взгляд расходится со стратегией компании, направленной на включение
в систему приложений общего назначения. Модульная (т.е. не интегрированная) архитектура спо-
собствует тем способам расширения операционной системы, которые столь важны для Microsoft.
Итак, при доступе к отсутствующей части отображенного в память файла, обработчик INT OEh
VMM вызывает программу, зарегистрированную KERNEL32. KERNEL32 вызывает VWIN32,
которая, в свою очередь, обращается к функции IFSMgr_RingO_FileIO. Как видно из рис. 14.7,
IFSMGR затем осуществляет свой обычный 32BFA (32-битовый доступ к файлам), вызывая, на-
пример, VxD VFAT. VFAT вызывает супервизор ввода-вывода (IOS), который обращается либо к
VxD Fast Disk, либо к драйверу отображения реального режима (RMM).
Заметим,* что на рис. 14.7 не упоминается DOS. Когда Windows 95 загружает исполняемый
Win32-MOflynb, наподобие CLOCK.EXE, DOS в буквальном смысле не попадает в кадр. Отображен-
ные в память файлы являются великолепным примером истинного обхода DOS. Однако это резко
отличается от обычного положения вещей в Windows 95.
WIN16 повсюду: сага о WIN16LOCK
Один из маркетинговых лозунгов разработчиков Microsoft “Win32 повсюду”: Microsoft хочет
продвигать идею, что Win32 API является самым надежным вариантом мобильного API, который со
временем будет работать на любой возможной компьютерной платформе. Впрочем, при более
подробном знакомстве с внутренней организацией Windows 95, удивляешься, почему этот лозует
не такой — "Win 16 повсюду”: куда не взглянешь в Windows 95 — всюду Win 16-код. Имеются
вполне веские причины, чтобы Windows 95 полагалась на 16-битовый код; но совершенно непонят-
но, почему Microsoft отрицает, что Windows 95 полагается на 16-битовый код в той мере, в которой
это есть. Это отчасти проясняется, если рассмотреть имеющий позорную известность Winl6Lock.
В этой главе я загружал Win 16 Clock под Windows 3.1, a Windows 95 и Win32 Clock под
Win32s и Windows 95. И вот последнее, что бы мне хотелось сделать с Win32 Clock: заставить их
на мгновение остановиться. Нет, тиканье часов не причиняет мне головную боль. Я хочу приостано-
вить Win32 Clock, чтобы разобраться с Winl6Lock.
Сперва несколько общих замечаний. Для начала скажем, что Winl6Lock теперь называется
Winl6Mutex. В одной из своих многочисленных легких насмешек в адрес Microsoft Адриан Кинг
(Adrian King, Windows 95 изнутри, с. 189) говорит: “Вот что значит маркетинг. Winl6Mutex назы-
вался Winl6Lock. После ряда технических споров, об эффективности работы Windows 95 в много-
задачном режиме в отделе маркетинга решили, что Winl6Mutex вызывает меньше нежелательных
ассоциаций, и Winl6Lock был переименован”.
442
Неофициальная Windows 95
Итак, что же такое Winl6Mutex, урож-
денный Winl6Lock? Термин “Mutex” означает
взаимное исключение: то, что обеспечивает каж-
дому ресурсу одновременно только единствен-
ного пользователя. В Windows 95 блоки-
ровка/взаимное исключение повсюду исполь-
зуется в коде Winl6 API (в первую очередь в
модулях USER и GDI, но также и в других,
. включая, как мы увидим, даже Win 16
KERNEL). Win 16 API никогда не разраба-
тывался как реентерабельный, чтобы обслужи-
вать много вызовов одновременно. Возможно,
по этой причине Windows 3.x никогда не
позволяла Windows GUI запускаться в более
чем одной VM одновременно: VM работают в
приоритетной многозадачности, и Win 16 DLL
не были подготовлены для обслуживания много-
кратных вызовов.
В Windows 95 ветви выполнения, как и VM,
работают в приоритетной многозадачности, и вет-
ви могут вызывать Windows API. Только Win32
ветви являются многозадачными, и Win32 API
предположительно написан с возможностью реен-
терабельности. Однако, как мы знаем, многие
вызовы Win32 API всего лишь обращаются к их
Winlfr-эквивалентам. Таким образом, две ветви
могут войти в Winl6 DLL одноременно, если не
принять меры для предотвращения такой си-
туации.
"В книге Адриана Кинга Windows 95 изнут-
ри (с. 186-192) приведено превосходное обсуж-
дение всех возможных компромиссов, включая
объяснение, почему невозможно переписать весь
Winl 8-код для его реентерабельности. Решение
Microsoft заключается в использовании в Win 16
некоторото эквивалента флага InDOS. Он из-
вестен как Winl6Lock. или же Winl6Mutex.
Все Win32 API, которые обращаются к Winl6,
должны вначале захватить Winl6Mutex, а по
окончании освободить его. Поскольку базовые
USER32 и GDI32 API полагаются на Winl6,
Рис. 14.7. Доступ по указателю к отображенному в
память файлу может завершиться действительным
дисковым вводом-выводом, осуществленным каким-то
Windows VxD
задача, захватившая Winl6Lock, может легко
приостановить выполнение других Win32-зaдaч.
Winl6Lock развязал огромную дискуссию,
вышедшую за пределы команды разработчиков
Chicago в Microsoft. Кинг отметил (с. 186): “В
конце 1993 года этот вопрос стал, пожалуй,
наиболее популярным предметом обсуждения на посвященных Windows 95 форумах CompuServe и
на встречах независимых разработчиков, которые организовывала Microsoft”.
Теперь многие из этих дебатов кажутся глупыми, и приходится согласиться с Кингом, что
Microsoft избрала лучший возможный путь. Многие из этих дискуссий просто сводились к
оскорблениям со стороны приверженцев NT и OS/2. Тем не менее, это остается решающим
событием.
Глава 14. Clock: смесь 32-битового и 16-битового кодов
443
Общее мнение разработчиков, что Winl6Mutex представляет потенциальную опасность только
тогда, когда вы запускаете Win 16-приложения. Конечно, каждый будет изредка запускать Winl6-
приложения, так что ситуация не прояснилась. Но давайте подумаем о будущем, когда Win32-npH-
ложения пойдут нескончаемым потоком (т.е. когда они начнут появляться на полках интеллек-
туального программного обеспечения по цене $79,95). Перестанет ли Winl6Mutex быть потенци-
альной проблемой, если вы запускаете только Win32-npEUK^einiH?
Ряд экспертов отвечают положительно. Например, мой друг Мэтт Петрек (Matt Pietrek, PC
Magazine, September 27, 1994, p. 307) пишет, что “чем скорее вы переделаете ваши приложения в
32-битовые, тем лучше. Если ваша система не выполняет никаких 16-битовых программ, то
Winl6Mutex не может быть источником беспокойства...” И далее: “Ни одна из функций KERNEL32
не блокируется Winl6Mutex”. Аналогично, Адриан Кинг (Windows 95 изнутри, с. 192) советует,
что при отсутствии Win 16-приложений проблемы также отсутствуют: “Оболочка, и спулер печати
представляют собой 32-битовые приложения, поэтому эти наиболее часто используемые компоненты
вообще не будут иметь отношения к семафору”. И дальше: “Возможные недостатки такого решения
[Winl6Mutex] могут проявиться, когда пользователь запускает сразу несколько 16- и 32-битовых
приложений, что для разработчиков должно стать еще одним побуждающим мотивом сконцент-
рировать свое внимание на Win32-npwnM<eHHHx” (с. 192).
Но, как мы видели где-то в этой книге, система Windows 95 (по крайней мере Chicago бета-1)
всегда имеет две выполняемые Wmie-задачи, TIMER и MSGSRV32. Но самое главное, мне не впол-
не ясно, почему Winl6Lock перестанет быть потенциальной проблемой, если не запущены Winl6-
задачи. Кинг пишет (с. 190), что “Задача [ветвь] Win32, в которой не производится необходимых
для перехода к Winl6 преобразований, никогда не останавливается Winl6Mutex”. Но известно, что
наиболее используемые Win32 API в USER32 обращаются к USER и что наиболее часто исполь-
зуемые Win32 API в GDI32 также обращаются к GDI. Кинг мог бы утверждать это только для
ветвей с интенсивными вычислениями, которые не делают никаких обращений к Win32 API. Вот о
таких ветвях можно сказать с уверенностью, что они не будут блокироваться Winl6Lock.
Однако большинство Win32-BeTBeft должны будут захватывать и освобождать Winl6Lock.
Здесь, как я понимаю, слова о том, что Winl6Lock “не может быть источником беспокойства” при
отсутствии Winie-задач (условие, которое невыполнимо для текущей версии Chicago), пред-
полагают, что API вполне можно доверять. Например: “коды USER и GDI будут быстро выпол-
няться и освобождать Winl6Mutex. Никакая 32-битовая ветвь никогда не будет задерживать
Winl6Mutex на сколько-нибудь значительный период времени” (Microsoft Systems Journal,
September 1994, p. 23).
To, что это оказывается принятием желаемого за действительное, демонстрирует небольшая про-
грамма W16LOCK (листинг 14.8), которую мы будем использовать для остановки Win32-nporpaM-
мы Clock на любой промежуток времени, задаваемый числом в командной строке.
ЛИСТИНГ 14.8. W16L0CK.C
// w16lock.c
«include <stdlib.h>
edefine WIN32_LEAN_AND_MEAN
«include “windows.h"
«define MSG(s) MessageBox(0, s, “W16L0CK", MB_OK)
void fail(const char »s) { MSG(s); exit(1); }
void (WINAPI *GetpWin16Lock)(DWORD *pWin16Lock);
void (WINAPI *_EnterSysLevel)(DWORD lock);
void (WINAPI *_LeaverSysLevel)(DWORD lock);
int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance,
LPSTR IpszCmdLine, int nCmdShow)
{
// Код Microsoft С в соответствии c MSJ, May 1991, pp.135-6
444
Неофициальная Windows 95
«define argc __argc
«define argv __argv
extern int _argc;
extern char **_argv;
DWORD Win16Lock = 0;
int iter = (argc < 2) ? 10000 : atoi(argv[1]);
int i;
«define GET(mod, func) {\
if(!(func = GetProcAddress(GetModuleHandle(mod), #func)))\
fail(“Can’t link to" mod “I" #func);\
}
GET(“KERNEL32”, GetpWin16Lock);
GET(“KERNEL32”, _EnterSysLevel);
GET(“KERNEL32", _LeaveSysLevel);
GetpWin16Lock(&Win16Lock);
if(Win16Lock == 0)
fail(“GetpWin16Lock didn’t work”);
_EnterSysLevel(Win16Lock);
for(i = 0; i < iter; i++)
(void) GetVersion(); // или любой другой вызов, который не обращается к Win16
_LeaveSysLevel(Win16Lock);
return 0;
1
W16Lock использует недокументированную функцию GetpWinl6Lock, экспортируемую из
KERNEL32 для получения адреса того, что называется Winl6llock. Она захватывает Winl6Lock
таким же способом, как это делают все API, использующие переходники: вызовом _EnterSysLevel,
а освобождает с помощью _LeaveSysLevel. Заметим, что Winl6Lock не является настоящим
взаимным исключением; это один из нескольких системных уровней. W16Lock использует
динамическую компоновку времени выполнения, чтобы обратиться к GetpWinl6Lock,
_EnterSysLevel и _LeaveSysLevel API.
После захвата Winl6Lock, W16Lock (которая, позвольте мне напомнить вам, является Win32-
программой) входит в цикл, вызывая Win32 API GetVersion столько раз, сколько указано в
командной строке (или 10000 раз, если вы ничего не указали).
Я выбрал GetVersion, поскольку она не обращается к Winl6. Однако можно выбрать любой
другой Win32 API, который не обращается к Winl6. Отметим, однако, что некоторые API из
KERNEL32 обращаются к KRNL386, поэтому является ошибочным предположение, что “Функции в
KERNEL32 могут вызываться 32-битовыми ветвями без опасения блокирования Winl6Mutex, так
как KERNEL32 никогда не обращается к KRNL386” {Microsoft Systems Journal, September 1994, p.
23). Как мы уже видели в предыдущих главах, KERNEL32 определенно может обращаться к
KRNL386.
Когда выполняется цикл программы W16Lock, Clock и другие Win32-приложения, использую-
щие USER или GDI, замораживаются. Часы останавливаются. Если вы запустите WINBEZMT,
кривые Безье не будут изгибаться. Когда W16Lock завершает цикл и вызывает _LeaveSysLevel, все
снова оживает.
Почему программа Clock остановилась? Какие функции, зависящие от Winl6Lock, она вызывает?
Оказывается, все ту же вездесущую процедуру QT_Thunk, которая вызывает _EnterSysLe-
vel(Winl6Lock). Мы рассматривали QT_Thunk в главе 13, но в то время я не упоминал о
_EnterSysLevel или Winl6Lock.
Итак, какие Win32 API прибегают к услугам QT_Thunk? Да почти каждая процедура в
USER32 и GDI32. И, как мы видели в предыдущих главах, многие функции KERNEL32 тоже.
Запуская Win32 Clock, я поместил контрольную точку отладчика на QT_Thunk и обнаружил,
Глава 14. Clock: смесь 32-битового и 16-битового кодов
445
например, что такие API KERNEL32, как GetProfileString, GetPrivateProfileString и
GetSystemDirectory вызывают QT_Thunk, и, следовательно, ожидают Winl6Lock. Главное, что это
же касается вызовов загрузки 16-битовых модулей.
Тот факт, что кто-то обращается к _EnterSysLevel(Winl6Lock), не означает, что текущая ветвь
будет заблокирована. Обычно тот, кто вызывает, просто встречает блокировку и немедленно
возвращается. Трассируя _EnterSysLevel, однако, можно пойти по пути, когда одна ветвь должна
быть заблокирована, пока другая ветвь не снимет блокировку:
QT.Thunk
_EnterSysLevel+OA EDX»Win16Lock
_VxDCa110 2A001Dh
VWIN32+1A1h
VMM Wait-Semaphore EAX=COFD1OE0
Существует много способов сойти с этого пути. Так будет ли ветвь действительно использовать
Wtal6Mutex в чистой Wln32-CHCTeMe? Опять же, трудно сказать, что такое чистая Win32-CHcreMa,
но я полагаю, что система Windows 95, загруженная в отказоустойчивом режиме и выполняющая
только Explorer, является настолько чистой, насколько это возможно. В этой конфигурации я по-
местил контрольную точку на следующий код в JEnterSysLevel:
0137:BFF71B9B 52 PUSH EDX
0137:BFF71B9C 51 PUSH ECX
D137:BFF71B9D 52 PUSH EDX
0137:BFF71B9E 681D002A00 PUSH 002A001D
0137:BFF71BA3 Е864030000 CALL KERNEL32!VxDCallO
Сработала ли она из-за Winl6Lock? (Заметим, что есть и другие блокировки, которые пере-
даются к _EnterSysLevel). Да. Даже когда я просто перемещал окна, этот код постоянно вызывался.
Аналогичное происходило, когда выполнялась WINBEZMT.
Но это все же не означает, что ветвь будет блокироваться. Все сводится к обращению к функ-
ции Wait_Semaphore из VMM, которая вызывается VWIN32 сервисом 2A001Dh. В сущности,
Winl6Lock является семафором VMM. Во многих случаях Wait_Semaphore также может мгновенно
завершаться. Но контрольная точка, помещенная на части Wait_Semaphore, где ветвь должна
действительно ожидать, срабатывает очень часто, даже когда выполняются только Win32-
приложения.
Есть одна интересная особенность у Winl6Lock. Вспомните, что, кроме обращений Win32 к
Winl6, есть также много обращений Winl6 к WIN32. Всякий раз, когда какой-то Win 16-код обра-
щается к Win32 (K32Thkl632Prolog), Winl6Lock должен уменьшиться. Когда он возвращается в
родную среду Winl6 (K32Thkl632Epilog), Winl6Lock должен увеличиться.
Я убежден, что специалисты в Microsoft приняли правильное решение при выборе Winl6Lock.
Windows 95 не является NT. Если пользователи и разработчики хотят NT, они ее получат. Но
настораживают утверждения Microsoft, что Winl6Lock не может быть вероятной проблемой в чи-
стой Win32-CHCTeMe Windows 95, тогда как просто нет такого понятия, как чистая Win32-cHcreMa
Windows 95 (да оно и не нужно), а, самое важное!, и так очевидно, что Winl6Lock будет
использоваться до тех пор, пока Win32 API не перестанет обращаться к Winl6.
Мы увидели, что Microsoft использует Winl6Lock как не очень изящный способ заставить
разработчиков переключиться на приложения с использованием Win32 API. (Winl6Lock “должен
стать еще одним побуждающим мотивом сконцентрировать свое внимание на Win32-npBUK^emiHx”.)
Это было подхвачено компьютерной прессой — “Чем скорее вы переделаете ваши приложения в 32-
битовые, тем лучше!”
В этом много иронии. Причина, по которой существует Winl6Lock, прежде всего состоит в том,
что Microsoft не хочет полностью переписывать на 32-битовый код ядро Windows DLL. И одна из
основных причин состоит в том, что 32-битовый код “пожирнее”, чем 16-битовый код. Как говорит
Адриан Кинг (с. 187):
446
Неофициальная Windows 95
Перешкыванве всех подсистем: KERNEL, USER я GDI в виде 32-битового кода привело бы к резкому
увеличению необходимого для работы объема памяти. Одним только модулям USER и GDI требуется
порядка 800 Кбайт (в сноске добавлено: “Из запланированного для работы всей системы объема памяти
в три мегабайта”). Специальные оценки показали, что переход к 32-битовому коду увеличил бы
требования к памяти примерно на сорок процентов, что для модулей USER и GDI составило бы более
одного мегабайта. Учитывая, что Windows 95 должна работать на четырехмегабайтовой системе, такое
требование было признано неприемлемым.
32-битовый код предоставляет огромные выгоды для программ, которые на самом деле должны
работать с 32-битовыми величинами, но бесконтрольное, расточительное использование 32 битов
приведет к “раздутому” коду. Я не уверен в справедливости сравнения, но Win32-Bepcwi игры
Freecell, которая распространяется вместе с Win32s, занимает 49188 байтов, в то время как ее
Winl6-BepCHH, которая поставляется с бета-1 версией Chicago, занимает только 33184 байта. Win32-
программа Clock, которая поставляется с Chicago, занимает 38400 байтов, a Win 16-версия из Win-
dows 3.1 — только 16416 байтов. Я написал крошечное приложение для Windows под названием
MessageBox для вывода на экран строки “hello world!”. Исполняемый модуль Win 16 занял 3668
байтов, a Win32-BepcHH — 9216 байтов. Эти сравнения не вполне справедливы, но обратите вни-
мание на слова Кинга, что собственный код Microsoft увеличился бы на 40%. Если бы Windows
распухла на 40%, перейдя на чисто 32-битовый код, тогда ваше приложение сделало бы то же самое.
Когда не! никакой нужды в переходе к 32-битовому коду, увеличение размера кода — плохая идея.
Microsoft знает, что 16-битовый код зачастую необходим. Сохранение 16-битового кода в ядре
Windows API достаточно важно, чтобы Microsoft ввела Winl6Lock. Стыдно, что Microsoft пытается
использовать тот же самый Winl6Lock как мотив для переключения сил разработчиков на 32-
битовый код.
Другой изобретенный Microsoft способ принуждения к переходу всех и вся на 32-битовый
код — независимо от того, имеет ли это смысл для данной программы — состоит в том, чтобы не
давать логотип “Windows 95 Compatible” всякой программе, которая не использует Win32 API.
Тут просматривается определенная схема: Windows 95 принимает множество весьма разумных
компромиссов, вроде сохранения большого количества Win 16-кода в API и избежания исполь-
зования OLE в оболочке (см. главу 2). В то же самое время Microsoft сообщает, что другие ком-
пании не должны следовать этим же компромиссам, и даже отрицает, что сама их делала. Общий
подход Microsoft: “Делайте, как я говорю, а не так, как я делаю”. Разработчиков намного больше
устроил бы обратный подход.
Глава 14. Clock: смесь 32-битового и 16-битового кодов
447
Эпилог
Microsoft — повсюду,
где вы хотите быть
Я несколько дней ломал голову, чем же закончить эту книгу, когда мне вдруг подвернулась
отличная тема в статье из журнала Wall St. Journal (October 14, 1994).
Microsoft собирается купить акции компании Intuit
Корпорация Microsoft, стремясь захватить один из последних оставшихся ключевых рынков, заключила
соглашение о покупке фирмы, специализирующейся в области программного обеспечения для финан-
совых операций — компании Intuit Inc., стоимость которой составляет около полутора миллиардов
долларов. Сделка, получившая одобрение со стороны правления компании Intuit, станет самым дорого-
стоящим приобретением в сфере программного обеспечения и свидетельствует о драматическом увели-
чении степени консолидации в одной из свободно развивающихся отраслей производства.
В разделе “Обновление индустрии” я представил мрачную картину будущего индустрии про-
граммного обеспечения для персональных компьютеров: рост крупного бизнеса, постоянное усиле-
ние влияния корпорации Microsoft и окончательный переход рынка программного обеспечения к
небольшим фирмам из 2-3 человек, использующим Visual Basic for Application для производства
отдельных “примочек” для Microsoft Office.
Одной из ярких точек в этом мраке была компания Intuit, создатель популярного пакета для
финансовых операций Quicken. Компания Intuit была одной из немногих, которые преуспели в
борьбе против Microsoft. Говорят, что пакет Quicken имеет 6000000 активных пользователей против,
возможно, 500000 пользователей пакета Microsoft Money. Казалось бы, программное обеспечение
для финансовых операций является областью, где другие компании (не Microsoft) могли бы хорошо
проявить себя.
Но теперь, как сообщает Wall St. Journal, “производство программного обеспечения для персо-
нальных компьютеров потеряет одного из своих ярчайших представителей. В течение нескольких
лет многие производители программного обеспечения следили за руководителем компании Intuit
Скоттом Куком, небольшая компания которого процветала на рынке, захваченном несколькими
большими производителями”.
В некотором отношении компания Intuit и Скотт Кук все еще остаются положительными при-
мерами. Как отмечает Лоренс Фишер в New York Times (16 октября 1994 г.), Голиаф не победил
Давида: “Голиаф бросил свое оружие и полез за чековой книжкой”. Кук, начинавший с Quicken,
простой программы заполнения чеков, сегодня получает акции компании Microsoft на сумму около
330 млн. долларов.
Это прекрасная история о личном успехе, которая доказывает, что, даже не имея отношения к
компании Microsoft, можно зарабатывать деньги на производстве программного обеспечения для
персональных компьютеров. К сожалению, есть и плохие новости: компания Microsoft захватывает
“чужую” территорию; одним конкурентом стало меньше; все меньше и меньше компаний контро-
лируют программное обеспечение. Нет ничего особенно дурного в стремлении компании Microsoft
целиком контролировать программное обеспечение общего назначения для персональных компью-
теров, — и решение Департамента юстиции Соединенных Штатов по делу Microsoft, кажется, сви-
448
Неофициальная Windows 95
детельствует о том, что при этом за компанией не замечено каких бы то ни было серьезны?
нарушений антимонопольных законов — но монополия компании Microsoft, как и любая другая, г.
конечном счете приведет к диктату цен и замедлит темпы развития.
Дело не в приложениях
Почему компания Microsoft решила заплатить 1,5 миллиарда долларов за программу, которая
помогает , людям вести чековые книжки? Почему Microsoft платит компании Intuit значительно
больше, чем заплатила за прекрасную базу данных FoxPro?
Чтобы понять это, необходимо уяснить, что компания Microsoft не просто захватывает одну
прикладную область за другой. Компания захватывает приложения и закладывает их в свою
операционную систему. А это большая разница. По многим направлениям компания Microsoft
действительно не имеет бизнеса приложений. Правда, приложения в настоящее время приносят 63%
доходов компании по сравнению с 42% в 1989 году (Economist, 17 сентября 1994 г.). Однако это не
главное направление развития компании, так как приложения значительно менее выгодны по
сравнению с операционными системами.
Операционная система Windows является основой бизнеса компании Microsoft, и эта основа
должна постоянно расширяться, переопределяться или и то, и другое одновременно. Трудность
здесь заключается в необходимости поддерживать узкую монополию компании Microsoft на
вездесущий стандарт. (Компания Intel не достигла успеха в этом и в настоящее время имеет
серьезного конкурента в лице AMD.) Как говорил мне в октябре 1993 года вице-президент
компании Microsoft Брэд Сильверберг: “Если Windows заморозить и больше не развивать, то она
может легко превратиться в обычный товар. Компания не хочет оказаться на рынке BIOS”.
Чтобы постоянно поддерживать развитие операционной системы, компания Microsoft расширяет
ее за счет приложений. Компания не хочет “оказаться на рынке BIOS” (другими словами,
заниматься созданием легко клонируемого программного обеспечения) и в то же время она не хочет
заниматься только приложениями. Даже ее набор приложений Microsoft Office становится все более
и более похожим на часть операционной системы. Microsoft Office становится платформой,
системным программным обеспечением.
Компания Microsoft может получать большую часть своих доходов от приложений, но она по-
прежнему остается фирмой, занимающейся в основном системным программным обеспечением.
Таким образом, BIOS и приложения не являются объектом бизнеса для Microsoft. Ее бизнес
находится где-то между ними: системное программное обеспечение, которое постоянно растет,
растет, растет...
То, что Windows 95 является главным шагом в расширении операционной системы, заметно
даже посторонним. Например:
Многие продукты, разработанные меньшими компаниями, в настоящее время попали под господство
операционной системы компании Microsoft. Г ля?,я на раннюю версию преемника Windows — Chicago,
можно обнаружить встроенные заготовки для подключения диалоговой Службы фирмы Microsoft. В
конце 1994 г. (возможно в середине 1995 г.) компания будет иметь возможность ежемесячно отправлять
несколько миллионов копий последних результатов потенциальным подписчикам. America Online не
может даже мечтать о таком количестве. Ей придется переживать об объеме содержимого, а не о
качестве самой службы связи.
— Andrew Kessler, “The monolith: Like the ominous black slab in ‘2001: A Space Odyssey’, Microsoft Corp,
continues to expand its software line, leaving few vendors in its wake,” Forbes, 14 March 1994.
Посмотрим теперь, как приобретение пакета Quicken соответствует всему этому. Чтобы за-
платить 1,5 миллиарда долларов, компания должна иметь гораздо более серьезные намерения, чем
желание просто продавать программное обеспечение для финансовых операций. Действительно,
газета New York Times отмечала, что вице-президент компании Microsoft Майк Мэплз “заметно
избегал называть пакет Quicken приложением”. Нет, Microsoft преследует более далекую цель для
этого программного обеспечения.
Эпилог
449
’ Какоеотнцшение программанроверки чеков может имел» к системному программному обеспе-
чению? У New York Times, есть хорошее объяснение:
Все становится понятным лишь тогда, когда рассматривается в более широком контексте появления
рынка оперативной (on-line) торговли. Пользователи пакета Quicken уже могут оплачивать свои счета
электронным способом, и существует карточка Quicken Visa, которая позволяет получать ежемесячный
электронный бюллетень через диск или модем.
В то время как компании Intuit немного не хватило времени, чтобы установить связи с основными
банками, компания Microsoft может сделать пакет Quicken фактически стандартом для банковского
обслуживания на дому с использованием персональных компьютеров и модемов сегодня, а завтра под-
ключить к этой технологии интерактивное телевидение и кабельные системы. Инвестирование и страхо-
вое обслуживание станут в дальнейшем естественными расширениями.
Компания Microsoft надеется, что компания Intuit введет ее в мир оперативного банковского
обслуживания (on-line banking). Кук станет исполнительным вице-президентом по вопросам элек-
тронной коммерции в компании Microsoft. Обратите внимание на должность. Появится ли в буду-
щем карточка Microsoft Visa?
Не провидцы, но приспособленцы
Оперативные банковские операции — это как раз та новая область, которую осваивает ком-
пания Microsoft. Кабельное телевидение, телефонная связь, оперативное обслуживание: вы слышали
обо всем этом. Но важно не путать мечты компании Microsoft с реальностью. Компания Microsoft
положила свои яйца в несколько корзин. Только из нескольких что-то вылупилось. Например, при
всех разговорах компании о кабельном телевидении и телефонном обслуживании, дела в данном
направлении идут хуже, чем хотелось бы. Не совсем ясно, что, кроме таланта отпугивать потен-
циальных партнеров, принесет компания Microsoft своей “цифровой” братии.
У компании Microsoft было несколько известных неудач, включая OS/2 1.x, LAN Manager и
Windows NT. Microsoft — не та компания, которая обладает даром предвидения; это компания в
высшей степени приспособленческая (в лучшем смысле этого слова). Адриан Кинг хорошо
разъясняет эту точку зрения в своей книге Windows 95 изнутри (с. 25), рассказывая о том, как он
понял, что “спустя семь лет Windows наконец-то добьется успеха". Кинг говорит, что Гейтс и
Баллмер “попытаются убедить вас, что все так и было задумано с самого начала. Не верьте".
В качестве примера того, как компания Microsoft предполагала в действительности планировать
работу, интересно взглянуть на ту часть истории, которая происходила за покупкой компании
Intuit. Репортер Newsweek Майк Мейер присутствовал на двух “BillG встречах”, первая с командой
Microsoft по бейсболу, — она заканчивается обсуждением покупки ведущего спортивного журнала,
а затем встреча с группой Microsoft Money. Гейтс обвиняет их в том, что их детище сильно уступает
пакету Quicken компании Intuit.
Затем, как уже было в истории компании Microsoft, происходит то, что в считанные мгновения
превращает поражение в победу. Разговор переходит к более широкому обсуждению направлений в бан-
ковской деятельности. “Куда мы идем и что это нам дает. Банки — это динозавры, — говорит Гейтс. —
Мы можем ‘обойти’ их”. Менеджер группы Microsoft Money недоволен союзом с большой банковско-
кредитной компанией: “Слишком медленно”. Вместо этого он предлагает иметь дело с небольшой, легко
контролируемой, клиринговой компанией. “Почему бы нам не купить их?”, — спрашивает Гейтс,
глубоко задумываясь. Ему приходит в голову, что люди, выполняющие банковские операции на дому,
будут заполнять чеки, пользуясь программным обеспечением компании Microsoft. Затем компания
Microsoft может проводить все эти операции через свои новые отделы, взимая плату с каждой такой
операции. Внезапно Гейтс перестал досадовать на группу Money. У него появляется представление о
том, как “трансформировать мировую финансовую систему”. “Это ‘горшок с золотом’, — заявляет ои,
ударяя кулаками по столику, торжествующий, голодный и взвинченный. — Дайте мне только
прорваться в это дело, и, клянусь, мы сделаем кучу денег!"
450
Неофициальная Windows 95
Теперь Microsoft действует. Всего за три часа она разработала планы покупки по крайней мере двух
компаний, разорвала союз со своим главным финансовым учреждением, выбрала другое и сделала
основные шаги в “два невероятно новых мира”, как назвал их Гейтс, — домашние банковские операции
и спортивные развлечения. Другой компании, возможно, потребовались бы месяцы, чтобы сделать так
много.
— Michael Meyer, “Culture Club”, Newsweek, 11 July 1994.
Этот небольшой эпизод многое говорит о том, как действует компания Microsoft. Компания
обладает огромной способностью выходить победителем из казалось бы проигрышных ситуаций.
Каждый, кто хочет выжить, занимаясь программным обеспечением, должен осознавать, что
Microsoft теперь доминирует в индустрии й, по-видимому, так будет продолжаться даже в начале
XXI века. Не суетитесь, пытаясь изменить это положение, но имейте в виду, что Microsoft знает о
будущем не больше вас или меня. Как отмечали Стивен Мейнс и Пол Эндрюс в своей великолепной
биографии Гейтса, стратегия “Microsoft повсюду” выглядит почти так, как будто компания “сделала
ставку на каждый номер в рулетке”.
Гейтс и компания Microsoft могут позволить себе играть таким образом, благодаря средствам,
поступающим от их “дойной коровы” — MS DOS. Мы же должны быть более осмотрительными.
Мы не должны уделять внимания всему, что считает важным Microsoft, как-то: NT, OLE, ODBC,
TAPI, MAPI, WOSA, NDIS, MFC, VBA и OCX (перечислено далеко не все). Не все из этого
оказалось действительно важным. Мейнс и Эндрюс рассказывают о том, что Microsoft продолжает
делать ставки: “Игрокам, которые предпочитают видеть в компании Microsoft монолит, способный
навязать рынку свою волю, кажется, что огромное количество этих ставок не принесет большой
отдачи”.
В то же время Microsoft обладает реальным влиянием на индустрию программного обеспечения
для PC. Windows 95 еще больше укрепит это положение. Следовательно, разработчикам
необходимо понимать системы Microsoft и снаружи, и изнутри. Изучив даже маленький кусочек
кода Windows, вы поймете, как работают десятки миллионов компьютеров. Это вовсе не то же
самое, что изучать каждый новый интерфейс, важный с точки зрения Microsoft. Повторю еще раз,
Microsoft не знает, что окажется важным, а что — нет. Но если вы четко понимаете, как работают
системы фирмы Microsoft, то окажетесь отлично подготовленными к пониманию Windows 95,
Windows 96 , Windows 97 и чего угодно, что может преподнести нам Microsoft.
Эпилог" 1 ’г 7
451
Оглавление
Предисловие редактора. Книга о Windows 95 и не только 5
Предисловие 7
Обновление индустрии. Влияние Windows 95 12
Постоянно расширяющаяся операционная система 13
Уроки истории: MS DOS 5.0 и 6.0 16
Что принадлежит операционной системе? 19
Логотип совместимости с Windows 95 21
Является ли Microsoft Office операционной системой? 21
Microsoft и Justice Department 23
Windows 95: опасности и возможности 27
Глава 1
Добро пожаловать в Windows 95 31
Обход COMMAND.COM... 34
...но еще и обход DOS? 37
Зачем пытаться опровергать заявления Microsoft? 46
Эти вездесущие диаграммы фирмы Microsoft 46
Декларируемая интеграция 49
Windows 95 и DOS 51
Кто боится MS DOS? 62
WfW 3.11: заброшенная операционная система 63
Глава 2
Наблюдения за процессом загрузки Chicago 67
От WINBOOT.SYS к WIN.COM 67
От реестра к XMS 71
От IFSHIP.SYS к WIN.COM 75
От WIN.COM к KRNL386.EXE 76
Внутри менеджера виртуальной машины 80
VXD: TSR 1990-х годов 88
От KRNL386.EXE к CAB32.EXE 92
Исследование Explorer 98
452
Неофициальная Windows 95
Глава 3
Связь между DOS и Windows 101
Система оповещения Windows INT 2fh 102
Данные экземпляров 105
Встроенные VxD 106
Огрехи FAKEWIN 108
FAKEWIN изнутри 109
Глава 4
Женитьба в Редмонде 114
Данные экземпляра DOS и SDА 117
DOS-флаг IN_WIN3E 120
Режим V86 123
Импорт глобальной ЕММ 123
Интерфейс оповещений DOSMGR 124
Функция идентификации TSR 126
Глава 5
Два лика WINDOWS 132
Внутреннее ядро WINDOWS 132
Расширители DOS и будущее DOS 133
DOSX: многоцелевой расширитель DOS и DPMI-сервер 136
RUNDOSX 141
Использование библиотеки DPMI SHELL 144
MEMLOOP 145
Глава 6
DOS защищенного режима: WIN386 и MS DPMI 148
DPMIINFO 153
MSDPMI 157
Глава 7
Откуда взялись эти 32BFA и LFN? 160
Вперед к WINDOWS 95: VMM32 164
Поддержка длинных имен файлов в Windows 95 171
Сага о VXD DBLSPACE 180
DOS: четыре шага к нирване 182
Оглавление
453
Глава 8
Постепенное исчезновение DOS 184
Обход DOS 185
Получение и установка текущего дисковода 195
Windows 95: продолжает обходить DOS, но поддерживает TSR 203
INTVECT: еще один пример VxD-перехватчика INT 21h 206
Глобальные и локальные перехватчики INT 21h 209
Роль IFSHLP.SYS и У86-обработчиков завершения вызова 210
32BFA и сети, CD-ROM, дискеты 234
Замещение кода реального режима: уже не ново 236
Еще один старый пример: TEST2F16 240
Прерывания 101: IDT против IVT 242
Глава 9
Кто главнее: Windows или DOS? 247
“Одно на другом”? 247
Запуск DOS в защищенном режиме 249
Менеджер виртуальных машин (VMM) 250
Режим V86 и бит РЕ 252
Глава J0
Как Windows запускает DOS 256
V86TEST 256
IOPL и флаг прерывания (Interrupt Flag) 265
Запуск DOS в виртуальной машине 274
Имитация против отражения прерываний 275
Опции управления DOS 278
Глава 1 1
Кому нужна DOS? 280
Что же на самом деле показывает V86TEST? 281
Windows за работой? 282
Наиболее распространенные вызовы Windows INT 2lh 290
Снова возвращаясь в Windows 293
Эффект 32-битового доступа к файлам 295
Как насчет обращений к BIOS? 298
454
Неофициальная Windows 95
Глава 12
Исследование с помощью программы WV86TEST 305
Разрывает все связи с DOS? 306
WinWord и DOS 311
Explorer из Windows 95 и DOS 314
PSP и другие структуры данных DOS в Windows 95 315
Win32-BepcHH FindNextFile — это функция 714Fh INT 21h 324
Код WV86TEST 334
Windows 95 и DOS защищенного режима 340
Глава 13
Thunk! KERNEL32 вызывает KRNL386 341
Запуск Win 16-приложений из Explorer 344
САВ32: KERNEL32 использует Winl6 KERNEL 347
Кто вызывает INT 2lh? 359
Где в Windows 95 текущий каталог? 361
От Explorer до Create PSP за шесть простых шагов 373
Код WSPY21 382
Обработчик INT 21h из KERNEL и KernelDosProc 396
Глава 14
Clock: смесь 32-битового и 16-битового кодов 403
16/16: Winl6 Clock под WfW 3.11 403
32/16: Clock из Windows 95 под Win32s 405
32/32: Clock в Windows 95 413
Действительно ли Windows 95 обращается прямо к DOS? И что же Она там делает? 417
Win32-flecKpHnTopbi файлов и thunk-переключение 427
Чтение PSP из WIN32 428
Использование Win32-flecKpinrropoB файлов 432
Настоящий обход DOS: загрузка исполняемого модуля
и файлы отображения памяти 439
WIN16 повсюду: сага о WIN16LOCK 442
Эпилог. Microsoft — повсюду, где вы хотите быть 448
Дело не в приложениях 449
Не провидцы, но приспособленцы 450
Оглавление ' 455
Научно-популярное издание
Шульман Эндрю
Неофициальная Windows 95
Руководство разработчика, посвященное исследованию
основ Windows™ 95 “Chicago”
Научные редакторы канд. физ.-мат. наук И.И. Дериев, Ю.Ю.Плаксюк
Литературный редактор Л.Н.Важенина
Технический редактор А.С.Грожин
Художественные редакторы Е.П.Дынник, В.Г.Павлютин
Группа подготовки перевода И.З.Линков, В.М.Лысенко, В.Е.Миронов,
Л.И. Обухов, А, В. Романов, В.В.Романов
Оригинал-макет подготовлен в отделе компьютерной верстки фирмы "Диалектика" на базе
лицензионного соглашения с АО "Информейшн Компьютер Энтерпрайз" (ICE).
Подписано к печати 15.08.95. Формат 84x108/16. Бумага офсетная. Гарнитура
Укр.Петербург. Печать офсетная. Усл.печ.л. 48,72. Тираж 10000 экз. Заказ N 02.15089 .
Книжная редакция фирмы "Диалектика".
Украина, 252022 Киев-22, просп. Глушкова, 6.
Тел./факс: (044) 266-40-74.
Отпечатано с готовых фотоформ
на комбинате печати издательства "Преса УкраТни".
Украина, 252047 Киев-47, просп. Победы, 50.