«Намертво прибитая к ядру» графическая подсистема

:

То, что принято называть «графикой в ядре» обычно относится к win32k. Win32k.sys представляет собой ядерную часть графической подсистемы. Загружается пользовательским процессом smss.exe в процессе инициализации всех остальных подсистем. Путь к исполняемому образу для «kmode» подсистемы прописан здесь:

Как же это происходит?

Здесь (на стек трейсе в нижней части скриншота) хорошо видно, что инициирует загрузку win32k процесс пользовательского режима smss (который в том числе инициализирует файлы подкачки, реестр, переменные окружения, сохраняет дамп памяти, если до этого был bugcheck, при посредстве wininit запускает service control manager и local security authority subsystem, создает logon сессии и т.п..), а одна из первых вещей, которые делает сам win32k — это «налаживание связей» с ядром. И вот зачем: win32k находится на более высоком уровне по сравнению с ядром, поэтому ядро не может иметь зависимость (под «зависимостью» в данном случае понимается классический «reason to change») от (конкретной реализации) win32k, но и ядро и win32k могут безопасно зависеть от интерфейса. Таким интерфейсом является структура KWIN32_CALLOUTS_FPNS и функция для регистрации конкретной реализации этого интерфейса в ядре — PsEstablishWin32Callouts.

Кроме того, win32k регистрирует несколько типов объектов (в частности Desktop и WindowStation) через интерфейсы общего назначения, предоставляемые Object Manager-ом.

Таким образом НИКАКИХ зависимостей от win32k у ядра нет. Более того, до NT4 все user/gdi API обрабатывалось в csrss и, естественно, тормозило. Начиная с NT4 ЧАСТЬ user/gdi примитивов была перенесена в ядро для повышения производительности.

В общем win32k можно полностью убрать, можно заменить собственной ядерной частью, а можно реализовать все полностью в пользовательском режиме (используя, например, ioctl-ы для связи с ядром), но это будет тормозить. Единственная причина, по которой это не делается — потому что это не нужно. Можно написать по-другому — да, написать существенно лучше — вряд ли. Ну а переписывание ради самого переписывания — не лучшая идея.

Практика — критерий истины или «MinWin на коленке»


Для исключения недоразумений, хочу сразу же сказать: то что я буду делать не является MinWin-ом. Не является уже хотя бы потому, что настоящий MinWin содержит минимальный набор пользовательских (user mode) бинарников, я же собираюсь продемонстрировать полностью загруженное ядро (еще одно отличие — MinWin содержит минимальный набор драйверов, у меня же набор драйверов не меняется по сравнению с обычной загрузкой) вообще без пользовательского режима (ок, один процесс и одна dll-ка там все таки есть, но надо же хоть как то показать пользователю, что что-то происходит). Дополнительным поводом к размышлению может служить то, что настоящий MinWin появился в связи с работой по «расслоению» кода Windows 7, то же, что буду делать я в принципе возможно и на XP и даже на NT3.51

Итак, если вдумчиво прочитать то, что написано в предыдущем разделе, можно догадаться, что нам нужно заменить smss на такой, который не инициализирует подсистемы, но при этом все еще остается более-менее интерактивным. smss.exe — это обычный native процесс (приближенно, native приложение — это такое приложение, которое линкуется только с ntdll.dll и соотственно использует для работы только Native API). К счастью для меня Alex Ionescu — бывший главный разработчик ReactOS — уже написал подобное приложение в рамках (давно закрытого) проекта tinykrnl. Это приложение не собирается под amd64, не собирается на последнем WDK, имеет несколько багов, но в целом работает. Следующую картинку можно открыть архиватором — там содержатся исходники и скомпилированный amd64 бинарник небольшого приложения native.exe:

Прошу меня простить, но я не могу выложить готовый образ потому что это нелегально, поэтому выкладываю код, который может собрать vhd-образ из инсталляционного образа.
Следующий код можно исполнить ТОЛЬКО на Win7. Соханить его куда нибудь во временный каталог под именем, к примеру minwin.ps1, положить рядом install.wim (находится в каталоге \sources) c en-us инсталляционного диска Windows 7 (это важно — копируются только нужные для этой локализации NLS файлы), сохранить в этот каталог файл native.exe из прикрепленной выше картинки, перейти в этот самый каталог в elevated консоли и выполнить следующее:

powershell -executionpolicy bypass .\minwin.ps1

Для краткости:
1. Рядом в одном каталоге должны лежать следующие файлы: minwin.ps1, install.wim и native.exe
2. Запускать minwin.ps1 нужно только после смены текущего каталога на каталог, содержащий вышеназванные файлы

Дисклеймер: все нижеследующее Вы делаете на свой страх и риск. Команды довольно очевидны и не должны нанести никакого вреда, но это «наколеночное» творчество, поэтому оно не обязано работать в любых условиях. Не выполняйте этот скрипт, если Вы не понимаете значение КАЖДОЙ команды (тем более, что выполнение должно производиться из-под повышенного пользователя). Если нет — ниже приведена картинка того, как это в конце концов выглядит. В упрощенном варианте можно просто переименовать native.exe в smss.exe, скопировать его поверх существующей smss.exe в уже загруженной виртуальной машине (подойдет любая x64 винда — от XP до 7) и перегрузиться.
Сам скрипт:

$vhdName = "disk.vhd"
$wimName = "install.wim"
$vhdDisk = "V"
$wimMountName = "MountedWim"

md $wimMountName
dism /mount-wim /wimfile:$wimName /index:1 /mountdir:$wimMountName

#del $vhdName
@"
create vdisk file=$pwd\$vhdName type=expandable maximum=128
select vdisk file=$pwd\$vhdName
attach vdisk
create partition primary
format fs=ntfs quick
assign letter=$vhdDisk
active
"@ | diskpart

bcdboot "$pwd\$wimMountName\Windows" /s "${vhdDisk}:"
#cmd /c "$wimMountName\Windows\System32\bootsect /nt60 ${vhdDisk}: /mbr /force"

$disk = gwmi win32_diskdrive -filter "Model = 'Msft Virtual Disk SCSI Disk Device'"
$part = $disk.GetRelated("win32_diskpartition") | select -first 1
$bootmgr = [wmi]"root\wmi:BcdObject.Id=`"{9dea862c-5cdd-4e70-acc1-f32b344d4795}`",StoreFilePath=`"${vhdDisk}:\\boot\\bcd`""
$osloader = [wmi]"root\wmi:BcdObject.Id=`"$($bootmgr.GetElement(0x23000003).Element.Id)`",StoreFilePath=`"${vhdDisk}:\\boot\\bcd`""

$bootmgr.SetQualifiedPartitionDeviceElement(0x11000001, 0, $disk.Signature, $part.StartingOffset)
$osloader.SetQualifiedPartitionDeviceElement(0x11000001, 0, $disk.Signature, $part.StartingOffset)
$osloader.SetQualifiedPartitionDeviceElement(0x21000001, 0, $disk.Signature, $part.StartingOffset)
$osloader.SetStringElement(0x12000002, "\Windows\system32\winload.exe")
$osloader.SetStringElement(0x22000002, "\Windows")
$osloader.SetBooleanElement(0x26000022, $true)

md "${vhdDisk}:\Windows\System32\config\", "${vhdDisk}:\Windows\Fonts", "${vhdDisk}:\Windows\inf", "${vhdDisk}:\Windows\SysWOW64"
copy -r "$wimMountName\Windows\System32\drivers" "${vhdDisk}:\Windows\System32\drivers"
del "${vhdDisk}:\Windows\System32\drivers\termdd.sys"
copy "$wimMountName\Windows\Fonts\vgaoem.fon" "${vhdDisk}:\Windows\Fonts"
copy "$wimMountName\Windows\inf\errata.inf" "${vhdDisk}:\Windows\inf"
copy "$wimMountName\Windows\System32\config\SYSTEM" "${vhdDisk}:\Windows\System32\config"
copy "$wimMountName\Windows\SysWOW64\ntdll.dll" "${vhdDisk}:\Windows\SysWOW64"
copy native.exe "${vhdDisk}:\Windows\System32\smss.exe"

"ntoskrnl.exe", "hal.dll", "ci.dll", "pshed.dll", "clfs.sys", "kdcom.dll", "ntdll.dll", "apisetschema.dll",
"winload.exe", "bootvid.dll", "bootres.dll", "l_intl.nls", "c_1252.nls", "c_437.nls" |% {
    copy (Join-Path "$wimMountName\Windows\System32" $_) "${vhdDisk}:\Windows\System32"
}

@"
select vdisk file=$pwd\$vhdName
detach vdisk
"@ | diskpart

dism /unmount-wim /mountdir:$wimMountName /discard

Выглядит это примерно так:

Если все сделано правильно (и при определенном везении :-) ), то через какое то время в том же каталоге появится файл disk.vhd — его можно запускать в виртуальной машине (тестировалось в VirtualBox, но не вижу причин, по которым это не должно работать в Virtual PC, Hyper-V или еще где нибудь):

Ну и несколько замечаний напоследок.

1. Копируются почти 300 драйверов, загружаются — примерно 100, реально нужны на конкретной системе — штук 10-20.
2. Восьмимегабайтный SYSTEM хайв из install.wim можно вполне нормально заменить на двухмегабайтный из boot.wim, а можно и вообще вручную слепить что нибудь на сотню килобайт.
3. bcdboot копирует все локализации bootmgr-а и несколько мегабайт шрифтов (в основном для CJK) — можно вырезать
4. Можно заметить, что при создании образа удаляется termdd.sys — это сделано не потому, что он не работает. Просто он создает еще одно устройство-клавиатура (которое в нормальном режиме используется для «нажатия кнопок» на удаленной машине в терминальной сессии), а модифицировать native.exe на чтение нажатий со всех клавиатур мне оказалось лень.
5. Графику при загрузке можно подавить и отказаться от bootvid.dll и bootres.dll

Ну и самое главное, win32k.sys в создаваемый образ не копируется, что не мешает этому образу относительно сносно работать вообще без графики. При большом желании, как я уже говорил, можно прикрутить полностью консольную оболочку, можно вырезать Windows подсистему и оставить только Posix (это было бы весьма странным желанием, как по мне, но люди довольно часто хотят странного). Или (еще более странное желание) реализовать нативный X11 поверх NT ядра. Можно заменить smss и «полностью контролировать» все что работает выше ядра, а можно оставить smss и парой правок в реестре изменить набор загружаемых подсистем. Короче, никакой «намертво прибитой к ядру» графики попросту нет.

Спасибо за внимание