在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)",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],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