在windows系统下面蓝屏是经常发生的事情,下面就来跟随reactOS系统的源代码看一下windows蓝屏的实现。引起蓝屏的函数实现如下面所示,这个字符串组成函数是不是和蓝屏打印出来的信息一样。而系统的关闭正是有这句引起的。至于整个输出函数也很简单,就是调用最后MACHVtbl结构体的成员函数实现。看到这里不禁对操作系统模块化有一个直观的理解。这也就是为什么可以用C++实现操作系统的原因。因为如果这里将整个MACHVtbl中的函数指针用虚函数实现也是类似的。只不过C++会加入一些不必要的东西,而这里在最底层的系统当中是显得多余的。至于这里用一个结构体来管理函数,原因也很简单,这样便于组成一张表——所有在MACHVtbl当中的函数,都可以通过MACHVtbl来管理。
VOID NTAPI KeBugCheckEx( IN ULONG BugCheckCode, IN ULONG_PTR BugCheckParameter1, IN ULONG_PTR BugCheckParameter2, IN ULONG_PTR BugCheckParameter3, IN ULONG_PTR BugCheckParameter4) { char Buffer[70]; sprintf(Buffer, "*** STOP: 0x%08lX (0x%08lX, 0x%08lX, 0x%08lX, 0x%08lX)", BugCheckCode, BugCheckParameter1, BugCheckParameter2, BugCheckParameter3, BugCheckParameter4); UiMessageBoxCritical(Buffer); assert(FALSE); for (;;); } VOID UiMessageBoxCritical(IN PCSTR MessageText) { TuiPrintf(MessageText); }
下面的难点是不定项参数的分析,由于所有的参数都放到了堆栈当中。所以不管多少参数都可以通过堆栈指针来实现,通过堆栈指针跳过第一个参数,然后找到后面的参数。
#define _AUPBND (sizeof (int) - 1) #define _ADNBND (sizeof (int) - 1) typedef char *va_list; //将要获得的参数定义为char*类型 #define _Bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))//进行四字节对齐 #define va_arg(ap, T) (*(T *)(((ap) += (_Bnd (T, _AUPBND))) - (_Bnd (T,_ADNBND)))) #define va_end(ap) (void) 0//表明参数全部扫描结束 #define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_Bnd (A,_AUPBND))))//跳过第一个参数,这个参数在printf函数里面是format字符串
至于va_arg的解释,需要明确一点,由于压入堆栈的是不确定长度的参数,所以最左边的参数最先入栈,而堆栈的指针依次递减。所以这里分为两步来分析。首先将整个宏当中的符号简化:(*(T *)(((ap) += (对齐)) - (对齐)))。在这里看起来相当于没有加,实际不然,第一步加的时候有赋值的等号,这里使得ap下移,然后减去后面的对齐,将类型强制转化为T*,然后取值,一个宏完成两件事。至于类型T的实现,当然是根据format里面在%后面的类型而具体看待了。
int TuiPrintf(const char *Format, ...) { int i; int Length; va_list ap; CHAR Buffer[512]; va_start(ap, Format); Length = _vsnprintf(Buffer, sizeof(Buffer), Format, ap); va_end(ap); if (Length == -1) Length = sizeof(Buffer); for (i = 0; i < Length; i++) { MachConsPutChar(Buffer[i]); } return Length; } VOID MachConsPutChar(int Ch) { MachVtbl.ConsPutChar(Ch); }根据MACHInit函数,我们可以知道MachVtbl结构体当中的PcConsPutChar函数指针指向PcConsPutChar函数,所以这里实际调用的是PcConsPutChar函数。
PcConsPutChar(int Ch) { REGS Regs; if ('\n' == Ch) { PcConsPutChar('\r'); } if ('\t' == Ch) { PcConsPutChar(' '); PcConsPutChar(' '); PcConsPutChar(' '); PcConsPutChar(' '); PcConsPutChar(' '); PcConsPutChar(' '); PcConsPutChar(' '); PcConsPutChar(' '); return; } Regs.b.ah = 0x0E; Regs.b.al = Ch; Regs.w.bx = 1; Int386(0x10, &Regs, &Regs); } Int386_regsin: .long 0 Int386_regsout: .long 0 PUBLIC _Int386 _Int386: mov eax, dword ptr [esp + 4] mov dword ptr ds:[BSS_IntVector], eax mov eax, dword ptr [esp + 8] mov dword ptr [Int386_regsin], eax mov eax, dword ptr [esp + 12] mov dword ptr [Int386_regsout], eax//首先取出三个参数,其中中断号放到全局范围的变量当中,其中BSS用于存放实模式下的一些数据 push ds push es push fs push gs pusha //保存所有寄存器 mov esi, dword ptr [Int386_regsin] //将输入参数复制到edi所指向的地址 mov edi, BSS_RegisterSet mov ecx, REGS_SIZE / 4 rep movsd mov bx, FNID_Int386 //FNID_Int386标志实模式下面的Int386的编号,将这个编号放入到BX里面,由于模式转化是不会影响到寄存器的值的,所以可以这样传递数据 mov dword ptr [ContinueAddress], offset Int386_return //continueAddress用于存放返回地址,然而SwitchToReal是不会有返回值的,因为这里实际上会改变系统的模式,所以会有一个跳转到保护模式的过程,然后将continueAddress通过某种方式写入到EIP当中就可以了 jmp SwitchToReal Int386_return: mov esi, BSS_RegisterSet mov edi, dword ptr [Int386_regsout] mov ecx, REGS_SIZE / 4 rep movsd //将返回值写入到传进来的数据当中 popa pop gs pop fs pop es pop ds ret