Как реализовать порты ввода-вывода в симуляторе процессора?

Процессоры х86 имеют восемь регистров отладки

DR0-DR7

, два из которых

DR4.5

отправлены в резерв и не используются. В савокупности, отслеживая обращения к памяти они позволяют устанавливать аппаратные точки-останова на её чтение, запись, или исполнение. Более того, если в регистре управления процессором

CR4

сброшен бит[3] "Debug Extensions", то под надзор силовых структур попадают и обращения к портам ввода-вывода I/O. Взяв за основу фишку с отслеживанием физических портов, можно написать к примеру эмулятор какого-нибудь внешнего девайса, типа флопика или LPT/COM устройства.

По сути, регистры отладки DRx доступны только в реальном режиме процессора, однако имеется забытый инженерами люк, через который можно оперировать ими и в защищённом режиме, прямо под юзером. Суть в том, что при переключении процесса планировщиком, он сохраняет весь контекст активного треда в структуре "Context", куда сбрасываются в т.ч. и регистры отладки.

Функциями

Get/SetThreadContext()

мы можем сохранять и восстанавливать контекст своего (или чужого) потока в любой момент - это позволяет модифицировать отладочные регистры в сохранённом контексте. Именно так эмулирует обращения к портам I/O весь нормальный софт, а не указанными выше способами. В Win7 х64 контекст процесса выглядит как в дампе ниже, а на системах х32 немного иначе:

Context

0: kd> dt _context
nt!_CONTEXT
   +0x000 P1Home           : Uint8B
   +0x008 P2Home           : Uint8B
   +0x010 P3Home           : Uint8B
   +0x018 P4Home           : Uint8B
   +0x020 P5Home           : Uint8B
   +0x028 P6Home           : Uint8B
   +0x030 ContextFlags     : Uint4B
   +0x034 MxCsr            : Uint4B

   +0x038 SegCs            : Uint2B
   +0x03a SegDs            : Uint2B
   +0x03c SegEs            : Uint2B
   +0x03e SegFs            : Uint2B
   +0x040 SegGs            : Uint2B
   +0x042 SegSs            : Uint2B
   +0x044 EFlags           : Uint4B

   +0x048 Dr0              : Uint8B  ;<---- Регистры отладки!
   +0x050 Dr1              : Uint8B
   +0x058 Dr2              : Uint8B
   +0x060 Dr3              : Uint8B
   +0x068 Dr6              : Uint8B
   +0x070 Dr7              : Uint8B

   +0x078 Rax              : Uint8B
   +0x080 Rcx              : Uint8B
   +0x088 Rdx              : Uint8B
   +0x090 Rbx              : Uint8B
   +0x098 Rsp              : Uint8B
   +0x0a0 Rbp              : Uint8B
   +0x0a8 Rsi              : Uint8B
   +0x0b0 Rdi              : Uint8B
   +0x0b8 R8               : Uint8B
   +0x0c0 R9               : Uint8B
   +0x0c8 R10              : Uint8B
   +0x0d0 R11              : Uint8B
   +0x0d8 R12              : Uint8B
   +0x0e0 R13              : Uint8B
   +0x0e8 R14              : Uint8B
   +0x0f0 R15              : Uint8B
   +0x0f8 Rip              : Uint8B

   +0x100 FltSave          : _XSAVE_FORMAT
   +0x100 Header           : [2] _M128A
   +0x120 Legacy           : [8] _M128A

   +0x1a0 Xmm0             : _M128A
   +0x1b0 Xmm1             : _M128A
   +0x1c0 Xmm2             : _M128A
   +0x1d0 Xmm3             : _M128A
   +0x1e0 Xmm4             : _M128A
   +0x1f0 Xmm5             : _M128A
   +0x200 Xmm6             : _M128A
   +0x210 Xmm7             : _M128A
   +0x220 Xmm8             : _M128A
   +0x230 Xmm9             : _M128A
   +0x240 Xmm10            : _M128A
   +0x250 Xmm11            : _M128A
   +0x260 Xmm12            : _M128A
   +0x270 Xmm13            : _M128A
   +0x280 Xmm14            : _M128A
   +0x290 Xmm15            : _M128A

   +0x300 VectorRegister       : [26] _M128A
   +0x4a0 VectorControl        : Uint8B
   +0x4a8 DebugControl         : Uint8B
   
   +0x4b0 LastBranchToRip      : Uint8B
   +0x4b8 LastBranchFromRip    : Uint8B
   +0x4c0 LastExceptionToRip   : Uint8B
   +0x4c8 LastExceptionFromRip : Uint8B

Регистры

DR0:7

позволяют устанавливать всего 4 аппаратных точек-останова за 1-раз. При этом в

DR0:3

мы должны указать адрес бряка,

DR6

является информационным, а

DR7

играет роль конфигуратора. Рисунок ниже описывает назначения бит каждого из регистров. Чтобы перехватить обращения к портам, нужно "тип точки" выставить в 10b.