《軟體除錯》讀書筆記:第13章 硬錯誤和藍屏

Ox9A82發表於2016-04-12

會話管理器程式SMSS.exe是系統啟動後的第一個使用者態程式,負責啟動和監護windows子系統程式:CSRSS.exe和登陸管理程式:WinLogon

SMSS.exe從登錄檔中查詢子系統exe檔案的位置,並且啟動它

CSRSS是windows子系統程式,自NT4以後視窗管理和GDI的主體實現被移出了CSRSS放到了win32k.sys中。

CSRSS監管著所有windows執行緒和程式,每個程式在建立後都要到這裡登記才能執行,退出時也要報告登出。(維護了Windows子系統層面上的記錄結構)

CSRSS具有桌面管理、終端登入、控制檯管理、HardError報告等方面的重要作用。

為了適應惡劣環境下的錯誤提示的需要,Windows定義了硬錯誤提示機制。

硬錯誤本意是硬體有關錯誤,後來泛指嚴重的錯誤。

硬錯誤可以在使用者態使用也可以在核心態使用。

硬錯誤的核心處理函式是核心中的ExpRaiseHardError函式。

在我們看這個函式的原始碼前,先想一想,傳送硬錯誤訊息,首先要的是找到傳送硬錯誤的埠。

這個埠怎麼找?是通過一系列的設定和標誌位來判斷的。  

如下是具體的程式內容

  1 NTSTATUS
  2 ExpRaiseHardError (
  3     IN NTSTATUS ErrorStatus,
  4     IN ULONG NumberOfParameters,
  5     IN ULONG UnicodeStringParameterMask,
  6     IN PULONG_PTR Parameters,
  7     IN ULONG ValidResponseOptions,
  8     OUT PULONG Response
  9     )
 10 {
 11     PTEB Teb;
 12     PETHREAD Thread;
 13     PEPROCESS Process;
 14     ULONG_PTR MessageBuffer[PORT_TOTAL_MAXIMUM_MESSAGE_LENGTH/sizeof(ULONG_PTR)];
 15     PHARDERROR_MSG m;
 16     NTSTATUS Status;
 17     HANDLE ErrorPort;
 18     KPROCESSOR_MODE PreviousMode;
 19     BOOLEAN DoingShutdown;
 20 
 21     PAGED_CODE();
 22     //要傳送的訊息結構
 23     m = (PHARDERROR_MSG)&MessageBuffer[0];
 24     PreviousMode = KeGetPreviousMode();
 25 
 26     DoingShutdown = FALSE;
 27     //如果引數要求關機,就檢查是否有關機許可權
 28     if (ValidResponseOptions == OptionShutdownSystem) {
 29 
 30         //
 31         // Check to see if the caller has the privilege to make this call.
 32         //
 33 
 34         if (!SeSinglePrivilegeCheck (SeShutdownPrivilege, PreviousMode)) {
 35             return STATUS_PRIVILEGE_NOT_HELD;
 36         }
 37 
 38         ExReadyForErrors = FALSE;
 39         HardErrorState = SHUTDOWN;
 40         DoingShutdown = TRUE;
 41     }
 42 
 43     Thread = PsGetCurrentThread();
 44     Process = PsGetCurrentProcess();
 45 
 46     //
 47     // If the default handler is not installed, then
 48     // call the fatal hard error handler if the error
 49     // status is error
 50     //
 51     // Let GDI override this since it does not want to crash the machine
 52     // when a bad driver was loaded via MmLoadSystemImage.
 53     //
 54 
 55     if ((Thread->CrossThreadFlags & PS_CROSS_THREAD_FLAGS_HARD_ERRORS_DISABLED) == 0) {
 56         //如果滿足這個標誌位說明使用者態HardError系統還沒有準備好,只能在核心態去提示這個HardError了
 57         if (NT_ERROR(ErrorStatus) && (HardErrorState == STARTING || DoingShutdown)) {
 58         //而所謂的在核心態提示HardError就是執行這個函式了,實質上就是一個KeBugCheckEx,也就是說想著核心態提示HardError只能是通過丟擲藍屏
 59             ExpSystemErrorHandler (
 60                 ErrorStatus,
 61                 NumberOfParameters,
 62                 UnicodeStringParameterMask,
 63                 Parameters,
 64                 (BOOLEAN)((PreviousMode != KernelMode) ? TRUE : FALSE));
 65         }
 66     }
 67 
 68     //
 69     // If the process has an error port, then if it wants default
 70     // handling, use its port. If it disabled default handling, then
 71     // return the error to the caller. If the process does not
 72     // have a port, then use the registered default handler.
 73     //
 74 
 75     ErrorPort = NULL;
 76     //這個就是通過各種標誌位來判斷硬錯誤埠是什麼了。對於一個程式來說只有異常埠和除錯埠兩個東西,並沒有單獨指定硬錯誤埠的結構。
 77     if (Process->ExceptionPort) {
 78         //異常埠存在時
 79         if (Process->DefaultHardErrorProcessing & 1) {
 80             //這個標誌位存在時,異常埠就是硬錯誤埠
 81             ErrorPort = Process->ExceptionPort;
 82         } else {
 83 
 84             //
 85             // If error processing is disabled, check the error override
 86             // status.
 87             //
 88 
 89             if (ErrorStatus & HARDERROR_OVERRIDE_ERRORMODE) {
 90                 //滿足這個條件的話,也是異常埠
 91                 ErrorPort = Process->ExceptionPort;
 92             }
 93         }
 94     } else {
 95         if (Process->DefaultHardErrorProcessing & 1) {
 96             ErrorPort = ExpDefaultErrorPort;
 97         } else {
 98 
 99             //
100             // If error processing is disabled, check the error override
101             // status.
102             //
103 
104             if (ErrorStatus & HARDERROR_OVERRIDE_ERRORMODE) {
105                 ErrorPort = ExpDefaultErrorPort;
106             }
107         }
108     }
109     //雖然上面比較的那麼熱鬧。。但是ExpDefaultErrorPort和ExeceptionPort的值其實是一樣的
110     //都是CSRSS程式的\Windows\ApiPort埠。這是一個LPC埠物件
111 
112     if ((Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_HARD_ERRORS_DISABLED) != 0) {
113         ErrorPort = NULL;
114     }
115     //設定了禁止硬錯誤就把ErrorPort清零
116     if ((ErrorPort != NULL) && (!IS_SYSTEM_THREAD(Thread))) {
117         Teb = (PTEB)PsGetCurrentThread()->Tcb.Teb;
118         try {
119             if (Teb->HardErrorMode & RTL_ERRORMODE_FAILCRITICALERRORS) {
120                 ErrorPort = NULL;
121             }
122         } except (EXCEPTION_EXECUTE_HANDLER) {
123             ;
124         }
125     }
126 
127     if (ErrorPort == NULL) {
128         *Response = (ULONG)ResponseReturnToCaller;
129         return STATUS_SUCCESS;
130     }
131     //自己給自己發異常?出現問題了
132     if (Process == ExpDefaultErrorPortProcess) {
133         if (NT_ERROR(ErrorStatus)) {
134             ExpSystemErrorHandler (ErrorStatus,
135                                    NumberOfParameters,
136                                    UnicodeStringParameterMask,
137                                    Parameters,
138                                    (BOOLEAN)((PreviousMode != KernelMode) ? TRUE : FALSE));
139         }
140         *Response = (ULONG)ResponseReturnToCaller;
141         Status = STATUS_SUCCESS;
142         return Status;
143     }
144 
145     m->h.u1.Length = HARDERROR_API_MSG_LENGTH;
146     m->h.u2.ZeroInit = LPC_ERROR_EVENT;
147     m->Status = ErrorStatus & ~HARDERROR_OVERRIDE_ERRORMODE;
148     m->ValidResponseOptions = ValidResponseOptions;
149     m->UnicodeStringParameterMask = UnicodeStringParameterMask;
150     m->NumberOfParameters = NumberOfParameters;
151 
152     if (Parameters != NULL) {
153         try {
154             RtlCopyMemory (&m->Parameters,
155                            Parameters,
156                            sizeof(ULONG_PTR)*NumberOfParameters);
157         } except (EXCEPTION_EXECUTE_HANDLER) {
158         }
159     }
160 
161     KeQuerySystemTime(&m->ErrorTime);
162     //執行傳送函式
163     Status = LpcRequestWaitReplyPortEx (ErrorPort,
164                                         (PPORT_MESSAGE) m,
165                                         (PPORT_MESSAGE) m);
166 
167     if (NT_SUCCESS(Status)) {
168         switch (m->Response) {
169             //作為一個返回值
170             case ResponseReturnToCaller :
171             case ResponseNotHandled :
172             case ResponseAbort :
173             case ResponseCancel :
174             case ResponseIgnore :
175             case ResponseNo :
176             case ResponseOk :
177             case ResponseRetry :
178             case ResponseYes :
179             case ResponseTryAgain :
180             case ResponseContinue :
181                 break;
182             default:
183                 m->Response = (ULONG)ResponseReturnToCaller;
184                 break;
185         }
186         *Response = m->Response;
187     }
188 
189     return Status;
190 }

這個函式把硬錯誤資訊傳送到了CSRSS程式的\Windows\ApiPort埠上去。

接下來就是看CSRSS程式是怎麼接受並處理這個硬錯誤訊息的。

CSRSS程式中專門啟用了一個執行緒來監聽並處理\Windows\ApiPort埠的內容

CsrApiRequestThread就是這個專門用來監聽的工作執行緒

PORT_MESSAGE是傳送的LPC埠的資料結構。

CSRSS負責後續的處理工作,並進行使用者層面的彈出提示。

相關文章