Ретроразработка драйвера для Windows 3.1

Kate

Administrator
Команда форума
ieuffxsruzy7hkmnruauq-gt3pu.png

Word, запущенный в Windows 3.1 с удобным разрешением 1152х864

Много месяцев назад я попробовал свои силы в написании 256-цветного драйвера высокого разрешения для Windows 3.1. Попытка была успешной, но работа пока ещё не завершена. В процессе я заново открыл для себя множество забытых вещей и узнал ещё больше новых. Этот пост основан на заметках, которые я делал по ходу разработки.

▍ Исходный код и среда разработки​


В качестве фундамента я взял пример 256-цветного драйвера Video 7 (V7) SuperVGA из Windows 3.1 DDK. Драйвер целиком написан на ассемблере (ого!) и состоит из десятков файлов исходного кода общим размером 1,5 МБ. Этот драйвер не был идеальным в качестве опорной точки, но определённо оказался лучшим из имеющихся в наличии.

Первым делом мне нужно было определиться со средой разработки. Хотя я мог сделать всё в VM, мне хотелось этого избежать. При разработке драйвера дисплея очевидно придётся много раз перезапускать Windows и неизбежно перезагружать машину, поэтому для удобства понадобилось бы не менее двух VM.

Поэтому вместо этого я решил настроить всё на моей хост-системе с 64-битной Windows 10. Запустить оригинальные 16-битные инструменты разработки я бы не смог, но это была лишь меньшая из проблем. Критически важным элементом был MASM 5.NT.02 — 32-битная версия MASM 5.1, спасённая из старого Windows NT SDK. Исходный код Windows 3.1 DDK сильно нацелен на MASM 5.1, и конвертация под другой ассемблер стала бы серьёзным проектом, который бы вызвал много багов.

К счастью, MASM 5.NT.02 работает вполне неплохо и ассемблирует исходный код без проблем. Для всего остального я использовал инструменты Open Watcom 1.9: wmake, wlink и wrc (утилита make, компоновщик и компилятор ресурсов). Чтобы перенести двоичный файл драйвера из хост-системы в VM, я использовал образ флоппи-диска. Это проще и быстрее, чем любые подключения по сети.

После того как всё стало собираться, началось настоящее веселье: я приступил к модификации драйвера Video 7, чтобы он заработал на другом «оборудовании».

▍ Трудности и лишения​


К счастью, в примере драйвера было не так много кода, специфичного для Video 7. К сожалению, код для конкретного оборудования был разбросан по всей кодовой базе.

Первым делом я должен был унифицировать код переключения банков, критически важный для производительности. В примере драйвера была примерно полудюжина различных процедур переключения банков (и нет, я не знаю почему). Я заменил их на одну и сделал так, чтобы переключение банков выполнялось только при необходимости (например, когда текущий банк отличается от запрашиваемого).

Вы спросите, почему бы не использовать линейный буфер кадров (LFB)? С самого начала я не хотел ограничивать драйвер режимом Windows 386 Enhanced mode. Использовать LFB в Standard mode сложно. Перепрограммировать базу селекторов просто, но всё ломается, когда система работает с разбиением памяти на страницы, а LFB не отображается. Хуже того, в real mode использовать LFB просто невозможно.

Затем настало время мучительного удаления кода отрисовки, относящегося к V7. Его оказалось больше, чем я ожидал. Оборудование V7 имеет защёлки и регистры блиттинга фрагментов паттернов для ускорения некоторых операций. Кроме того, практически во всём коде есть пути полностью программной отрисовки на случай невозможности сделать это аппаратно, но не все они чётко помечены. Мне пришлось приложить некоторые усилия, чтобы принудительно пустить всю отрисовку по программному пути.

С этим был связан только один ужасно неприятный баг. Драйвер предполагал, что регистры блиттинга фрагментов паттернов выполняют поворот паттернов. Принудительное использование чисто программной отрисовки могло привести к ситуации, когда паттерн поворачивается некорректно. Это вызывало очень заметные визуальные проблемы при отрисовке окон, потому что прямоугольник выделения (линия паттерна) оставлял за собой «мусор».

Я отказался от всех попыток использовать в драйвере внеэкранную память. Хотя на реальном оборудовании это обычно приводит к росту производительности, в VM это не так. Из-за траты ресурсов на переключение банков перемещение данных из системной памяти всегда выполняется быстрее.

Код отрисовки в драйвере V7 подразумевает, что одна растровая строка никогда не пересекает границу банка. Это существенно упрощает логику отрисовки, поскольку отсутствует необходимость потенциального переключения банков между двумя соседними пикселями. В оригинальном драйвере использовался шаг в 1K, что обеспечивает максимальное разрешение по горизонтали в 1024 пикселя. Я изменил его на 2K, что позволило использовать разрешения до 2048 пикселей по горизонтали. Эту систему потенциально можно сделать гибче для экономии видеопамяти, но, по крайней мере, на текущий момент это не стоило таких усилий.

В коде отрисовки курсора мыши был ещё один неприятный баг. Драйвер V7 практически всегда использует аппаратный курсор и откат к программной отрисовке почти никогда или вообще никогда не используется. При определённых обстоятельствах вход в процедуру сохранения содержимого экрана под курсором может произойти с установленным флагом направления, что приведёт к копированию данных не в том направлении и к повреждению ни в чём не виноватой памяти. Проблему удалось решить единственным CLD в нужном месте.

Кроме того, мне пришлось разбираться с вопросом, почему цвета в моём драйвере отличаются от цветов в драйверах VGA/SVGA для Windows 3.1. Я узнал, что по какой-то причине драйвер 8514/A для Windows 3.0 и 3.1 (канонический драйвер высокого разрешения) на самом деле использовал другую цветовую схему. Это задокументировано в DDK Windows 3.0 и 3.1, но не приведено никакого объяснения, почему цвета должны отличаться.

▍ Развлечения с инструментами​


Компоновщик (wlink) вызвал одну очень интересную проблему. По умолчанию wlink включает оптимизацию дальних вызовов, заменяя дальние вызовы близкими. Эта оптимизация почти всегда безопасна и повышает производительность, однако не в случае драйвера дисплея Windows. Драйвер «компилирует» процедуры отрисовки, копируя фрагменты кода из сегмента кода в стек, ассемблируя по необходимости их нужные части и изменяя константы в коде. wlink оптимизировал дальние вызовы внутри сегмента кода, что было замечательно, но когда этот код копировался в стек, вызовы сегмента кода на самом деле должны были быть дальними. Разобравшись, в чём была проблема, я просто отключил оптимизацию дальних вызовов.

В качестве побочного проекта я также написал довольно неоптимизированный инструмент wmapsym — функциональный эквивалент Microsoft MAPSYM, но использующий в качестве входных данных map-файлы Watcom. Это оказалось чрезвычайно полезно при отладке драйвера.

Кстати, об отладке — для отладки драйверов Windows 3.1 используется WDEB386, более-менее стандартный отладчик Microsoft, схожий с SYMDEB, отладчиком ядра OS/2, NTSD и другими. Я использовал его с перенаправлением ввода и вывода на последовательный порт. Он выполнял перенаправление в pipe на хост-машине и подключённый к pipe клиент PuTTY.

rxvqvrujnlr3om88ftzqx9hc0yu.png

WDEB386 в PuTTY

▍ Ещё более ретро​


Функциональность драйвера дисплея в Windows 3.1 Standard и 386 Enhanced не так уж важна. Единственная область, где она очень критична — это поддержка сессий DOS. В 386 Enhanced есть целый отдельный VxD (VDDVGA), занимающийся виртуализацией видео.

Даже при переключении в полноэкранную сессию DOS-драйвер дисплея остаётся активным в 386 Enhanced mode, но уведомления он получает при помощи вызовов dev_to_foreground (переключение на фон) и dev_to_background (возврат обратно). В Standard mode при переключении в полноэкранную сессию DOS-драйвер завершается при помощи вызова Disable, а при обратном процессе включается обратно при помощи Enable.

В Windows 3.0 всё становится ещё интереснее. В Standard и 386 Enhanced mode различия с Windows 3.1 минимальны. Однако Windows 3.0, работающая в real mode — это совершенно другое дело. Мне пришлось модифицировать драйвер так, чтобы он не использовал API, доступные только в protected mode, и решал, что делать, уже в среде выполнения (при помощи WinFlags).

Было бы здорово, если бы нашёлся пример драйвера Video 7 из Windows 3.0 DDK. Увы, мне не удалось его обнаружить.

Чтобы заставить драйвер работать в Windows 2.x, нужно было проделать ещё больше работы. Базовая структура драйвера осталась той же, в нём просто нужно реализовать меньшее количество вызовов GDI. По большей части, в real mode Windows 2.x чрезвычайно схожа с Windows 3.0. Разница в том, что вызовы API, добавленные для поддержки protected mode (например, AllocCSToDSAlias), совершенно отсутствуют в Windows 2.x. Код отрисовки, по сути, идентичен, однако инициализация и выгрузка должны быть немного другими.

Теоретически можно было бы даже импортировать специфические для Windows 3.x процедуры динамическим образом и использовать один двоичный файл для Windows 2.x и 3.x. На практике это неосуществимо, потому что драйверам также нужен разный формат ресурсов (Microsoft существенно изменила формат ресурсов между Windows 2.x и 3.0). Поэтому было гораздо проще создать отдельный двоичный драйвер Windows 2.x и использовать условную компиляцию для применения путей кода Windows 3.x или 2.x.

Дополнительная сложность здесь возникла и в том, что я не мог найти компилятор ресурсов, способный работать с ресурсами Windows 2.x и запускающийся в 32-битной Windows. Для финализации двоичного драйвера 2.x я прибег к запуску RC из Windows 2.x SDK в DOS VM. Некрасиво, но полностью функционально.

 
Сверху