About Handling Nmi

来源:SoftRCE.net

本文基于Windows2003以上32位系统,因为XP处理NMI很弱,我们后面再说。
最近为硬件写驱动,需要处理关于NMI的一些东西,2003或者Vsita32上如果你不想弄的太不复杂,可以调用新增的 KeRegisterNmiCallback函数注册一个回调函数等待处理.但是在XP下就很困难,解决的办法有两个:hook或者update.所谓 hook,这个不难理解,hook在哪里就仁者见仁了;而update就是仿造2003下的实现方法,在XP下自己实现一些处理例程,虽然要建立一些必要 的结构,也要修改特定的描述符,显得繁杂,但是比起hook来更有成就感.另外,研究和处理中断的方法类似,大家可以自己调试编码其他的中断,会有很多启 发的. 项目的代码不好公开,那就把以前记录的一些资料和最新的体会写下来,希望对大家有用。

Nmi(Non Maskable Interrupt),一般在紧急的电源故障、总线超时或者存储器奇偶校验出错时被发出,对应的中断向量号为2,即int 2。CPU有两根引脚INTR和NMI接受外部中断请求信号:INTR接受可屏蔽中断请求,NMI接受不可屏蔽中断请求。下面是调试器的结果:

kd> !idt -a

Dumping IDT:
00: 8082409e nt!KiTrap00
01: 8082421a nt!KiTrap01
02: Task Selector = 0x0058
03: 8082462a nt!KiTrap03
04: 808247a6 nt!KiTrap04
05: 80824904 nt!KiTrap05
06: 80824a86 nt!KiTrap06
07: 808250e6 nt!KiTrap07
08: Task Selector = 0x0050
09: 80825518 nt!KiTrap09
0a: 80825632 nt!KiTrap0A
//…
ff: 808231b0 nt!KiUnexpectedInterrupt207

相关的中断可以查阅详细资料[1],我们只关心下面两个:
IDTEntry _KiTrap02, D_INT032 ; 2: NMI/NPX Error
IDTEntry _KiTrap08, D_INT032 ; 8: Double Exception
即Windbg中显示的:
02: Task Selector = 0x0058
08: Task Selector = 0x0050
表 示的是系统为处理这两个int而设置的任务门,KGDT_DF_TSS(50h)和KGDT_NMI_TSS(58h)。那么是系统什么时候设置的 呢?ntoskrnl加载的时候,KERNEL_ENTRY_POINT是KiSystemStartup()函数,反汇编不难看出它的流程,你也可以参 考WangYu的一篇文章[2],或者可以参考ReactOs的代码,不过我们简单地反汇编一些:

0) 这里涉及一些结构,用Windbg可以清晰地看到:
kd> !pcr 0
kd> dt -r1 nt!_KPCR
kd> dt nt!_KIDTENTRY (_KGDTENTRY)
kd> dt -r1 nt!_KTHREAD
kd> dt -r1 nt!_KPROCESS

typedef struct _IDTENTRY{
unsigned short   OffsetLow;    //中断执行代码偏移量的底16位
unsigned short   Selector;       //选择器,也就是寄存器
unsigned char     Reserved;    //保留位,始终为零
unsigned char     Type:4;         //中断门,陷阱门和任务门[5]
unsigned char     SegmentFlag:1;   //段标识位  1:CS\DS段描述符  0:门\系统描述符
unsigned char     DPL:2;         //权级,0:内核级,3:用户级
unsigned char     Present:1;   //呈现标志位
unsigned short    OffsetHigh;  //中断执行代码偏移量的高16位
}IDTENTRY,*PIDTENTRY;[4]

typedef struct _KGDTENTRY {
USHORT  LimitLow;
USHORT  BaseLow;
union {
struct {
UCHAR   BaseMid;
UCHAR   Flags1;
UCHAR   Flags2;
UCHAR   BaseHi;
} Bytes;
struct {
ULONG   BaseMid : 8;
ULONG   Type : 5;
ULONG   Dpl : 2;
ULONG   Pres : 1;
ULONG   LimitHi : 4;
ULONG   Sys : 1;
ULONG   Reserved_0 : 1;
ULONG   Default_Big : 1;
ULONG   Granularity : 1;
ULONG   BaseHi : 8;
} Bits;
} HighWord;
} KGDTENTRY, *PKGDTENTRY;

1) 首先是为NMI建立任务门,这样我们就能捕捉到NMI了:
INIT:006053CE mov eax, [ebp+var_10]                    ; 之前由GetMachineBootPointers得到的Idt
INIT:006053D1 lea ecx, [eax+10h]                             ; 16/8 -> 2号中断描述符
INIT:006053D4 mov byte ptr [ecx+5], 85h                 ; 10000101 -> dpl=0, present, taskgate
INIT:006053D8 mov word ptr [ecx+2], 58h                ; KGDT_NMI_TSS(58h)
INIT:006053DE lea ecx, [edi+58h]
INIT:006053E1 mov byte ptr [ecx+5], 89h                  ; 10001001 -> 32bit, dpl=0, present, TSS32, not busy
INIT:006053E5 mov edx, offset _KiNMITSS
INIT:006053EA mov eax, edx
INIT:006053EC mov [ecx+2], ax                                  ; KgdtBaseLow(2h)
INIT:006053F0 shr eax, 10h
INIT:006053F3 mov [ecx+7], ah                                   ; KgdtBaseHi(7h)
INIT:006053F6 mov [ecx+4], al                                    ; KgdtBaseMid(4h)
INIT:006053F9 mov eax, 68h                                       ; MINIMUM_TSS_SIZE(68h)
INIT:006053FE mov [ecx], ax                                        ; KgdtLimitLow(0h)
INIT:00605401 push edx
INIT:00605402 push edx
INIT:00605403 call KiInitializeTSS(x)

2) 然后是建立堆栈(32位):
INIT:00605408 pop edx
INIT:00605409 mov eax, cr3
INIT:0060540C mov [edx+1Ch], eax                                            ; TssCr3(1ch)
INIT:0060540F mov eax, offset P0BootStack
INIT:00605414 mov eax, [eax+38h]
INIT:00605417 mov [edx+4], eax                                                  ; TssEsp0(4h) , NMI stack
INIT:0060541A mov [edx+38h], eax
INIT:0060541D mov dword ptr [edx+20h], offset _KiTrap02           ; 处理例程
INIT:00605424 mov dword ptr [edx+24h], 0                                  ; eflags
INIT:0060542B mov word ptr [edx+4Ch], 8                                   ; KGDT_R0_CODE(8h) -> CS
INIT:00605431 mov word ptr [edx+58h], 30h                                ; KGDT_R0_PCR(30h) -> FS
INIT:00605437 mov word ptr [edx+50h], ss
INIT:0060543A mov word ptr [edx+48h], 23h                                ; KGDT_R3_DATA(20h) + RPL_MASK(3h)
INIT:00605440 mov word ptr [edx+54h], 23h                                ; 初始化ES,DS
INIT:00605446 push offset _KiDoubleFaultStack                         ; 使用int 8号的Double Fault堆栈
//…
INIT:0060545D call KiInitializePcr(x,x,x,x,x,x,x)

—————————————————————————————————————————
如 果想在XP里设置Handler处理NMI,那么也该仿造以上过程,设置处理例程KiTrap02,把NMI-TSS里的EIP指向你的处理代码,然后建 立堆栈,最后还要在iret后面添加jmp回到你的处理代码.原因简单地说,就是每当接受一个NMI中断,处理器会在内部屏蔽再次响应NMI,这一屏蔽过 程直到执行中断返回指令IRET后才结束。还是直接看反汇编的KiTrap02代码吧:

.text:00484748 ; =============== S U B R O U T I N E =====================
.text:00484748
.text:00484748 _KiTrap02 proc near
.text:00484748
.text:00484748 var_8 = dword ptr -8
.text:00484748 var_4 = dword ptr -4
.text:00484748
.text:00484748 cli
.text:00484749 mov eax, ds:0FFDFF040h                                  ; nt!_KPCR + 40 == _KTSS
.text:0048474E mov ecx, ds:0FFDFF124h                                  ; nt!_KTHREAD
.text:00484754 mov edi, [ecx+38h]                                               ; Edi= _KPROCESS
.text:00484757 mov ecx, [edi+18h]                                               ; ULONG_PTR DirectoryTableBase[2]
.text:00484757                                                                          ; DirectoryTableBase[1]-> hyperspace
.text:00484757                                                                          ; DirectoryTableBase[0]-> CR3
.text:0048475A mov [eax+1Ch], ecx                                              ; TSS.Edx= DirectoryTableBase[0]
.text:0048475D mov cx, [edi+30h]
.text:00484761 mov [eax+66h], cx                                           ; TSS.IoMapBase= KPROCESS.IopmOffset
.text:00484765 mov ecx, [edi+20h]                                               ; LdtDescriptor:_KGDTENTRY
.text:00484768 test ecx, ecx
.text:0048476A jz short loc_484770
.text:0048476A
.text:0048476C mov cx, 48h                                                          ;KGDT_LDT(0x48)
.text:0048476C
.text:00484770 loc_484770:
.text:00484770 mov [eax+60h], cx                                                  ; set TSS.LDT
.text:00484774 push dword ptr ds:0FFDFF040h                        ; PcTss
.text:0048477A mov eax, ds:0FFDFF03Ch                                   ; PcGdt(3c)
.text:0048477F mov ch, [eax+5Fh]                                               ; KGDT_NMI_TSS(58)+KgdtBaseHi(7)
.text:00484782 mov cl, [eax+5Ch]                                                ; KGDT_NMI_TSS(58)+KgdtBaseMid(4)
.text:00484785 shl ecx, 16
.text:00484788 mov cx, [eax+5Ah]                                               ; KGDT_NMI_TSS(58)+KgdtBaseLow(2)
.text:0048478C mov ds:0FFDFF040h, ecx                                    ; PcTss
.text:0048478C                                                                        ;// PCR.TSS points to the NMI TSS
.text:0048478C
.text:00484792 pushf
.text:00484793 and [esp+8+var_8], 11111111111111111011111111111111b
.text:0048479A popf                                  ;// Clear Nested Task bit in EFLAGS, no more NMI handler
.text:0048479A
.text:0048479B mov ecx, ds:0FFDFF03Ch                                   ; PcGdt
.text:004847A1 lea eax, [ecx+58h]                                                  ; lea eax, [ecx] +KGDT_NMI_TSS
.text:004847A4 mov byte ptr [eax+5], 10001001b
; 32bit,dpl=0, present, TSS32, not busy
.text:004847A4                                                                              ; // Clear the busy bit in the TSS selector
.text:004847A4
.text:004847A8 mov eax, [esp+4+var_4]                                        ; var_4 = KiSaveProcessorState(…)
.text:004847AB push 0
//… …
.text:0048480B push ebp
.text:0048480C call KiSaveProcessorState(x,x)
.text:0048480C
.text:00484811 call KiHandleNmi() ; <—————- Our CallBack Routines here..
.text:00484811
.text:00484816 or al, al                                                              ; if any CallBackRoutine handled nmi trap..
.text:00484818 jnz short Nmi_Magic_Handled                              ; Jump to restore and wait..
.text:00484818
.text:0048481A cmp ds:NmiUnknowFlag, 0                                  ;  Nmi counts (Can anybody tell?)
.text:00484821 jnz short loc_484825                                               ; Setup Kernel debugger…
.text:00484821
.text:00484823 jmp short loc_484849                                             ; Pass nmi to Hal.dll,actually crash..
.text:00484823
.text:00484825 ; —————————————————————————
.text:00484825 loc_484825:
.text:00484825 cmp ds:NmiUnknowFlag, 8                                   ; 0 No more Nmi, just pass to HAL
.text:00484825                                                             ; 1-7 Nested Nmi, just pass to HAL and do crash
.text:00484825                                                                                    ; 8 Get into KdDebugger
.text:00484825                                                                                    ; >8 Dead Lock
.text:0048482C jb short loc_484849
.text:0048482C
.text:0048482E jnz short loc_484847

.text:00484830 cmp ds:_KdDebuggerNotPresent, 0               ; if ( !_KdDebuggerNotPresent )
.text:00484837 jnz short loc_484847
.text:00484839 cmp ds:_KdDebuggerEnabled, 0                    ; if ( _KdDebuggerEnabled )
.text:00484840 jz short loc_484847
.text:00484842 call KeEnterKernelDebugger() ; KeEnterKernelDebugger();
.text:00484847 loc_484847:                                                         ; else
.text:00484847 jmp short loc_484847                                       ;  while(1);
.text:00484849 loc_484849:
.text:00484849 inc ds:NmiUnknowFlag
.text:0048484F push 0
.text:00484851 call ds:HalHandleNMI(x) ; <—————- Default Nmihandler in Hal
.text:00484851
.text:00484857 dec ds:NmiUnknowFlag
.text:0048485D jnz short _Nmi_DoCrash                                       ; But can we reach here? hmm..
.text:0048485D
.text:0048485F Nmi_Magic_Handled:
.text:0048485F mov eax, ds:0FFDFF040h                                         ; eax= _KTSS
.text:00484864 cmp word ptr [eax], 58h                                 ; if KGDT_NMI_TSS(58h)
.text:00484868 jz short _Nmi_DoCrash                          ;In another Nmi,we can’t handle it,just crash
.text:00484868
.text:0048486A add esp, 8Ch                                                           ; Nmi handled,so let’s restore
.text:0048486A
.text:00484870 pop dword ptr ds:0FFDFF040h                                ; restore PcTss
.text:00484876 mov ecx, ds:0FFDFF03Ch                                          ; PcGdt
.text:0048487C lea eax, [ecx+28h]                                                       ; lea eax, [ecx] +KGDT_TSS
.text:0048487F mov byte ptr [eax+5], 8Bh                                       ; 32bit, dpl=0, present,TSS32, *busy*
.text:00484883 pushf
.text:00484884 or [esp+4+var_4], 4000h                                        ; Set Nested Task bit in EFLAGS
.text:0048488B popf                                                                            ; then iretd will start a tast switch
.text:0048488C iret
.text:0048488D jmp _KiTrap02 ; <——–!!!!!!!!!!!!!!!!!!!!!
.text:00484892
.text:00484892 _Nmi_DoCrash:
.text:00484892 mov eax, 2
.text:00484897 jmp _KiSystemFatalException        ; Crash!! UNEXPECTED_KERNEL_MODE_TRAP
.text:00484897
.text:00484897 _KiTrap02 endp

大 致的流程是设置返回后中断门需要的信息,更新TSS,保存CPU状态,调用用户注册的NMI处理例程;如果不能处理,则交给默认的Nmi Handler,也就是HalHandleNMI(x),但是它并不能做什么实质性的解决工作,只是准备BugCheck…上面还有一些需要详细解释 的:

1) KiSaveProcessorState对多核系统是必须的,因为NMI一次只能由一个CPU来处理,如果不保存,会在Bugcheck的时候出现dump出来的信息不准确的情况。
2) NmiUnknowFlag按我的理解是NMI的触发次数,一种情况是iret进BIOS int 10触发另一个NMI,或者由NMI激发进入SMM(查考我之前的一篇文章),而SMM下设置了SMI Handler,那么就会可能再出现NMI,而在SMM RSM切换出来的时候,由于SMM的state save map里并不保存NMI的状态信息,所以RSM必然触发NMI,这样的话我们很有可能面对处理两次或者更多NMI(Nested Nmi)。所以我们必须判断当前是不是nested Nmi,是的话尝试处理。
3) 处理嵌套的Nmi有几种情况,一是调试器触发导致nested Nmi,这个不用担心;对于SMM触发的,那就必须在SMM内部解决掉NMI,这就是我上篇文章里提到的一些内容[6],还是那句老话,想接收并处理(unmask)中断,简单的iret是不行的(或者可行,只是我用的测试方法不对:),如果可以,麻烦指点一二),得想其他办法,参考我的文章;
4) .text:00484825 cmp ds:NmiUnknowFlag, 8 这个我在一年多前刚看到的时候也很迷惑,但是之后的一个显卡驱动项目让我明白了意思:由于BIOS里的显卡部分会处理int 10,如果处理例程里修改了KGDT_TSS,就可能引发Nmi,而且这是个恶性循环,由于TSS的错位,会产生更多错误的NMI来弥补错误。而这句就是 判断是不是超过8个NMI了,是的话神也处理不了了,但是又不能直接Bugcheck,因为这样又会”自作多情”地又调用显卡处理,所以只能死锁掉 (while..)。

5) 关于刷新BIOS…这个和我上篇[6]里也是有关联的.Map BIOS那部分内存简单,你可以translate address,但是想真正修改却是个麻烦事.这个先放着,等有机会好好和大家交流.

言 归正传,在XP下,NMI一旦触发,都是交给HalHandleNMI,然后直接crash掉系统.Win2003下提供了一个很好的机制 KeRegisterNmiCallback, 你可以注册Nmi的处理例程,这样发生Nmi中断的时候,你可以进行一些收尾工作,但是注意在回调函数里有很多的限制,不能有系统函数调用,不能去获取 SpinLock,必须使用Interlocked系列进行数据操作等等..

函数原型:
PVOID
KeRegisterNmiCallback(
PNMI_CALLBACK CallbackRoutine,
PVOID Context
);

还原出来的源代码如下:

static PVOID gKiNmiCallbackListHead= NULL;
KSPIN_LOCK KiNmiCallbackListLock;
typedef struct _KNMI_CALLBACK_RECORD
{
struct _KNMI_CALLBACK_RECORD *PreCallBackRecord;
PNMI_CALLBACK CallbackRoutine;
PVOID Context;
_KNMI_CALLBACK_RECORD *CurCallBackRecord;
} KNMI_CALLBACK_RECORD, *PKNMI_CALLBACK_RECORD;
// 以上是全局变量和结构体的声明.
PVOID _KeRegisterNmiCallback( IN PNMI_CALLBACK CallbackRoutine,
IN OPTIONAL PVOID Context )
{
PVOID result;
PKNMI_CALLBACK_RECORD pKNmiCallbackRecord;
KIRQL OldIrql;
result = ExAllocatePoolWithTag(NonPagedPool, sizeof(KNMI_CALLBACK_RECORD), ‘IMNK’);
pKNmiCallbackRecord = (PKNMI_CALLBACK_RECORD)result;
if ( result )
{
pKNmiCallbackRecord->CallbackRoutine = CallbackRoutine;
pKNmiCallbackRecord->Context = Context;
pKNmiCallbackRecord->CurCallBackRecord = result;
OldIrql = KfAcquireSpinLock(&KiNmiCallbackListLock);
pKNmiCallbackRecord->PreCallBackRecord = gKiNmiCallbackListHead;
InterlockedCompareExchange( (PLONG)&gKiNmiCallbackListHead, (LONG)pKNmiCallbackRecord,(LONG)

gKiNmiCallbackListHead );
KfReleaseSpinLock(&KiNmiCallbackListLock, OldIrql);
result = pKNmiCallbackRecord->CurCallBackRecord;
}
return result;
}
NTSTATUS _KeDeregisterNmiCallback(PVOID Handle)
{
KIRQL OldIrql;
PKNMI_CALLBACK_RECORD *tmpCallBackRecord;
PKNMI_CALLBACK_RECORD CurCallBackRecord;
NTSTATUS result;

CurCallBackRecord = (PKNMI_CALLBACK_RECORD)gKiNmiCallbackListHead;
OldIrql = KfAcquireSpinLock(&KiNmiCallbackListLock);
tmpCallBackRecord = &(PKNMI_CALLBACK_RECORD)gKiNmiCallbackListHead;

if ( !gKiNmiCallbackListHead )
{
KfReleaseSpinLock(&KiNmiCallbackListLock, OldIrql);
result = STATUS_INVALID_HANDLE;

return result;
}
do
{
if ( (ULONG)(CurCallBackRecord->CurCallBackRecord) == (ULONG)Handle )
break;
tmpCallBackRecord = (PKNMI_CALLBACK_RECORD*)CurCallBackRecord;
CurCallBackRecord = *(PKNMI_CALLBACK_RECORD*)CurCallBackRecord;
}
while ( CurCallBackRecord );
if ( CurCallBackRecord && (ULONG)(CurCallBackRecord->CurCallBackRecord) == (ULONG)Handle )
{
*tmpCallBackRecord = *(PKNMI_CALLBACK_RECORD*)CurCallBackRecord;
KfReleaseSpinLock(&KiNmiCallbackListLock, OldIrql);
ExFreePoolWithTag(CurCallBackRecord, ‘IMNK’); result = 0;
}
else
{
KfReleaseSpinLock(&KiNmiCallbackListLock, OldIrql);
result = STATUS_INVALID_HANDLE;

}
return result;
}
其中,gKiNmiCallbackListHead是系统的一个全局变量,它指向最后注册的CallBackRecord,以反向的链表把所有的CallBack连接起来.正如MSDN中描述的:
When a nonmaskable interrupt occurs, the system calls each registered callback in reverse order from the orderin which they were registered.
For the first callback, the system passes FALSE as the Handled parameter. For each subsequent callback, if anyprevious callback returned TRUE, the system passes TRUE as the Handled parameter, otherwise it passes FALSE. If any callback returns a value of TRUE, the system considers the interrupt to have been handled.Otherwise, thesystem calls the HAL’s default handler for the interrupt, which normally causes the system to bug check.

正如前面的反汇编代码,NMI处理的流程大致为KiSystemStartup -> _KiTrap02 -> KiHandleNmi -> HalHandleNMI.把其他两个函数的代码也还原如下:
BOOLEAN _KiHandleNmi()
{
PKNMI_CALLBACK_RECORD CurCallBackRecord;
BOOLEAN nmiHandled;
CurCallBackRecord = gKiNmiCallbackListHead;
nmiHandled = FALSE;
while ( CurCallBackRecord )
{
nmiHandled |= (CurCallBackRecord->CallbackRoutine)(CurCallBackRecord->Context, nmiHandled) ;
CurCallBackRecord = CurCallBackRecord->PreCallBackRecord;
}
return nmiHandled;
}
// HalHandleNMI比较长,加了点注释:
void _HalHandleNMI(PVOID NmiInfo)
{
UCHAR EISAExNmiStatus;
UCHAR PortNmiStatus;
UCHAR EisaPort;
PUCHAR Port;
UCHAR EisaNMIMsgBuff[28];
ULONG i;

HalpNMIInProgress = 1;
HalpDoingCrashDump = 1;
EisaPort = READ_PORT_UCHAR((PUCHAR)0x61); // 0x61 -> SYSTEM_CONTROL_PORT_B

if ( InbvIsBootDriverInstalled() )
{
InbvAcquireDisplayOwnership();
InbvResetDisplay();
InbvSolidColorFill(0, 0, 639, 479, 4);
InbvSetTextColor(15); InbvInstallDisplayStringFilter(0);
InbvEnableDisplayString(1); InbvSetScrollRegion(0, 0, 639, 479);
}

HalDisplayString(“\n*** Hardware Malfunction\n\n”);
HalDisplayString(“Call your hardware vendor for support\n\n”);
if ( EisaPort & 0x80 )
HalDisplayString(“NMI: Parity Check / Memory Parity Error\n”);

if ( EisaPort & 0x40 )
HalDisplayString(“NMI: Channel Check / IOCHK\n”);

if ( HalpBusType == 1 ) // #define MACHINE_TYPE_EISA 1
{
EISAExNmiStatus = READ_PORT_UCHAR((PUCHAR)0x461); // 0x461 -> EISA_EXTENDED_NMI_STATUS
EisaPort = EISAExNmiStatus;
if ( (char)EISAExNmiStatus < 0 )
HalDisplayString(“NMI: Fail-safe timer\n”);
if ( EisaPort & 0x40 )
HalDisplayString(“NMI: Bus Timeout\n”);
if ( EisaPort & 0x20 )
HalDisplayString(“NMI: Software NMI generated\n”);
EisaPort = 1;
port = (EisaPort << 12) + 0xC80;
do {
WRITE_PORT_UCHAR(Port, 0xFF);
if ( (char)READ_PORT_UCHAR(Port) >= 0 )
{
PortNmiStatus = READ_PORT_UCHAR(Port + 4);
if ( PortNmiStatus & 0x2 )
{
if ( PortNmiStatus != 0xFFFFFFFF )
{ // EisaNMIMsg[]: “NMI: Eisa IOCHKERR board %\n”
memcpy(EisaNMIMsgBuff, &EisaNMIMsg, sizeof(EisaNMIMsgBuff));
i = 0;
if ( EisaNMIMsgBuff )
{
while ( EisaNMIMsgBuff[i] != ‘%’ )
{
++i;
if ( !EisaNMIMsgBuff[i] )
goto DisplayMsg;
}
EisaNMIMsgBuff[i] = (EisaPort > 9 ? ‘A’-10 : ‘0’) + EisaPort;
}
DisplayMsg: HalDisplayString(EisaNMIMsgBuff);
}
}
}
++EisaPort; Port += 0x1000;
} while ( EisaPort <= 0xF );
}
HalDisplayString(“\n*** The system has halted ***\n”);
if ( HalpNMIDumpFlag ) //Registry\Machine\System\CurrentControlSet\Control\CrashControl
KeBugCheckEx(0x80, ‘ODT’, 0, 0, 0);
if ( !KdDebuggerNotPresent )
{
if ( KdDebuggerEnabled )
KeEnterKernelDebugger();
} while ( 1 );
}

有意思的是HalpNMIDumpFlag这个全局标志(HalpGetNMICrashFlag),它决定了NMI触发而且要Crash系统的时候是否生成dump[3]. 其实就是注册表的这个位置:HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control \CrashControl,CrashDumpEnabled设置为2,NMICrashDump设置为1.这个可以为服务器生成dump以调试,比如 DELL PE2850,设置好BISO允许使用NMI Button后,你只要按下机器上的NMI按钮就可以了.

todo… :)


[1] [IDT] http://en.wikipedia.org/wiki/Interrupt_descriptor_table
[GDT] http://wiki.osdev.org/GDT_Tutorial
[2] [Windows NT5.2 的启动过程] http://advdbg.org/blogs/advdbg_system/articles/409.aspx
[3] [NMI Crash Dump Switch Support] http://www.microsoft.com/whdc/system/sysinternals/dmpsw.mspx
[4] [解析Windows2000的IDT扩展机制] http://www.xfocus.net/articles/200309/618.html
[5] [任务状态段和控制门] http://dev.csdn.net/article/17/17358.shtm
[6] About the SMM rootkit ] http://www.softrce.net/post/19.html

相关日志

发表评论