Đọc mã lỗi Windows — cấu trúc ba lớp Win32, HRESULT và NTSTATUS
· Go Komura · Windows, Mã lỗi, HRESULT, NTSTATUS, Win32 API, Khắc phục sự cố, Gỡ lỗi, Phát triển Windows
“Màn hình ứng dụng hiện lỗi 0x80004005. Nghĩa là gì?” — Trong tư vấn điều tra sự cố, câu hỏi kiểu này là kinh điển. Ai từng dán số từ hộp thoại lỗi thẳng vào công cụ tìm kiếm rồi bị một loạt bài không liên quan chào đón — lỗi Windows Update, thư mục dùng chung không kết nối, lỗi thời gian chạy VBA, lỗi kết nối cơ sở dữ liệu — và càng rối hơn, có rất nhiều người cùng cảnh.
Điều đó xảy ra vì 0x80004005 (E_FAIL) là mã chung, nghĩa duy nhất là “thất bại không chỉ định”. Cùng mã được dùng trong vô số tình huống, nên tìm chỉ theo mã không đến được nguyên nhân. Ngược lại, mã như 0x80070005, nếu bạn biết cấu trúc, có thể tách trong vài giây trước khi tìm thành “số lỗi Win32 5 = từ chối truy cập, bọc thành HRESULT”.
Mã lỗi Windows, vì lý do lịch sử, tạo ba lớp — mã lỗi Win32, HRESULT và NTSTATUS — và được chuyển giữa các lớp. Khi cấu trúc đó nằm trong đầu, bạn tự phán được “lớp nào, bên nào trả mã này” và “mã cốt yếu là gì”, và nước cờ mở đầu điều tra trở nên nhanh hơn nhiều.
Hướng tới nhân viên CNTT doanh nghiệp vừa và nhỏ cùng nhà phát triển ứng dụng Windows, bài viết này sắp xếp cách phân biệt và tách ba hệ mã lỗi, quan hệ với ngoại lệ .NET, và tra cứu thực tế bằng err.exe cùng PowerShell — dựa trên Microsoft Learn và đặc tả công bố [MS-ERREF] tính đến tháng 8 năm 2026.
1. Kết luận trước
- Mã lỗi Windows chủ yếu là ba hệ. Mã lỗi Win32 (thập phân nhỏ mà
GetLastErrortrả), HRESULT (mã 32 bit từ COM trở đi, hex bắt đầu 0x8 hoặc thập phân âm), và NTSTATUS (mã lớp hạt nhân; lỗi bắt đầu 0xC).123 - Thập phân và thập lục phân là ký hiệu khác của cùng mã. “Lỗi 5”, “0x5” và “16 bit thấp của 0x80070005” đều chỉ ERROR_ACCESS_DENIED (từ chối truy cập).1
- 0x8007xxxx là “lỗi Win32 được bọc”. Đó là mã lỗi Win32 nằm trong HRESULT FACILITY_WIN32 (7); chuyển 16 bit thấp sang thập phân là có mã cốt yếu. Đây là mẫu quan trọng nhất khi đọc mã lỗi.45
- 0x80004005 (E_FAIL) không phải mã nguyên nhân. Nó nghĩa là “Unspecified failure” và không mang thêm thông tin. Thay vì đào mã này, hãy tìm ngữ cảnh nguồn và nhật ký kèm theo.6
- Thập phân âm (-2147467259 và tương tự) là HRESULT. Bit có trọng số cao nhất của 32 bit (bit thất bại) được đặt, nên hiển thị có dấu là âm. Chuyển sang hex rồi đọc.2
- Giá trị 8 chữ số bắt đầu 0xC là NTSTATUS. 0xC0000005 (vi phạm truy cập) và 0xC0000135 (không tìm thấy DLL) xuất hiện liên tục trong nhật ký sự kiện và dump lúc sập. Chúng không liên quan tới số lỗi Win32 5.7
- Cùng mã đổi nghĩa theo ngữ cảnh. Nguyên nhân lỗi 5 trải từ ACL, nâng quyền, antivirus, tệp bị giữ, và nữa, còn “không tìm thấy tệp” của lỗi 2 thường là DLL phụ thuộc. Luôn đọc nghĩa mã cùng API nào thất bại với cái gì.1
- Công cụ chuyển và tra là chuẩn.
certutil -errorvànet helpmsgcó sẵn trong Windows;Win32Exceptioncủa PowerShell lấy thông báo; trên máy phát triển err.exe (Microsoft Error Lookup Tool); khi phân tích dump là!errorcủa WinDbg.8910 - Trong .NET, HRESULT được ánh xạ sang kiểu ngoại lệ. HRESULT đã biết đi tới kiểu tương ứng (E_ACCESSDENIED → UnauthorizedAccessException, v.v.); chưa biết thành COMException; giá trị gốc còn trong
Exception.HResult.11
Một câu, mẫu điều tra mã lỗi Windows là “chỉnh ký hiệu về hex → phán lớp nào là mã → tách và lấy mã cốt yếu → đọc cùng ngữ cảnh”.
2. Windows có ba hệ mã lỗi
Trước hết, bản đồ tổng thể. Mã lỗi Windows chia chủ yếu thành ba hệ sau, theo lớp trả chúng.
| Hệ | Bên chính trả | Dáng điển hình | Ví dụ đại diện |
|---|---|---|---|
| Mã lỗi Win32 | API Win32 (GetLastError), mã thoát lệnh |
Thập phân nhỏ (0–15999) | 5 = ERROR_ACCESS_DENIED |
| HRESULT | Thành phần COM, shell, trình cài, nhiều khung | Hex 8 chữ số bắt đầu 0x8, hoặc thập phân âm | 0x80004005 = E_FAIL |
| NTSTATUS | Hạt nhân, trình điều khiển, API gốc (ntdll) | Lỗi là hex 8 chữ số bắt đầu 0xC | 0xC0000005 = STATUS_ACCESS_VIOLATION |
Lịch sử chúng xếp chồng theo thứ tự này: mã lỗi Win32 thừa hưởng số lỗi MS-DOS, NTSTATUS mà hạt nhân NT dùng nội bộ, và HRESULT được thiết kế khi đưa COM vào để “gói thành công/thất bại và nguồn gốc vào 32 bit”. Trên Windows hiện tại, luồng chuyển là hàng ngày: hạt nhân trả NTSTATUS, hệ thống con Win32 chuyển thành mã lỗi Win32, và lớp COM bọc tiếp thành HRESULT.124
flowchart TB
accTitle: Luồng chuyển giữa ba hệ
accDescr: Hệ thống con Win32 chuyển NTSTATUS mà hạt nhân trả thành mã lỗi Win32, và lớp COM bọc tiếp thành HRESULT
kernel["Hạt nhân và trình điều khiển"] --> nt["NTSTATUS(lỗi 0xC…)"]
nt -->|Hệ thống con Win32 chuyển| win["Mã lỗi Win32(5 và tương tự)"]
win -->|Lớp COM bọc| hr["HRESULT(0x8007xxxx)"]
Hình 1: Luồng chuyển giữa các lớp. NTSTATUS hạt nhân thành lỗi Win32, rồi được bọc tiếp thành HRESULT.
2.1. Quen đọc thập phân và thập lục phân thay nhau
Trước khi phân biệt ba hệ, bạn cần hấp thụ dao động ký hiệu. Cùng mã được hiện thập phân hoặc hex tùy tình huống.
- “Lỗi 5”, “mã lỗi: 0x5” → cùng ERROR_ACCESS_DENIED
- “Lỗi 1223”, “0x4C1” → cùng ERROR_CANCELLED
- “0x80070005”, “-2147024891” → cùng HRESULT
Trong PowerShell việc chuyển là một dòng.
# Decimal → hex
'0x{0:X8}' -f 1223 # 0x000004C1
'0x{0:X8}' -f -2147024891 # 0x80070005 (negative = HRESULT to hex)
# Hex → decimal
0x4C1 # 1223
Khi thấy thập phân âm bắt đầu “-214…”, hãy phản xạ chuyển sang hex. Chỉ thế đã cắt nhiều lạc lối ở cửa vào điều tra.
flowchart TB
accTitle: Ba dáng của cùng mã
accDescr: Lỗi thập phân 5, hex 0x5 và 16 bit thấp của 0x80070005 đều chỉ cùng ERROR_ACCESS_DENIED
d["Ký hiệu thập phân: lỗi 5"] --> same["ERROR_ACCESS_DENIED"]
h["Ký hiệu hex: 0x5"] --> same
l["16 bit thấp của 0x80070005"] --> same
same -.-> memo["Ký hiệu khác, cùng mã"]
Hình 2: Thập phân, hex và 16 bit thấp của HRESULT chỉ là ký hiệu khác của cùng mã.
3. Mã lỗi Win32 — GetLastError và FORMAT_MESSAGE
3.1. Hành vi cơ bản của GetLastError
Nhiều API Win32 như CreateFile và RegOpenKeyEx báo thất bại bằng giá trị trả (FALSE, NULL, INVALID_HANDLE_VALUE, v.v.) và lưu mã lỗi chi tiết trong “last-error code” giữ theo luồng. Bên gọi lấy bằng GetLastError ngay sau khi xác nhận thất bại.13
Có hai lưu ý thực tế.13
- Đọc ngay sau thất bại. Nếu chèn thêm lời gọi API khác (hàm ghi nhật ký, chẳng hạn) ở giữa, lời gọi đó có thể ghi đè last-error code.
- Đừng dựa vào giá trị khi thành công. Một số API xóa last-error code về 0 khi thành công; một số không đụng. Quy tắc là xác nhận thất bại từ giá trị trả rồi đọc.
sequenceDiagram
accTitle: Đọc GetLastError ngay sau thất bại
accDescr: Sau khi xác nhận thất bại từ giá trị trả, lấy last-error code bằng GetLastError ngay, không chèn lời gọi API khác
participant app as App
participant api as Win32 API
app->>api: Lời gọi CreateFile
api-->>app: Giá trị trả thất bại
app->>api: GetLastError
api-->>app: Mã 5
Note over app: Chèn API khác ở giữa có thể ghi đè
Hình 3: Đọc last-error code ngay sau thất bại. Chèn lời gọi API khác ở giữa có thể ghi đè.
Để lấy chuỗi thông báo từ mã, dùng FormatMessage với cờ 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);
}
Để lại cả thập phân lẫn hex và văn bản thông báo trong nhật ký ứng dụng của bạn, như thế này, làm điều tra sau nhanh hơn một bước.
flowchart TB
accTitle: Tra thông báo từ mã và để trong nhật ký
accDescr: Truyền cờ FORMAT_MESSAGE_FROM_SYSTEM cho FormatMessage để lấy chuỗi thông báo của mã lỗi, và để thập phân, hex cùng văn bản trong nhật ký
code["Mã lỗi(ví dụ: 5)"] --> fm["Lấy chuỗi bằng FormatMessage"]
fm --> msg["Văn bản thông báo"]
msg --> log["Ghi vào nhật ký"]
log -.-> both["Viết thập phân, hex và văn bản cùng nhau"]
Hình 4: Chuyển mã lỗi thành chuỗi thông báo bằng FormatMessage, và để thập phân, hex cùng văn bản trong nhật ký.
3.2. Các mã đại diện xuất hiện liên tục ngoài hiện trường
Mã lỗi Win32 được định nghĩa trong khoảng 0–15999, và Microsoft Learn có danh sách đầy đủ.1 Trong số đó, những khuôn mặt bạn gặp đi gặp lại khi điều tra sự cố là sau.
| Thập phân | Hex | Ký hiệu | Nghĩa |
|---|---|---|---|
| 2 | 0x2 | ERROR_FILE_NOT_FOUND | Không tìm thấy tệp đã chỉ định |
| 3 | 0x3 | ERROR_PATH_NOT_FOUND | Không tìm thấy đường dẫn đã chỉ định |
| 5 | 0x5 | ERROR_ACCESS_DENIED | Truy cập bị từ chối |
| 32 | 0x20 | ERROR_SHARING_VIOLATION | Tiến trình khác đang dùng, không thể truy cập |
| 87 | 0x57 | ERROR_INVALID_PARAMETER | Tham số không đúng |
| 122 | 0x7A | ERROR_INSUFFICIENT_BUFFER | Bộ đệm truyền vào quá nhỏ |
| 998 | 0x3E6 | ERROR_NOACCESS | Truy cập không hợp lệ tới vị trí bộ nhớ |
| 1223 | 0x4C1 | ERROR_CANCELLED | Thao tác bị người dùng hủy |
Trong số này, 998 (ERROR_NOACCESS) không phải “từ chối truy cập” mà là cách Win32 nói vi phạm truy cập bộ nhớ, hình dạng của NTSTATUS STATUS_ACCESS_VIOLATION bàn sau khi đã chuyển sang lớp Win32. Cẩn thận nhầm với số 5. Cũng vậy, 1223 (ERROR_CANCELLED) là mã xuất hiện khi người dùng chọn “Không” trên hộp thoại nâng quyền UAC, chẳng hạn — nghiêng về “đã hủy” hơn là lỗi.
flowchart TB
accTitle: Lỗi 998 và 5 là việc khác nhau
accDescr: 998 là vi phạm truy cập bộ nhớ, vi phạm truy cập NTSTATUS đã chuyển sang lớp Win32, và khác nghĩa với 5 đại diện từ chối truy cập
nt["NTSTATUS 0xC0000005"] -->|Chuyển sang lớp Win32| e998["Lỗi 998(ERROR_NOACCESS)"]
e998 -.-> m1["Nghĩa là vi phạm truy cập bộ nhớ"]
e5["Lỗi 5(từ chối truy cập)"] -.-> m2["Vấn đề quyền. Việc khác với 998"]
Hình 5: Lỗi 998 là vi phạm truy cập NTSTATUS đã chuyển sang lớp Win32, việc khác với từ-chối-truy-cập 5.
3.3. Cùng mã đổi nghĩa theo ngữ cảnh
Quan trọng hơn thuộc bảng mã đại diện là cảm giác rằng mã lỗi chỉ nói “loại thất bại”.
- Lỗi 5 (từ chối truy cập): ứng viên nguyên nhân rộng — ACL NTFS thiếu, ghi vùng bảo vệ không có đặc quyền quản trị, chặn antivirus hoặc AppLocker, đặc quyền tài khoản dịch vụ thiếu, v.v.
- Lỗi 2 (không tìm thấy tệp): không nhất thiết là tệp người dùng chỉ định. DLL phụ thuộc mà EXE cố tải ngầm, tệp cấu hình thấy sai chỗ vì chuyển hướng registry (32 bit/64 bit), đường dẫn mà mở rộng biến môi trường thất bại — “tệp nào” không tìm thấy không thấy từ mã.
- Lỗi 32 (vi phạm chia sẻ): “tiến trình nào đang giữ” là câu hỏi thật, nhưng mã không nói điều đó.
flowchart TB
accTitle: Nguyên nhân lỗi 5 do ngữ cảnh quyết định
accDescr: Cùng từ chối truy cập vẫn có vài ứng viên nguyên nhân như ACL thiếu hoặc thiếu đặc quyền quản trị, và cần xác định API nào thất bại với cái gì
e5["Lỗi 5(từ chối truy cập)"] --> c1["ACL thiếu"]
e5 --> c2["Thiếu quyền quản trị"]
e5 --> c3["Chặn sản phẩm bảo mật"]
e5 --> c4["Đặc quyền dịch vụ thấp"]
c1 --> next["Procmon: đích thất bại"]
c2 --> next
c3 --> next
c4 --> next
Hình 6: Mã chỉ nói “loại thất bại”. Lỗi 5 có vài ứng viên nguyên nhân, và cần xác định đích.
Công cụ đo “API nào, với tên đối tượng nào, trả kết quả nào” là Process Monitor. Cách dùng được bàn chi tiết trong “Hướng dẫn thực hành Process Monitor (ProcMon)”. Tra nghĩa mã lỗi và xác định đích thất bại là hai bánh cùng một xe.
4. HRESULT — Đọc cấu trúc đóng gói vào 32 bit
4.1. Bố cục bit
HRESULT là định dạng đóng gói thành công/thất bại, nguồn gốc và mã chi tiết vào một giá trị 32 bit. Đặc tả công bố [MS-ERREF] định nghĩa bằng bố cục sau.2
| Vị trí bit | Tên | Nghĩa |
|---|---|---|
| 31 | S | Severity. 0 = thành công, 1 = thất bại |
| 30 | R | Dự trữ (một phần severity khi ánh xạ NTSTATUS) |
| 29 | C | Bit Customer. 1 nghĩa là mã do người khác Microsoft định nghĩa |
| 28 | N | 1 nghĩa là giá trị NTSTATUS được ánh xạ vào không gian HRESULT |
| 27 | X | Dự trữ (0) |
| 26–16 | Facility | Mã facility chỉ nguồn gốc (11 bit) |
| 15–0 | Code | Mã chi tiết trong facility (16 bit) |
Bit S có trọng số cao nhất là 1, tức HRESULT mà ký hiệu hex bắt đầu từ 0x8 trở lên là thất bại. Hiển thị như số nguyên 32 bit có dấu làm nó âm — đó là thân phận của “-214…” đã nói.
flowchart TB
accTitle: Quan hệ bit S và hiển thị âm
accDescr: HRESULT thất bại đặt bit S có trọng số cao nhất thành 1, nên hex bắt đầu từ 0x8 trở lên, và dưới dạng số nguyên 32 bit có dấu là âm
s["Bit S = 1(thất bại)"] --> hex["Hex bắt đầu từ 0x8 trở lên"]
hex --> neg["Hiển thị có dấu là âm"]
neg --> back["Thấy âm thì chuyển hex rồi đọc"]
Hình 7: HRESULT thất bại bắt đầu từ 0x8 trở lên vì bit S là 1, và hiển thị có dấu là âm.
Các giá trị Facility đại diện như sau.5
| Facility | Giá trị | Dáng hex | Nghĩa |
|---|---|---|---|
| FACILITY_NULL | 0 | 0x8000xxxx | Mã chung rộng (E_FAIL, E_UNEXPECTED, v.v.) |
| FACILITY_RPC | 1 | 0x8001xxxx | Nguồn RPC |
| FACILITY_ITF | 4 | 0x8004xxxx | Lỗi do giao diện định nghĩa (nghĩa phụ thuộc giao diện) |
| FACILITY_WIN32 | 7 | 0x8007xxxx | Mã lỗi Win32 được bọc |
| FACILITY_WINDOWS | 8 | 0x8008xxxx | Giao diện bổ sung do Microsoft định nghĩa |
4.2. Tách 0x80004005 và 0x80070005
Hãy tách thực sự.
Với 0x80004005: S=1 (thất bại), Facility=(0x80004005 » 16) & 0x7FF = 0 (FACILITY_NULL), Code=0x4005. Mã FACILITY_NULL chung, định nghĩa là E_FAIL “Unspecified failure”.6 Nói cách khác mã này chỉ mang nghĩa “thất bại không thể báo chi tiết”. Khi thấy 0x80004005, dừng đào chính mã ở đó, và chuyển trọng lượng điều tra sang “thành phần nào trả” và “có chi tiết trong nhật ký sự kiện hoặc nhật ký ứng dụng cùng lúc không”.
Với 0x80070005: S=1, Facility=7 (FACILITY_WIN32), Code=0x0005=5. Bạn thấy đó là số lỗi Win32 5 (ERROR_ACCESS_DENIED) bọc thành HRESULT. Bí danh E_ACCESSDENIED về bản chất là giá trị này.6
Dù cùng “từ chối truy cập”, 0x80070005 là bọc một thất bại cụ thể xảy ra ở lớp Win32, và lượng thông tin hoàn toàn khác 0x80004005.
flowchart TB
accTitle: Tách 0x80004005 và 0x80070005
accDescr: 0x80004005 là mã FACILITY_NULL chung E_FAIL, không mang chi tiết, và nên chuyển sang điều tra ngữ cảnh; 0x80070005 là FACILITY_WIN32 và đọc được như bọc số lỗi Win32 5, từ chối truy cập
a["0x80004005"] --> af["Facility=0(FACILITY_NULL)"]
af --> ac["Code=0x4005 → E_FAIL"]
ac --> ax["Thất bại không chỉ định. Sang điều tra ngữ cảnh"]
b["0x80070005"] --> bf["Facility=7(FACILITY_WIN32)"]
bf --> bc["Code=0x0005 → 5"]
bc --> bx["ERROR_ACCESS_DENIED"]
Hình 8: Cùng “thất bại” vẫn khác lượng thông tin sau khi tách. 0x80070005 có thể đi tới số lỗi Win32 5.
4.3. Mẫu quan trọng nhất: 0x8007xxxx = HRESULT_FROM_WIN32
Để chuyển thất bại từ lớp dưới chỉ trả được mã lỗi Win32 lên lớp trên trả HRESULT (phương thức COM hoặc runtime .NET), winerror.h cung cấp macro HRESULT_FROM_WIN32.4 Hành vi là “lưu mã lỗi Win32 vào 16 bit thấp, đặt Facility thành FACILITY_WIN32 (7), và đặt bit S thành 1”.
flowchart TB
accTitle: HRESULT_FROM_WIN32 hoạt động thế nào
accDescr: Lưu mã lỗi Win32 vào 16 bit thấp, đặt Facility thành 7 và bit S thành 1, rồi lắp HRESULT 0x8007xxxx
win["Mã lỗi Win32(ví dụ: 5)"] --> low["Lưu vào 16 bit thấp"]
low --> fac["Đặt Facility thành 7"]
fac --> sbit["Đặt bit S thành 1"]
sbit --> hr["0x80070005"]
Hình 9: HRESULT_FROM_WIN32 lưu lỗi Win32 vào 16 bit thấp và đặt Facility=7 cùng bit 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)
Để đọc chiều kia, lấy 16 bit thấp trong PowerShell.
0x80070005 -band 0xFFFF # 5 → ERROR_ACCESS_DENIED
0x80072EE7 -band 0xFFFF # 12007 → ERROR_INTERNET_NAME_NOT_RESOLVED (WinINet)
Như ví dụ thứ hai, lỗi WinINet và WinHTTP (hàng 12000) cũng được định nghĩa trong không gian mã lỗi Win321, nên 0x8007xxxx mạng tách được bằng cùng thủ tục. Đưa “khi thấy 0x8007, chuyển 4 chữ số thấp sang thập phân” vào trí nhớ cơ là kỹ năng thực tế số một bài viết muốn bạn mang về.
Có lưu ý ngược với 0x8004xxxx (FACILITY_ITF). Mã FACILITY_ITF có bên định nghĩa nghĩa khác theo giao diện, nên cùng giá trị 32 bit có thể nghĩa khác nếu bên trả khác.5 Với 0x8004xxxx lạ, đừng tìm generic mà tìm trong tài liệu thành phần đã trả (thư viện, SDK trình điều khiển, sản phẩm máy chủ).
flowchart TB
accTitle: Cách tra đổi giữa 0x8007 và 0x8004
accDescr: FACILITY_WIN32 0x8007xxxx đọc được bằng tách cơ học 16 bit thấp, còn FACILITY_ITF 0x8004xxxx có bên định nghĩa nghĩa khác theo giao diện nên tra trong tài liệu thành phần trả
hr{"Facility là?"} -->|7, WIN32| w["Chuyển 16 bit thấp sang thập phân"]
hr -->|4, ITF| i["Nghĩa khác theo bên trả"]
w --> ww["Đọc như lỗi Win32"]
i --> ii["Tra trong tài liệu bên trả"]
Hình 10: 0x8007xxxx tách được cơ học; 0x8004xxxx tra trong tài liệu thành phần trả.
5. NTSTATUS — Mã lớp hạt nhân và thế giới sự cố
5.1. Bố cục và Severity
NTSTATUS là mã 32 bit mà hạt nhân, trình điều khiển thiết bị và API gốc ntdll dùng, bố cục giống HRESULT mà không giống hệt.3
| Vị trí bit | Tên | Nghĩa |
|---|---|---|
| 31–30 | Sev | Severity. 00 = thành công, 01 = thông tin, 10 = cảnh báo, 11 = lỗi |
| 29 | C | Bit Customer |
| 28 | N | Dự trữ (0, để ánh xạ sang HRESULT được) |
| 27–16 | Facility | Facility (12 bit) |
| 15–0 | Code | Mã chi tiết |
Vì severity 2 bit, bạn đọc loại từ chữ số hex đầu. 0xC… là lỗi (11), 0x8… cảnh báo (10), 0x4… thông tin (01), 0x0–0x3… thành công. Ngoại lệ điểm dừng 0x80000003 (STATUS_BREAKPOINT) là ví dụ đại diện “cảnh báo, không phải lỗi”.37
flowchart TB
accTitle: NTSTATUS đọc được loại từ chữ số đầu
accDescr: Vì severity 2 bit, NTSTATUS đọc là lỗi nếu chữ số hex đầu 0xC, cảnh báo nếu 0x8, thông tin nếu 0x4, thành công nếu 0x0 đến 0x3
head{"Chữ số hex đầu là?"} -->|0xC| e["Lỗi"]
head -->|0x8| w["Cảnh báo"]
head -->|0x4| i["Thông tin"]
head -->|0x0–0x3| s["Thành công"]
w -.-> ex["Ví dụ: 0x80000003 là cảnh báo"]
Hình 11: NTSTATUS đọc được loại từ chữ số hex đầu. 0x80000003 là “cảnh báo, không phải lỗi”.
5.2. Nơi bạn gặp — mã ngoại lệ, mã STOP và nhật ký sự kiện
Tình huống nhân viên CNTT và nhà phát triển gặp NTSTATUS chủ yếu liên quan sự cố.
- Mã ngoại lệ sập ứng dụng: “Exception code: 0xc0000005” ghi trong nhật ký sự kiện “Application Error (event ID 1000)” là NTSTATUS. Giá trị đại diện như sau.7
| Giá trị | Ký hiệu | Nghĩa |
|---|---|---|
| 0xC0000005 | STATUS_ACCESS_VIOLATION | Vi phạm truy cập (truy cập bộ nhớ bất hợp pháp) |
| 0xC0000135 | STATUS_DLL_NOT_FOUND | Không tìm thấy DLL cần và không thể khởi động |
| 0xC00000FD | STATUS_STACK_OVERFLOW | Tràn ngăn xếp |
| 0xC0000374 | STATUS_HEAP_CORRUPTION | Hỏng heap |
- Mã STOP màn hình xanh: nhìn thoáng giống, nhưng mã STOP (mã bug check) là hệ đánh số riêng, tách khỏi NTSTATUS, như 0x0000009F (DRIVER_POWER_STATE_FAILURE), và có tài liệu riêng.14 Chỉ nhớ phân biệt “0xC0000005 là NTSTATUS; STOP 0x9F là mã bug check và không được tra bảng NTSTATUS” là đủ.
- Cột Result của Process Monitor: NAME NOT FOUND và ACCESS DENIED ở cột Result Procmon là tên hiển thị của NTSTATUS mà hạt nhân trả (STATUS_OBJECT_NAME_NOT_FOUND, STATUS_ACCESS_DENIED). Đó cũng là chỗ cảm nhận tương ứng lớp: quan sát thất bại I/O tệp bằng từ vựng NTSTATUS rồi thấy thất bại đó chuyển thành lỗi Win32 và tới ứng dụng.
flowchart TB
accTitle: Phân biệt mã ngoại lệ với mã STOP
accDescr: Đọc mã ngoại lệ nhật ký sự kiện như NTSTATUS; tra mã STOP màn hình xanh trong tài liệu mã bug-check riêng, hệ khác
q{"Mã xuất hiện ở đâu?"} -->|Mã ngoại lệ| nt["Đọc như NTSTATUS"]
q -->|Mã STOP| bc["Tra bảng mã bug-check"]
nt -.-> n1["Ví dụ: 0xC0000005"]
bc -.-> b1["Ví dụ: 0x0000009F"]
Hình 12: Mã ngoại lệ nhật ký sự kiện là NTSTATUS; mã STOP màn hình xanh là hệ khác. Đừng tra nhầm bảng.
Điều tra ngoài mã ngoại lệ, tức bắt và phân tích dump sự cố, được bàn trong “Dẫn nhập thu thập crash dump Windows” và “Đọc crash dump bằng WinDbg + SOS”.
5.3. Quan hệ với HRESULT — bit N và RtlNtStatusToDosError
Cầu giữa NTSTATUS và hai lớp kia có hai đường.
- Ánh xạ vào không gian HRESULT: Đặt bit N HRESULT (0x10000000) đưa giá trị NTSTATUS vào không gian HRESULT nguyên trạng (macro HRESULT_FROM_NT trong winerror.h). Ánh xạ 0xC0000005 thành 0xD0000005, chẳng hạn. Khi thấy HRESULT bắt đầu 0xD, thủ tục đúng là bỏ bit N và đọc như NTSTATUS.2
- Chuyển thành mã lỗi Win32:
RtlNtStatusToDosErrorcủa ntdll chuyển NTSTATUS thành mã lỗi Win32 tương ứng. Giá trị không có tương ứng định nghĩa thành ERROR_MR_MID_NOT_FOUND.12 Ví dụ STATUS_ACCESS_VIOLATION (0xC0000005) chuyển thành ERROR_NOACCESS (998), và STATUS_OBJECT_NAME_NOT_FOUND (0xC0000034) thành ERROR_FILE_NOT_FOUND (2). Cũng hữu ích nhớ rằng từ vựng giàu của hạt nhân đôi khi bị làm tròn thành phân biệt thô hơn ở lớp Win32.
flowchart TB
accTitle: Hai cầu từ NTSTATUS sang lớp khác
accDescr: NTSTATUS được chuyển sang lớp khác hai cách: ánh xạ vào không gian HRESULT bằng đặt bit N, và chuyển thành mã lỗi Win32 bởi RtlNtStatusToDosError
nt["NTSTATUS(0xC0000005)"] -->|Đặt bit N| hr["HRESULT(0xD0000005)"]
nt -->|RtlNtStatusToDosError| win["Lỗi Win32 998(ERROR_NOACCESS)"]
win -.-> memo["ERROR_MR_MID_NOT_FOUND nếu không định nghĩa tương ứng"]
Hình 13: Có hai cầu NTSTATUS. Đầu 0xD đọc như NTSTATUS sau khi bỏ bit N.
6. COM và .NET — Cách mã lỗi được ánh xạ sang ngoại lệ
6.1. Phong cách COM — HRESULT + IErrorInfo
Phương thức COM về cơ bản trả HRESULT, nhưng có giới hạn những gì đóng gói vào 32 bit, nên bổ sung cơ chế IErrorInfo có thể chuyển chuỗi mô tả lỗi và nguồn gốc riêng. Trong C++, lớp _com_error do trình biên dịch hỗ trợ xử lý HRESULT và IErrorInfo cùng nhau. Ứng dụng hộp thoại lỗi hiện “mã + mô tả” thường mang mô tả qua cơ chế này.
flowchart TB
accTitle: IErrorInfo bổ sung HRESULT
accDescr: Có giới hạn những gì đóng gói vào HRESULT 32 bit, nên chuỗi mô tả lỗi và nguồn gốc được chuyển riêng bằng IErrorInfo, và trong C++ lớp _com_error xử lý cả hai cùng nhau
hr["HRESULT(chỉ 32 bit)"] --> lim["Có giới hạn những gì đóng gói"]
lim --> ei["IErrorInfo mang mô tả"]
ei --> ce["_com_error xử lý chúng cùng nhau"]
ce -.-> dlg["Mã + mô tả hộp thoại"]
Hình 14: Chuỗi mô tả không vừa HRESULT 32 bit được IErrorInfo mang riêng.
6.2. Phong cách .NET — từ HRESULT sang kiểu ngoại lệ
Khi runtime .NET nhận thất bại HRESULT trong COM interop, nó chuyển thành ngoại lệ. HRESULT đã biết được ánh xạ sang kiểu ngoại lệ tương ứng; chưa biết thành COMException.11
flowchart TB
accTitle: Ánh xạ từ HRESULT sang ngoại lệ .NET
accDescr: HRESULT thất bại nhận trong COM interop được chuyển sang kiểu ngoại lệ tương ứng nếu đã biết, hoặc COMException nếu chưa, và trong mọi trường hợp giá trị gốc còn trong Exception.HResult
hr["HRESULT thất bại"] --> known{"Ánh xạ đã biết?"}
known -->|Yes| typed["Chuyển sang kiểu ngoại lệ tương ứng"]
known -->|No| comex["Chuyển sang COMException"]
typed --> keep["Giá trị gốc còn trong Exception.HResult"]
comex --> keep
Hình 15: .NET ánh xạ HRESULT sang kiểu ngoại lệ, và giá trị gốc còn trong Exception.HResult trên mọi ngoại lệ.
| HRESULT | Kiểu ngoại lệ .NET |
|---|---|
| E_ACCESSDENIED (0x80070005) | UnauthorizedAccessException |
| E_OUTOFMEMORY (0x8007000E) | OutOfMemoryException |
| E_INVALIDARG (0x80070057) | ArgumentException |
| E_NOTIMPL (0x80004001) | NotImplementedException |
| Giá trị không có ánh xạ định nghĩa | COMException (giá trị gốc trong thuộc tính ErrorCode) |
Trên mọi ngoại lệ, HRESULT gốc còn trong thuộc tính Exception.HResult. Nhánh xử lý ngoại lệ I/O tệp như “chỉ thử lại khi vi phạm chia sẻ” có thể viết bằng giá trị này.
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 và GetLastError
Khi gọi API Win32 trực tiếp qua P/Invoke, chỉ định SetLastError = true trên DllImport (hoặc LibraryImport), rồi lấy bằng Marshal.GetLastWin32Error (từ .NET 6, tương đương GetLastPInvokeError). Tự định nghĩa GetLastError thành P/Invoke rồi gọi là không chính xác, vì lời gọi API trong runtime có thể ghi đè giá trị.15
flowchart TB
accTitle: Lấy lỗi cuối trong P/Invoke
accDescr: Chỉ định SetLastError là true và lấy bằng Marshal.GetLastWin32Error là đúng; P/Invoke GetLastError trực tiếp không chính xác vì runtime ghi đè
pi["Gọi API Win32 qua P/Invoke"] --> ok["Chỉ định SetLastError=true"]
ok --> get["Lấy bằng GetLastWin32Error"]
pi --> ng["Định nghĩa gọi GetLastError trực tiếp"]
ng --> bad["Runtime ghi đè và không chính xác"]
Hình 16: Trong P/Invoke, dùng SetLastError=true và Marshal.GetLastWin32Error như một bộ. Gọi GetLastError trực tiếp không chính xác.
[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 tra chuỗi thông báo hệ điều hành từ mã lỗi Win32, nên bạn dùng nguyên để để cả mã lẫn thông báo trong nhật ký. Câu hỏi thiết kế bắt ngoại lệ ở lớp nào và để trong nhật ký thế nào được bàn trong “Nên đặt catch và ghi nhật ký ở đâu trong xử lý ngoại lệ?”.
7. Công cụ chuyển và điều tra thực tế — tham chiếu nhanh copy-dán
7.1. err.exe (Microsoft Error Lookup Tool)
Công cụ tra lỗi độc lập Microsoft phân phối. Nó đi qua rất nhiều tệp header như winerror.h và ntstatus.h rồi liệt kê định nghĩa và thông báo khớp mã đã chỉ định.8
err 0x80070005
err 5
err 0xC0000005
Một số có thể trúng ở vài header (ví dụ “5” khớp định nghĩa ở nhiều chỗ ngoài Win32 ERROR_ACCESS_DENIED), nên ứng viên nào hợp lý phải chọn theo ngữ cảnh. Tên tệp tải có phiên bản (Err_6.4.5.exe tại thời điểm viết), và cũng lưu ý định nghĩa mã dựa trên header lúc đóng gói.8
flowchart TB
accTitle: Chọn kết quả tìm err.exe theo ngữ cảnh
accDescr: err.exe đi qua rất nhiều tệp header và liệt kê định nghĩa khớp, nên khi vài ứng viên xuất hiện cho cùng số, chọn cái hợp lý theo ngữ cảnh
in["Nhập err 5"] --> scan["Đi qua rất nhiều header"]
scan --> hits["Vài định nghĩa trúng"]
hits --> pick["Chọn ứng viên hợp lý theo ngữ cảnh"]
Hình 17: err.exe là tìm xuyên header, nên vài ứng viên có thể xuất hiện, và cái hợp lý được chọn theo ngữ cảnh.
7.2. Lệnh có sẵn trong Windows
Những gì dùng được không cần cài thêm là certutil và net helpmsg. Tùy chọn -error của certutil hiện văn bản thông báo tương ứng mã lỗi, và nhận HRESULT thập lục phân hoặc thập phân.9
certutil -error 0x80070005
certutil -error 5
net helpmsg 5
net helpmsg chỉ cho mã lỗi Win32 thập phân, nhưng trong môi trường tiếng Việt thông báo trở về ngôn ngữ hệ điều hành đã bản địa hóa, nên bạn dùng nguyên để giải thích cho người dùng.
flowchart TB
accTitle: Cách chọn giữa các lệnh chuẩn
accDescr: Mã lỗi Win32 thập phân tra bằng net helpmsg; mã gồm hex, kể cả HRESULT, tra bằng tùy chọn -error của certutil
q{"Mã bạn có là?"} -->|Win32 thập phân| net["net helpmsg"]
q -->|Gồm hex| cert["certutil -error"]
net -.-> jp["Thông báo bản địa hóa trở về"]
cert -.-> any["Nhận cả hex lẫn thập phân"]
Hình 18: Cách chọn giữa các lệnh chuẩn. Lỗi Win32 thập phân là net helpmsg; nếu gồm hex thì certutil -error.
7.3. Bộ one-liner 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. !error của WinDbg
Để tra mã khi phân tích dump, phần mở rộng !error của WinDbg nhanh. Mặc định nó diễn giải như mã lỗi Win32; truyền 1 làm đối số thứ hai thì diễn giải như 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>
Trong dump sự cố, !analyze -v tự hiện mã ngoại lệ (NTSTATUS), nên luồng là xác nhận nghĩa từ đó bằng !error <code> 1.
flowchart TB
accTitle: Luồng xác nhận mã ngoại lệ trong WinDbg
accDescr: Trong dump sự cố lệnh analyze tự hiện mã ngoại lệ; truyền mã đó cho phần mở rộng error với đối số thứ hai 1 và xác nhận nghĩa như NTSTATUS
dump["Mở dump sự cố"] --> an["Chạy !analyze -v"]
an --> exc["Mã ngoại lệ được hiện"]
exc --> chk["Xác nhận nghĩa bằng !error code 1"]
Hình 19: Khi phân tích dump, tra mã ngoại lệ mà !analyze -v hiện bằng !error và cờ 1.
8. Thủ tục điều tra — Từ phán lớp đến khớp ngữ cảnh
Ghép kiến thức đến đây thành thủ tục điều tra thực sự một mã lỗi.
- Chuẩn hóa ký hiệu. Nếu thập phân âm, chuyển sang hex 8 chữ số. Đệm không hex ngắn hơn 8 chữ số rồi đọc.
- Phán lớp nào là mã. Như bảng phán dưới, vài chữ số đầu gần như quyết.
- Tách và lấy mã cốt yếu. Thao tác cơ học: 16 bit thấp nếu 0x8007xxxx, bỏ bit N nếu 0xDxxxxxxx.
- Tra tên và định nghĩa bằng công cụ. Xác nhận tên ký hiệu và thông báo bằng err.exe, certutil hoặc
!error. - Khớp với ngữ cảnh. Xác định ứng dụng nào, thao tác nào, API nào, thất bại với cái gì, từ nhật ký ứng dụng, nhật ký sự kiện và Procmon. Mã là “loại thất bại”; ngữ cảnh là “chỗ nguyên nhân”.
flowchart TB
accTitle: Thủ tục điều tra mã lỗi
accDescr: Mẫu điều tra chỉnh ký hiệu về hex, phán lớp từ vài chữ số đầu, tách và lấy mã cốt yếu, tra tên và định nghĩa bằng công cụ, rồi khớp ngữ cảnh
fix["Chuẩn hóa sang hex"] --> judge{"Chữ số đầu?"}
judge -->|Thập phân| d1["Đọc như lỗi Win32"]
judge -->|0x8007| d2["16 bit thấp → thập phân"]
judge -->|0xC| d3["Đọc như NTSTATUS"]
judge -->|0xD| d4["Bỏ bit N rồi đọc"]
d1 --> tool["Tra tên/định nghĩa"]
d2 --> tool
d3 --> tool
d4 --> tool
tool --> ctx["Khớp ngữ cảnh(Procmon)"]
Hình 20: Mẫu điều tra. Chuẩn hóa ký hiệu, phán lớp và tách, tra tên, rồi khớp ngữ cảnh.
| Dáng | Ứng viên đầu | Cách tách và chuyển |
|---|---|---|
| Thập phân 1 đến 5 chữ số (5, 1223, v.v.) | Mã lỗi Win32 | Nguyên trạng vào net helpmsg hoặc err.exe |
| Thập phân âm (-2147024891, v.v.) | HRESULT | Chuyển sang hex 8 chữ số, rồi phán các hàng dưới |
| 0x8007xxxx | HRESULT (FACILITY_WIN32) | Chuyển 16 bit thấp sang thập phân và đọc như Win32 |
| 0x8004xxxx | HRESULT (FACILITY_ITF) | Tra trong tài liệu thành phần trả |
| 0x8000xxxx | HRESULT (FACILITY_NULL) | Mã chung như E_FAIL. Chuyển trọng lượng sang điều tra ngữ cảnh |
| 0xCxxxxxxx | NTSTATUS (lỗi) | !error <code> 1; chuyển sang Win32 rồi đọc nếu cần |
| 0xDxxxxxxx | Ánh xạ HRESULT của NTSTATUS | Bỏ bit N (0x10000000) và đọc như NTSTATUS |
| Facility riêng như 0x8024xxxx | HRESULT riêng vùng tính năng | Xác định vùng từ giá trị Facility và tới tài liệu riêng (0x8024… là Windows Update)2 |
Điều đặc biệt hiệu quả ở bước 5 “khớp ngữ cảnh” là cột Result của Process Monitor. Dù ứng dụng chỉ hiện “0x80070002”, Procmon nói trong một hàng “tiến trình nào, với đường dẫn nào, được trả NAME NOT FOUND”. Để tra phía nhật ký sự kiện, xem thêm “Dẫn nhập Windows Event Log và ETW”.
9. Đọc sai thường gặp — mẫu đưa điều tra đi đường dài
Cuối cùng, các mẫu đọc sai xuất hiện trong tư vấn thực tế.
Đọc sai 1: Nghĩ 0x80004005 là “mã chỉ nguyên nhân cụ thể”
E_FAIL là “Unspecified failure”, và cùng giá trị xuất hiện ở Windows Update, mạng và cơ sở dữ liệu. Thử mọi biện pháp hiện khi tìm mã này gần như chắc là đường dài. Thu hẹp không từ mã mà từ “ứng dụng nào, thao tác nào, nhật ký khác cùng lúc”.6
Đọc sai 2: Không nhận ra thập phân âm là HRESULT
Trường hợp tìm nhật ký nói “Error -2147467259 occurred” nguyên trạng, hoặc rối vì “lỗi dấu trừ?”. Khi thấy số âm, chuyển sang hex. Chỉ thế cho biết đó là 0x80004005 (E_FAIL), và nối với kiến thức đọc sai 1.
Đọc sai 3: Tra cả 8 chữ số 0x8007xxxx mà không nhìn lỗi Win32 bên dưới
Bản chất 0x80070005 là “5 = từ chối truy cập”. Nghĩ về “lỗi Win32 5 nghĩa gì trong ngữ cảnh thao tác này” sau khi lấy 16 bit thấp đến lõi nhanh hơn tìm cả 8 chữ số.
Đọc sai 4: Giả định “cùng mã = cùng nguyên nhân”
Nếu từng “lỗi 5 do antivirus gây”, bạn dễ nhảy sang cùng biện pháp ở lỗi 5 lần sau. Dù cùng mã, nếu API thất bại và tài nguyên đích khác thì nguyên nhân là việc khác. Xác nhận nghĩa mã và xác định đích bằng Procmon hoặc tương tự là một bộ, mỗi lần.
Đọc sai 5: Nhầm lỗi Win32 5 với 0xC0000005, và mã STOP với NTSTATUS
Coi ERROR_ACCESS_DENIED và STATUS_ACCESS_VIOLATION là một vì mối “5” đưa điều tra đi hai hướng hoàn toàn khác — vấn đề quyền đối với lỗi chương trình. Cũng vậy, mã STOP màn hình xanh là hệ khác NTSTATUS, nên tra 0x9F trong bảng NTSTATUS không ra câu trả lời có nghĩa.14
flowchart TB
accTitle: Lỗi 5 và 0xC0000005 có hướng điều tra khác
accDescr: Lỗi Win32 5 nên điều tra như vấn đề quyền, còn NTSTATUS 0xC0000005 như lỗi chương trình; coi chúng là một đưa điều tra sang hướng khác
a["Lỗi Win32 5"] --> ad["Điều tra vấn đề quyền"]
b["NTSTATUS 0xC0000005"] --> bd["Điều tra lỗi chương trình"]
a -.-> memo["Mã không liên quan của hệ khác"]
b -.-> memo
Hình 21: Đừng coi chúng là một vì mối “5”. Lỗi 5 đi về vấn đề quyền; 0xC0000005 đi về lỗi chương trình.
10. Tóm tắt
- Mã lỗi Windows là cấu trúc ba lớp mã lỗi Win32, HRESULT và NTSTATUS. Trước hết phán lớp nào, bên nào trả mã.
- Dao động ký hiệu (thập phân / hex / âm) chỉnh được cơ học. Chuyển số âm sang hex 8 chữ số rồi đọc.
- HRESULT là cấu trúc bit S/R/C/N/X + Facility (11 bit) + Code (16 bit), và 0x8007xxxx là mẫu quan trọng nhất, lỗi Win32 được bọc. Chuyển 16 bit thấp sang thập phân và lấy mã cốt yếu.
- Mã chung như 0x80004005 (E_FAIL) không chỉ nguyên nhân. Phán dừng đào mã và chuyển sang điều tra ngữ cảnh được chính vì bạn biết cấu trúc.
- Bạn gặp NTSTATUS như mã ngoại lệ sự cố hoặc ở cột Result Procmon. 0xC0000005 là vi phạm truy cập, không liên quan lỗi Win32 5. Mã STOP lại là hệ khác.
- Trong .NET, HRESULT được ánh xạ sang kiểu ngoại lệ, và giá trị gốc còn trong Exception.HResult. Trong P/Invoke, dùng SetLastError=true và Marshal.GetLastWin32Error như một bộ.
- Công cụ tra là certutil -error và net helpmsg (chuẩn), err.exe (máy phát triển), one-liner PowerShell, và !error của WinDbg.
- Thủ tục là “chuẩn hóa ký hiệu → phán lớp → tách → tra tên → khớp ngữ cảnh”. Mã nói loại thất bại; chỗ nguyên nhân do ngữ cảnh nói.
Lần sau gặp mã lỗi lạ, hãy nhìn vài chữ số đầu trước khi dán vào ô tìm kiếm. 4 chữ số thấp nếu 0x8007, NTSTATUS nếu 0xC, chuyển hex nếu âm — tách 10 giây này phần lớn quyết thời gian điều tra tiếp theo.
Bài viết liên quan
- Đọc crash dump bằng WinDbg + SOS — Hướng dẫn thực tiễn phân tích sau khi thu thập
- Dẫn nhập thu thập crash dump Windows - WER/ProcDump/WinDbg
- Nên đặt catch và ghi nhật ký ở đâu trong xử lý ngoại lệ?
- Hướng dẫn thực hành Process Monitor (ProcMon) — Ghim “thiết lập không áp dụng” và “ACCESS DENIED” trong 10 phút
- Dẫn nhập Windows Event Log và ETW — Đặt nhật ký ứng dụng nghiệp vụ của bạn lên các cơ chế chuẩn của OS
Lĩnh vực tư vấn liên quan
KomuraSoft LLC nhận điều tra sự cố bắt đầu từ mã lỗi — “tôi không biết mã lỗi này nghĩa gì”, “0x80070005 chỉ xuất hiện ở môi trường cụ thể” —, thiết kế xử lý lỗi cho ứng dụng trộn Win32 API, COM và .NET, và xác định nguyên nhân bằng dump sự cố cùng Process Monitor. Tư vấn từ một ảnh chụp hộp thoại lỗi là đủ.
- Phát triển ứng dụng Windows
- Điều tra lỗi và phân tích nguyên nhân gốc rễ
- Tư vấn kỹ thuật và rà soát thiết kế
- Liên hệ với chúng tôi
Liên kết tham khảo
-
Microsoft Learn, Debug system error codes. Mục lục danh sách mã lỗi hệ thống Win32 (0–15999); lấy thông báo cho mã mà
GetLastErrortrả bằng FormatMessage và cờ FORMAT_MESSAGE_FROM_SYSTEM; rằng lỗi WinINet/WinHTTP (hàng 12000) được định nghĩa trong không gian này; và phương pháp điều tra bằng Microsoft Error Lookup Tool cùng lệnh !err. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 -
Microsoft Open Specifications, [MS-ERREF]: HRESULT. Bố cục bit HRESULT (bit S, R, C, N và X, Facility 11 bit, Code 16 bit); rằng bit N chỉ giá trị NTSTATUS ánh xạ vào không gian HRESULT; và danh sách mã facility gồm FACILITY_WINDOWS_UPDATE (36). ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Open Specifications, [MS-ERREF]: NTSTATUS. Bố cục bit NTSTATUS (Sev 2 bit, bit C, bit N, Facility 12 bit, Code 16 bit); và rằng severity chia bốn loại: thành công (00), thông tin (01), cảnh báo (10) và lỗi (11). ↩ ↩2 ↩3
-
Microsoft Learn, HRESULT_FROM_WIN32 macro. Định nghĩa macro winerror.h ánh xạ mã lỗi hệ thống Win32 sang giá trị HRESULT. ↩ ↩2 ↩3
-
Microsoft Learn, Structure of COM Error Codes. Vai trò bit severity HRESULT và trường facility; giá trị FACILITY_NULL, FACILITY_RPC, FACILITY_ITF, FACILITY_WIN32 và FACILITY_WINDOWS; và rằng mã FACILITY_ITF nghĩa được định nghĩa theo giao diện và cùng giá trị có thể nghĩa khác. ↩ ↩2 ↩3
-
Microsoft Learn, Common HRESULT values. Rằng E_FAIL (0x80004005) là “Unspecified failure”; và định nghĩa các giá trị HRESULT thường gặp như E_ACCESSDENIED (0x80070005), E_INVALIDARG (0x80070057) và E_OUTOFMEMORY (0x8007000E). ↩ ↩2 ↩3 ↩4
-
Microsoft Open Specifications, [MS-ERREF]: NTSTATUS values. Danh sách giá trị NTSTATUS gồm STATUS_ACCESS_VIOLATION (0xC0000005), STATUS_DLL_NOT_FOUND (0xC0000135), STATUS_STACK_OVERFLOW (0xC00000FD), STATUS_HEAP_CORRUPTION (0xC0000374) và STATUS_BREAKPOINT (0x80000003). ↩ ↩2 ↩3
-
Microsoft Learn, The Microsoft Error Lookup Tool. Rằng đó là công cụ độc lập hiện văn bản thông báo gắn với mã trạng thái thập lục phân xuyên các tệp header như Winerror.h; rằng tên tệp tải là Err_6.4.5.exe; và rằng cần lưu ý định nghĩa đóng gói tính tại thời điểm biên dịch. ↩ ↩2 ↩3
-
Microsoft Learn, certutil. Rằng tùy chọn -error của certutil hiện văn bản thông báo gắn với mã lỗi, và rằng ký hiệu lỗi gồm tên ký hiệu được dùng dạng như 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND). ↩ ↩2
-
Microsoft Learn, !error. Rằng phần mở rộng !error của WinDbg giải mã và hiện giá trị lỗi Win32, Winsock, NTSTATUS và NetAPI; và rằng chỉ định 1 làm cờ diễn giải như NTSTATUS. ↩ ↩2
-
Microsoft Learn, How to: Map HRESULTs and exceptions. Cơ chế ánh xạ qua lại giữa HRESULT COM và ngoại lệ .NET; bảng tương ứng như E_NOTIMPL → NotImplementedException; rằng HRESULT không có ánh xạ tường minh được chuyển thành COMException; và rằng Message, Source và tương tự của ngoại lệ được khởi tạo từ thông tin IErrorInfo. ↩ ↩2
-
Microsoft Learn, RtlNtStatusToDosError function (winternl.h). Rằng đó là hàm chuyển mã NTSTATUS thành mã lỗi hệ thống Win32 tương ứng; rằng ERROR_MR_MID_NOT_FOUND được trả khi không định nghĩa tương ứng; và rằng hàm chuyển ngược không tồn tại. ↩ ↩2
-
Microsoft Learn, Last-Error Code. Rằng last-error code được giữ theo luồng; rằng nên lấy bằng GetLastError ngay sau thất bại; rằng API ghi đè mã bằng 0 khi thành công và API không đụng lẫn nhau; và rằng bit 29 dành cho mã do ứng dụng định nghĩa. ↩ ↩2
-
Microsoft Learn, Bug check code reference. Danh sách mã bug check (mã STOP) hiện trên màn hình xanh, và cách hiện thông tin về mã bằng phần mở rộng !analyze của WinDbg. Rằng đó là hệ đánh số riêng, tách khỏi NTSTATUS, xác nhận được từ danh sách. ↩ ↩2
-
Microsoft Learn, Marshal.GetLastWin32Error Method. Rằng đó là cách lấy last-error code của lời gọi P/Invoke đã đặt cờ SetLastError; rằng P/Invoke GetLastError trực tiếp không đáng tin vì ghi đè bởi lời gọi API trong runtime; và rằng từ .NET 6 nên dùng GetLastPInvokeError. ↩
Bài viết liên quan
Các bài viết gần đây có cùng thẻ để tìm hiểu sâu hơn những chủ đề lân cận.
API thread pool Win32 — Đồng thời mà không tạo luồng, với CreateThreadpoolWork
Bạn đang rải các lời gọi CreateThread khắp mã native? Bài viết này giải thích API thread pool Win32 được thiết kế lại từ Vista — bốn đối ...
Named pipe trong thực tiễn — IPC chuẩn của Windows, từ thiết kế đến bảo mật
Hướng dẫn thực tiễn về named pipe, cơ chế giao tiếp giữa các tiến trình chuẩn của Windows. Bài viết sắp xếp, từ nguồn gốc, việc chọn giữa...
Ứng dụng hỏng khi thức dậy từ ngủ — Sự kiện nguồn Windows và cách viết ứng dụng nghiệp vụ sống sót
Bạn mở laptop và kết nối của ứng dụng nghiệp vụ đã chết — nguyên nhân là thiết kế chưa tính đến ngủ. Bài viết này trình bày luồng thông b...
DllMain và Loader Lock — Lý do thật sự bạn bị bảo "đừng làm gì trong khởi tạo DLL"
Vì sao bạn không được gọi LoadLibrary hoặc đồng bộ với luồng khác từ DllMain. Dựa trên nguồn gốc, bài viết giải thích cách loader lock tu...
"Không phản hồi" thực sự là gì — Cách Windows quyết định ứng dụng đã treo, và cách thiết kế ứng dụng không treo
"Không phản hồi" của Windows là cơ chế trong đó hệ điều hành phán rằng một cửa sổ chưa lấy thông điệp trong 5 giây và thay nó bằng cửa sổ...
Chủ đề liên quan
Các trang này đặt chủ đề trong bối cảnh rộng hơn của dịch vụ và quyết định.
Chủ đề kỹ thuật Windows
Cổng vào phát triển Windows, điều tra lỗi và khai thác tài sản hiện có.
Dịch vụ liên quan đến chủ đề này
Bài viết liên quan trực tiếp đến các dịch vụ sau.
Phát triển ứng dụng Windows
Ứng dụng nghiệp vụ, tích hợp thiết bị và công cụ liên lạc, từ yêu cầu đến phát triển.
Câu hỏi thường gặp
Các câu hỏi thường gặp khi tư vấn về chủ đề của bài viết.
- Lỗi 0x80004005 nghĩa là gì?
- 0x80004005 là HRESULT E_FAIL, và nghĩa là "Unspecified failure" (thất bại không chỉ định). Nói cách khác nó chỉ cho biết "một thất bại đã xảy ra mà không thể báo lý do chi tiết"; không phải mã đại diện cho nguyên nhân. Cùng 0x80004005 xuất hiện ở những chỗ không liên quan — mạng, Windows Update, VBA, trình điều khiển cơ sở dữ liệu — vì lý do này. Khi thấy mã này, đừng đào nghĩa của mã; thu hẹp nguyên nhân từ ngữ cảnh ứng dụng nào và thao tác nào sinh ra nó, cùng thông tin lỗi khác trong nhật ký sự kiện hoặc nhật ký chi tiết.
- Mã lỗi âm như -2147467259 là gì?
- Đó là HRESULT 32 bit hiển thị dưới dạng thập phân có dấu. HRESULT đặt bit có trọng số cao nhất khi thất bại, nên dưới dạng số nguyên có dấu nó luôn âm. Trong PowerShell, '0x{0:X8}' -f -2147467259 chuyển nó lại thành thập lục phân (ở ví dụ này 0x80004005 = E_FAIL). Khi thấy số âm bắt đầu bằng -214… trong nhật ký hoặc thông báo lỗi tập lệnh, bước đầu chuẩn là chuyển sang hex rồi tra.
- Cách dễ nhất để tra nghĩa mã lỗi là gì?
- Không cần cài thêm, dùng net helpmsg 5 ở dấu nhắc lệnh (lỗi Win32 thập phân) và certutil -error 0x80070005. certutil cũng nhận HRESULT thập lục phân và hiện tên ký hiệu cùng văn bản thông báo. Trong PowerShell, [System.ComponentModel.Win32Exception]::new(5).Message lấy thông báo đã bản địa hóa. Trên máy phát triển, giữ công cụ tra chính thức của Microsoft err.exe (Microsoft Error Lookup Tool); nó tìm xuyên Win32, HRESULT và NTSTATUS rồi liệt kê định nghĩa khớp một lần.
- 0xC0000005 là loại lỗi gì?
- Đó là NTSTATUS STATUS_ACCESS_VIOLATION, tức vi phạm truy cập (truy cập bộ nhớ bất hợp pháp). Đó là mã bạn thấy nhiều nhất dưới dạng "Exception code" trong nhật ký sự kiện hoặc dump sự cố khi ứng dụng sập, và cho biết lỗi chương trình như giải tham chiếu con trỏ không hợp lệ hoặc truy cập bộ nhớ đã giải phóng. Tên giống lỗi Win32 5 (ERROR_ACCESS_DENIED = từ chối truy cập), nhưng là mã không liên quan của hệ khác, đừng nhầm. Cách tin cậy để xác định nguyên nhân là bắt dump sự cố và phân tích trong WinDbg.
- Vì sao nguyên nhân khác mỗi lần, dù cùng mã lỗi?
- Vì mã lỗi chỉ đại diện "loại thất bại", còn "cái gì thất bại và vì sao" do ngữ cảnh gọi quyết định. Lỗi 5 (từ chối truy cập), chẳng hạn, là cùng mã cho những nguyên nhân hoàn toàn khác — quyền NTFS thiếu, thiếu đặc quyền quản trị, chặn antivirus, v.v. Trong tình huống tương tự, nếu tiến trình khác vẫn mở tệp bạn nhận mã khác (lỗi 32 = vi phạm chia sẻ), và đọc mã đúng sẽ đổi chỗ bạn tìm. Lỗi 2 (không tìm thấy tệp) cũng thường không phải tệp chính mà là DLL phụ thuộc hoặc tệp cấu hình. Sau khi tra nghĩa mã, xác nhận API nào thất bại với tài nguyên nào bằng Process Monitor hoặc tương tự là đường ngắn đến nguyên nhân.