Blog. Just Blog

Как получить список физических дисков и узнать их размер

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Как получить список физических дисков и узнать их размер
Как получить список физических дисков и узнать их размер

Как вы знаете, физические диски, установленные в системе, обозначаются символическими ссылками вида "\\.\PhysicalDrive0", "\\.\PhysicalDrive1" и так далее. Однако, в WinAPI нет простых штатных функций, чтобы получить их список. Для того, чтобы сделать это, в интернетах предлагают несколько различных способов. Например, самый дуболомный, это последовательный перебор всех значений от 0 до MAX_DRIVES. В разных компиляторах это значение может меняться от 16 до 26, также не исключено, что значение может дополнительно ограничиваться системой. Дальше в цикле формируется символическая ссылка с текущим индексом и с помощью вызова CreateFile осуществляется попытка открыть это устройство. Если функция вернула ERROR_FILE_NOT_FOUND, то такого физического диска в системе нет, если ошибки нет, то ссылка доступна для использования, в противном случае диск присутствует, но по какой-то причине у вас нет к нему доступа. В принципе, это решение имеет место на существование, но есть вариант более правильный.

Список дисков хранится в ключе реестра HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Disk\Enum в виде перечня параметров, имеющих числовые имена. Там же находится параметр Count, в котором хранится количество подключенных дисков и вспомогательный параметр NextInstance, определяющий индекс, который получит следующий подключенный диск. При подключении или отключении диска данные в реестре обновляются. Насколько я могу судить по личным наблюдениям на разных системах, нумерация физических дисков в системе всегда сквозная и непрерывная, но никто не может дать стопроцентную гарантию, что так будет всегда и везде.

Значит для получения списка всех физических дисков системы достаточно перебрать параметры указанного ключа реестра и использовать их имена в качестве числового значения в символической ссылке "\\.\PhysicalDriveX". Начнем с сегмента данных:
  1. section '.data' code readable writeable
  2.  
  3. szRegKey     db 'SYSTEM\CurrentControlSet\Services\Disk\Enum',0
  4.  
  5. hKey         dd ?               ; Хэндл открытого ключа реестра
  6.  
  7. dValuesIndex dd ?               ; Индекс при перечислении значений
  8. szValueName  rb 100h            ; Название ключа реестра
  9. dValueType   dd ?               ; Тип значения ключа реестра
  10.  
  11. tmp          dd ?
Теперь код. Тут ничего сложного, открываем ключ реестра, по очереди перебираем его параметры. Тип параметра должен быть REG_SZ, а имя должно состоять из одной или двух цифр. Вторую проверку я немного упростил, проверяю только первый символ имени.
  1.         ; Открыть ветку реестра
  2.         ; HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Disk\Enum
  3.         invoke  RegOpenKey,HKEY_LOCAL_MACHINE,szRegKey,hKey
  4.         or      eax,eax
  5.         jnz     loc_done
  6.  
  7.         ; Перебрать все значения, начиная с 0
  8.         mov     [dValuesIndex],0
  9. loc_scan_values:
  10.         ; Получить значение и его тип из ключа
  11.         mov     [tmp],MAX_PATH
  12.         invoke  RegEnumValue,[hKey],[dValuesIndex],szValueName,\
  13.                 tmp,NULL,dValueType,NULL,NULL
  14.         or      eax,eax
  15.         jnz     loc_no_more_values
  16.  
  17.         ; Ключ должен иметь тип STRING
  18.         cmp     [dValueType],REG_SZ
  19.         jne     loc_next_values
  20.  
  21.         ; Ключ должен быть типа "0", "1" и т.п.
  22.         cmp     byte [szValueName],'0'
  23.         jb      loc_next_values
  24.         cmp     byte [szValueName],'9'
  25.         ja      loc_next_values
  26.  
  27.         ; В переменной szValueName имя параметра
  28.         ...
  29.         ; Выполнить нужные действия с найденным диском
  30.         ...
  31.  
  32. loc_next_values:
  33.         ; Проверить следующее значение
  34.         inc     [dValuesIndex]
  35.         jmp     loc_scan_values
  36.  
  37. loc_no_more_values:
  38.         ; Закрыть ключ
  39.         invoke  RegCloseKey,[hKey]
  40. loc_done:
Этот способ гарантированно работает на всех версиях Windows, начиная от Windows 2000 и заканчивая самыми современными системами. Ну вот, получать список физических дисков мы научились. Теперь, чтобы эти данные не пропадали без дела, давайте узнаем размер каждого найденного диска. Делать это мы будем при помощи функции DeviceIoControl двумя способами: через IOCTL_DISK_GET_DRIVE_GEOMETRY и IOCTL_DISK_GET_LENGTH_INFO. Почему два способа? Это мы увидим дальше. А пока определим все нужные константы и структуры, которые коробочный FASM не знает. Не забывайте про выравнивание структур в сегменте данных на границу 16 байт.
  1. IOCTL_DISK_GET_DRIVE_GEOMETRY = 00070000h
  2. IOCTL_DISK_GET_LENGTH_INFO    = 0007405Ch
  3.  
  4. struct DISK_GEOMETRY
  5.         Cylinders         dd ?
  6.                           dd ?
  7.         MediaType         dd ?
  8.         TracksPerCylinder dd ?
  9.         SectorsPerTrack   dd ?
  10.         BytesPerSector    dd ?
  11. ends
  12.  
  13. struct GET_LENGTH_INFORMATION
  14.         Length  dq ?
  15. ends
  16.  
  17. align 16
  18. diskg  DISK_GEOMETRY
  19.  
  20. align 16
  21. diskl  GET_LENGTH_INFORMATION
  22.  
  23. szSymLink    rb 100h
Данные есть, переходим к коду. У нас есть индекс физического диска, полученный из параметра реестра, на его основе надо сформировать символическую ссылку и попытаться открыть устройство. Опять же на личном опыте я столкнулся с тем, что подключенные флешки в реестре определялись как физические устройства, но на этапе открытия этого устройства функция CreateFile завершалась с ошибкой. Таким образом, в процессе перебора отсеиваются все подключенные устройства, не являющиеся жесткими дисками.
  1.         ; Сформировать символическую ссылку на диск
  2.         invoke  wsprintf,szSymLink,mask,szValueName
  3.         add     esp,12
  4.  
  5.         ; Открыть устройство
  6.         invoke  CreateFile,szSymLink,GENERIC_READ,FILE_SHARE_READ,\
  7.                 0,OPEN_EXISTING,0,0
  8.         cmp     eax,-1
  9.         je      loc_next_values
  10.         mov     ebx,eax
  11.  
  12.         ; Получить геометрию диска
  13.         invoke  DeviceIoControl,ebx,IOCTL_DISK_GET_DRIVE_GEOMETRY,\
  14.                 0,0,diskg,sizeof.DISK_GEOMETRY,tmp,0
  15.  
  16.         ; Получить размер диска
  17.         invoke  DeviceIoControl,ebx,IOCTL_DISK_GET_LENGTH_INFO,\
  18.                 0,0,diskl,sizeof.GET_LENGTH_INFORMATION,tmp,0
  19.  
  20.         ; Закрыть устройство
  21.         invoke  CloseHandle,ebx
Первый способ получения размера диска - это перемножить геометрические параметры диска, а именно количество цилиндров, количество дорожек в цилиндре, количество секторов в дорожке и количество байт в секторе. Математика нехитрая, но конкретно в этом примере используется 32-битная арифметика, и если вы являетесь счастливым обладателем жесткого диска емкостью более 4 терабайт, то скорее всего вы получите ошибку переполнения.
  1.         ; Общее количество секторов
  2.         xor     edx,edx
  3.         mov     eax,[diskg.TracksPerCylinder]
  4.         imul    eax,[diskg.SectorsPerTrack]
  5.         mov     ecx,eax
  6.         xor     edx,edx
  7.         mov     ebx,[diskg.Cylinders]
  8.         mul     ebx
  9.  
  10.         ; Размер диска
  11.         mov     eax,ecx
  12.         ; EAX = размер одного цилиндра в байтах
  13.         imul    eax,[diskg.BytesPerSector]
  14.         ; Перевести в килобайты
  15.         shr     eax,10
  16.         imul    eax,[diskg.Cylinders]
  17.         ; EAX = размер диска в килобайтах
Второй способ, более правильный и потому рекомендуемый к применению, это разбор результатов, полученных при использовании параметра IOCTL_DISK_GET_LENGTH_INFO. Тут никаких ограничений по математике нет, выводить на экран значение QWORD даже в 32-битной системе мы умеем. Но для чистоты эксперимента результат тоже будет пересчитан до разрядности килобайтов, что также может привести к описанным выше проблемам.
  1.         ; Размер диска в байтах
  2.         mov     eax,dword [diskl.Length+0]
  3.         mov     edx,dword [diskl.Length+4]
  4.         ; Перевести в килобайты
  5.         mov     ebx,1024
  6.         div     ebx
  7.         ; EAX = размер диска в килобайтах
И вот теперь самое интересное. Если вы посмотрите тестовый пример, то увидите, что значения емкости диска, полученные разными способами, заметно отличаются. Я не смог найти официального объяснения этой непонятке, но по сведениям из разных источников, различие получается из-за особенностей формирования значений геометрии диска. Они не кратны степени 2, потому получаются погрешности в сторону уменьшения. Так что если вы хотите получить максимально точный размер физического диска, то используйте значения, полученные с помощью IOCTL_DISK_GET_LENGTH_INFO. Данные геометрии использовать не надо.

В приложении пример программы с исходным текстом, которая получает список физических дисков из реестра, а затем определяет их размер.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Pysical.Disks.Enumeration.Demo.zip (4,256 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 5959 | Комментариев: 9

Метки: Assembler, HDD
Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (17.01.2018 в 08:40):
Если использовать простой перебор без чтения реестра, то будет работать и без админа.
Владимир (13.01.2018 в 00:29):
==DJ==[ZLO] был прав, надо было просто запустить с правами Админа, тогда работает. Спасибо за программу !
rtfm (12.01.2018 в 02:14):
По поводу последнего коммента - примеры в UserMode:
IDENTIFY_DEVICE: TxBench, Ssd-z, HddIdentifyDrive
HPA/DCO: DiskCheckup, ATATool
rtfm (11.01.2018 в 16:25):
Спасибо за пример.
Для более точного решения 2-го вопроса (размер каждого найденного диска), как вариант, можно разбирать IDENTIFY_DEVICE_DATA -> CurrentSectorCapacity.
Т.е. точная емкость в KiB будет равна CurrentSectorCapacity*LBA/1024.

В этом случае, возможно, придется учитывать a) HPA; b) размер LBA (512 или 4k)
ManHunter (11.01.2018 в 10:45):
На больших винтах 4+ Тб тестовая прога будет падать с ошибкой переполнения или просто молча падать. Причину я указал в статье.
==DJ==[ZLO] (11.01.2018 в 10:30):
Спасибо! Все работает. Тестил на : win2k3,win7x86-x64,win10x64 - так же проверил на WinPE win2k3,win7x64,win10x64 Strelec. У Владимира возможно нет прав на чтения куста.
Ellephant (11.01.2018 в 10:18):
У мну тоже показало Вынь7-64
Exit (11.01.2018 в 06:58):
Гм, а на Win-7 x64 все запустилось и показало ))
Владимир (10.01.2018 в 23:32):
Добрый вечер, Дмитрий,
запустил, открывается окошко, но внутри пусто, ничего не отображается.
OС: x64 Win 10. Очень хотелось бы увидеть её в действии.
Спасибо.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0053 сек.) / Память: 4.5 Mb
Наверх