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

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

    Скидки до 10%

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

Статья Системные таймеры, Часть[4] – Local APIC

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

1. Общие сведения. Legacy-таймеры PIT и RTC.
2. Шина PCI – таймер менеджера питания ACPI.
3. ACPI таблицы – таймер HPET.
4. Таймер контролёра Local-APIC.
5. Счётчик процессора TSC.
6. Win - профилирование кода.
---------------------------------------

Основным недостатком рассмотренных ранее таймеров является время доступа к ним, т.к. все эти устройства расположены за пределами центрального процессора CPU. Чтобы дотянуться до их контроллёров, процессор вынужден ждать освобождения шины FSB/DMI, посредством которой он тесно связан с чипсетом. Это тормозит процесс и несколько снижает ценность таймеров. Но есть и другая проблема..

Поскольку шина в системе одна, то с приходом многоядерных CPU тучи сгустились. Движимые инстинктом выживания, все ядра хором набрасывались на общую шину и под натиском грубой силы она проседала – запросы выстраивались в длинную очередь и шина фактически становилась узким местом в архитектуре. Здесь стало очевидно, что гендерные нормы уже устарели и не могут использоваться в наши дни. Так появился снискавший себе заслуженную славу локальный (по отношению к процессору) контролёр прерываний "Local APIC" – рассмотрим его работу подробней..



APIC – общие сведения

Если процессор не вещь-в-себе, он должен выстраивать систему социальных взаимоотношений с устройствами. Исторически, для этих целей были предусмотрены линии IRQ – Interrupt Request – запрос на прерывание. Любое (способное вести диалог) устройство обязано иметь эту линию в своей конструкции: например клавиатура посылает по ней сигнал IRQ при каждом нажатии на клавишу, диск – по готовности данных, лан – по приходу пакета, таймер – по истечении времени, и т.д. Таким образом получаем N-ное количество сигнальных линий, число которых равно числу активных устройств на материнской плате.

Теперь эта шина-IRQ заходит в системный контролёр прерываний PIC – Peripheral Interrupt Controller – древний девайс эпохи неолита. Он мог обслуживать всего 8 устройств, т.к. имел именно 8 входных линий, и 1 выход INTR к процессору. Но на плате источников прерываний IRQ могло быть больше, поэтому в архитектуре было два PIC’a, которые соединялись каскадно – выход INTR первого заходил на вход второго, итого получали уже 15-входов. На момент появления этих контролёров, ни о каких мульти/процессорных системах SМР никто даже не слышал, так-что PIC изначально был заточен на взаимодействие с одним процессором и со-временем от него пришлось избавиться.

Основным преимуществом усовершенствованного
стала возможность посылать по выходной линии INTR запросы конкретным ядрам одного процессора, т.е. получили маршрутизацию прерываний. Но для этого потребовалось наличия ещё одного устройства Local-APIC, которым снабдили каждое ядро. Так в типичной конфигурации МР-систем два этих контролёра работают только в паре, и не могут в полной мере выразить себя друг-без-друга. В исключительных ситуациях (когда у процессора нет своего LAPIC) внешний IOAPIC работает в режиме эмуляции устаревшего PIC, ..в остальных-же случаях режим назвали "симметричным", как на рисунке ниже:

apic_00.png


• Во-первых, у внешнего контролёра IOAPIC общее число входов IRQ увеличилось с 15 до 240, хотя на практике используются гораздо меньше (кол-во активных на данный момент линий можно прочитать из регистра-версии IOAPIC, см.код ниже).
• Во-вторых, локальные контролёры LAPIC способны сами решать проблемы своего ядра посредством 5-ти внутренних прерываний, в числе которых: обработка ошибок, контроль температуры, монитор производительности, и прерывания от работающего на частоте системной шины – локального таймера. Для обслуживания этих прерываний LAPIC имеет собственную таблицу LVT – Local Vector Table. Ещё одним плюсом стал отложенный вызов процедур обработчиков-прерываний DPC - .
• В третьих, под свойства каждого из 240-входов IOAPIC, выделяется индивидуальный 64-битный регистр под названием “IOAPIC.REDIRECTION.TABLE”. В старших его битах [63:54] хранится адрес получателя данного прерывания, которыми могут быть или физический процессор, или-же одно из 256 его ядер с идентификаторами LAPIC-ID. Вот как отображает эту инфу софт “PCIScope”:

ioApicPCI.png


Рассмотрим основные поля представленной таблицы..
Первый столбец "INT-IN" – это входные пины в IOAPIС. Во-втором столбце лежит "вектор прерывания" – это порядковый номер дескриптора в системной таблице IDT (именно в дескрипторах хранятся указатели на процедуры обработки прерываний). Мы можем маскировать векторы, в результате чего процессор не будет реагировать на них. Последний "Destination" определяет получателя, которыми могут быть как процессор, так и логические его ядра, адресуемые по их LAPIC-ID. Третий столбец "Delivery Mode" – это режим доставки прерываний из IOAPIC в LAPIC. Всего имеются 6-вариантов, для которых выделяются 3-бита:


Delivery-Mode.png


Вся эта информация хранится в регистрах IOAPIC.REDIRECTION.TABLE контролёра. Сам контролёр отображается на системную память по адресу 0xFEC00000, где и находятся 4 привязанных к нему порта. Первый порт называется "INDEX" – через него осуществляется доступ ко всем\остальным регистрам. Второй порт "DATA" позволяет считывать значение регистра по указанному индексу. Например записав нуль в порт-индекса 0xFEC00000, мы сможем прочитать из порта-данных 0xFEC00010 идентификатор контролёра IOAPIC и т.д.

ioApic.png


Здесь видно, что в нижней таблице первые три регистра с индексами 0..2 являются информационными и доступны только для чтения Read-Only. Зато дальше, (начиная с индекса 0х10 и вплоть до 0x1ЕE) выстроились в ряд упомянутые выше 64-битные регистры IOAPIC.REDIRECTION.TABLE. Под каждую из 240-линий IRQ выделяется индивидуальный регистр, где и хранятся исчерпывающие свойства данного прерывания (см.назначение бит в таблице).

Вот демонстрационный пример по сбору данных, которые показал нам софт "PCIScope" на втором рисунке. Здесь, начиная с индекса 0х10 и шагом 2 я обхожу все регистры-перенаправлений, попутно сбрасывая на консоль основные их поля и биты. Поскольку Win не даёт нам доступ к кернел-пространству с адресом 0xFEC00000, то прожка для реального режима с переходом в нереальный:


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

caption     db  13,10,' IO-APIC info v.01 '
            db  13,10,' ==============================='
            db  13,10,' Set 4Gb unreal-mode.......: OK!'
            db  13,10
            db  13,10,' Read IOAPIC registers'
            db  13,10,'   IOAPIC-ID...............: $'
ioVersion   db  13,10,'   IOAPIC Version..........: $'
ioTotalPin  db  13,10,'   IOAPIC Total IRQ pin....: 0..$'
apicRedTbl  db  13,10
            db  13,10,' Interrupt Redirection'
            db  13,10,'   -----+--------+------+----------+----------'
            db  13,10,'    IRQ | Vector | Mask |  Mode    | Dest'
            db  13,10,'   -----+--------+------+----------+---------- $'
intIrq      db  13,10,'    0x$'
intVect     db        '    0x$'
intMask     db        '     $'
intMode     db        '     $'
intDest     db        '    $'

modeTable   dw  0,fixed, 1,lowest, 2,smi, 4,nmi, 5,init, 7,ext
fixed       db  'Fixed  $'
lowest      db  'Lowest $'
smi         db  'SMI    $'
nmi         db  'NMI    $'
init        db  'INIT   $'
ext         db  'ExtINT $'
unknown     db  'Unknown$'

phy         db  'Processor $'
logical     db  'LAPIC ID $'
irq         db  -1

apicIndex   dd   0xfec00000     ;// порт-индекса IOAPIC
apicData    dd   0xfec00010     ;// порт-данных IOAPIC
counter     db   16             ;// счётчик регистров IOAPIC.REDIRECTION.TABLE (у меня всего 16)
index       db   0x10           ;// индекс первого IOAPIC.RED.TABLE

;// таблица дескрипторов для нереального режима
align       16
descTable   dq   0                   ;// нулевой декскриптор в GDT
            dq   0x00cf93000000ffff  ;// 4Gb-дескриптор для регистра FS
newGdt      dw   $ - descTable       ;// размер новой таблицы
gdtBase     dd   0                   ;// будет указателем на неё.
;//******************
;//******************

start:   mov    ax,3            ;// очищаем консоль
         int    0x10            ;//

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

;//*****(2) Переход в защищённый режим ********************
         cli                  ;// запретить все прерывания
         in     al,0x70       ;//  ..включая NMI
         or     al,10000000b  ;//
         out    0x70,al       ;//
         mov    eax,cr0       ;//
         or     al,1          ;//
         mov    cr0,eax       ;// теперь процессор в P-Mode!
         jmp    $+2           ;//
         mov    ax,8          ;// смещение дескриптора в GDT
         mov    fs,ax         ;// записать его в регистр FS
         mov    eax,cr0       ;//
         and    al,not 1      ;//
         mov    cr0,eax       ;// процессор вернулся в R-Mode!
         jmp    $+2           ;//
         xor    ax,ax         ;//
         mov    fs,ax         ;//
         in     al,0x70       ;// снять запрет с прерываний
         and    al,not 10000000b
         out    0x70,al       ;//
         sti                  ;//

;//*****(3) Теперь у нас есть доступ к 4Gb в RM *********
;//***** Читаем регистр "IOAPIC-ID" контролёра **********
         mov    dx,caption          ;//
         call   Message             ;// Un-Real mode OK!!!
         mov    edi,[apicIndex]     ;// EDI = порт-индекса для записи
         mov    esi,[apicData]      ;// ESI = порт-данных для чтения
         mov    byte[fs:edi],0   ;//<-- индекс нуль = регистр "IOAPIC-ID"
         mov    eax,[fs:esi]        ;// считать его в EAX
         shr    eax,24              ;// оставить биты [31:24]
         mov    ebx,10              ;// вывод в 10-тичном
         call   Hex2Asc             ;// показать "IOAPIC-ID"!

;//***** Регистр "IOAPIC-Ver" ******************************************
;// в нём лежит версия и число 64-битных записей, начиная с индекса 0х10
         mov    dx,ioVersion        ;//
         call   Message             ;//
         mov    edi,[apicIndex]     ;//
         mov    esi,[apicData]      ;//
         mov    byte[fs:edi],1   ;//<-- индекс 1
         mov    eax,[fs:esi]        ;// считать регистр "IOAPIC-Ver" в EAX
         push   eax                 ;// запомнить..
         and    eax,0xff            ;// оставить только мл.байт [7:0]
         mov    ebx,16              ;// выводить будем в HEX
         call   Hex2Asc             ;// Показать версию IOAPIC

         mov    dx,ioTotalPin       ;// Кол-во входных пинов
         call   Message             ;//  ..лежат в битах [24:16]
         pop    eax                 ;//
         shr    eax,16              ;// вправо на 16
         and    eax,0xff            ;// оставить только мл.байт
         mov    ebx,10              ;// вывод в 10-тичном
         call   Hex2Asc             ;//

;//*****(4) Циклический обход всех регистров "IOAPIC.REDIRECTION.TABLE"
;// это макс.240 64-битных регистров начиная с индекса 0х10 ***********
         mov    dx,apicRedTbl       ;//
         call   Message             ;//
         mov    edi,[apicIndex]     ;//
         mov    esi,[apicData]      ;//

@cycle:  inc    [irq]               ;// ..следующий IRQ
         mov    al,[index]          ;// взять в AL индекс из переменной
         mov    byte[fs:edi],al     ;// записать его в регистр-индекса IOAPIC
         mov    eax,[fs:esi]        ;// считать очередной регистр в EAX
         cmp    al,-1               ;// в его битах [7:0] лежит вектор
         je     @fuck               ;//   ..пропустить, если это 0хFF
         push   eax eax eax eax     ;// запомнить для вывода данных на консоль!
         mov    dx,intIrq           ;// выводим номер пина IRQ
         call   Message             ;//
         movzx  eax,[irq]           ;//  ..(он лежит в переменной)
         mov    ebx,16              ;// вывод в HEX
         call   Hex2Asc             ;//
;// Вектор по номеру IRQ
         mov    dx,intVect      ;//
         call   Message         ;//
         pop    eax             ;//
         and    eax,0xff        ;// он лежит в младшем байте [7:0]
         mov    ebx,16          ;//
         call   Hex2Asc         ;//
;// Маска прерывания
         mov    dx,intMask      ;//
         call   Message         ;//
         pop    eax             ;//
         bt     eax,16          ;// лежит в бите[16]
         call   PrintBool       ;// вывести её в лог.виде 0/1
;// Режим доставки может иметь 6-вариантов
         mov    dx,intMode      ;//
         call   Message         ;//
         pop    ebx             ;// ..лежит в битах [10:8]
         shr    ebx,8           ;// сдвинуть вправо на 8-бит
         and    ebx,111b        ;// оставить только 3 мл.бита
         push   esi             ;//
         mov    esi,modeTable   ;// ESI указывает на таблицу
         mov    ecx,6           ;//   ..всего элементов (режимов) в ней
@@:      lodsw                  ;// АХ = очередной режим из таблицы
         cmp    al,bl           ;// сравнить со-считанным из регистра значением
         je     @found          ;// если нашли..
         add    esi,2           ;// иначе: сл.элемент в таблице
         loop   @b              ;// промотать цикл(@@) ECX-раз..

         mov    dx,unknown      ;// прокол! нет совпадений
         call   Message         ;// выводим мессагу
         jmp    @f              ;//   ..и на нижнюю метку(@@).

@found:  mov    dx,[si]         ;// Нашли - взять адрес мессаги.
         call   Message         ;// на консоль её.
@@:      pop    esi             ;//
;// Получатель прерывания CPU или LAPIC
         mov    dx,intDest      ;//
         call   Message         ;//
         pop    eax             ;//
         bt     eax,11          ;// зарыт в бите [11]
         mov    dx,phy          ;//
         jc     @f              ;//
         mov    dx,logical      ;//
@@:      call   Message         ;//

;// Сделали очередной круг..
;// переходим к сл.регистру "IOAPIC.REDIRECTION.TABLE"
@fuck:   add    [index],2       ;// увеличить индекс на 2
         dec    [counter]       ;// уменьшить счётчик кол-ва регистров
         jnz    @cycle          ;// Повторить цикл!

@exit:   xor    ax,ax           ;// ждём клаву..
         int    16h             ;//
         int    20h             ;// на выход!

;//***** П Р О Ц Е Д У Р Ы *************************
Message: mov    ah,9
         int    21h
retn
;//---------
PrintBool:
         mov    al,'1'
         jc     @f
         dec    al
@@:      int    29h
retn
;//---------
Hex2Asc:
         xor    ecx,ecx
isDiv:   xor    edx,edx
         div    ebx
         push   edx
         inc    ecx
         or     eax,eax
         jnz    isDiv
isOut:   pop    eax
         cmp    al,9
         jle    noHex
         add    al,7
noHex:   add    al,30h
         int    29h
         loop   isOut
retn

ioApicInfo.png


Обратите внимание, что в данном случае векторы всех прерываний замаскированы логической единицей. Значит при включении машины как биос так и новороченый EFI используют устаревший контролёр PIC с его 15- входами. Снимает маску и включает IOAPIC позже уже загрузчик операционной системы, когда переходит в защищённый режим и выстраивает свою таблицу IDT – Interrupt Descriptor Table. Кроме того на х64 видно, что контролёр имеет 120 входных пинов IRQ, в то время-как на х32 их всего 24.

Отметим, что прерывания IRQ посылают только физические устройства, поэтому они относятся к классу "аппаратных". Есть ещё и программные прерывания, например INT-2Eh (вызов системных сервисов Win), или INT-03h (брекпоинт) – они вызываются нашим кодом, поэтому к контролёру IOAPIC не имеют никакого отношения.



Шина APIC и векторы прерываний

В масштабах истории, моногамный брак APIC+LAPIC появился относительно недавно. Во-времена господства древнего контролёра PIC, взаимосвязь его с CPU осуществлялась по системной пар-шине FSB. В современной-же архитектуре, IOAPIC общается с локальным контролёром посредством специально выделенной для этих целей трёхпроводной последовательной шины 3-Ware-APIC (см.рис.1). Этот тоннель позволяет освободить системную шину DMI от проблем обслуживания прерываний.

По двум из трёх сигнальных линий 3-Ware-APIC гуляет 64-битный трафик в виде данных регистра IOAPIC.RED.TABLE, а третья линия – сигнал тактовой частоты. Протокол новой шины поддерживает механизм арбитража ядер по приоритетам 0-15, а сам приоритет динамически меняется после каждой передачи сообщений.

Получив 64-битные данные, LAPIC выделяет из них вектор прерывания, который зашит в младшем байте пакета (см.последнюю таблицу). Теперь логика умножает этот вектор на 8 (размер дескриптора в таблице IDT) и таким образом получает адрес обработчика возникшего прерывания для последующего его вывоза.

В системной таблице IDT собраны 8-байтные дескрипторы прерываний - всего их 256. На эту таблицу указывает 6-байтный регистр IDTR процессора, в младшем слове которого лежит размер таблицы, а в старшем двойном слове – указатель на неё. Отладчик WinDbg на команду !pcr (Processor Control Region) отзывается списком системных ресурсов, где среди прочих будет и указатель на IDT. Дальше можно запросить уже дамп самой таблицы командой dq address (dump qword) – вот пример:


IDT_Dbg.png


На этом рисунке, внутри зелёного блока видим дескрипторы прерываний, каждый из которых имеет свой вектор (порядковый номер в данной таблице). Например по вектору(0) лежит дескриптор со-значением 0x804d8e00'0008f350 и т.д.. Как-правило формат большинства дескрипторов совпадает, однако из общего пула здесь можно выделить два – это начиная с нуля второй (немаскируемое прерывание NMI) со-значение 0x00008500'0058113e, и дескриптор по вектору #8 (Double Fault – двойной отказ, исключение #DF). Они из другой оперы и в данном случае нам не интересны.

Процессоры х86 делят дескрипторы на три типа: для шлюзов прерываний Int, ловушек Trap и задач Task.
Мы остановимся лишь на шлюзе-прерываний, который имеет тип 0хЕ (выделен красным). В библии свидетелей интела том.3 есть битовая карта с его описанием:


IDT_Descr.png


Так из дескриптора мы вытащили указатель на функцию прерывания, и командой "Unassembler" можно запросить код его обработчика:

Код:
lkd> u 804df350
          nt!KiTrap00:
804df350  6a00            push    0
804df352  66c74424020000  mov     word ptr [esp+2],0
804df359  55              push    ebp
804df35a  53              push    ebx
804df35b  56              push    esi
804df35c  57              push    edi
804df35d  0fa0            push    fs
804df35f  bb30000000      mov     ebx,30h
804df364  8ee3            mov     fs,bx
804df366  648b1d00000000  mov     ebx,dword ptr fs:[0]
804df36d  53              push    ebx
...

WinDbg имеет спец/расширение для просмотра таблицы IDT – запрашивается оно командой !idt (просмотр списка только активных векторов), или она-же с параметром -a (вывод полного списка 256, включая зарезервированные исключения с векторами 0..1Fh):

Код:
lkd> !idt -a
      Dumping IDT:
00:    804df350  nt!KiTrap00
01:    804df4cb  nt!KiTrap01
02:    Task Selector = 0x0058
03:    804df89d  nt!KiTrap03
04:    804dfa20  nt!KiTrap04
05:    804dfb81  nt!KiTrap05
06:    804dfd02  nt!KiTrap06
07:    804e036a  nt!KiTrap07
08:    Task Selector = 0x0050
09:    804e078f  nt!KiTrap09
0a:    804e08ac  nt!KiTrap0A
0b:    804e09e9  nt!KiTrap0B
0c:    804e0c42  nt!KiTrap0C
0d:    804e0f38  nt!KiTrap0D
0e:    804e164f  nt!KiTrap0E
0f:    804e197c  nt!KiTrap0F
10:    804e1a99  nt!KiTrap10
11:    804e1bce  nt!KiTrap11
12:    Task Selector = 0x00A0
13:    804e1d34  nt!KiTrap13
14:    804e197c  nt!KiTrap0F
15:    804e197c  nt!KiTrap0F
16:    804e197c  nt!KiTrap0F
17:    804e197c  nt!KiTrap0F
18:    804e197c  nt!KiTrap0F
19:    804e197c  nt!KiTrap0F
1a:    804e197c  nt!KiTrap0F
1b:    804e197c  nt!KiTrap0F
1c:    804e197c  nt!KiTrap0F
1d:    804e197c  nt!KiTrap0F
1e:    804e197c  nt!KiTrap0F
1f:    80711fd0  hal!HalpApicSpuriousService
20:    00000000

В процессе формирования таблицы IDT нужно придерживаться определённых правил..
Во-первых, таблица должна быть заполнена полностью, т.е. состоять из всех 256 дескрипторов. Если вектор не используется, отсылаем его к заглушке IRET (Interrupt Return).

Во-вторых, первые 32-вектора процессор рассчитывает получить в своё распоряжение и мы не должны препятствовать ему в этом. Например при ошибке деления на нуль #DE, он на аппаратном уровне передаёт управление в IDT по вектору(0), где и должен лежать указатель на соответствующий обработчик. Поэтому векторы в диапазоне 00..1Fh процессор забирает под всякого рода свои исключения, а пользовательские – должны начинаться с 20h и до FFh
(см.лог выше).


Таймер Local-APIC
Частота = системная шина | счётчик = 32 бит


Реакция локального APIC на обработку аппаратных прерываний достаточно прозрачна. Если в двух словах, то внешний IOAPIC получает IRQ от устройства Х, и по цепочке пересылает в LAPIC связанный с ним 64-битный свой регистр IOAPIC.RED.TABLE. Дальше LAPIC извлекает из этих 64-бит вектор-прерывания и ставит его в очередь "DPC-Queue" до лучших времён. Позже, векторы вываливаются из очереди и по ним управление передаётся в системную таблицу IDT, от куда и происходит вызов соответствующего обработчика.

Однако ядро процессора имеет и свои/внутренние устройства контроля – всего их 6, в числе которых и некая пародия на таймер. Для обслуживания этих источников, в LAPIC предусмотрена таблица LVT – Local Vector Table. Это закрытая для всех зона и существует она как самостоятельный механизм. LVT состоит из шести 32-битных регистров, битовая маска которых представлена в таблице ниже:


lapicLVT.png


Априори, все отображаются на память по адресу 0xFEE00000, хотя для уверенности можно считать его базу с модельно-специфичного регистра MSR-0x1b. Как видно из этой таблицы, встроенный таймер характеризует регистр по смещению Base+0x320. В младший байт этого регистра мы должны зашить вектор-прерывания в системной таблице IDT. Бит[12] позволяет узнать, обработал-ли процессор наше прерывание, или на текущий момент поставил его в очередь DPC. Единичное значение бита[16] накладывает маску на вектор (т.е. запрещает прерывание от таймера), а бит[17] задаёт режим работы счётчика – однократный или циклический.

Кроме регистра LVT, локальный таймер имеет ещё три регистра, в которых определяются уже его свойства. Он тактируется частотой системной шины FSB/DMI, причём не эффективной (с применением множителя), а реальной частотой от клокера. Как-правило эта частота имеет начальный порог 100/133 MHz и дальше по возрастающей, в зависимости от архитектуры и модели чипсета. Чтобы угомонить разбушевавшийся таймер, можно применить к этой частоте локальный делитель, значение которого указывается в регистре Base+0x3E0 (см.таблицу ниже). Делитель может принимать значение степени двойки до 128, т.е. 1,2,4,8,16,32,64 и 128.

Теперь в регистре Base+0x380 остаётся указать начальное значение счётчика, от которого со-скорость частоты шины он будет стремиться к нулю. Если битом[17] таймер установлен на периодичность (а именно в таком режиме работают таймеры всех протестированных мною систем), то при каждом достижении счётчиком нуля, таймер будет генерить прерывание, а процессор будет передавать управление по указанному нами вектору, в таблицу IDT.

Интересным моментом тут является то, что если инициализировать счётчик макс.значением 0xFFFFFFFF, процессор проигнорирует его и принудительно выставит в нём значение частоты системной шины FSB/DMI в герцах. Соответственно если делитель таймера выставить на 1, то в регистре 0x380 получим частоту системной шины – демо/пример ниже подтверждает эту теорию.


lapicTmr.png


Программирование таймера LAPIC представляет собой проблему как в защищённом Win, так и в реальном досе – этому есть несколько причин. Ну с виндой всё понятно.. она не даёт нам доступа к памяти выше 0x7FFFFFFF (нужно писать драйвер). Поэтому придётся химичить в реальном режиме, но и тут нас поджидают неприятности..

Дело в том, что для своей работы LAPIC требует защищённый режим с таблицей дескрипторов прерываний IDT. А раз так, значит для простой задачи нужно будет писать огромное кол-во обработчиков, чтобы была возможность элементарно выводить инфу на экран, пользоваться клавиатурой, обрабатывать исключения и ещё куча всего. При желании конечно-же можно перекроить под линейные адреса готовые обработчики реального режима, но для обычного демо/примера такая овчинка никак не стоит выделки, поэтому мы поступим иначе..

Если мастдай не хочет пускать нас в своё кернел-пространство, то придётся вытаскивать его контрабандным путём, тупо сбросив 1К-байтный дамп памяти 0xFEE00000 в бинарник. Пусть мы при этом лишаемся возможности записи, зато сможем прочитать регистры и сделать хоть какие-то выводы. Раньше, прямо из юзера мы могли прочитать ядерную память открыв через CreateFile() секцию \Device\PhysicalMemory. Однако начиная с Win-2003 системные сторожа запретили этот финт, выставив ему флаг при открытии KERNEL_ONLY.

Чтобы проблема чтения регистров не превратилась в проблему доступа к памяти, мы выберем самый простой путь и воспользуемся сторонней утилитой
Двумя кликами мыши она позволит сбросить в файл любой регион системного пространства, а мы потом подключим этот файл к своей программе, и таким образом получим псевдо/регистры контролёра LAPIC. Последовательность действий представлена на рис.ниже:

lapicDump.png


После нажатия пимпы ОК мы получим бинарник с именем MFEE00000.bin – это готовый слепок памяти. Вариант удобен тем, что позволяет снять дамп с разных машин и сравнить значения их регистров LAPIC. Напомню, что база (стартовый адрес) у всех будет одинаковой. Теперь, для подключения внешних файлов, ассемблер FASM имеет директиву file, аргументом которой является только имя файла. В своей демке, я выделил под этот бинарник отдельную секцию, чтобы он не путался среди остальных данных. Вот пример:

C-подобный:
format   pe console
include 'win32ax.inc'
entry    start
;//-----------
.data
caption    db  13,10,' Local APIC info v.01 '
           db  13,10,' ==================================='
           db  13,10,' Base address...........: 0xFEE00000'
           db  13,10,'   LAPIC-ID.............: %d',0
lapicVer   db  13,10,'   LAPIC Version........: %d'
           db  13,10,'   LAPIC LVT entries....: %d',0
lapicLVT   db  13,10
           db  13,10,' LVT registers.: Vector | Mask'
           db  13,10,'   ---------------------+-----'
           db  13,10,'   Sensor......:  0x%02X  | %d',0
perfMon    db  13,10,'   PerfMon.....:  0x%02X  | %d',0
lInt0      db  13,10,'   LINT0.......:  0x%02X  | %d',0
lInt1      db  13,10,'   LINT1.......:  0x%02X  | %d',0
timer      db  13,10,'   Timer.......:  0x%02X  | %d',0
lapicTimer db  13,10
           db  13,10,' LAPIC Timer registers..'
           db  13,10,'   Timer divide.........: %d',0
tmrInit    db  13,10,'   Timer initial count..: %d',0
tmrCounter db  13,10,'   Timer current counter: %d',0
tmrFreq    db  13,10,'   Timer frequency......: %d.%d MHz',0
buff       db  0
;//===================================
section '.lapic' data readable     ;// секиця под образ регистров LAPIC
lapicBase:                         ;// метка для обращения
file    'XP_FEE00000.bin'          ;// зашиваем бинарник в свое тело!
;//===================================

.code
start:
;//*****(0) LAPIC ID Register. Смещение = 0х20 *****
        mov     eax,[lapicBase+0x20]  ;//
        shr     eax,24                ;// лежит в битах [31:24]
       cinvoke  printf,caption,eax    ;//

;//*****(1) LAPIC Ver Register. Смещение = 0х30 *****
        mov     eax,[lapicBase+0x30]  ;//
        mov     ebx,eax               ;//
        and     eax,0xff              ;// оставить только мл.байт (версия)
        shr     ebx,16                ;// биты[24:16] - кол-во элементов в LVT
        inc     bl                    ;//
       cinvoke  printf,lapicVer,eax,ebx

;//*****(2) LVT Thermal Sensor. Смещение = 0х330 *****
        mov     eax,[lapicBase+0x330]    ;//
        call    GetVectorMask            ;// вызвать процедуру (см.ниже)
       cinvoke  printf,lapicLVT,eax,edx
;//*****(3) LVT PerfMon. Смещение = 0х340 *****
        mov     eax,[lapicBase+0x340]    ;//
        call    GetVectorMask            ;//
       cinvoke  printf,perfMon,eax,edx
;//*****(4) LVT LINT0. Смещение = 0х350 *****
        mov     eax,[lapicBase+0x350]    ;//
        call    GetVectorMask            ;//
       cinvoke  printf,lInt0,eax,edx
;//*****(5) LVT LINT1. Смещение = 0х360 *****
        mov     eax,[lapicBase+0x360]    ;//
        call    GetVectorMask            ;//
       cinvoke  printf,lInt1,eax,edx
;//*****(6) LVT Timer. Смещение = 0х320 *****
        mov     eax,[lapicBase+0x320]    ;//
        call    GetVectorMask            ;//
       cinvoke  printf,timer,eax,edx

;//******************************************
;//*****(7) LAPIC Timer Registers ***********
;//******************************************
;//-- Делитель таймера
;// он лежит россыпью в битах 3,1,0
;// т.е. из 4-х мл.бит нужно вырезать второй
        mov     eax,[lapicBase+0x3e0]
        and     eax,1111b       ;// оставить мл.тетраду
        mov     ebx,eax         ;//  ..(запомнить в ЕВХ)
        shr     bl,1            ;// сдвинуть на 1 вправо
        and     bl,100b         ;// оставить только второй бит
        and     al,11b          ;// в AL оставить 2 младших
        or      bl,al           ;// в битах[2:0] BL получили код делителя
                           ;// теперь из кода нужно получить число от 1 до 128.
        mov     eax,2           ;// искать начнём с делителя 2
        mov     ecx,7           ;// всего вариантов..
@@:     cmp     bh,bl           ;// перебор возможных
        je      @f              ;// если нашли
        shl     al,1            ;// иначе: степень двойки
        inc     bh              ;// сл.вариант..
        loop    @b              ;// промотать ЕСХ-раз
        mov     eax,1           ;// код 111b соответствует делителю 1.
@@:     push    eax             ;// запомнить делитель!
       cinvoke  printf,lapicTimer,eax
;//-- Счётчик инициализации
;// как-правило он равен частоте шины FSB/делитель
        mov     eax,[lapicBase+0x380]  ;//
        push    eax                    ;// запомнить для получения частоты таймера!
       cinvoke  printf,tmrInit,eax
;//-- Текущий счётчик
        mov     eax,[lapicBase+0x390]
       cinvoke  printf,tmrCounter,eax
;//-- Частота таймера
        pop     eax ebx         ;// ЕАХ= счётчик инициализации, ЕВХ= делитель
        mul     ebx             ;// вычислить произведение!
        mov     ebx,1000000     ;// перевести герцы в MHz
        cdq                     ;//   ...
        div     ebx             ;//     ...
       cinvoke  printf,tmrFreq,eax,edx

       cinvoke  gets,buff       ;// ждать клаву..
       cinvoke  exit,0          ;// на выход!

;//----------- П Р О Ц Е Д У Р А -----------
;// на входе принимает регистр с таблицы LVT
proc  GetVectorMask             ;//
        mov     ebx,eax         ;//
        and     eax,0xff        ;// ЕАХ = вектор прерывания
        mov     edx,1           ;//
        bt      ebx,16          ;// проверить бит[16]
        jc      @f              ;// если взведён..
        dec     dl              ;// иначе: DL -1
@@:     ret                     ;// EDX = маска в лог.виде 0/1
endp
;//-----------
section '.idata' import data readable
library  msvcrt,'msvcrt.dll'
import   msvcrt, printf,'printf',exit,'exit',gets,'gets'

lapicProc.png


Этой прожке я вскармливал дампы двух/своих машин – один сделал на семёрке, другой на хрюше. Обратите внимание, что в обоих случаях, системы вообще не используют таймер LAPIC, т.к. векторы всех локальных прерываний в таблице LVT замаскированы – в активном состоянии находится только монитор производительности PerfMon. Однако таймер продолжает работать в холостую, а значит ОС всё-же обращается иногда к нему.

Делитель опорной частоты выставлен в единицу (см.жёлтый блок), зато значение инициализации счётчика разное. Запустив любой софт по сбору информации о железе можно обнаружить, что Init совпадает с частотой системной шины Bus-Speed. Тогда по логике вещей, произведение делителя и значения инициализации должно возвращать рабочую частоту таймера, хотя при других делителях эту теорию проверить мне так и не удалось.

Судя по регистрам LVT, к таймеру привязан вектор 0xFD, а к монитору 0xFE. Чтобы убедиться, так-ли это на самом деле, можно в отладчике WinDbg и запросить таблицу IDT – в самом хвосте списка должны будут лежать указатели, на обслуживающие эти прерывания функции. И точно.. один ProfileInterrupt (профилирование кода по таймеру), а другой PerormanceMonitor под кличкой PerfInterrupt:

lapicIdt.png



Заключение

В эпилоге темы хотелось-бы отметить, что у каждого ядра процессора своя память и свой контролёр LAPIC. Соответственно базовый адрес их регистров 0xFEE00000 будет одинаковым для всех ядер. На самом деле это иллюзия, т.к. мы имеем дело с виртуальной памятью, и у одного адреса фреймы физической DRAM-памяти будут разными.

Другими словами, если прочитать счётчик таймера одного ядра и сравнить его с счётчиком соседнего ядра, то с вероятность 99% значения будут разными, хотя адрес регистров совпадает. Это-же относится и к выключенному состоянию таймера – на ядре(0) он может быть замаскирован, а на ядре(1) включён.
 
Последнее редактирование:

romik losev

Member
26.05.2020
9
-1
BIT
0
Все в картинках и красках, напоминает рассказ об устройстве вселенной - жутко интересно, но совершенно бесполезно. При решении каких задач эти знания могут понадобиться?
 
  • Не нравится
Реакции: Mikl___

romik losev

Member
26.05.2020
9
-1
BIT
0

отвечу по простому - сам ты мартышка ) По сути, ставить эксперементы и копаться в ядре бессмвсленное занятие - потому что имеет только академически интерес. С практической точки зрения - это мусорная инфа так же как знание последней тоерии развития вселенной.
 
  • Не нравится
Реакции: Mikl___

Marylin

Mod.Assembler
Red Team
05.06.2019
305
1 378
BIT
318
ставить эксперементы и копаться в ядре бессмвсленное занятие - потому что имеет только академически интерес
почему-же? Система предоставляет несколько своих функций API, которые используют разные физические таймеры. Соответственно если ты в курсе дела, то будешь иметь выбор, какую из API использовать для конкретной задачи.
 
  • Нравится
Реакции: batu5ai и Mikl___

antmbx

One Level
17.05.2020
4
3
BIT
0
Польза для криптографов, а именно - кусочек теории для изучения псевдослучайных последовательностей.
А вообще - для кругозора очень полезно.
 
  • Нравится
Реакции: Mikl___

romik losev

Member
26.05.2020
9
-1
BIT
0
Польза для криптографов, а именно - кусочек теории для изучения псевдослучайных последовательностей.
А вообще - для кругозора очень полезно.

Что касается кругозора, самый важный жизненый ресурс - это время. Тратить время на всякую хрень - это большая роскош. Есть патентованые способы получения псевдослучайных чисел, на которые ты можешь сослаться в случае проблем. Интерфейсы ядра могут быть интересны только при написании драйверов, но и для этого есть специальные инструменты. Умничать на тему низкоуровневого програмирования и программировать - это две большие разницы.
 
  • Не нравится
Реакции: Mikl___

antmbx

One Level
17.05.2020
4
3
BIT
0
То, что время - это самый важный жизненный ресурс - согласен )
Вот только, Вы сами себе противоречите.... Если Вам не интересно и считаете это хренью - не тратьте время на чтение и написание километровых комментариев )) Заодно и ветка комментов засоряться не будет мусором, а люди кому интересно смогут быстро найти нужные и интересные для себя комменты.
 
  • Нравится
Реакции: g00db0y и Mikl___

Mikl___

Green Team
01.09.2019
28
47
BIT
53
самый важный жизненНый ресурс - это время. Тратить время на всякую хрень - это большая роскошЬ. . . . Умничать на тему низкоуровневого програмМирования и программировать - это две большие разницы.
Сударь, я три дня гналась за вами, чтобы сказать, как вы мне безразличны. . . (Евгений Шварц "Обыкновенное чудо")
 
  • Нравится
Реакции: Marylin

Hardreversengineer

Green Team
20.08.2019
49
28
BIT
14
Что касается кругозора, самый важный жизненый ресурс - это время. Тратить время на всякую хрень - это большая роскош. Есть патентованые способы получения псевдослучайных чисел, на которые ты можешь сослаться в случае проблем. Интерфейсы ядра могут быть интересны только при написании драйверов, но и для этого есть специальные инструменты. Умничать на тему низкоуровневого програмирования и программировать - это две большие разницы.
Меня убивает невежество современных программистов. Поколение, не знающее математику, программирующие мышкой, а высшее мастерство - скопировать код с stackoverflow (я не понимаю, почему вы вечно лезете в раздел форума по ассемблеру, он не для вас). Я рад за искусственный интеллект, наконец-то компилятор умнее среднестатистического программиста. Тебя это не смущает ? Ты не знаешь что за тебя сделает компилятор в итоге, ты не знаешь какие функции используешь и как они написаны и как устроены. Даже не знаешь, о чём говоришь. Твои слова только и описывают твоё типичное поведение: "сослаться на кого-то в случае проблем". Да и в целом, ты в лишний раз подтверждаешь мою гипотезу, о том что, слово "умничать", в своей речи, используют глупые завистливые люди.
 
  • Нравится
Реакции: Marylin, nmk890 и Mikl___
Мы в соцсетях:

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