• Познакомьтесь с пентестом веб-приложений на практике в нашем новом бесплатном курсе

    «Анализ защищенности веб-приложений»

    🔥 Записаться бесплатно!

  • CTF с учебными материалами Codeby Games

    Обучение кибербезопасности в игровой форме. Более 200 заданий по Active Directory, OSINT, PWN, Веб, Стеганографии, Реверс-инжинирингу, Форензике и Криптографии. Школа CTF с бесплатными курсами по всем категориям.

Статья ASM. Модель драйвера графического адаптера WDDM

Всем привет!
В данной статье рассматриваются вопросы доступа к свойствам графических адаптеров на языке ассемблера. Мы заправим свои мозговые баки топливом DirectX смешав его в должной мере с OpenGL, заглянем в современную модель драйверов видеоадаптеров Vista+, ознакомимся с базовыми терминами графической инфраструктуры, и смешаем весь этот коктейль с исполняемым кодом. Основной акцент делается на способ вычисления объёма локальной видеопамяти VRAM свыше 4 Gb, что является проблематичным на 32-битных системах Windows.

Оглавление:

1. Интерфейсы DirectX и OpenGL;
2. Модель драйверов дисплея WDDM;
3. Доступ к инфраструктуре DXGI;
4. Методы и функции D3DKMT_xx;
5. Практика – чтение данных видеоадаптера;
6. Итоги.
-----------------------------------------


1. Интерфейсы DirectX и OpenGL

Словосочетание DirectX знакомо большинству из нас, но осмелюсь предположить, что не все смогут дать ему точное определение. Чтобы далее не плавать в терминах, сразу определимся с основными понятиями графической подсистемы. Это огромный клубок, размотать который под силу не каждому, и по большому счёту в представленном на ваш суд материале я не преследовал эти цели. Мы пробежимся лишь по макушкам, чтобы перед глазами выстроилась хоть какая-то картина.

Microsoft DirectX – это набор функций прикладного уровня Win32API, для обработки связанных с графикой и мультимедиа задач. Впервые был представлен миру в 1995 году, в штатной поставке Win95. Тогда названия этих API начинались с префикса Direct типа: Direct3D, DirectDraw, Sound, Play, Music и т.д. Бытует мнение, что на презентации один из журналистов высмеял такой зоопарк имён, и сходу предложил термин DirectX, как обобщённый класс всех этих API. В средствах массовой (дез)информации, API трёхмерной графики Direct3D наиболее разрекламированный компонент, а потому названия DirectX и Direct3D стали синонимами.

Наряду с проприетарным DirectX, ещё в то время активно продвигалась и альтернативная технология OpenGL (Open Graphics Library) от компании "Khronos Group" 1994 г. Так разработчикам был представлен выбор – OpenGL или DirectХ, и началась борьба между сторонниками кроссплатформенного GL (Win, Linux, Mac), и DХ3D исключительно для Win. На практике-же два этих интерфейса часто пересекались, поскольку OpenGL не включает в себя все функции DirectX, типа поддержка звука или джойстика. Таким образом DХ3D и GL – это конкурирующие интерфейсы пользовательских API, хотя всегда ходят парой:


Код:
• Win2k/XP (2004г) - DirectX_v9 + OpenGL_v2;
• WinVista (2008г) - DirectX_v10 + OpenGL_v3;
• Win7/8.1 (2011г) - DirectX_v11 + OpenGL_v4;
• Win10 (2015г)  - DirectX_v12 + OpenGL_v4.6.

Первая версия(5) DХ3D вызвала широкий резонанс и разочаровала программистов. Даже простые операции в ней требовали ручного создания и отправки соответствующих примитивов в буферы исполнения. В OpenGL-же, для смены состояния графического объекта достаточно было вызвать одну\единственную функцию. Другими словами, то-что представлялось в GL одной строкой, требовало чуть-ли не сотни строк кода DX3D. Так пузырь DirectХ на старте оглушительно лопнул, оставив за собой кратер, который можно было наблюдать с луны.

Но отбиваясь от града тухлых яиц M$ продолжала гнуть свою линию и развивать DX3D-API, в результате чего начиная с DX9 бывшие критики признали, что теперь он ничуть не хуже чем OpenGL по простоте использования. DirectХ в некоторых случаях преследует политику модели компонентных объектов COM, а поскольку директивы ассемблера не поддерживают её в полной мере, это выводит нас из зоны комфорта и приходится реализовать всё вручную. Как мы убедимся позже в практической части, СОМ в асме не так-уж страшен, как его малюют – главное иметь документацию, чем мелкософт наградил нас в своём репозитории.


1.1. Графический процессор GPU – общие сведения

GPU в режиме 3D работают с т.н. "полигональной графикой", когда любой объект представляется как набор миниатюрных 2D-треугольников. Графический объект задается "вершинами" и "полигонами" (боковые линии треугольников). Цвет на полигоны накладывается по спец.алгоритмам закраски, с использованием заранее подготовленных плоских изображений "текстур". Шейдеры (shader) – это программные процедуры видеоадаптера для наложения текстур, преломления света, затенения участков объектов и множества других параметров. Задача GPU сводится к тому, чтобы закрасить как можно больше полигонов за единицу времени. Ускорители в графических 3D-адаптерах (Accelerator) в первую очередь были созданы для аппаратного ускорения работы с полигонами.

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

Второй-же, пиксельный процессор растеризует графику, подготавливая её к "рендерингу" – процессу преобразования 3D модели в плоское 2D-изображение, пригодное для вывода на дисплей (заключительный этап). После наложения текстуры на объект, в пиксельном процессоре происходит запись значений в "буфер кадра" – отображаемую на дисплее область графической памяти. На данном этапе выполняются и доп.операции, типа гамма-коррекция, сжатие Z-координат etc. Полная длина конвейера пиксельного процессора впечатляет – это порядка 200 ступеней, большая часть из которых отведена под операции обработки текстур.


1.2. Память процессоров GPU

Процессоры дискретных (внешних) графических адаптеров снабжены индивидуальной оперативной памятью VRAM (Video Random Access Memory), и при её ограниченном объёме могут запрашивать доп.память у системы. Подобного рода окна в системном ОЗУ назвали "апертурой", для отображения которой в память видеокарты используются таблицы GART (Graphics Address Remapping Table, перераспределение адресов). Адрес апертуры BIOS задаёт через специальные регистры чипсета, а её размер определяется параметром "AGP aperture size":


GART.png


Апертуры часто практиковали карты с отжившим свой век интерфейсом AGP (Accelerated Graphics Port). Порт поддерживал работу в двух режимах: [1] DiME (Direct Memory Execute) когда код графики исполнялся прямо внутри системной памяти без копирования её в память адаптера, и [2] режим DMA (Direct Memory Access, прямой доступ к памяти без участия процессора), когда акселератор 3D при вычислениях рассматривал свою\локальную память как первичную, а если её недостаточно, то подкачивал в неё данные из апертуры системной памяти.

В наше время порт AGP уже давно не используется, и его место занял более высокоскоростной PCI-Express, который работает только в режиме DMA. Однако чипсеты Intel для встроенных карт IGD (Integrated Graphics Device) "воруют" память у системы и по сей день, только технология называется теперь
, или "Dynamic Video Memory Technology" (динамическое распределение ОЗУ для видео).


2. Модель драйверов дисплея WDDM

Теперь, когда у нас есть базовые сведения, переместимся по туннелям времени в современный мир.
Начиная с Windows Vista, графическая подсистема претерпела огромные изменения. В частности это коснулось задействованной модели драйверов, которую назвали WDDM или "Windows Display Driver Model". Входящая в состав WDDM инфраструктура DXGI (DirectX Graphics Infrastructure) – это компонент пользовательского режима для Vista+, который связывает конкретные API DirectX и OpenGL c библиотеками WDDM.

DXGI предоставляет API-функции и методы СОМ для решения таких задач как: перечисление графических адаптеров и мониторов, запроса их свойств и режимов отображения, выбора форматов выходных буферов, и визуализации кадров на дисплее. Для перехода на модель драйвера WDDM производители адаптеров должны написать совершенно иные драйверы мини-порта дисплея. В отличии от устаревшей модели ХР.хDDM, усовершенствованный драйвер WDDM находится в пользовательском пространстве, и большая его часть сосредоточена в библиотеке dxgi.dll.

Модель не использует сервисы движка Gdi32.dll (Graphics Device Interface), а проходит через среду выполнения runtime d3d10.dll и подсистему графического ядра Dxgkrnl.sys. Версия[1] DXGI требует DirectХ v10 и выше – цель его заключается во взаимодействии с драйвером режима ядра и аппаратным обеспечением, как показано на следующей схеме:


WDDM.png


Рассмотрим ключевые компоненты этого рисунка:

Direct3D API: это пользовательский интерфейс Direct3D, библиотеки которого можно найти в папке system32. Идут в штатной поставке OC Vista+.
Юзер-драйвер (UMD): компонент драйвера, который предоставляет функциональные возможности Direct3D. Это библиотеки dll, которые прилагаются к драйверам конкретной графической карты и поставляются производителем оборудования. Для встроенных карт Intel дров олицетворяется файлом igdumd32.dll (Integrated Graphics Device UM-Driver), а для карт Nvidia это nvd3dum.dll и прочие. Обратите внимание, что UMD имеет отношение только к Direct3D.
Интерфейсы сторонних поставщиков: Win32API, не основанные на Microsoft DirectX, такие как OpenGL, Vulkan, CUDA и т.д. Компонент известен как ICD (устанавливаемый клиентский драйвер). DXGI фактически является связующим звеном между DХ3D и GL. Клиентские ICD общаются с низкоуровневым интерфейсом WDDM через библиотеку мастдая gdi32.dll.
Интерфейс WDDM: это набор низкоуровневых API, которые предоставляют функции ядра, коду пользователя. Имеют префикс "D3DKMTхх" и живут в dxgi.dll. Нашему приложению предоставлен выбор – обращаться к DXGI напрямую, или вызывать API среды выполнения d3d10,11,12.dll. Прямой вызов функций DXGI полезен, когда софт планирует создать список устройств или настроить способ представления данных. С введением инженерами данного слоя, в системе перестали появляться связанные с графикой голубые экраны BSOD, чем страдала Win-XP. Теперь ошибки возникают не в ядре, а исключительно на пользовательском уровне, и для их устранения достаточно просто перезагрузить либу.
Ядро графики DirectX: это компонент, который реализует базовую функциональность. Графическое ядро отвечает за обеспечение совместного использования ресурсов GPU несколькими программными процессами, и управление памятью видеоадаптера. Код компонента находится в файле Dxgkrnl.sys, с прицепом диспетчера памяти видеоадаптера dxgmms1.sys.
Драйвер режима ядра (KMD): напрямую взаимодействует с аппаратным устройством, от имени ядра графики DirectX.


3. Доступ к интерфейсу DXGI

Поговорим теперь о программном доступе к инфраструктуре DXGI..
Выше упоминалось, что одноимённая библиотека dxgi.dll содержит в себе как методы СОМ, так и привычные нам функции Win32API. Причём методы компонентных объектов предлагают более широкие возможности по управлению и сбору информации об оборудовании – с них и начнём. Не смотря на то, что процесс этот скучный, и чем-то напоминает супружеский долг после 30 лет совместного проживания, результат вполне оправдывает вложенный депозит, открывая нам доступ в нёдра модели драйверов WDDM.

Что такое СОМ и как с ним работать на асме подробно обсуждалось в статье про
дизассемблер, а потому не буду повторяться – мы сделаем ставку на СОМ конкретно взятого DXGI. Структура входящих в его состав интерфейсов с привязанными к ним глобальными идентификаторами GUID выглядит так, где основными являются пара "IID_IDXGI_Factory", а остальные наследуются от них. Более подробную информацию о фабриках DXGI можно найти на или :

C-подобный:
;// Основные интерфейсы DXGI
;//--------------------------------------
IID_IDXGI_Factory          dd  0x7b7166ec, 0x44ae21c7, 0xaec91ab2, 0x69e31a32
IID_IDXGI_Factory1         dd  0x770aae78, 0x4dbaf26f, 0x3c2529a8, 0x87b3d183

;// Наследуется от "Factory"
;//--------------------------------------
IID_IDXGI_Object           dd  0xaec22fb8, 0x463976f3, 0xeb28e09b, 0x2e7aa643

;// Наследуются от "Object"
;//--------------------------------------
IID_IDXGI_Adapter          dd  0x2411e7e1, 0x4ccf12ac, 0x989714bd, 0xc04d53e8
IID_IDXGI_Adapter1         dd  0x29038f61, 0x46263839, 0x6808fd91, 0x051a0179
IID_IDXGI_Device           dd  0x54ec77fa, 0x44e61377, 0xfd88328c, 0x4cc8445f
IID_IDXGI_Device1          dd  0x77db970f, 0x48ba6276, 0x010728ba, 0x2c39b443
IID_IDXGI_DeviceSubObject  dd  0x3d3e0379, 0x4d58f9de, 0xd6186cbb, 0xa6f19229
IID_IDXGI_KeyedMutex       dd  0x9d8e1289, 0x465fd7b3, 0x0e252681, 0x5df89a34
IID_IDXGI_Output           dd  0xae02eedb, 0x4690c735, 0x8d5a528d, 0xaa1302c2
IID_IDXGI_Resource         dd  0x035f3ab4, 0x4e50482e, 0x7f8a1fb4, 0x0b96d88b
IID_IDXGI_Surface          dd  0xcafcb56c, 0x48896ac3, 0x239e47bf, 0xec60d2bb
IID_IDXGI_Surface1         dd  0x4AE63092, 0x4c1b6327, 0xe1bfae80, 0x862ba32e
IID_IDXGI_SwapChain        dd  0x310d36a0, 0x4c0ad2e7, 0x9d6a04aa, 0x6a88b823

Первое, что нужно сделать, это функцией CoInitialize() из ole32.dll активизировать системный СОМ-сервер, после чего при помощи CreateDXGIFactory1() из dxgi.dll получить указатель на основной интерфейс "IID_IDXGI_Factory1". Так мы получим доступ к виртуальной таблице методов интерфейса "IDXGI_Factory", среди которых нас будет интересовать метод перечисления видеоадаптеров EnumAdapters().

C-подобный:
struct IUnknown
  QueryInterface            dd  0  ;// This, InterfaceId, pInterface
  AddRef                    dd  0  ;// This
  Release                   dd  0  ;// This
ends

struct IDXGI_Object
  Header                    IUnknown
  SetPrivateData            dd  0   ;// This, Name, DataSize, DataSize
  SetPrivateDataInterface   dd  0   ;// This, Name, *pUnknown
  GetPrivateData            dd  0   ;// This, Name, *pDataSize, *pDataSize
  GetParent                 dd  0   ;// This, riid, **ppParent
ends

struct IDXGI_Factory
  Header                    IDXGI_Object
  EnumAdapters              dd  0   ;// This,Index,ppAdapter   <------- Наш клиент!
  MakeWindowAssociation     dd  0   ;// This,WindowHandle,Flags
  GetWindowAssociation      dd  0   ;// This,pWindowHandle
  CreateSwapChain           dd  0   ;// This,pDevice,pDesc,ppSwapChain
  CreateSoftwareAdapter     dd  0   ;// This,Module,ppAdapter
ends

struct IDXGI_Adapter
  Header                    IDXGI_Object
  EnumOutputs               dd  0   ;// This,Output,ppOutput
  GetDesc                   dd  0   ;// This,pDesc
  CheckInterfaceSupport     dd  0   ;// This,InterfaceName,pUMDVersion
ends

Как видим, структура интерфейса "IDXGI_Factory" (представляющая собой вирт.таблицу vTable) включает в себя две предыдущие структуры "IUnknown+IDXGI_Object", которые я обозначил здесь как поле "Header". Более того, любой интерфейс DXGI начинается с такого заголовка, а первым методом в них всегда будет метод перечисления содержимого QueryInterface(). Таким образом, чтобы найти указатель на метод EnumAdapters(), нужно проделать следующие шаги:

C-подобный:
;// Получить в переменную 'hFactory' линк на интерфейс 'IDXGI_Factory'
         invoke  CoInitialize,0
         invoke  CreateDXGIFactory1,IID_IDXGI_Factory1,hFactory

;// Найти и вызвать 'IDXGI_Factory::EnumAdapter'
         mov     esi,[hFactory]  ;// указатель на интерфейс
         mov     esi,[esi]       ;// взять из него адрес метода QueryInterface()
         mov     eax,[esi+IDXGI_Factory.EnumAdapters]  ;// получить адрес метода EnumAdapters()

         push    hAdapter        ;// сюда получим указатель
         push    [Index]         ;// индекс адаптера начиная с нуля
         push    [hFactory]      ;// This = в каком интерфейсе искать
         call    eax             ;// EnumAdapter()!

Поскольку меня интересует информация о видеоадаптере, далее нужно вызвать метод GetDesc() из интерфейса "IDXGI_Adapter", который осуществляется так:

C-подобный:
;// Найти и вызвать 'IDXGI_Adapter::GetDesc'
         mov     esi,[hAdapter]
         mov     esi,[esi]
         mov     eax,[esi+IDXGI_Adapter.GetDesc]

         push    buff            ;// сюда получим структуру "DXGI_ADAPTER_DESC"
         push    [hAdapter]      ;// This = в каком интерфейсе искать
         call    eax
;//----------------------
struct DXGI_ADAPTER_DESC
  Description               dw  128 dup(0)  ;// Unicode имя адаптера
  VendorId                  dd  0
  DeviceId                  dd  0
  SubSysId                  dd  0
  Revision                  dd  0
  DedicatedVideoMemory      dd  0   ;// локальная память видеоадаптера
  DedicatedSystemMemory     dd  0   ;// апертура из системной памяти
  SharedSystemMemory        dd  0   ;// оставшаяся память из общего пула
  AdapterLuid               dq  0   ;// идентификатор LUID адаптера в системе
ends

Посмотрим, что мы тут имеем..
Если не считать имя активного видеоадаптера, Vid\Did его производителя и ревизию, метод GetDesc() вернул нам и распределение памяти адаптера. Когда на борту дискретная карта с локальной VRAM, в поле "DedicatedVideoMemory" мы получим её объём. Если-же имеем дело со-встроенной картой, то как-правило в этом поле лежит нуль, а следующее "SystemMemory" будет хранить значение апертуры, нагло захваченной адаптером у системы.

Нюансы подобного рода не знакомы утилитам типа WMI (см.запрос в консоли: wmic.exe PATH Win32_videocontroller GET AdapterRAM) – она возвращает лишь сумму трёх\представленных полей. В свою очередь LUID является 64-битным одноразовым значением (Local Unique ID), и при следующей перезагрузке системы будет уже иным.

В качестве примера, тут я привёл лишь запрос метода GetDesc() из интерфейса "IDXGI_Adapter", хотя имеются ещё как-минимум 10 родственных интерфейсов DXGI (см.список выше), и в каждом из них свои методы. Основная проблема здесь в том, что метод GetDesc() возвращает объём VRAM лишь в диапазоне до 4 Gb, поскольку соответствующие поля в структуре размером DWORD. Но сейчас есть карты с памятью 8 и более гигов, и чтобы получить их размер можно воспользоваться функцией D3DKMTQueryAdapterInfo() из той-же либы dxgi.dll. Она возвращает уже по максимуму 64-битные значения, чего вполне хватает на все случаи жизни.


4. Функции D3DKMT_xx

Поверхностно ознакомившись с довольно некомфортной для ассемблера моделью компонентных объектов СОМ, плавно переместимся в привычное нам русло Win32-API. Примечательным является то, что большинство API-функций из dxgi.dll имеют точные свои ксерокопии и в библиотеке gdi32.dll, причём это не перенаправляющие указатели, а полные дубликаты, в чём можно убедиться открыв эти либы в любом парсере экспорта типа Hiew32.

В данном случае нас интересует функция D3DKMTQueryAdapterInfo() для перечисления свойств адаптера, которая весьма своенравна и требует нехилых подготовительных телодвижений. В единственном своём аргументе она ожидает указатель на предварительно заполненную нами структуру "D3DKMT_QUERYADAPTERINFO" с таким прототипом:


C-подобный:
struct D3DKMT_QUERYADAPTERINFO
  hAdapter                  dd  0  ;// In. дескриптор адаптера, о котором извлекается информация
  Type                      dd  0  ;// In. KMTQUERYADAPTERINFOTYPE - тип информации, которую нужно извлечь
  pPrivateDriverData        dd  0  ;// In. указатель на приёмный буфер
  PrivateDriverDataSize     dd  0  ;// In. размер буфера в байтах
ends
;//--------------------------------------
;// Константы типа извлекаемой инфы
;//--------------------------------------
;// typedef enum _KMTQUERYADAPTERINFOTYPE
;//
     KMTQAITYPE_UMDRIVERPRIVATE         =  0
     KMTQAITYPE_UMDRIVERNAME            =  1
     KMTQAITYPE_UMOPENGLINFO            =  2
     KMTQAITYPE_GETSEGMENTSIZE          =  3
     KMTQAITYPE_ADAPTERGUID             =  4
     KMTQAITYPE_FLIPQUEUEINFO           =  5
     KMTQAITYPE_ADAPTERADDRESS          =  6
     KMTQAITYPE_SETWORKINGSETINFO       =  7
     KMTQAITYPE_ADAPTERREGISTRYINFO     =  8
     KMTQAITYPE_CURRENTDISPLAYMODE      =  9
     KMTQAITYPE_MODELIST                = 10
     KMTQAITYPE_CHECKDRIVERUPDATESTATUS = 11
     KMTQAITYPE_VIRTUALADDRESSINFO      = 12
     KMTQAITYPE_DRIVERVERSION           = 13
     KMTQAITYPE_ADAPTERTYPE             = 15

Обратите внимание на поле "Type" структуры. Здесь я перечислил всего 15 типов базовой информации, которые поддерживают все системы начиная с Vista. Если-же посмотреть в инклуде на общее их кол-во, то получается аж 47 констант, просто одни требуют систему не ниже Win8, а другие исключительно Win10. Поэтому (чтобы не было осечки) я выбрал универсальный вариант, дабы код можно было портировать на всю линейку Win.

Основную проблему при вызове D3DKMTQueryAdapterInfo() представляет способ получения дескриптора графического адаптера (см.поле hAdapter), получить который можно цепочкой из трёх функций ниже по такому алго:


1. CM_Get_Device_Interface_List() из библиотеки setupapi.dll, передав ей в первом аргументе константу "GUID_DISPLAY_DEVICE_ARRIVAL". Константа действительна для всех Win и прописана в прикреплённом инклуде. На выходе из этой функции получим строку с идентификатором адаптера вида:
\\?\PCI#VEN_8086&DEV_29C2&SUBSYS_75291462&REV_10#3&10#{1ca05180-a699-450a-9a0c-de4fbe3ddd89}, где GUID в хвосте и есть глобальная константа дисплея.
2. MultiByteToWideChar(), чтобы перевести данный идентификатор из ANSI в Unicode, т.к. следующая функция ожидает на входе именно Юникод.
3. D3DKMTOpenAdapterFromDeviceName() из либы gdi32.dll возвращает уже дескриптор указанного видеоадаптера, а на входе ждёт указатель на структуру "D3DKMT_OPENADAPTERFROMDEVICENAME" с таким прототипом:
C-подобный:
struct D3DKMT_OPENADAPTERFROMDEVICENAME
   pDeviceName      dd  0  ;// In. линк на Unicode-имя адаптера
   hAdapter         dd  0  ;// Out. "D3DKMT_HANDLE"
   AdapterLuid      dq  0  ;// Out. LUID
ends

Так во-втором поле мы получим дескриптор, который сразу-же в виде аргумента "hAdapter" отфутболим в функцию D3DKMTQueryAdapterInfo(). В практической части я запрашиваю всего 4 типа информации о видеокарте, среди которых есть и "KMTQAITYPE_GETSEGMENTSIZE=3". Именно этот запрос возвращает объём VRAM выше 4 Gb, а вот как выглядит формат структуры на выходе (все поля размером QWORD=8 байт):

C-подобный:
struct D3DKMT_SEGMENTSIZEINFO
  DedicatedVideoMemorySize   dq  0  ;// Out. размер памяти, выделенной из видеоадаптера
  DedicatedSystemMemorySize  dq  0  ;// Out. размер апертуры ОЗУ для графики
  SharedSystemMemorySize     dq  0  ;// Out. расшаренная видео-память
ends

Помимо функции D3DKMTQueryAdapterInfo() в библиотеке gdi32.dll есть ещё одна интересная, но никак недокументированная на MSDN функция D3DKMTQueryStatistics(). У себя на сайте мелкософт нас не использовать данную функцию, мол она только для системных нужд. Но беда не в том, что наверху лгут, а в том, что внизу верят. Как и следует из её названия, она призвана собирать различную статистику о видеоадаптере, например время работы GPU в текущем приложении и многое другое. Вот какие запросы статистики ей можно передавать:

C-подобный:
;// enum _D3DKMT_QUERYSTATISTICS_TYPE
;//
  D3DKMT_QUERYSTATISTICS_ADAPTER                = 0
  D3DKMT_QUERYSTATISTICS_PROCESS                = 1
  D3DKMT_QUERYSTATISTICS_PROCESS_ADAPTER        = 2
  D3DKMT_QUERYSTATISTICS_SEGMENT                = 3
  D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT        = 4
  D3DKMT_QUERYSTATISTICS_NODE                   = 5
  D3DKMT_QUERYSTATISTICS_PROCESS_NODE           = 6
  D3DKMT_QUERYSTATISTICS_VIDPNSOURCE            = 7
  D3DKMT_QUERYSTATISTICS_PROCESS_VIDPNSOURCE    = 8
;//<------- Win8+
  D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT_GROUP  = 9
  D3DKMT_QUERYSTATISTICS_PHYSICAL_ADAPTER       = 10


5. Практика – чтение данных видеоадаптера

Под занавес приведу код приложения, который соберёт основную информацию об активном графическом адаптере. Кстати начиная с Win8 инженеры ввели понятие "виртуальный адаптер" и дали ему кличку "BasicRander". Он вступает в игру, когда система не находит драйверов от производителя, что даёт возможность запускать систему с абсолютно незнакомым железом. По этой причине в программе нужно организовать цикл, чтобы перечислить все имеющиеся карты, включая виртуальную.

Другой момент – это способ получения версии DirectX. Поиск в Google выдал мне несколько вариантов, и все как-минимум с сотней строк сишного кода. Это меня никак не вдохновило и я пошёл иным путём. Если учесть, что каждая версия DirectX несёт с собой свои Runtime-библиотеки, то достаточно будет в цикле загружать их по имени через LoadLibrary() и если ошибка, то переходить к следующей. Список этих библиотек выглядит так: d3d9.dll и дальше d3d10,11,12.dll для DirectX v9..12 соответственно. Вариант топорный, но что показательно – в нём нет ничего плохого, поскольку он решает проблему и исправно воркает на всех системах Win от 7 до 10. Вот исходник программы с реализацией задуманного:


C-подобный:
format   pe console
entry    start
;//-------
section '.inc' data readable
include 'win32ax.inc'
include 'equates\dxgi.inc'
;//-------
.data
d3Fname     db  'd3d1'       ;//<---- Имя библиотеки DirectX,
verIndex    db  '2.dll',0    ;//<----  ..и его продолжение для цикла.
dxVersion   dd  12
devName     rb  512

hFactory    dd  0
hAdapter    dd  0
Index       dd  0
listOffset  dd  buff
fpuResult   dq  0
fpuMb       dd  1024*1024    ;// Byte to Mbyte

align 16
disp        DISPLAY_DEVICEA
oa          D3DKMT_OPENADAPTERFROMDEVICENAME
qai         D3DKMT_QUERYADAPTERINFO
stat        D3DKMT_QUERYSTATISTICS
buff        db  0
;//---------
.code
start:   invoke  SetConsoleTitle,<'*** Graphic adapter info v1.0 ***',0>

;// Получить версию DirectX
@getDxVersion:
         invoke  LoadLibrary,d3Fname
         or      eax,eax
         jnz     @ok
         dec     byte[verIndex]   ;// если ошибка..
         dec     [dxVersion]
         jmp     @getDxVersion

@ok:     invoke  FreeLibrary,eax
        cinvoke  printf,<10,' DirectX ver...:  %d',10,0>,[dxVersion]

         invoke  EnumDisplayDevicesA,0,0,disp,0
        cinvoke  printf,<10,' Object name...:  %s',\
                         10,' Adapter.......:  %s',0>,\
                         disp.DeviceName,disp.DeviceString

;//************************************************************
;//***** Работа с СОМ-интерфейсом, и методами DXGI ************
;//************************************************************
         invoke  CoInitialize,0
         invoke  CreateDXGIFactory1,IID_IDXGI_Factory1,hFactory

;// Найти и вызвать 'IDXGI_Factory::EnumAdapter'
         mov     esi,[hFactory]
         mov     esi,[esi]
         mov     eax,[esi+IDXGI_Factory.EnumAdapters]
         push    hAdapter        ;// сюда получим указатель
         push    [Index]         ;// индекс адаптера начиная с нуля
         push    [hFactory]      ;// This = в каком интерфейсе искать
         call    eax             ;// EnumAdapter!!!

;// Найти и вызвать 'IDXGI_Adapter::GetDesc'
         mov     esi,[hAdapter]
         mov     esi,[esi]
         mov     eax,[esi+IDXGI_Adapter.GetDesc]
         push    buff            ;// сюда получим структуру "DXGI_ADAPTER_DESC"
         push    [hAdapter]      ;// This = в каком интерфейсе искать
         call    eax

         mov     esi,buff
         mov     eax,[esi+DXGI_ADAPTER_DESC.DedicatedVideoMemory]
         mov     ebx,[esi+DXGI_ADAPTER_DESC.DedicatedSystemMemory]
         mov     ecx,[esi+DXGI_ADAPTER_DESC.SharedSystemMemory]
         shr     eax,20
         shr     ebx,20     ;//<------ байты в мегабайты
         shr     ecx,20
        cinvoke  printf,<   10,' Vid/Pid.......:  %04x-%04x  rev.%x',\
                            10,' LUID..........:  %08x%08x',\
                         10,10,' Mem from Video....:  %03d Mb',\
                            10,' Mem from System...:  %03d Mb  <--- BIOS Aperture Size',\
                            10,' Shared memory.....:  %03d Mb',0>,\
                         [esi+DXGI_ADAPTER_DESC.VendorId],\
                         [esi+DXGI_ADAPTER_DESC.DeviceId],\
                         [esi+DXGI_ADAPTER_DESC.Revision],\
                         dword[esi+DXGI_ADAPTER_DESC.AdapterLuid+4],\
                         dword[esi+DXGI_ADAPTER_DESC.AdapterLuid],\
                         eax,ebx,ecx

         mov     esi,buff
         mov     eax,[esi+DXGI_ADAPTER_DESC.DedicatedVideoMemory]
         add     eax,[esi+DXGI_ADAPTER_DESC.DedicatedSystemMemory]
         add     eax,[esi+DXGI_ADAPTER_DESC.SharedSystemMemory]
         shr     eax,20
        cinvoke  printf,<10,' Total Video RAM...:  %d Mb',0>,eax

;//***************************************************************
;//***** Функции класса D3DKMTхх *********************************
;//***************************************************************

         invoke  CM_Get_Device_Interface_List, GUID_DISPLAY_DEVICE_ARRIVAL,0,buff,512,0
         mov     edi,[listOffset]
@GetVideoadapter:
         invoke  MultiByteToWideChar,CP_UTF8,0,edi,-1,devName,512
        cinvoke  printf,<10,10,' Adapter ID........:  %.65ls',0>,devName

         mov     [oa.pDeviceName],devName
         invoke  D3DKMTOpenAdapterFromDeviceName,oa
        cinvoke  printf,<   10,' Adapter handle....:  %08x',\
                            10,' Adapter LUID......:  %08x%08x',0>,\
                            [oa.hAdapter],dword[oa.AdapterLuid+4],dword[oa.AdapterLuid]

;// Различная инфа от D3DKMTQueryAdapterInfo() ----------
;//----- Адрес видео-адаптера на шине PCI ---------------
         mov     eax,[oa.hAdapter]
         mov     [qai.hAdapter],eax
         mov     [qai.Type],KMTQAITYPE_ADAPTERADDRESS
         mov     [qai.pPrivateDriverData],buff
         mov     [qai.PrivateDriverDataSize],sizeof.D3DKMT_ADAPTERADDRESS
         invoke  D3DKMTQueryAdapterInfo,qai

         mov     esi,buff
        cinvoke  printf,<   10,' PCI Bus\Dev\Func..:  %02x:%02x:%02x',0>,\
                            [esi+D3DKMT_ADAPTERADDRESS.BusNumber],\
                            [esi+D3DKMT_ADAPTERADDRESS.DeviceNumber],\
                            [esi+D3DKMT_ADAPTERADDRESS.FunctionNumber]

;//----- Информация из реестра --------------------------
         mov     eax,[oa.hAdapter]
         mov     [qai.hAdapter],eax
         mov     [qai.Type],KMTQAITYPE_ADAPTERREGISTRYINFO
         mov     [qai.pPrivateDriverData],buff
         mov     [qai.PrivateDriverDataSize],sizeof.D3DKMT_ADAPTERREGISTRYINFO
         invoke  D3DKMTQueryAdapterInfo,qai

         mov     esi,buff
         mov     eax,esi
         add     eax,D3DKMT_ADAPTERREGISTRYINFO.AdapterString
         mov     ebx,esi
         add     ebx,D3DKMT_ADAPTERREGISTRYINFO.BiosString
         mov     ecx,esi
         add     ecx,D3DKMT_ADAPTERREGISTRYINFO.DacType
         mov     edx,esi
         add     edx,D3DKMT_ADAPTERREGISTRYINFO.ChipType
        cinvoke  printf,<   10,' Adapter string....:  %ls',\
                            10,' Bios string.......:  %ls',\
                            10,' Dac type..........:  %ls',\
                            10,' GPU chip name.....:  %ls',0>,eax,ebx,ecx,edx

;//----- Размер памяти видео-адаптера более 4 Gb -------
         mov     eax,[oa.hAdapter]
         mov     [qai.hAdapter],eax
         mov     [qai.Type],KMTQAITYPE_GETSEGMENTSIZE
         mov     [qai.pPrivateDriverData],buff
         mov     [qai.PrivateDriverDataSize],sizeof.D3DKMT_SEGMENTSIZEINFO
         invoke  D3DKMTQueryAdapterInfo,qai

         mov     esi,buff
         push    esi esi esi
         push    dword[esi+D3DKMT_SEGMENTSIZEINFO.DedicatedVideoMemorySize+4]
         push    dword[esi+D3DKMT_SEGMENTSIZEINFO.DedicatedVideoMemorySize]
         fild    qword[esp]
         fidiv   [fpuMb]
         fstp    [fpuResult]
         add     esp,8
        cinvoke  printf,<   10,' Mem from Video....:  %04.0f Mb',0>,\
                         dword[fpuResult],dword[fpuResult+4]
         pop     esi
         push    dword[esi+D3DKMT_SEGMENTSIZEINFO.DedicatedSystemMemorySize+4]
         push    dword[esi+D3DKMT_SEGMENTSIZEINFO.DedicatedSystemMemorySize]
         fild    qword[esp]
         fidiv   [fpuMb]
         fstp    [fpuResult]
         add     esp,8
        cinvoke  printf,<   10,' Mem from System...:  %04.0f Mb',0>,\
                         dword[fpuResult],dword[fpuResult+4]
         pop     esi
         push    dword[esi+D3DKMT_SEGMENTSIZEINFO.SharedSystemMemorySize+4]
         push    dword[esi+D3DKMT_SEGMENTSIZEINFO.SharedSystemMemorySize]
         fild    qword[esp]
         fidiv   [fpuMb]
         fstp    [fpuResult]
         add     esp,8
        cinvoke  printf,<   10,' Shared VGA mem....:  %04.0f Mb',0>,\
                         dword[fpuResult],dword[fpuResult+4]
         pop     esi
         fild    [esi+D3DKMT_SEGMENTSIZEINFO.DedicatedVideoMemorySize]
         fild    [esi+D3DKMT_SEGMENTSIZEINFO.DedicatedSystemMemorySize]
         fild    [esi+D3DKMT_SEGMENTSIZEINFO.SharedSystemMemorySize]
         fadd    st0,st1
         fadd    st0,st2
         fidiv   [fpuMb]
         fstp    [fpuResult]
        cinvoke  printf,<   10,' Total Video RAM...:  %04.0f Mb',0>,\
                         dword[fpuResult],dword[fpuResult+4]

;//***** Цикл обхода всех адаптеров для Win8+ ******************
         invoke  CM_Get_Device_Interface_List, GUID_DISPLAY_DEVICE_ARRIVAL,0,buff,512,0
         mov     edi,[listOffset]
         xor     eax,eax
         repne   scasb
         cmp     byte[edi],'\'
         jne     @f
         mov     [listOffset],edi
         jmp     @GetVideoadapter

;//*******************************************************************
;//***** Недокументированная статистика ******************************
;//*******************************************************************
@@:     cinvoke  printf,<10,10,' ****** D3DKMTQueryStatistics() ******',10,0>
         mov     [stat.Type],D3DKMT_QUERYSTATISTICS_PROCESS
         mov     eax,dword[oa.AdapterLuid]
         mov     ebx,dword[oa.AdapterLuid+4]
         mov     dword[stat.AdapterLuid],eax
         mov     dword[stat.AdapterLuid+4],ebx
         invoke  D3DKMTQueryStatistics,stat
        cinvoke  printf,<   10,' Stat return code..:  %x  (0 = OK)',0>,eax


@exit:  cinvoke  _getch
        cinvoke  exit,0
;//-------
section '.idata' import data readable
library   msvcrt,'msvcrt.dll',kernel32,'kernel32.dll',user32,'user32.dll',\
          ole32,'ole32.dll',dxgi,'dxgi.dll',gdi32,'gdi32.dll',setupapi,'setupapi.dll'
import    dxgi,CreateDXGIFactory1,'CreateDXGIFactory1'
import    ole32,CoInitialize,'CoInitialize'
include  'api\msvcrt.inc'
include  'api\kernel32.inc'
include  'api\user32.inc'
include  'api\gdi32.inc'
include  'api\setupapi.inc'

NVidia_Win7.png


На следующем этапе я снял внешнее своё видео "GeForce 9500 GT", и запустил систему только на встроенной карте. Вот лог где видно, что адаптер не имеет своей локальной памяти и запрашивает у системы апертуру размером всего 64 Mb. Зато у дискретной карты GeForce на предыдущем скрине имеется локальная VRAM в объёме 246 Mb, поэтому апертура ему не нужна.

Кроме того код правильно определил тип процессора GPU под ником "GMA-3100", цифро-аналоговый преобразователя DAC (Digital-to-Analog Converter), и версию DirectX v11. Обратите внимание на версию WDDM – дискретная карта знакома с расширенными функциями v1.1, в то время-как для встроенной карты требуется дефолтная не выше v1.0:


Intel_Win7.png


Ну и последний тест я провёл на своём буке с Win10 х64. Как оказалось у него карта встроенная размером 128 метров, хотя под графические нужды система выделяет макс 2 Gb. Выше упоминалась, что современные карты работают в режиме динамического выделения системной памяти DVMT: когда память адаптеру действительно нужна – она выделяется, а когда в ней нет необходимости, то на автомате освобождается. Код в цикле обнаружил и виртуальную карту "BasicRender", у которой даже нет адреса "Bus\Dev\Func" на системной шине PCI, а только динамическая память, а так-же сменилась версия DirectX на 12:

Win10.png



6. Заключение

Прежде чем поставить жирную точку и убрать перо с бумагой в стол, хотелось-бы сказать пару слов о работе с графическими объектами на ассемблере. Здесь я вообще не затрагивал данную тему, чтобы не смешивать два параллельных направления. Всех, кого интересует работа с графикой отправляю на сайт wasm.in, где модератор достопочтенного ресурса (и по совместительству участник нашего форума) @Mikl___ приводит массу таких примеров, например основной
, и уроки от .

Что касается рассмотренного выше кода, то просьба проверить и откликнуться тех, у кого на борту имеется видеоадаптер с памятью более 4 Gb. Интересует, правильно-ли код определит размер VRAM. В скрепке лежит исполняемый файл для тестов, и инклуд с описанием структур DXGI. Всем удачи, пока!
 

Вложения

  • DXGI.ZIP
    6,9 КБ · Просмотры: 264

DragonFly

Green Team
01.06.2020
223
65
BIT
3
мастдай, каждый раз читаю статьи и угараю)))

Что касается рассмотренного выше кода, то просьба проверить и откликнуться тех, у кого на борту имеется видеоадаптер с памятью более 4 Gb. Интересует, правильно-ли код определит размер VRAM. В скрепке лежит исполняемый файл для тестов, и инклуд с описанием структур DXGI.

либо я не вижу, либо забыл прикрепить файл
 

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 310
BIT
53
либо я не вижу, либо забыл прикрепить файл
да вроде лежит в скрепке..

Вообще-то термин употреблялся применительно к Win2000 и подразумевал "Must Die" (должен умереть),
но для большинства (и меня в том числе) вся линейка Win так осталась мастдаем.
Судя по всему он умирать так и не собирается, и даже наоборот активно развивается.
 

DragonFly

Green Team
01.06.2020
223
65
BIT
3
Отображает верно, все как в менеджере задач

1631526102701.png
 
Последнее редактирование модератором:
  • Нравится
Реакции: Marylin

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 310
BIT
53
@DragonFly спасибо!
1. А сколько всего системной оперативы ОЗУ установлено на машине?
2. Судя по всему видео встроенное?
3. Что возвращает WMI на запрос памяти? в ком.строке: wmic.exe PATH Win32_videocontroller GET AdapterRAM
 

DragonFly

Green Team
01.06.2020
223
65
BIT
3
64гб оперативы
один адаптер встроенный
один 2060 м обьемом 6 вроде, ща проверю
да 6
1631529222624.png

оно просто в гибридном режиме, поэтому может не показывает основную карту?
перезагружу для проверки

wmic.exe PATH Win32_videocontroller GET AdapterRAM

1631529320434.png
 

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 310
BIT
53
Ясно.. Значит точно WMI не возвращает более 4 Gb.
И кстати в доках было указано, что если карты в гибридном режиме, то нужен другой подход к организации цикла.
 

Hardreversengineer

Green Team
20.08.2019
48
26
BIT
1
Очень шикарная статья! Давно мечтал увидеть её здесь. Да ещё и рассмотрели один из тех вопросов, на который я даже ни разу не натыкался. Я хоть и несколько лет подряд занимаюсь графикой, но изложенное в статье узнаю впервые. Это просто удивительно. Интересно будет увидеть продолжение как и данной тематики, так и узнать про DirectSound, OpenAL, ASIO и тд.
 
Последнее редактирование:
  • Нравится
Реакции: Evgeny D и Marylin

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 310
BIT
53
Дискретный режим
Странно.. Почему-то функция CM_Get_Device_Interface_List() не смогла вернуть имя адаптера (Adapter ID), а в данном примере ошибки я не обрабатывал. Поэтому и остальная часть ушла в разнос и показывает память от фонаря. Надо будет протестить и на других машинах. Спасибо за тесты!
 

DragonFly

Green Team
01.06.2020
223
65
BIT
3
Странно.. Почему-то функция CM_Get_Device_Interface_List() не смогла вернуть имя адаптера (Adapter ID), а в данном примере ошибки я не обрабатывал. Поэтому и остальная часть ушла в разнос и показывает память от фонаря. Надо будет протестить и на других машинах. Спасибо за тесты!
Всегда пожалуйста, пиши если что ;)
 
Последнее редактирование модератором:
23.09.2023
6
0
BIT
3
Такую статью нужно было лет так 10 назад написать - тогда бы ей цены небыло. А так неплохо неплохо.
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!