• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Статья Системные таймеры, Часть[3] – HPET и таблица ACPI

Предыдущие части:

3. ACPI таблицы – таймер HPET.
---------------------------------------


HPET – High Precision Event Timers
частота: 14.318180 MHz | счётчик: 64-бита


Высокоточный таймер событий HPET берёт на себя функции сразу двух устаревших таймеров PIT и RTC. Если у PIT'a было всего 3-канала, то у HPET их уже 32, добрая половина которых лежит в резерве и не используются. Работая на максимально возможной для таймеров частоте 14.31818 MHz он имеет разрешение (время между двумя тиками) равное 1/14318180 ~70 ns. Помимо поддержки часов реального времени, Hpet является источником прерываний для мультимедийных приложений, что позволяет им плавно воспроизводить видео/аудио. Не остаётся в стороне и сама система, отслеживая по щелчкам этого таймера разнообразные свои событий типа оснастки "Performance monitor".

Hpet придерживается совершенно иной политики, нежели рассмотренные ранее таймеры. Он имеет один 64-битный счётчик и 32 компаратора, каждый из которых наделён своими регистрами и программируется отдельно. Такой подход позволяет одному устройству генерить до 32-х прерываний IRQ в произвольные интервалы времени.

Сначала мы записываем в регистр значение требуемого тайм-аута, и компаратор начинает сравнивать его с тиками центрального счётчика. В момент когда два эти значения совпадут, на выходе получим IRQ и если канал запрограммирован на периодичность, логика Hpet аппаратно добавит наш тайм-аут к предыдущему значению, тем-самым вычислив время следующего прерывания IRQ. Визуально это можно представить так:


hpet_scheme.png


BIOS имеет специальную опцию, которая активирует Hpet-контролёр. Примечательным является то, что если эта опция отключена и таймер не задействован, некоторые функции Win32-API могут возвращать ошибку. В частности это касается привязанной к Hpet функции QueryPerformanceCounter(), а так-же всех остальных, где фигурирует термин Performance. Это самый ламерский способ обнаружения данного таймера в системе.


Программное включение HPET

Начиная с чипсетов ICH(6) таймер можно задействовать, даже если биос не имеет соответствующей опции (например требует обновления). Для этого находим базовый адрес рут-комплекса RCBA (как это сделать рассматривалось в предыдущей части), и сместившись от него к регистру 3404h взводим в нём бит[7]. C этого момента рычаги управлением Hpet будут спроецированы в память, а по какому именно адресу – нужно будет указать в битах[1:0]. Биос предлагает нам на выбор один из 4-х регионов памяти и мы можем выбрать любой из них (как-правило первый). В даташите на ICH7 цепочка этих действий расписывается так:

hpet_config.png


Значит под регистры Hpet выделяется ровно 1 Кбайт памяти (0x03FF), причём базой для всех регионов служит адрес 0xFED00000. Проблема в том, что на программном уровне эта база не доступна нам как в защищённом Win (нужен драйвер), так и в реальном DOS (ограничение памяти 1Mb). Выкручиваться из данной ситуации при помощи виндозного драйвера не имеет смысла, т.к. системные guard'ы всё-равно прихлопнут его, и в лучшем случае нужно будет бежать за подписью в Microsoft. Так-что выбора у нас нет и придётся набивать руку в реальном режиме, открыв в нём доступ ко-всему 4Gb пространству памяти. Остановимся на этом чуть подробней...



UnReal Mode – преодолеваем 1 Мбайтный барьер

Если в защищённом режиме РМ память виртуальная, то в реальном RM она сегментная. По сути всё доступное ОЗУ делится на сегменты в обоих режимах, только в РМ их размер программно выставлен на максимум, а в RM имеется ограничение 64 Кб. Размеры определяются в дескрипторах сегментов, которые собраны в одну системную таблицу Global-Descriptor-Table или просто GDT. У каждого из шести сегментных регистров процессора CS,DS,SS,ES,FS,GS свой 8-байтный дескриптор в этой таблице, а значит мы можем оперировать ими в отдельности.

Для поиска таблицы GDT в системном пространстве, процессор имеет специальный регистр GDTR. Размер его 6-байт (на х32), где младшие два хранят длину этой таблицы в байтах, а старшие 4 – её адрес в памяти. Прочитать регистр можно инструкцией ассемблера sgdt, а записать в него новые значения инструкцией lgdt (store/load соответственно).

А что если взять редко-используемый в RM сегментный регистр FS и модифицировать его дескриптор так, чтобы снять с него 64К ограничение? Правда для этого нужно сначала перейти из реального в защищённый режим, ..сменить в нём дескриптор, и вернуться обратно в реальный. После этих манипуляций, нам будет доступна вся линейная память системы 4Gb, которую можно адресовать через хакнутый регистр FS. В своё время, этот недокументированный режим процессора был открыт сразу несколькими программистами в разных концах света, поэтому и называют его кому-как нравится, например: Un-Real (нереальный, что-то среднее), Flat-Real или плоский, Big-Real и т.д. в этом духе.

На форумах можно встретить горячие споры на эту тему, мол "раз-уж мы перешли в защищённый режим, то почему-бы просто не остаться в нём?". Всё правильно.. только в этом случае мы лишимся системных прерываний, т.к. нужно будет выстраивать новую таблицу IDT для РМ (Interrupt-Descriptor-Table), а это лишние проблемы. Более того, некоторые инструкции процессора являются привилегированными и работают только в реальном режиме – терять их было-бы не разумно.

На рисунке ниже, подноготная 8-байтного дескриптора. Чтобы добиться своей цели, нужно сбросить базу в нуль, а лимит наоборот выставить на максимум 0хFFFFFFFF. Однако под лимит здесь выделяется всего 20-бит (синий блок, 2^20=1.048.576 или 1Мб), а для адресации 4Gb нам нужны все 32-бита. Расширить лимит до максимума позволяет бит гранулярности G (выделен красным), который и выставим в единицу. Теперь процессор будет считать лимит не в байтах, а в 4К-блоках (фактически это множитель 4096). В итоге, новоиспечённый дескриптор FS должен иметь у нас 8-байтное значение 0х00CF93000000FFFF, где число F определяет (приоритетный в данном случае) лимит:

segDescriptor.png


Если посмотреть на дескриптор с высоты птичьего полёта, то из 64К сегмента делает 4Gb всего один бит-гранулярности[G] – остальные биты нам не интересны, ..ну если только [E] (expand-down) при котором адрес растёт наоборот, как у стека. Например привилегия DPL напрочь отсутствует в R-моде, и введена только для защиты страниц виртуальной памяти ядра оси, в защищённом режиме. Это-же касается и битов S-W-A, т.к. в реальном режиме нет никакой защиты и подкачки страниц [P]. Однако правила этикета лучше соблюдать и оформлять дескрипторы согласно документации.



Поиск и сканирование ACPI-таблицы

Расширенный "интерфейс конфигурации и управления питанием ACPI" существует в системе не только как Power-Managament. Это корневой перечислитель всех устройств на мат.плате, о чём собственно и свидетельствует словосочетание "интерфейс конфигурации.." в его названии. Модуль ACPITLB.bin в прошивке биос содержит таблицу ACPI, которую он сбрасывает в память и заполняет при включении машины. В этой таблице находятся паспорта всех имеющихся на борту девайсов, в числе которых и герой этой статьи – таймер HPET.

Поиск ACPI-таблицы осуществляется по сигнатуре "RSD PTR" в 128-Кбайтном диапазоне адресов реального режима от E0000 до FFFFF. Если он даст результат, мы упрёмся в структуру под названием Root-System-Description-Pointer RSDP. На своей машине я обнаружил её по сегментному адресу F000:A4F0 таким кодом:


C-подобный:
         mov    ax,0xE000        ;// стартовый сегмент для поиска
         xor    di,di            ;//  ..смещение в нём нуль.
@findAcpiTable:                  ;//
         mov    es,ax            ;// AX в сегментный регистр
         cmp    dword[es:di],'RSD '  ;// сравнить поле с сигнатурой!
         je     @found           ;// если совпало..
         add    di,16            ;// иначе: переходим к сл.параграфу памяти
         or     di,di            ;// весь 64К-сегмент проверили?
         jnz    @findAcpiTable   ;// продолжить, если нет..
         xor    di,di            ;// иначе: сбрасываем смещение
         add    ax,0x1000        ;// переходим к сл.сегменту 0хF000.
         or     ax,ax            ;// уже проверили его?
         jnz    @findAcpiTable   ;// нет - повторить..
         call   ERROR            ;// иначе: прокол!
@found:    ;//  в ES:DI лежит адрес структуры RSDP

По смещению 10h от начала этой структуры, будет лежать линейная база ACPI-таблицы в памяти – как видим адрес её 0х7F7B0000 (у вас может быть другим), а это далеко за пределами жалкого метра реальной моды. Именно поэтому и нужно было модифицируя дескриптор FS открыть всё адресное пространство, чтобы получить доступ к этому адресу:

rsd_ptr.png


интерфейса ACPI заточена под 64-битные системы, так-что форматы таблиц у них отличаются. В частности разрядность базовых адресов уже 8-байт вместо 4-х, и соответственно все оффсеты дружненько съезжают с насиженных мест. Поэтому перед разбором таблиц обязательно нужно проверять версию ACPI, которая лежит в поле по смещению[15] от начала RSDP (см.рис.выше), ..иначе рискуем вместо реальных данных получить винегрет.

На этапе тестирования своего кода, можно позвать на помощь
. Этот монстр прямо из прикладного уровня Win сдампит на экран любой/закрытый регион системной памяти, от нуля и до самого чердака 0xFFFFFFFF – рекомендую!


Структура таймера HPET

Будем считать, что на этом этапе имеем линейную базу ACPI-таблицы в памяти – теперь в сценарии появляется новый сюжетный поворот.. Продвигаясь поиском вглубь этой базы, мы будем натыкаться на информационные блоки различных устройств. HPET тоже в их числе и наряду со-всеми удостоился чести быть прописанным в этой "коммуналке" (обнаружить его можно по одноимённой сигнатуре). Вот в каком виде поймала структуру "HPET" утилита RW:

hpet_RW.png


Детальное описание всех полей этой структуры можно найти только в
, ..лично я её больше нигде не встречал. Помимо прочего, в ней прошита база регистров контролёра таймера, которая лежит по смещению 0х2С и в данном случае равна 0xFED00000. Важно иметь в виду, что на материнской плате могут быть установлены несколько устройств HPET, тогда для каждого из них биос создаст свою структуру. Наиболее значимые её поля представлены на рисунке ниже:

hpetStruct.png


Таким образом, чтобы через густые джунгли системных таблиц пробраться к регистрам таймера Hpet, нужно пройтись по цепочке указателей начиная с реального режима, и заканчивая верхними адресами расширенной памяти 0xFED00000. Это "увлекательное" путешествие закончится тем, что мы получим доступ к следующим регистрам таймера, список которых так-же можно найти только в спеках на HPET:

hpet_reg.png


Здесь я специально упомянул регистры чипсета по адресу RCBA+3404h. Дело в том, что это поле вызывает сомнения и всецело доверять ему не нужно. Самый универсальный вариант поиска базы регистров Hpet – это парсинг таблицы ACPI. Например у меня поле 3404h было приговорено к расстрелу без объяснения причин и в нём лежит зеро, хотя в ACPI-таблице устройство таймера присутствует – имейте это в виду.

Перед практикой, кратко ознакомимся с назначением регистров Hpet.
Как видно из скрина выше, под них выделяется 400h-байт памяти, или ровно 1-Кбайт в привычном нам виде. Каждый из 32-х таймеров одного устройства Hpet наделён тремя 8-байтными регистрами (выделены цветом), которыми можно задавать опции компараторам (тайм-аут, периодичность, прерывания). Четыре первых регистра считаются глобальными и больше относятся к самому устройству Hpet, нежели к отдельным его таймерам. Тема эта достаточно внятно расписывается в спеке на Hpet, поэтому не буду её здесь дублировать.



Практическая часть

Чем созирцать всё это со-стороны, лучше напишем демонстрационным пример того, как можно реализовать чтение/запись регистров Hpet на практике. На чём здесь нужно заострить внимание, так это на преодоление 1-Мбайтного барьера в реальном режиме. Значит подготавливаем таблицу дескрипторов для сегментного регистра FS, а остальные регистры типа CS/DS не трогаем – в скрытой их части уже лежит соответствующий дескриптор с дефолтным лимитом RM =64К.

При переходе в защищённый режим для записи в FS нового значения, обязательно нужно запрещать все прерывания, включая немаскируемые. Если этого не сделать, то при первом-же IRQ (например от часов RTC или таймера PIT) система моментально уйдёт в ребут даже не успев указать на причину. Снять обычные прерывания можно инструкцией CLI (clear int), а для немаскируемых придётся лезть в порт 70h и взводить в нём старший бит[7]. Вот пример, где конструкции if 0 / end if позволяют комментировать целые блоки кода
(на этапе тестирования):

C-подобный:
org 100h
jmp start

caption    db  13,10,' HPET info v.01 '
           db  13,10,' ====================================='
           db  13,10,' Set 4Gb unreal-mode.............: OK!'
           db  13,10
           db  13,10,' Old Global-Desc-Table...........: Limit = 0x$'
newMess    db  13,10,' New Global-Desc-Table...........: Limit = 0x$'
findAcpi   db  13,10
           db  13,10,' Find ACPI-table'
           db  13,10,'    RSDP (pointer)...............: $'
rsdt       db  13,10,'    RSDT (table linear address)..: 0x$'
revision   db  13,10,'    ACPI revision................: $'
findHpet   db  13,10
           db  13,10,' Find HPET struct in ACPI-table..: $'
hptNum     db  13,10,'    Device 0-31..................: $'
hptVen     db  13,10,'    Vendor ID....................: 0x$'
countSize  db  13,10,'    Main counter size (bit)......: $'
hptCount   db  13,10,'    Total comparators............: $'
hptMinVal  db  13,10,'    Comparators min.value........: $'
regBar     db  13,10,'    HPET registers base addr.....: 0x$'
registers  db  13,10
           db  13,10,' **** HPET registers value ****'
           db  13,10,' Name *General Capabilities*'
           db  13,10,'    Timer resolution.............: $'
legacyRt   db  13,10,'    Kill legacy PIT/RTC..........: $'

ok         db  'Found! $'
mError     db  'ERROR! $'
space      db  '. Base = 0x$'
nsec       db  ' ns $'

rev        db   0       ;// версия ACPI-интерфейса
offs       dw   0       ;// указатель на структуру RSDP
acpiBase   dd   0       ;// база acpi-таблицы
hpetBase   dd   0       ;// база регистров Hpet
oldGdt     dq   0       ;// под текущую GDT

align      16           ;// выравнивание на 16-байт границу
descTable  dq   0       ;// нулевой декскриптор в новой таблице GDT
           dq   0x00cf93000000ffff  ;// 4Gb-дескриптор для регистра FS

newGdt     dw   $ - descTable   ;// размер новой таблицы
gdtBase    dd   0               ;// будет указателем на неё.
;//******************

start:   mov    ax,3            ;// ставим в/режим 80х25
         int    0x10            ;//
         sgdt   fword[oldGdt]   ;// считать текущий GDTR

;//*****(1) Вычисляем линейную базу новой таблицы GDT *****
         xor    eax,eax         ;//
         mov    ax,ds           ;// её сегмент
         shl    eax,4           ;// сдвинуть на 4-бита влево
         add    ax,descTable    ;// добавить адрес начала
         mov    [gdtBase],eax   ;// вписать в переменную
;//if 0
         lgdt   fword[newGdt]   ;// обновить регистр GDTR!

;//*****(2) Переход в защищённый режим ********************
;//===== и обновляем дескриптор регистра FS =4Gb ==========
         cli                  ;// запретить все М-прерывания
         in     al,0x70       ;//  ..включая NMI-прерывания
         or     al,10000000b  ;//    ..бит[7] =1
         out    0x70,al       ;//      ..

         mov    eax,cr0       ;// управляющий регистр
         or     al,1          ;// взвести мл.бит РЕ
         mov    cr0,eax       ;// теперь процессор в P-Mode!
         jmp    $+2           ;// очистить конвейер CPU
         mov    ax,8          ;// смещение дескриптора в GDT
         mov    fs,ax         ;// записать его в регистр FS
         mov    gs,ax         ;//   ..(можно и в GF для пары)

         mov    eax,cr0       ;// управляющий регистр
         and    al,not 1      ;// сбросить в нём бит[1]
         mov    cr0,eax       ;// процессор вернулся в R-Mode!
         jmp    $+2           ;// сбросить все инструкции с конвейера
         xor    ax,ax         ;// записать любое значение в FS,
         mov    fs,ax         ;//  ..чтобы изменения вступили в силу.

         in     al,0x70       ;// снять запрет с прерываний
         and    al,not 10000000b
         out    0x70,al       ;//
         sti                  ;// Set Interrupt.
;//end if
         mov    dx,caption    ;// выводим шапку программы,
         call   Message       ;//   ..Un-Real mode OK!!!

;//*****(3) Различная информация для юзера **************
;//======== Old Global Desc-Table =======================
         mov    ax,word[oldGdt]  ;// старое значение регистра GDTR
         mov    ecx,2            ;// размер в байтах для вывода на консоль
         call   PrintHex         ;// выводим лимит старой таблицы GDT
         mov    dx,space         ;//  ..(разделитель)
         call   Message          ;//
         mov    eax,dword[oldGdt+2]  ;// адрес старой таблицы
         mov    ecx,4            ;//  размер = dword
         call   PrintHex         ;// Print EAX

;//==== New Global Desc-Table ====
         mov    dx,newMess       ;// обновлённые данные
         call   Message          ;//
         mov    ax,[newGdt]      ;// лимит
         mov    ecx,2            ;//
         call   PrintHex         ;//
         mov    dx,space         ;//
         call   Message          ;//
         mov    eax,[gdtBase]    ;// база
         mov    ecx,4            ;//
         call   PrintHex         ;//

;//*****(4) Find ACPI-table ****************************
;//===== Ищем сигнатуру "RSD" в диапазоне E0000:FFFFF ==
         mov    dx,findAcpi      ;//
         call   Message          ;//

         mov    ax,0xE000        ;// сегмент для поиска
         xor    di,di            ;//  ..смещение в нём нуль.
@findAcpiTable:                  ;//
         mov    es,ax            ;// AX в сегментный регистр
         cmp    dword[es:di],'RSD '  ;// сравнить поле с сигнатурой!
         je     @found           ;// если совпало..
         add    di,16            ;// иначе: переходим к сл.параграфу
         or     di,di            ;// весь 64К-сегмент проверили?
         jnz    @findAcpiTable   ;// повторить, если нет..
         xor    di,di            ;// иначе: сбрасываем смещение
         add    ax,0x1000        ;// переходим к сл.сегменту 0хF000.
         or     ax,ax            ;// уже проверили его?
         jnz    @findAcpiTable   ;// нет - повторить..
         call   ERROR            ;// иначе!

;//*****(5) Нашли структуру "RSDP" !!! ****************
;//===== выводим мессагу "RSDP (pointer)" =============
@found:  mov    [offs],di        ;// запомнить смещение
         push   di                   ;//...^^^
         mov    bl,byte[es:di+15]    ;// версия ACPI
         mov    [rev],bl         ;// запомнить
         mov    ecx,2            ;// в АХ лежит сегмент RSDP
         call   PrintHex         ;// вывести его на консоль
         mov    al,':'           ;//  ..(разделитель)
         int    29h              ;//
         pop    ax               ;// АХ = смещение
         mov    ecx,2            ;//
         call   PrintHex         ;//

;//===== выводим мессагу "RSDT (базу ACPI-таблицы)" ===
         mov    dx,rsdt          ;//
         call   Message          ;//
         mov    di,[offs]        ;// офсет
         add    di,16            ;// сместится к полю 10h
         mov    eax,dword[es:di] ;// взять его значение
         mov    [acpiBase],eax   ;// запомнить линейную базу!!!
         mov    ecx,4            ;//
         call   PrintHex         ;// вывести её на консоль.

         mov    dx,revision      ;// версия ACPI
         call   Message          ;//
         mov    al,[rev]         ;//
         add    al,'0'           ;//
         int    29h              ;//

;//*****(6) Поиск сигнатуры "HPET" в ACPI-таблице *****
;//===== адрес её линейный, поэтому через регистр FS ==
         mov    dx,findHpet      ;//
         call   Message          ;//
;//if 0
         mov    ecx,(256*1024)/16  ;// область поиска = 256 Kb в параграфах
         mov    esi,[acpiBase]     ;// адрес базы ACPI
         and    si,0xfff0          ;// делаем его кратным 16
@findHpetSidnature:
         cmp    dword[fs:esi],'HPET' ;// первый пошёл!
         je     @f                   ;// выйти, если нашли
         add    esi,16               ;// иначе: прыг на сл.параграф
         loop   @findHpetSidnature   ;// промотать ECX-раз..
         call   ERROR                ;// облом :((((
;//end if
;//*****(7) Нашли!!! Парсим структуру HPET ***************
;//===== выводить будем поля 24,2C,34,35 (см.рис.выше) ===
@@:      push   esi esi esi esi  ;// запомнить линейную базу Hpet
         mov    dx,ok            ;//
         call   Message          ;//

         mov    dx,hptNum        ;//
         call   Message          ;//
         pop    esi              ;//
         add    esi,34h          ;// номер устройства Hpet
         movzx  eax,byte[fs:esi] ;// читаем через FS
         call   Hex2Asc          ;//

         mov    dx,hptVen        ;//
         call   Message          ;//
         pop    esi              ;//
         add    esi,24h          ;// Vendor ID
         mov    eax,[fs:esi]     ;//
         mov    ebp,eax          ;//
         shr    eax,16           ;//
         mov    ecx,2            ;//
         call   PrintHex         ;//

         mov    dx,countSize     ;//
         call   Message          ;//
         mov    eax,64           ;//
         bt     ebp,13           ;// разрядность счётчика
         jc     @f               ;//
         shr    eax,1            ;//
@@:      call   Hex2Asc          ;//

         mov    dx,hptCount      ;//
         call   Message          ;//
         mov    eax,ebp          ;// кол-во компараторов
         shr    eax,8            ;//
         and    eax,11111b       ;//
         inc    al               ;//
         call   Hex2Asc          ;//

         mov    dx,hptMinVal     ;//
         call   Message          ;//
         pop    esi              ;//
         add    esi,35h          ;// мин.значение тайм-аута
         movzx  eax,word[fs:esi] ;//
         call   Hex2Asc          ;//

         mov    dx,regBar        ;//
         call   Message          ;//
         pop    esi              ;//
         add    esi,2Ch          ;// база регистров Hpet
         mov    eax,[fs:esi]     ;//
         mov    [hpetBase],eax   ;// запомнить её для чтения!!!
         mov    ecx,4            ;//
         call   PrintHex         ;//
;//if 0
;//*****(8) Читаем регистры HPET *************************
;//=======================================================
         mov    dx,registers     ;//
         call   Message          ;//
         mov    esi,[hpetBase]   ;//
         add    esi,4            ;// разрешение таймера
         mov    eax,[fs:esi]     ;//  ..(оно указывается,
         mov    ebx,1000000      ;//    ..в наносек.блоках).
         xor    edx,edx          ;//
         div    ebx              ;// ЕАХ = длительность тика Hpet!
         push   edx              ;//
         call   Hex2Asc          ;//
         mov    al,'.'           ;//
         int    29h              ;//
         pop    eax              ;// прицепить тысячные
         call   Hex2Asc          ;//
         mov    dx,nsec          ;//
         call   Message          ;//

         mov    dx,legacyRt      ;// проверить на замену PIT/RTC
         call   Message          ;//
         mov    esi,[hpetBase]   ;//
         mov    ebx,[fs:esi]     ;//
         mov    al,'1'           ;//
         bt     bx,15            ;//
         jc     @f               ;// OK! если бит[15] =1
         dec    al               ;//
@@:      int    29h              ;//
;//end if

@exit:   xor    ax,ax     ;// ждём клаву
         int    16h       ;//
         int    20h       ;// GAME-OVER!!!

;//*************************************************
;//***** Р А З Л И Ч Н Ы Е  П Р О Ц Е Д У Р Ы ******
;//*************************************************
Message: mov    ah,9      ;//
         int    21h       ;//
retn
;//-----------
PrintHex:                 ;// Вывод ЕАХ в 16-тиричном
         cmp    ecx,4     ;// Аргумент: ЕСХ = разрядность в байтах
         je     @f        ;//
         shl    eax,16    ;//
@@:      shl    ecx,1     ;//
         xchg   eax,ebx   ;//
@@:      xor    al,al     ;//
         shld   eax,ebx,4 ;//
         add    al,'0'    ;//
         cmp    al,'9'    ;//
         jbe    @miss     ;//
         add    al,7      ;//
@miss:   int    29h       ;//
         shl    ebx,4     ;//
         loop   @b        ;//
retn
;//-----------
ERROR:   pop    ax        ;//
         mov    dx,mError ;//
         call   Message   ;//
         jmp    @exit     ;//
;//-----------            ;//
Hex2Asc: mov    ebx,10    ;//
         xor    ecx,ecx   ;//
isDiv:   xor    edx,edx   ;//
         div    ebx       ;//
         push   edx       ;//
         inc    ecx       ;//
         or     eax,eax   ;//
         jnz    isDiv     ;//
isOut:   pop    eax       ;//
         add    al,30h    ;//
         int    29h       ;//
         loop   isOut     ;//
retn

soft_result.png


Обратите внимание на минимальное значение тайм-аута =14318 – оно привязано к рабочей частоте Hpet = 14.318180 MHz. К сожалению все регистры не влезли в досовское окно, поэтому я вывел только 2 значения из них – это разрешение таймера ~70 ns, и факт отправки к праотцам устаревших PIT и RTC (их функции взял на себя сам Hpet).

Программировать в этом ключе довольно интересно. Мы становимся очевидцами многих системных событий, о которых можно только мечтать на прикладном уровне Win. Здесь нет надзирателей, зато есть неограниченные возможности знать о которых должен любой низкоуровневый программист. Жалко, что система всё больше абстрагирует нас от реального железа, предлагая в замен непонятно что.

В следующей части мы рассмотрим внутренние счётчики центрального процессора LAPIC и TSC. Не нужно путать их с таймерами, поскольку счётчик не является источником прерываний IRQ. Операционная система выстраивает на их основе программные таймеры, например для функций Win32-API типа Sleep() и прочие. До встречи..
 
Последнее редактирование:
Мы в соцсетях:

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