「應用程式畫面出現錯誤 0x80004005。這是什麼意思?」── 故障調查諮詢裡,這種問題是經典。把錯誤對話方塊上的數字直接貼進搜尋引擎,換來一堆不相干的文章——Windows Update 失敗、共用資料夾連不上、VBA 執行階段錯誤、資料庫連線失敗——反而更混亂的人很多。
會這樣,是因為 0x80004005(E_FAIL)是只表示「未指定失敗」的通用代碼。同一個代碼用在無數場合,只搜代碼到不了原因。另一方面,像 0x80070005 這種代碼,只要知道結構,搜尋前就能在幾秒內分解成「Win32 錯誤編號 5 = 拒絕存取,再包成 HRESULT」。
Windows 錯誤碼因歷史因素形成 Win32 錯誤碼、HRESULT、NTSTATUS 三層,而且會跨層轉換。把這個結構放進腦子後,就能自己判斷「這是哪一層、誰回傳的」以及「本質代碼是什麼」,調查的開局會快很多。
本文面向中小企業 IT 人員與 Windows 應用開發者,依 2026 年 8 月當下的 Microsoft Learn 與公開規格 [MS-ERREF],整理三種錯誤碼體系的分辨與分解、與 .NET 例外的關係,以及用 err.exe、PowerShell 的實務查詢。
1. 先講結論
- Windows 錯誤碼主要有三套體系。 Win32 錯誤碼(
GetLastError回傳的較小十進位)、HRESULT(COM 之後的 32 位元代碼,以 0x8 開頭的十六進位或負數十進位)、NTSTATUS(核心層代碼;錯誤以 0xC 開頭)。123 - 十進位與十六進位是同一代碼的不同記法。 「錯誤 5」、「0x5」、「0x80070005 的低 16 位元」都指向 ERROR_ACCESS_DENIED(拒絕存取)。1
- 0x8007xxxx 是「包起來的 Win32 錯誤」。 那是放進 HRESULT FACILITY_WIN32(7)的 Win32 錯誤碼;把低 16 位元轉成十進位就是本質代碼。這是讀錯誤碼最重要的模式。45
- 0x80004005(E_FAIL)不是原因代碼。 意思是「Unspecified failure」,沒有更多資訊。與其深挖這個代碼,不如找來源脈絡與伴隨的記錄。6
- 負數十進位(-2147467259 等)是 HRESULT。 32 位元的最高位(失敗位元)已設定,有號顯示就是負數。先轉成十六進位再讀。2
- 以 0xC 開頭的 8 位數是 NTSTATUS。 0xC0000005(存取違規)與 0xC0000135(找不到 DLL)在當機時的事件記錄與傾印裡不斷出現。與 Win32 錯誤編號 5 無關。7
- 同一代碼會隨脈絡改變意義。 錯誤 5 的原因涵蓋 ACL、提升權限、防毒、被占用的檔案等,錯誤 2 的「找不到檔案」常常是相依 DLL。一定要把代碼意義與「哪個 API 對什麼失敗」一起讀。1
- 轉換與查詢工具是標準配備。
certutil -error與net helpmsg內建於 Windows;PowerShell 的Win32Exception可取得訊息;開發機用 err.exe(Microsoft Error Lookup Tool);傾印分析用 WinDbg 的!error。8910 - 在 .NET 裡,HRESULT 會對應到例外型別。 已知的 HRESULT 對應到相應例外型別(E_ACCESSDENIED → UnauthorizedAccessException 等);未知的變成 COMException;原始值留在
Exception.HResult。11
用一句話來說,Windows 錯誤碼調查的型就是 「把記法對齊到十六進位 → 判斷是哪一層的代碼 → 分解取出本質代碼 → 連同脈絡一起讀」。
2. Windows 有三套錯誤碼體系
先看整體地圖。Windows 錯誤碼依回傳的層,主要分成以下三套體系。
| 體系 | 主要回傳者 | 典型外觀 | 代表例子 |
|---|---|---|---|
| Win32 錯誤碼 | Win32 API(GetLastError)、命令的結束碼 |
較小的十進位(0–15999) | 5 = ERROR_ACCESS_DENIED |
| HRESULT | COM 元件、殼層、安裝程式、許多框架 | 以 0x8 開頭的 8 位十六進位,或負數十進位 | 0x80004005 = E_FAIL |
| NTSTATUS | 核心、驅動程式、原生 API(ntdll) | 錯誤是以 0xC 開頭的 8 位十六進位 | 0xC0000005 = STATUS_ACCESS_VIOLATION |
歷史上是依這個順序堆疊:繼承 MS-DOS 錯誤編號的 Win32 錯誤碼、NT 核心內部使用的 NTSTATUS,以及導入 COM 時為了「把成敗與來源塞進 32 位元」而設計的 HRESULT。在現今 Windows,日常的轉換流程是:核心回傳 NTSTATUS,Win32 子系統把它轉成 Win32 錯誤碼,COM 層再包成 HRESULT。124
flowchart TB
accTitle: 三套體系之間的轉換流程
accDescr: Win32 子系統把核心回傳的 NTSTATUS 轉成 Win32 錯誤碼,COM 層再把它包成 HRESULT
kernel["核心與驅動程式"] --> nt["NTSTATUS(錯誤是 0xC…)"]
nt -->|Win32 子系統轉換| win["Win32 錯誤碼(5 等)"]
win -->|COM 層包起來| hr["HRESULT(0x8007xxxx)"]
圖 1: 跨層轉換流程。核心的 NTSTATUS 變成 Win32 錯誤,再被包成 HRESULT。
2.1. 習慣在十進位與十六進位之間互換閱讀
在分辨三套體系之前,先吸收記法的搖擺。同一代碼會依場合顯示成十進位或十六進位。
- 「錯誤 5」、「錯誤碼:0x5」 → 同一個 ERROR_ACCESS_DENIED
- 「錯誤 1223」、「0x4C1」 → 同一個 ERROR_CANCELLED
- 「0x80070005」、「-2147024891」 → 同一個 HRESULT
在 PowerShell 裡,轉換只要一行。
# Decimal → hex
'0x{0:X8}' -f 1223 # 0x000004C1
'0x{0:X8}' -f -2147024891 # 0x80070005 (negative = HRESULT to hex)
# Hex → decimal
0x4C1 # 1223
看到以「-214…」開頭的負數十進位,就反射性地轉成十六進位。 光這樣就能少在調查入口迷路很多。
flowchart TB
accTitle: 同一代碼的三種外觀
accDescr: 十進位錯誤 5、十六進位 0x5,以及 0x80070005 的低 16 位元,都指向同一個 ERROR_ACCESS_DENIED
d["十進位記法: 錯誤 5"] --> same["ERROR_ACCESS_DENIED"]
h["十六進位記法: 0x5"] --> same
l["0x80070005 的低 16 位元"] --> same
same -.-> memo["記法不同,代碼相同"]
圖 2: 十進位、十六進位與 HRESULT 的低 16 位元,只是同一代碼的不同記法。
3. Win32 錯誤碼 ── GetLastError 與 FORMAT_MESSAGE
3.1. GetLastError 的基本行為
CreateFile、RegOpenKeyEx 等許多 Win32 API 以傳回值(FALSE、NULL、INVALID_HANDLE_VALUE 等)表示失敗,並把詳細錯誤碼存在 每個執行緒持有的「最後錯誤碼」。呼叫端在確認失敗後立刻用 GetLastError 取出。13
實務上有兩點要注意。13
- 失敗後立刻讀。 中間若插入另一次 API 呼叫(例如記錄函式),那次呼叫可能覆寫最後錯誤碼。
- 成功時不要依賴這個值。 有的 API 成功時會把最後錯誤碼清成 0,有的不動它。規則是先從傳回值確認失敗再讀。
sequenceDiagram
accTitle: 失敗後立刻讀 GetLastError
accDescr: 從傳回值確認失敗後,立刻用 GetLastError 取出最後錯誤碼,中間不要插入另一次 API 呼叫
participant app as App
participant api as Win32 API
app->>api: CreateFile 呼叫
api-->>app: 失敗傳回值
app->>api: GetLastError
api-->>app: 代碼 5
Note over app: 中間插入另一次 API 可能覆寫
圖 3: 失敗後立刻讀最後錯誤碼。中間插入另一次 API 呼叫可能覆寫它。
要從代碼取得訊息字串,對 FormatMessage 指定 FORMAT_MESSAGE_FROM_SYSTEM 旗標。1
#include <windows.h>
#include <stdio.h>
void PrintLastError(const wchar_t* apiName)
{
DWORD code = GetLastError(); // Call immediately after failure (do not insert another API)
wchar_t message[512] = L"";
FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, code, 0, message, 512, nullptr);
wprintf(L"%s failed: %lu (0x%08lX) %s", apiName, code, code, message);
}
像這樣在自己應用的記錄裡留下 十進位、十六進位與訊息本文,之後的調查會快一步。
flowchart TB
accTitle: 從代碼查出訊息並寫進記錄
accDescr: 對 FormatMessage 指定 FORMAT_MESSAGE_FROM_SYSTEM 旗標以取得錯誤碼的訊息字串,並把十進位、十六進位與訊息本文留在記錄裡
code["錯誤碼(例: 5)"] --> fm["用 FormatMessage 取得字串"]
fm --> msg["訊息本文"]
msg --> log["寫進記錄"]
log -.-> both["十進位、十六進位與本文一起寫"]
圖 4: 用 FormatMessage 把錯誤碼轉成訊息字串,並把十進位、十六進位與本文一起留在記錄裡。
3.2. 現場不斷出現的代表代碼
Win32 錯誤碼定義在 0–15999,Microsoft Learn 有完整清單。1 其中故障調查裡反覆碰面的臉孔如下。
| 十進位 | 十六進位 | 符號 | 意義 |
|---|---|---|---|
| 2 | 0x2 | ERROR_FILE_NOT_FOUND | 找不到指定的檔案 |
| 3 | 0x3 | ERROR_PATH_NOT_FOUND | 找不到指定的路徑 |
| 5 | 0x5 | ERROR_ACCESS_DENIED | 存取被拒絕 |
| 32 | 0x20 | ERROR_SHARING_VIOLATION | 另一個處理程序正在使用,無法存取 |
| 87 | 0x57 | ERROR_INVALID_PARAMETER | 參數不正確 |
| 122 | 0x7A | ERROR_INSUFFICIENT_BUFFER | 傳入的緩衝區太小 |
| 998 | 0x3E6 | ERROR_NOACCESS | 對記憶體位置的無效存取 |
| 1223 | 0x4C1 | ERROR_CANCELLED | 使用者取消了操作 |
其中 998(ERROR_NOACCESS)不是「拒絕存取」,而是 記憶體存取違規的 Win32 表達,也就是稍後會談的 NTSTATUS STATUS_ACCESS_VIOLATION 轉到 Win32 層後的樣子。注意與編號 5 搞混。另外 1223(ERROR_CANCELLED)例如使用者在 UAC 提升對話方塊選「否」時會出現——比較像「被取消」而不是錯誤。
flowchart TB
accTitle: 錯誤 998 與 5 是不同的事
accDescr: 998 是記憶體存取違規,也就是 NTSTATUS 存取違規轉到 Win32 層,與表示拒絕存取的 5 意義不同
nt["NTSTATUS 0xC0000005"] -->|轉到 Win32 層| e998["錯誤 998(ERROR_NOACCESS)"]
e998 -.-> m1["意義是記憶體存取違規"]
e5["錯誤 5(拒絕存取)"] -.-> m2["權限問題。與 998 不同"]
圖 5: 錯誤 998 是 NTSTATUS 存取違規轉到 Win32 層,與拒絕存取的 5 是不同的事。
3.3. 同一代碼會隨脈絡改變意義
比背代表代碼表更重要的感覺是:錯誤碼只告訴你「失敗的種類」。
- 錯誤 5(拒絕存取):原因候選很廣——NTFS ACL 不足、沒有系統管理員權限就寫入受保護區域、防毒或 AppLocker 封鎖、服務帳戶權限不足等。
- 錯誤 2(找不到檔案):不一定是使用者指定的檔案。EXE 隱含要載入的相依 DLL、因登錄重新導向(32 位元/64 位元)而看錯位置的設定檔、環境變數展開失敗的路徑——「哪個檔案」找不到,從代碼看不出來。
- 錯誤 32(共用違規):「哪個處理程序握著它」才是真正的問題,但代碼不會告訴你。
flowchart TB
accTitle: 錯誤 5 的原因由脈絡決定
accDescr: 即使同樣是拒絕存取,也有 ACL 不足或缺少系統管理員權限等數個原因候選,需要特定哪個 API 對什麼失敗
e5["錯誤 5(拒絕存取)"] --> c1["ACL 不足"]
e5 --> c2["缺少系統管理員權限"]
e5 --> c3["安全性產品封鎖"]
e5 --> c4["服務權限偏低"]
c1 --> next["Procmon: 失敗對象"]
c2 --> next
c3 --> next
c4 --> next
圖 6: 代碼只告訴你「失敗的種類」。錯誤 5 有數個原因候選,必須特定對象。
用來量測「哪個 API、對哪個物件名稱、回傳哪個結果」的工具是 Process Monitor。用法詳見「Process Monitor(ProcMon)實戰指南」。查代碼意義與特定失敗對象,是同一輛車的兩個輪子。
4. HRESULT ── 讀懂塞進 32 位元的結構
4.1. 位元配置
HRESULT 是把成敗、來源與詳細代碼塞進單一 32 位元值的格式。公開規格 [MS-ERREF] 以下列配置定義。2
| 位元位置 | 名稱 | 意義 |
|---|---|---|
| 31 | S | Severity。0 = 成功,1 = 失敗 |
| 30 | R | 保留(對應 NTSTATUS 時是 severity 的一部分) |
| 29 | C | Customer 位元。1 表示 Microsoft 以外的人定義的代碼 |
| 28 | N | 1 表示對應進 HRESULT 空間的 NTSTATUS 值 |
| 27 | X | 保留(0) |
| 26–16 | Facility | 表示來源的 facility 代碼(11 位元) |
| 15–0 | Code | facility 內的詳細代碼(16 位元) |
最高位 S 位元為 1,也就是 十六進位記法從 0x8 以上開始的 HRESULT 是失敗。把它當有號 32 位元整數顯示就會是負數——這就是前面說的「-214…」的身分。
flowchart TB
accTitle: S 位元與負數顯示的關係
accDescr: 失敗 HRESULT 的最高位 S 位元為 1,因此十六進位從 0x8 以上開始,以有號 32 位元整數顯示則為負數
s["S 位元 = 1(失敗)"] --> hex["十六進位從 0x8 以上開始"]
hex --> neg["有號顯示為負數"]
neg --> back["看到負數就轉十六進位再讀"]
圖 7: 失敗 HRESULT 因 S 位元為 1 而從 0x8 以上開始,有號顯示為負數。
代表性的 Facility 值如下。5
| Facility | 值 | 十六進位外觀 | 意義 |
|---|---|---|---|
| FACILITY_NULL | 0 | 0x8000xxxx | 廣泛共通的代碼(E_FAIL、E_UNEXPECTED 等) |
| FACILITY_RPC | 1 | 0x8001xxxx | RPC 來源 |
| FACILITY_ITF | 4 | 0x8004xxxx | 介面定義的錯誤(意義依介面而定) |
| FACILITY_WIN32 | 7 | 0x8007xxxx | 包起來的 Win32 錯誤碼 |
| FACILITY_WINDOWS | 8 | 0x8008xxxx | 額外的 Microsoft 定義介面 |
4.2. 分解 0x80004005 與 0x80070005
實際分解看看。
對 0x80004005:S=1(失敗),Facility=(0x80004005 » 16) & 0x7FF = 0(FACILITY_NULL),Code=0x4005。這是通用的 FACILITY_NULL 代碼,定義為 E_FAIL「Unspecified failure」。6 也就是這個代碼 只帶「無法回報細節的失敗」這個意思。看到 0x80004005,就在那裡停止深挖代碼本身,把調查的重量移到「哪個元件回傳的」以及「事件記錄或應用記錄同時間有沒有細節」。
對 0x80070005:S=1,Facility=7(FACILITY_WIN32),Code=0x0005=5。可以看出是 把 Win32 錯誤編號 5(ERROR_ACCESS_DENIED)包成 HRESULT。別名 E_ACCESSDENIED 實質上就是這個值。6
即使同樣是「拒絕存取」,0x80070005 是 Win32 層發生的具體失敗的包裝,資訊量與 0x80004005 完全不同。
flowchart TB
accTitle: 0x80004005 與 0x80070005 的分解
accDescr: 0x80004005 是通用 FACILITY_NULL 代碼 E_FAIL,沒有細節,應轉入脈絡調查;0x80070005 是 FACILITY_WIN32,可看成 Win32 錯誤編號 5 拒絕存取的包裝
a["0x80004005"] --> af["Facility=0(FACILITY_NULL)"]
af --> ac["Code=0x4005 → E_FAIL"]
ac --> ax["未指定失敗。轉入脈絡調查"]
b["0x80070005"] --> bf["Facility=7(FACILITY_WIN32)"]
bf --> bc["Code=0x0005 → 5"]
bc --> bx["ERROR_ACCESS_DENIED"]
圖 8: 同樣是「失敗」,分解後資訊量不同。0x80070005 可以走到 Win32 錯誤編號 5。
4.3. 最重要的模式:0x8007xxxx = HRESULT_FROM_WIN32
要把只能回傳 Win32 錯誤碼的下層失敗,傳給回傳 HRESULT 的上層(COM 方法或 .NET 執行階段),winerror.h 提供 HRESULT_FROM_WIN32 巨集。4 行為是「把 Win32 錯誤碼放進低 16 位元,Facility 設為 FACILITY_WIN32(7),S 位元設為 1」。
flowchart TB
accTitle: HRESULT_FROM_WIN32 如何運作
accDescr: 把 Win32 錯誤碼存進低 16 位元,Facility 設為 7、S 位元設為 1,組出 0x8007xxxx HRESULT
win["Win32 錯誤碼(例: 5)"] --> low["存進低 16 位元"]
low --> fac["Facility 設為 7"]
fac --> sbit["S 位元設為 1"]
sbit --> hr["0x80070005"]
圖 9: HRESULT_FROM_WIN32 把 Win32 錯誤存進低 16 位元,並設定 Facility=7 與 S 位元。
ERROR_ACCESS_DENIED (5) --HRESULT_FROM_WIN32--> 0x80070005
ERROR_SHARING_VIOLATION (32) --HRESULT_FROM_WIN32--> 0x80070020
ERROR_INVALID_PARAMETER (87) --HRESULT_FROM_WIN32--> 0x80070057 (= E_INVALIDARG)
ERROR_OUTOFMEMORY (14) --HRESULT_FROM_WIN32--> 0x8007000E (= E_OUTOFMEMORY)
反向讀取時,在 PowerShell 取低 16 位元。
0x80070005 -band 0xFFFF # 5 → ERROR_ACCESS_DENIED
0x80072EE7 -band 0xFFFF # 12007 → ERROR_INTERNET_NAME_NOT_RESOLVED (WinINet)
如第二個例子,WinINet 與 WinHTTP 錯誤(12000 段)也定義在 Win32 錯誤碼空間1,因此網路方面的 0x8007xxxx 可用同一手續分解。把「看到 0x8007,就把低 4 位轉成十進位」練成肌肉記憶,是本文最希望你帶走的實務技能。
0x8004xxxx(FACILITY_ITF)有相反的注意點。FACILITY_ITF 代碼 每個介面定義意義的單位不同,因此同一個 32 位元值,回傳者不同就可能意思不同。5 對不熟悉的 0x8004xxxx,不要做通用搜尋,而要查回傳元件(程式庫、驅動程式 SDK、伺服器產品)的文件。
flowchart TB
accTitle: 0x8007 與 0x8004 的查法不同
accDescr: FACILITY_WIN32 的 0x8007xxxx 可機械地分解低 16 位元來讀,但 FACILITY_ITF 的 0x8004xxxx 每個介面定義意義的單位不同,所以要查回傳元件的資料
hr{"Facility 是?"} -->|7, WIN32| w["把低 16 位元轉成十進位"]
hr -->|4, ITF| i["意義依回傳者而異"]
w --> ww["當 Win32 錯誤讀"]
i --> ii["查回傳者的資料"]
圖 10: 0x8007xxxx 可機械分解;0x8004xxxx 要查回傳元件的資料。
5. NTSTATUS ── 核心層代碼與當機的世界
5.1. 配置與 Severity
NTSTATUS 是核心、裝置驅動程式與 ntdll 原生 API 使用的 32 位元代碼,配置類似 HRESULT 但並不相同。3
| 位元位置 | 名稱 | 意義 |
|---|---|---|
| 31–30 | Sev | Severity。00 = 成功,01 = 資訊,10 = 警告,11 = 錯誤 |
| 29 | C | Customer 位元 |
| 28 | N | 保留(0,以便對應到 HRESULT) |
| 27–16 | Facility | Facility(12 位元) |
| 15–0 | Code | 詳細代碼 |
因為 severity 是 2 位元,可從前導十六進位數字讀出種類。0xC… 是錯誤(11),0x8… 是警告(10),0x4… 是資訊(01),0x0–0x3… 是成功。中斷點例外 0x80000003(STATUS_BREAKPOINT)是「警告,不是錯誤」的代表例子。37
flowchart TB
accTitle: NTSTATUS 可從前導數字讀出種類
accDescr: 因為 severity 是 2 位元,NTSTATUS 可依前導十六進位數字讀成:0xC 為錯誤、0x8 為警告、0x4 為資訊、0x0 到 0x3 為成功
head{"前導十六進位是?"} -->|0xC| e["錯誤"]
head -->|0x8| w["警告"]
head -->|0x4| i["資訊"]
head -->|0x0–0x3| s["成功"]
w -.-> ex["例: 0x80000003 是警告"]
圖 11: NTSTATUS 可從前導十六進位數字讀出種類。0x80000003 是「警告,不是錯誤」。
5.2. 會遇到的地方 ── 例外代碼、STOP 代碼與事件記錄
IT 人員與開發者遇到 NTSTATUS 的情況,主要與當機有關。
- 應用程式當機的例外代碼:事件記錄「應用程式錯誤(事件識別碼 1000)」裡記錄的「Exception code: 0xc0000005」是 NTSTATUS。代表值如下。7
| 值 | 符號 | 意義 |
|---|---|---|
| 0xC0000005 | STATUS_ACCESS_VIOLATION | 存取違規(不合法的記憶體存取) |
| 0xC0000135 | STATUS_DLL_NOT_FOUND | 找不到必要 DLL,無法啟動 |
| 0xC00000FD | STATUS_STACK_OVERFLOW | 堆疊溢位 |
| 0xC0000374 | STATUS_HEAP_CORRUPTION | 堆積損毀 |
- 藍畫面 STOP 代碼:乍看相似,但 STOP 代碼(錯誤檢查代碼)是 與 NTSTATUS 分開的另一套編號,例如 0x0000009F(DRIVER_POWER_STATE_FAILURE),並有專用參考。14 只要記住「0xC0000005 是 NTSTATUS;STOP 0x9F 是錯誤檢查代碼,不可拿到 NTSTATUS 表查」這個區分就夠了。
- Process Monitor 的 Result 欄:Procmon Result 欄的 NAME NOT FOUND 與 ACCESS DENIED 是核心回傳的 NTSTATUS(STATUS_OBJECT_NAME_NOT_FOUND、STATUS_ACCESS_DENIED)的顯示名稱。也是能感受到層對應的地方:用 NTSTATUS 詞彙觀察檔案 I/O 失敗,那次失敗再轉成 Win32 錯誤到達應用。
flowchart TB
accTitle: 分辨例外代碼與 STOP 代碼
accDescr: 把事件記錄的例外代碼當 NTSTATUS 讀;藍畫面 STOP 代碼則查專用的錯誤檢查代碼參考,那是另一套體系
q{"代碼出現在哪?"} -->|例外代碼| nt["當 NTSTATUS 讀"]
q -->|STOP 代碼| bc["查錯誤檢查代碼表"]
nt -.-> n1["例: 0xC0000005"]
bc -.-> b1["例: 0x0000009F"]
圖 12: 事件記錄的例外代碼是 NTSTATUS;藍畫面 STOP 代碼是另一套體系。不要查錯表。
例外代碼以外的調查,也就是擷取並分析當機傾印,見「Windows 應用的 crash dump 收集入門」與「用 WinDbg + SOS 解讀當機傾印檔」。
5.3. 與 HRESULT 的關係 ── N 位元與 RtlNtStatusToDosError
NTSTATUS 與另外兩層之間的橋有兩條路。
- 對應進 HRESULT 空間:設定 HRESULT N 位元(0x10000000)會把 NTSTATUS 值原樣帶進 HRESULT 空間(winerror.h 的 HRESULT_FROM_NT 巨集)。例如把 0xC0000005 對應後變成 0xD0000005。看到以 0xD 開頭的 HRESULT,正確手續是剝掉 N 位元再當 NTSTATUS 讀。2
- 轉換成 Win32 錯誤碼:ntdll 的
RtlNtStatusToDosError把 NTSTATUS 轉成對應的 Win32 錯誤碼。沒有定義對應的值會變成 ERROR_MR_MID_NOT_FOUND。12 例如 STATUS_ACCESS_VIOLATION(0xC0000005)轉成 ERROR_NOACCESS(998),STATUS_OBJECT_NAME_NOT_FOUND(0xC0000034)轉成 ERROR_FILE_NOT_FOUND(2)。也值得記住:核心豐富的詞彙有時會在 Win32 層被收成較粗的區分。
flowchart TB
accTitle: 從 NTSTATUS 到其他層的兩座橋
accDescr: NTSTATUS 以兩種方式傳到其他層:設定 N 位元對應進 HRESULT 空間,以及由 RtlNtStatusToDosError 轉成 Win32 錯誤碼
nt["NTSTATUS(0xC0000005)"] -->|設定 N 位元| hr["HRESULT(0xD0000005)"]
nt -->|RtlNtStatusToDosError| win["Win32 錯誤 998(ERROR_NOACCESS)"]
win -.-> memo["沒有對應時為 ERROR_MR_MID_NOT_FOUND"]
圖 13: NTSTATUS 的橋有兩座。以 0xD 開頭的,剝掉 N 位元後當 NTSTATUS 讀。
6. COM 與 .NET ── 錯誤碼如何對應到例外
6.1. COM 風格 ── HRESULT + IErrorInfo
COM 方法基本上回傳 HRESULT,但 32 位元能塞的東西有限,因此作為補充,IErrorInfo 機制可以另外傳達錯誤描述字串與來源。在 C++,編譯器支援的 _com_error 類別會一併處理 HRESULT 與 IErrorInfo。錯誤對話方塊顯示「代碼 + 描述」的應用,常常是透過這個機制帶描述。
flowchart TB
accTitle: 補充 HRESULT 的 IErrorInfo
accDescr: 32 位元 HRESULT 能塞的東西有限,因此錯誤描述字串與來源由 IErrorInfo 另外傳達,C++ 裡由 _com_error 類別一併處理
hr["HRESULT(只有 32 位元)"] --> lim["能塞的東西有限"]
lim --> ei["IErrorInfo 帶描述"]
ei --> ce["_com_error 一併處理"]
ce -.-> dlg["對話方塊的代碼 + 描述"]
圖 14: 塞不進 32 位元 HRESULT 的描述字串,由 IErrorInfo 另外帶。
6.2. .NET 風格 ── 從 HRESULT 到例外型別
.NET 執行階段在 COM 互通收到 HRESULT 失敗時,會把它轉成例外。已知的 HRESULT 對應到相應例外型別;未知的變成 COMException。11
flowchart TB
accTitle: 從 HRESULT 到 .NET 例外的對應
accDescr: COM 互通收到的失敗 HRESULT,若有已知對應就轉成相應例外型別,否則轉成 COMException,兩種情況原始值都留在 Exception.HResult
hr["失敗 HRESULT"] --> known{"有已知對應?"}
known -->|Yes| typed["轉成相應例外型別"]
known -->|No| comex["轉成 COMException"]
typed --> keep["原始值留在 Exception.HResult"]
comex --> keep
圖 15: .NET 把 HRESULT 對應到例外型別,每個例外的原始值都留在 Exception.HResult。
| HRESULT | .NET 例外型別 |
|---|---|
| E_ACCESSDENIED (0x80070005) | UnauthorizedAccessException |
| E_OUTOFMEMORY (0x8007000E) | OutOfMemoryException |
| E_INVALIDARG (0x80070057) | ArgumentException |
| E_NOTIMPL (0x80004001) | NotImplementedException |
| 沒有定義對應的值 | COMException(原始值在 ErrorCode 屬性) |
每個例外的原始 HRESULT 都留在 Exception.HResult 屬性。檔案 I/O 例外處理裡「只在共用違規時重試」這類分支,可以用這個值來寫。
try
{
using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.None);
}
catch (IOException ex) when (ex.HResult == unchecked((int)0x80070020))
{
// 0x80070020 = HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION)
// Another process is holding the file — wait a little and retry, for example
}
6.3. P/Invoke 與 GetLastError
經由 P/Invoke 直接呼叫 Win32 API 時,在 DllImport(或 LibraryImport)指定 SetLastError = true,再用 Marshal.GetLastWin32Error 取出(.NET 6 起可用同等的 GetLastPInvokeError)。把 GetLastError 本身定義成 P/Invoke 再呼叫並不準確,因為執行階段內部的 API 呼叫可能覆寫這個值。15
flowchart TB
accTitle: 在 P/Invoke 取出最後錯誤
accDescr: 指定 SetLastError 為 true 再用 Marshal.GetLastWin32Error 取出才正確;直接 P/Invoke GetLastError 會因執行階段覆寫而不準
pi["經 P/Invoke 呼叫 Win32 API"] --> ok["指定 SetLastError=true"]
ok --> get["用 GetLastWin32Error 取出"]
pi --> ng["直接呼叫 GetLastError 的定義"]
ng --> bad["執行階段覆寫,不準確"]
圖 16: 在 P/Invoke 把 SetLastError=true 與 Marshal.GetLastWin32Error 當一組用。直接呼叫 GetLastError 不準確。
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern SafeFileHandle CreateFileW(string fileName, uint access, uint share,
IntPtr security, uint disposition, uint flags, IntPtr template);
// Receive the return value as SafeFileHandle, not IntPtr, and close it reliably with using
// (leaving it as IntPtr leaks a kernel handle)
using var handle = CreateFileW(@"C:\ProgramData\MyApp\config.dat",
0x80000000 /*GENERIC_READ*/, 0, IntPtr.Zero, 3 /*OPEN_EXISTING*/, 0, IntPtr.Zero);
if (handle.IsInvalid)
{
int code = Marshal.GetLastWin32Error(); // Example: 5
var message = new Win32Exception(code).Message; // Example: Access is denied.
logger.LogError("CreateFileW failed: {Code} (0x{Code:X8}) {Message}",
code, code, message);
}
Win32Exception 會從 Win32 錯誤碼查出作業系統訊息字串,因此可直接用來把代碼與訊息一起留在記錄。在哪一層捕捉例外、如何寫進記錄這個設計問題,見「應該在哪裡 catch 例外並輸出日誌、進行錯誤處理」。
7. 實務上的轉換與調查工具 ── 可複製的速查
7.1. err.exe(Microsoft Error Lookup Tool)
Microsoft 發行的獨立錯誤查詢工具。它會走過 winerror.h、ntstatus.h 等大量標頭檔,列出與指定代碼相符的定義與訊息。8
err 0x80070005
err 5
err 0xC0000005
同一個數字可能命中多個標頭(例如「5」除了 Win32 ERROR_ACCESS_DENIED 還會對上各處定義),因此 哪個候選合理必須依脈絡選擇。下載檔名帶版本(撰寫時為 Err_6.4.5.exe),也要注意代碼定義以打包當時的標頭為準。8
flowchart TB
accTitle: err.exe 搜尋結果依脈絡選擇
accDescr: err.exe 走過大量標頭檔並列出相符定義,因此同一數字出現數個候選時,依脈絡選擇合理的那個
in["輸入 err 5"] --> scan["走過大量標頭"]
scan --> hits["命中數個定義"]
hits --> pick["依脈絡選合理候選"]
圖 17: err.exe 是跨標頭搜尋,因此可能出現數個候選,合理的那個依脈絡選擇。
7.2. Windows 內建命令
不必額外安裝就能用的是 certutil 與 net helpmsg。certutil 的 -error 選項會顯示對應錯誤碼的訊息本文,並接受十六進位 HRESULT 或十進位。9
certutil -error 0x80070005
certutil -error 5
net helpmsg 5
net helpmsg 只適用十進位 Win32 錯誤碼,但在中文環境會回傳中文訊息,可直接用來向使用者說明。
flowchart TB
accTitle: 標準命令怎麼選
accDescr: 十進位 Win32 錯誤碼可用 net helpmsg 查;包含十六進位的代碼(含 HRESULT)可用 certutil 的 -error 選項查
q{"手上的代碼是?"} -->|十進位 Win32| net["net helpmsg"]
q -->|含十六進位| cert["certutil -error"]
net -.-> jp["會回傳中文訊息"]
cert -.-> any["十六進位與十進位都接受"]
圖 18: 標準命令怎麼選。十進位 Win32 錯誤用 net helpmsg;含十六進位就用 certutil -error。
7.3. PowerShell 一行指令集
# Win32 error code → OS message string
[System.ComponentModel.Win32Exception]::new(5).Message
# → Access is denied.
# Negative decimal → hex notation (confirm the identity of an HRESULT)
'0x{0:X8}' -f -2147467259 # 0x80004005
# 0x8007xxxx → the Win32 error code in the low 16 bits
0x80070005 -band 0xFFFF # 5
# HRESULT → confirm the exception .NET maps
[System.Runtime.InteropServices.Marshal]::GetExceptionForHR(-2147024891)
# → UnauthorizedAccessException (0x80070005)
# Win32 error code → HRESULT (reproduce the wrap)
'0x{0:X8}' -f (0x80070000 -bor 32) # 0x80070020
7.4. WinDbg 的 !error
在傾印分析中查代碼,WinDbg 的 !error 擴充很快。預設當成 Win32 錯誤碼解讀;第二個引數傳 1 則當成 NTSTATUS。10
0:000> !error 5
Error code: (Win32) 0x5 (5) - Access is denied.
0:000> !error 0xc0000005 1
Error code: (NTSTATUS) 0xc0000005 - <Access violation>
在當機傾印裡,!analyze -v 會自動顯示例外代碼(NTSTATUS),流程是再從那裡用 !error <code> 1 確認意義。
flowchart TB
accTitle: 在 WinDbg 確認例外代碼的流程
accDescr: 當機傾印裡 analyze 命令會自動顯示例外代碼;把該代碼連同第二個引數 1 傳給 error 擴充,以 NTSTATUS 確認意義
dump["開啟當機傾印"] --> an["執行 !analyze -v"]
an --> exc["顯示例外代碼"]
exc --> chk["用 !error code 1 確認意義"]
圖 19: 傾印分析時,用 !error 加上旗標 1 查 !analyze -v 顯示的例外代碼。
8. 調查程序 ── 從判斷層到對照脈絡
把目前的知識組成實際調查錯誤碼的程序。
- 正規化記法。 若是負數十進位,轉成 8 位十六進位。短於 8 位的十六進位前面補 0 再讀。
- 判斷是哪一層的代碼。 如下列表,前幾位幾乎就能決定。
- 分解取出本質代碼。 機械操作:0x8007xxxx 取低 16 位元,0xDxxxxxxx 剝掉 N 位元。
- 用工具查名稱與定義。 用 err.exe、certutil 或
!error確認符號名稱與訊息。 - 對照脈絡。 從應用記錄、事件記錄與 Procmon 特定哪個應用、哪個操作、哪個 API 對什麼失敗。代碼是「失敗的種類」;脈絡是「原因的位置」。
flowchart TB
accTitle: 調查錯誤碼的程序
accDescr: 把記法對齊到十六進位、從前幾位判斷層、分解取出本質代碼、用工具查名稱與定義,再對照脈絡的調查型
fix["正規化成十六進位"] --> judge{"前導數字?"}
judge -->|十進位| d1["當 Win32 錯誤讀"]
judge -->|0x8007| d2["低 16 位元 → 十進位"]
judge -->|0xC| d3["當 NTSTATUS 讀"]
judge -->|0xD| d4["剝掉 N 位元再讀"]
d1 --> tool["查名稱/定義"]
d2 --> tool
d3 --> tool
d4 --> tool
tool --> ctx["對照脈絡(Procmon)"]
圖 20: 調查的型。正規化記法、判斷層並分解、查名稱,再對照脈絡。
| 外觀 | 第一候選 | 如何分解與轉換 |
|---|---|---|
| 1 到 5 位十進位(5、1223 等) | Win32 錯誤碼 | 原樣給 net helpmsg 或 err.exe |
| 負數十進位(-2147024891 等) | HRESULT | 轉成 8 位十六進位,再依下面各列判斷 |
| 0x8007xxxx | HRESULT(FACILITY_WIN32) | 低 16 位元轉十進位,當 Win32 讀 |
| 0x8004xxxx | HRESULT(FACILITY_ITF) | 查回傳元件的文件 |
| 0x8000xxxx | HRESULT(FACILITY_NULL) | E_FAIL 等通用代碼。把重量移到脈絡調查 |
| 0xCxxxxxxx | NTSTATUS(錯誤) | !error <code> 1;必要時轉成 Win32 再讀 |
| 0xDxxxxxxx | NTSTATUS 的 HRESULT 對應 | 剝掉 N 位元(0x10000000)當 NTSTATUS 讀 |
| 0x8024xxxx 等自有 facility | 功能領域特有的 HRESULT | 從 Facility 值鎖定領域再查專用資料(0x8024… 是 Windows Update)2 |
第 5 步「對照脈絡」特別有效的是 Process Monitor 的 Result 欄。即使應用只顯示「0x80070002」,Procmon 也能用一列告訴你「哪個處理程序、對哪條路徑、被回傳 NAME NOT FOUND」。事件記錄側的查法見「Windows 事件記錄・ETW 入門」。
9. 常見誤讀 ── 讓調查繞遠路的模式
最後是實際諮詢裡出現的誤讀模式。
誤讀 1:以為 0x80004005 是「指出特定原因的代碼」
E_FAIL 是「Unspecified failure」,同一個值會出現在 Windows Update、網路與資料庫。把搜這個代碼得到的每項補救都試一遍,幾乎一定是繞遠路。不要從代碼縮小,而要從「哪個應用、哪個操作、同時間的其他記錄」縮小。6
誤讀 2:沒發現負數十進位是 HRESULT
把寫著「Error -2147467259 occurred」的記錄原樣搜尋,或被「負號錯誤?」搞混的情況。看到負數就轉成十六進位。光這樣就知道是 0x80004005(E_FAIL),並接到誤讀 1 的知識。
誤讀 3:查 0x8007xxxx 的整段 8 位,卻不看底下的 Win32 錯誤
0x80070005 的本質是「5 = 拒絕存取」。取出低 16 位元後思考「在這個操作的脈絡裡 Win32 錯誤 5 代表什麼」,比搜整段 8 位更快碰到核心。
誤讀 4:假定「同一代碼 = 同一原因」
一旦經歷過「錯誤 5 是防毒造成的」,下一次錯誤 5 就容易跳到同一補救。即使代碼相同,失敗的 API 與目標資源不同,原因就是另一件事。確認代碼意義,再用 Procmon 等特定對象,每一次都是一組。
誤讀 5:把 Win32 錯誤 5 與 0xC0000005、STOP 代碼與 NTSTATUS 搞混
因為「5」這個連結就把 ERROR_ACCESS_DENIED 與 STATUS_ACCESS_VIOLATION 當成同一件事,會把調查送到完全不同的方向——權限問題對上程式錯誤。另外,藍畫面 STOP 代碼與 NTSTATUS 是另一套體系,把 0x9F 拿到 NTSTATUS 表查不會得到有意義的答案。14
flowchart TB
accTitle: 錯誤 5 與 0xC0000005 的調查方向不同
accDescr: Win32 錯誤 5 應當權限問題調查,NTSTATUS 0xC0000005 應當程式錯誤調查;把它們當成同一件事會把調查送到不同方向
a["Win32 錯誤 5"] --> ad["調查權限問題"]
b["NTSTATUS 0xC0000005"] --> bd["調查程式錯誤"]
a -.-> memo["不同體系、互不相干的代碼"]
b -.-> memo
圖 21: 不要因為「5」這個連結就當成同一件事。錯誤 5 走向權限問題;0xC0000005 走向程式錯誤。
10. 摘要
- Windows 錯誤碼是 Win32 錯誤碼、HRESULT、NTSTATUS 的三層結構。先判斷是哪一層、誰回傳的代碼。
- 記法搖擺(十進位/十六進位/負數)可以機械地對齊。把負數轉成 8 位十六進位再讀。
- HRESULT 是 S/R/C/N/X 位元 + Facility(11 位元)+ Code(16 位元)的結構,0x8007xxxx 是最重要的模式:包起來的 Win32 錯誤。把低 16 位元轉成十進位,取出本質代碼。
- 0x80004005(E_FAIL)這類通用代碼不指出原因。停止深挖代碼、改做脈絡調查的判斷,正是因為知道結構才做得到。
- 會在當機例外代碼或 Procmon 的 Result 欄遇到 NTSTATUS。0xC0000005 是存取違規,與 Win32 錯誤 5 無關。STOP 代碼又是另一套體系。
- 在 .NET,HRESULT 對應到例外型別,原始值留在 Exception.HResult。在 P/Invoke 把 SetLastError=true 與 Marshal.GetLastWin32Error 當一組用。
- 查詢工具是 certutil -error 與 net helpmsg(標準)、err.exe(開發機)、PowerShell 一行指令,以及 WinDbg 的 !error。
- 程序是「正規化記法 → 判斷層 → 分解 → 查名稱 → 對照脈絡」。代碼告訴你的是失敗的種類;原因的位置由脈絡告訴你。
下次遇到不熟悉的錯誤碼,貼進搜尋框之前先看前幾位。0x8007 就看低 4 位,0xC 就是 NTSTATUS,負數就轉十六進位——這 10 秒的分解,很大程度上決定後面的調查時間。
相關文章
- 用 WinDbg + SOS 解讀當機傾印檔 ── 收集之後的實務分析入門
- Windows 應用的 crash dump 收集入門 - 先搞清楚 WER / ProcDump / WinDbg 怎麼分工
- 應該在哪裡 catch 例外並輸出日誌、進行錯誤處理
- Process Monitor(ProcMon)實戰指南 ── 在10分鐘內找出「設定未被讀取」「ACCESS DENIED」的原因
- Windows 事件記錄・ETW 入門 ── 讓業務應用程式的日誌搭上 OS 標準機制
相關諮詢領域
小村軟體有限公司承接從錯誤碼開始的故障調查——「不知道這個錯誤碼是什麼意思」、「0x80070005 只在特定環境出現」——以及混用 Win32 API、COM、.NET 的應用錯誤處理設計,還有用當機傾印與 Process Monitor 鎖定原因。從錯誤對話方塊的一張螢幕擷取開始諮詢也可以。
參考連結
-
Microsoft Learn, Debug system error codes. 關於 Win32 系統錯誤碼(0–15999)清單索引、用 FormatMessage 與 FORMAT_MESSAGE_FROM_SYSTEM 旗標取得
GetLastError回傳代碼的訊息、WinINet/WinHTTP 錯誤(12000 段)定義在此空間,以及用 Microsoft Error Lookup Tool 與 !err 命令調查的方法。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 -
Microsoft Open Specifications, [MS-ERREF]: HRESULT. 關於 HRESULT 位元配置(S、R、C、N、X 位元,11 位元 Facility,16 位元 Code)、N 位元表示對應進 HRESULT 空間的 NTSTATUS 值,以及包含 FACILITY_WINDOWS_UPDATE(36)的 facility 代碼清單。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Open Specifications, [MS-ERREF]: NTSTATUS. 關於 NTSTATUS 位元配置(2 位元 Sev、C 位元、N 位元、12 位元 Facility、16 位元 Code),以及 severity 分成成功(00)、資訊(01)、警告(10)、錯誤(11)四種。 ↩ ↩2 ↩3
-
Microsoft Learn, HRESULT_FROM_WIN32 macro. 關於把 Win32 系統錯誤碼對應到 HRESULT 值的 winerror.h 巨集定義。 ↩ ↩2 ↩3
-
Microsoft Learn, Structure of COM Error Codes. 關於 HRESULT severity 位元與 facility 欄位的角色、FACILITY_NULL、FACILITY_RPC、FACILITY_ITF、FACILITY_WIN32、FACILITY_WINDOWS 的值,以及 FACILITY_ITF 代碼依介面定義意義、同一值可能意思不同。 ↩ ↩2 ↩3
-
Microsoft Learn, Common HRESULT values. 關於 E_FAIL(0x80004005)是「Unspecified failure」,以及 E_ACCESSDENIED(0x80070005)、E_INVALIDARG(0x80070057)、E_OUTOFMEMORY(0x8007000E)等常見 HRESULT 值的定義。 ↩ ↩2 ↩3 ↩4
-
Microsoft Open Specifications, [MS-ERREF]: NTSTATUS values. 關於包含 STATUS_ACCESS_VIOLATION(0xC0000005)、STATUS_DLL_NOT_FOUND(0xC0000135)、STATUS_STACK_OVERFLOW(0xC00000FD)、STATUS_HEAP_CORRUPTION(0xC0000374)、STATUS_BREAKPOINT(0x80000003)的 NTSTATUS 值清單。 ↩ ↩2 ↩3
-
Microsoft Learn, The Microsoft Error Lookup Tool. 關於這是橫跨 Winerror.h 等各種標頭檔、顯示十六進位狀態代碼對應訊息本文的獨立工具、下載檔名為 Err_6.4.5.exe,以及打包定義以編譯當下為準這件事需要注意。 ↩ ↩2 ↩3
-
Microsoft Learn, certutil. 關於 certutil 的 -error 選項顯示錯誤碼對應的訊息本文,以及使用包含符號名稱的錯誤記法,例如 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND)。 ↩ ↩2
-
Microsoft Learn, !error. 關於 WinDbg 的 !error 擴充解碼並顯示 Win32、Winsock、NTSTATUS、NetAPI 錯誤值,以及指定旗標 1 時當成 NTSTATUS 解讀。 ↩ ↩2
-
Microsoft Learn, How to: Map HRESULTs and exceptions. 關於 COM HRESULT 與 .NET 例外的相互對應機制、E_NOTIMPL → NotImplementedException 等對應表、沒有明確對應的 HRESULT 會轉成 COMException,以及例外的 Message、Source 等從 IErrorInfo 資訊初始化。 ↩ ↩2
-
Microsoft Learn, RtlNtStatusToDosError function (winternl.h). 關於此函式把 NTSTATUS 代碼轉成對應的 Win32 系統錯誤碼、沒有定義對應時回傳 ERROR_MR_MID_NOT_FOUND,以及不存在反向轉換函式。 ↩ ↩2
-
Microsoft Learn, Last-Error Code. 關於最後錯誤碼依執行緒持有、應在失敗後立刻用 GetLastError 取出、成功時把代碼覆寫成 0 的 API 與不動它的 API 並存,以及位元 29 保留給應用程式定義代碼。 ↩ ↩2
-
Microsoft Learn, Bug check code reference. 關於藍畫面顯示的錯誤檢查代碼(STOP 代碼)清單,以及用 WinDbg 的 !analyze 擴充顯示代碼資訊的方法。從清單可確認它是與 NTSTATUS 分開的另一套編號。 ↩ ↩2
-
Microsoft Learn, Marshal.GetLastWin32Error Method. 關於取出設定了 SetLastError 旗標的 P/Invoke 呼叫最後錯誤碼的方法、直接 P/Invoke GetLastError 會因執行階段內部 API 呼叫覆寫而不可靠,以及從 .NET 6 起建議使用 GetLastPInvokeError。 ↩
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
從睡眠恢復就壞掉的應用程式 ── Windows 電源事件的機制,以及耐得住恢復的業務應用寫法
打開筆電,業務應用程式的連線卻斷了──原因是設計從未把睡眠算進去。本文依一次資訊整理 WM_POWERBROADCAST 的通知流程、Modern Standby 的行為、斷線/重連設計、睡眠抑制,以及調查指令。
DllMain 與載入器鎖定 ── 「DLL 初始化什麼都別做」真正的理由
為什麼不能從 DllMain 呼叫 LoadLibrary,也不能和其他執行緒同步。本文依一次資訊說明載入器鎖定如何序列化每個 DLL 通知、結構上必定成立的死結場景、延遲初始化的正確設計,以及無回應的調查方式。
「沒有回應」的真面目 ── Windows 如何判定應用程式當住,以及不卡住的設計
Windows 的「沒有回應」是作業系統判定視窗 5 秒未取出訊息後,換成幽靈視窗的機制。本文涵蓋該判定的內部、無回應的經典原因、把重工作移出 UI 執行緒的設計,以及調查無回應的程序。
Win32 執行緒集區 API ── 用 CreateThreadpoolWork 做「不自己建立執行緒」的並行
原生程式碼裡到處呼叫 CreateThread 嗎?本文依一次資訊解說 Vista 重新設計的 Win32 執行緒集區 API──work、timer、wait、io 四種物件、清理群組,以及回呼裡不該做的事。
具名管道實務 ── 從設計到安全性,整理 Windows 的標準 IPC
以實務角度解說 Windows 標準行程間通訊──具名管道。從一次資訊整理位元組/訊息模式的選擇、同時服務多個用戶端的伺服器設計、ACL 與偽裝的安全性,以及 .NET 的具名管道串流。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
故障調查 & 長期運行故障
整理間歇性故障、通訊診斷、長期運行當機、失敗路徑測試基礎的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
故障調查 & 根本原因分析
調查難以重現的故障、長時間執行後的問題、記憶體洩漏、通訊停滯等棘手的正式環境問題。
常見問題
整理諮詢這個主題時常見的問題。
- 錯誤 0x80004005 是什麼意思?
- 0x80004005 是 HRESULT E_FAIL,意思是「Unspecified failure(未指定的失敗)」。也就是只表示「發生了無法回報詳細原因的失敗」,並不是代表原因本身的代碼。網路、Windows Update、VBA、資料庫驅動程式等互不相干的場合會出現同一個 0x80004005,正是這個緣故。看到這個代碼時,不要深挖代碼本身的意思,而要從哪個應用、哪個操作產生它的脈絡,以及事件記錄或詳細記錄裡留下的其他錯誤資訊來縮小原因。
- 像 -2147467259 這種負數錯誤碼是什麼?
- 那是把 32 位元 HRESULT 以有號十進位顯示的結果。HRESULT 在失敗時會把最高位設為 1,因此以有號整數顯示時一定是負數。在 PowerShell 執行 '0x{0:X8}' -f -2147467259 就能還原成十六進位(此例為 0x80004005 = E_FAIL)。在記錄或指令碼錯誤訊息裡看到以 -214… 開頭的負數時,標準第一步是先轉成十六進位再查詢。
- 查詢錯誤碼意義最簡單的方法是什麼?
- 不必額外安裝就能用的是命令提示字元的 net helpmsg 5(十進位 Win32 錯誤)和 certutil -error 0x80070005。certutil 也接受十六進位 HRESULT,並顯示符號名稱與訊息本文。PowerShell 可用 [System.ComponentModel.Win32Exception]::new(5).Message 取得當地語系訊息。開發機上請備妥 Microsoft 官方錯誤查詢工具 err.exe(Microsoft Error Lookup Tool),它能橫跨 Win32、HRESULT、NTSTATUS 一次列出相符定義。
- 0xC0000005 是什麼樣的錯誤?
- 那是 NTSTATUS STATUS_ACCESS_VIOLATION,也就是存取違規(不合法的記憶體存取)。應用程式當機時,事件記錄的「例外代碼」或當機傾印裡最常看到這個代碼,表示無效指標解參考或存取已釋放記憶體等程式錯誤。名稱看起來像 Win32 錯誤 5(ERROR_ACCESS_DENIED = 拒絕存取),但是另一套體系、互不相干的代碼,不要搞混。要鎖定原因,可靠做法是擷取當機傾印並用 WinDbg 分析。
- 為什麼同一個錯誤碼每次原因都不一樣?
- 因為錯誤碼只表示「哪一種失敗」,「什麼失敗、為什麼失敗」由呼叫脈絡決定。例如錯誤 5(拒絕存取)可以是 NTFS 權限不足、缺少系統管理員權限、防毒封鎖等完全不同的原因卻得到同一個代碼。類似情況下若另一個處理程序仍開著檔案,會得到不同代碼(錯誤 32 = 共用違規),正確讀代碼就會改變調查位置。錯誤 2(找不到檔案)也常常不是主檔,而是相依 DLL 或設定檔。查過代碼意義後,再用 Process Monitor 等確認哪個 API 對哪個資源失敗,才是鎖定原因的捷徑。