根据ReactOS源代码分析windows蓝屏处理过程

在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

相关文章

一、前言 在组件方面react和Vue一样的,核心思想玩的就是组件...
前言: 前段时间学习完react后,刚好就接到公司一个react项目...
前言: 最近收到组长通知我们项目组后面新开的项目准备统一技...
react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...