פענוח קודי שגיאה ב-Windows — שלוש השכבות Win32, HRESULT ו-NTSTATUS
· עודכן בתאריך: · Go Komura · Windows, קודי שגיאה, HRESULT, NTSTATUS, Win32 API, פתרון תקלות, דיבוג, פיתוח Windows
היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 20 Aug 2026)
- פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22176039)
מאמר זה מאוחסן בארכיון Zenodo. להלן גם ה-DOI שתמיד מפנה לגרסה האחרונה וגם ה-DOI המקובע לגרסה שאתם קוראים.
Go Komura (2026). פענוח קודי שגיאה ב-Windows — שלוש השכבות Win32, HRESULT ו-NTSTATUS. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22176039 https://comcomponent.com/he/blog/windows-error-codes-win32-hresult-ntstatus/
- DOI (הגרסה האחרונה)
- 10.5281/zenodo.22176039
- DOI (הגרסה הזו)
- 10.5281/zenodo.22176040
“על מסך האפליקציה הופיעה השגיאה 0x80004005. מה זה אומר?” — בייעוץ troubleshooting, זו שאלה קלאסית. מי שהדביק את המספר מתיבת השגיאה ישר בחיפוש וקיבל ים של מאמרים לא קשורים — כשל של Windows Update, תיקייה משותפת שלא מתחברת, runtime error של VBA, כשל בחיבור ל-database — והתבלבל עוד יותר, לא לבד בזה.
זה קורה כי 0x80004005 (E_FAIL) הוא קוד גנרי שאומר רק “כישלון לא מוגדר”. אותו קוד משמש בהמון מצבים, ולכן חיפוש לפי הקוד בלבד לא מגיע לסיבה. לעומת זאת, קוד כמו 0x80070005 אפשר לפרק תוך שניות עוד לפני החיפוש, אם מכירים את המבנה: “שגיאת Win32 מספר 5 = הגישה נדחתה, עטופה כ-HRESULT”.
מסיבות היסטוריות, ל-Windows יש שלוש שכבות של קודי שגיאה — קודי שגיאת Win32, HRESULT ו-NTSTATUS — עם המרות ביניהן. ברגע שהמבנה ברור, אפשר לקבוע לבד מאיזו שכבה הגיע הקוד ומי החזיר אותו, ומה הקוד האמיתי מאחוריו, והשלב הראשון בחקירה מתקצר משמעותית.
המאמר מיועד לאנשי IT בארגונים קטנים ובינוניים ולמפתחי אפליקציות Windows. הוא עובר על איך מבחינים ומפרקים את שלוש המערכות, על הקשר ל-exceptions ב-.NET, ועל חיפוש מעשי עם err.exe ו-PowerShell — לפי Microsoft Learn ולפי המפרט שפורסם [MS-ERREF], נכון לאוגוסט 2026.
1. השורה התחתונה קודם
- לקודי שגיאה ב-Windows יש בעיקר שלוש מערכות. קודי שגיאת Win32 (המספר העשרוני הקטן ש-
GetLastErrorמחזיר), HRESULT (הקוד בן 32 bit מאז COM; hex שמתחיל ב-0x8, או decimal שלילי), ו-NTSTATUS (קודי שכבת ה-kernel; שגיאות מתחילות ב-0xC).123 - Decimal ו-hex הם ייצוגים שונים של אותו קוד. “שגיאה 5”, “0x5” ו”ה-16 bits הנמוכים של 0x80070005” כולם מצביעים על ERROR_ACCESS_DENIED (הגישה נדחתה).1
- 0x8007xxxx הוא Win32 error שעטפו מחדש. זה קוד שגיאת Win32 שמאוחסן ב-HRESULT עם FACILITY_WIN32 (7); ממירים את ה-16 bits הנמוכים ל-decimal ומקבלים את הקוד האמיתי. זה ה-pattern החשוב ביותר בקריאת קודי שגיאה.45
- 0x80004005 (E_FAIL) אינו קוד סיבה. הוא אומר “Unspecified failure” ואין בו מידע נוסף. במקום לחפור בקוד הזה, בודקים את ה-context של המקור ואת הלוגים שנלווים אליו.6
- Decimal שלילי (2147467259- וכדומה) הוא HRESULT. ה-most significant bit מתוך 32 (failure bit) דלוק, ולכן בתצוגה עם סימן הערך שלילי. ממירים ל-hex ואז קוראים.2
- ערך בן 8 ספרות שמתחיל ב-0xC הוא NTSTATUS. 0xC0000005 (access violation) ו-0xC0000135 (DLL not found) חוזרים כל הזמן ב-Event Log וב-dumps ברגע קריסה. אין להם קשר לשגיאת Win32 מספר 5.7
- לאותו קוד יש משמעות שונה לפי ה-context. הסיבה לשגיאה 5 יכולה להיות ACL, elevation, אנטי-וירוס, קובץ תפוס ועוד, ו”הקובץ לא נמצא” של שגיאה 2 הוא לעיתים קרובות DLL תלויה. תמיד קוראים את משמעות הקוד יחד עם איזה API נכשל מול מה.1
- כלי ההמרה והחיפוש כבר שם.
certutil -errorו-net helpmsgמובנים ב-Windows;Win32Exceptionב-PowerShell מחזיר את ההודעה; על מכונת פיתוח — err.exe (Microsoft Error Lookup Tool); בניתוח dump —!errorשל WinDbg.8910 - ב-.NET, HRESULT ממופה לסוג exception. HRESULT מוכר הולך לסוג המתאים (E_ACCESSDENIED → UnauthorizedAccessException וכדומה); לא מוכר הופך ל-COMException; הערך המקורי נשאר ב-
Exception.HResult.11
במשפט אחד, תבנית החקירה של קוד שגיאה ב-Windows היא “ליישר את הייצוג ל-hex → לקבוע מאיזו שכבה הקוד → לפרק ולהוציא את הקוד האמיתי → לקרוא אותו יחד עם ה-context”.
2. ב-Windows יש שלוש מערכות קודי שגיאה
קודם כל, המפה הכללית. קודי שגיאה ב-Windows מתפצלים בעיקר לשלוש המערכות הבאות, לפי השכבה שמחזירה אותם.
| מערכת | מי מחזיר בעיקר | איך זה נראה בדרך כלל | דוגמה |
|---|---|---|---|
| קוד שגיאת Win32 | Win32 API (GetLastError), exit code של פקודה |
מספר עשרוני קטן (0–15999) | 5 = ERROR_ACCESS_DENIED |
| HRESULT | רכיב COM, ה-shell, installer, הרבה frameworks | hex בן 8 ספרות שמתחיל ב-0x8, או decimal שלילי | 0x80004005 = E_FAIL |
| NTSTATUS | kernel, driver, native API (ntdll) | שגיאות הן hex בן 8 ספרות שמתחיל ב-0xC | 0xC0000005 = STATUS_ACCESS_VIOLATION |
היסטורית הן נערמו בסדר הזה: קודי שגיאת Win32 שירשו מספרי שגיאה של MS-DOS, NTSTATUS שה-NT kernel משתמש בו internally, ו-HRESULT שתוכנן עם כניסת COM כדי “לארוז success/failure ומקור ב-32 bit”. ב-Windows של היום זרימת ההמרה יומיומית: ה-kernel מחזיר NTSTATUS, Win32 subsystem ממיר אותו לקוד שגיאת Win32, ושכבת COM עוטפת אותו הלאה כ-HRESULT.124
flowchart TB
accTitle: זרימת ההמרה בין שלוש המערכות
accDescr: Win32 subsystem ממיר NTSTATUS שה-kernel החזיר לקוד שגיאת Win32, ושכבת COM עוטפת אותו הלאה כ-HRESULT
kernel["kernel ו-drivers"] --> nt["NTSTATUS (שגיאות 0xC…)"]
nt -->|Win32 subsystem ממירה| win["קוד שגיאת Win32 (5 וכדומה)"]
win -->|שכבת COM עוטפת| hr["HRESULT (0x8007xxxx)"]
איור 1: זרימת ההמרה בין השכבות. NTSTATUS של ה-kernel הופך לשגיאת Win32, ואז נעטף הלאה כ-HRESULT.
2.1. להתרגל לעבור בין decimal ל-hex
לפני שמבחינים בין שלוש המערכות צריך לדעת שאותו קוד מוצג לפעמים כ-decimal ולפעמים כ-hex, לפי המצב.
- “שגיאה 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
כשרואים decimal שלילי שמתחיל ב-“214…-“, ממירים מיד ל-hex. זה לבד חוסך הרבה בלבול בכניסה לחקירה.
flowchart TB
accTitle: שלושה מראים של אותו קוד
accDescr: שגיאה עשרונית 5, hex 0x5 וה-16 bits הנמוכים של 0x80070005 כולם מצביעים על אותו ERROR_ACCESS_DENIED
d["ייצוג עשרוני: שגיאה 5"] --> same["ERROR_ACCESS_DENIED"]
h["ייצוג hex: 0x5"] --> same
l["ה-16 bits הנמוכים של 0x80070005"] --> same
same -.-> memo["ייצוג שונה, אותו קוד"]
איור 2: decimal, hex וה-16 bits הנמוכים של HRESULT הם רק ייצוגים שונים של אותו קוד.
3. קודי שגיאת Win32 — GetLastError ו-FORMAT_MESSAGE
3.1. ההתנהגות הבסיסית של GetLastError
הרבה Win32 APIs כמו CreateFile ו-RegOpenKeyEx מסמנים כישלון ב-return value (FALSE, NULL, INVALID_HANDLE_VALUE וכדומה), ושומרים את קוד השגיאה המפורט ב-last-error code שמוחזק per-thread. הקורא שולף אותו ב-GetLastError מיד אחרי שווידא שהקריאה נכשלה.13
יש שתי אזהרות מעשיות.13
- קוראים מיד אחרי הכישלון. אם מכניסים באמצע קריאת API אחרת (פונקציית לוג, למשל), הקריאה הזו יכולה לדרוס את ה-last-error code.
- לא סומכים על הערך בהצלחה. חלק מה-APIs מאפסים את ה-last-error code ל-0 בהצלחה; חלק לא נוגעים בו. הכלל: קודם לוודא כישלון לפי ה-return value, ורק אז לקרוא.
sequenceDiagram
accTitle: לקרוא GetLastError מיד אחרי כישלון
accDescr: אחרי שווידאים כישלון לפי ה-return value, שולפים את ה-last-error code ב-GetLastError מיד, בלי להכניס קריאת API אחרת
participant app as App
participant api as Win32 API
app->>api: קריאת CreateFile
api-->>app: return value של כישלון
app->>api: GetLastError
api-->>app: קוד 5
Note over app: API אחר באמצע יכול לדרוס
איור 3: קוראים את ה-last-error code מיד אחרי כישלון. קריאת 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);
}
אם משאירים בלוג של האפליקציה גם decimal, גם hex וגם את טקסט ההודעה, כמו כאן, החקירה אחר כך מתקצרת משמעותית.
flowchart TB
accTitle: לשלוף הודעה מהקוד ולהשאיר בלוג
accDescr: מעבירים את הדגל FORMAT_MESSAGE_FROM_SYSTEM ל-FormatMessage כדי לקבל את מחרוזת ההודעה של קוד השגיאה, ומשאירים בלוג decimal, hex וטקסט
code["קוד שגיאה (דוגמה: 5)"] --> fm["קבלת המחרוזת ב-FormatMessage"]
fm --> msg["טקסט ההודעה"]
msg --> log["רישום בלוג"]
log -.-> both["לכתוב decimal, hex וטקסט יחד"]
איור 4: ממירים קוד שגיאה למחרוזת הודעה ב-FormatMessage, ומשאירים בלוג decimal, hex וטקסט יחד.
3.2. קודים נפוצים שחוזרים שוב ושוב בשטח
קודי שגיאת Win32 מוגדרים בטווח 0–15999, וב-Microsoft Learn יש רשימה מלאה.1 מתוכם, אלה שפוגשים שוב ושוב בחקירת תקלות:
| עשרוני | hex | symbol | משמעות |
|---|---|---|---|
| 2 | 0x2 | ERROR_FILE_NOT_FOUND | הקובץ שצוין לא נמצא |
| 3 | 0x3 | ERROR_PATH_NOT_FOUND | הנתיב שצוין לא נמצא |
| 5 | 0x5 | ERROR_ACCESS_DENIED | הגישה נדחתה |
| 32 | 0x20 | ERROR_SHARING_VIOLATION | process אחר משתמש בקובץ, אי אפשר לגשת |
| 87 | 0x57 | ERROR_INVALID_PARAMETER | הפרמטר שגוי |
| 122 | 0x7A | ERROR_INSUFFICIENT_BUFFER | ה-buffer שהועבר קטן מדי |
| 998 | 0x3E6 | ERROR_NOACCESS | גישה לא חוקית לכתובת זיכרון |
| 1223 | 0x4C1 | ERROR_CANCELLED | המשתמש ביטל את הפעולה |
מתוכם, 998 (ERROR_NOACCESS) אינו “הגישה נדחתה” אלא הייצוג של Win32 ל-access violation בזיכרון — הצורה של NTSTATUS STATUS_ACCESS_VIOLATION אחרי המרה לשכבת Win32. לא להתבלבל עם המספר 5. גם 1223 (ERROR_CANCELLED) הוא קוד שמופיע כשהמשתמש בוחר “לא” בתיבת ה-UAC elevation, למשל — יותר “בוטל” מאשר שגיאה.
flowchart TB
accTitle: שגיאות 998 ו-5 הן דברים שונים
accDescr: 998 היא access violation בזיכרון, access violation של NTSTATUS שהומרה לשכבת Win32, ושונה במשמעות מ-5 שמייצגת הגישה נדחתה
nt["NTSTATUS 0xC0000005"] -->|הומר לשכבת Win32| e998["שגיאה 998 (ERROR_NOACCESS)"]
e998 -.-> m1["המשמעות היא access violation בזיכרון"]
e5["שגיאה 5 (הגישה נדחתה)"] -.-> m2["בעיית הרשאות. דבר אחר מ-998"]
איור 5: שגיאה 998 היא access violation של NTSTATUS שהומרה לשכבת Win32, דבר אחר מדחיית-הגישה 5.
3.3. לאותו קוד יש משמעות שונה לפי ה-context
חשוב יותר מלשנן את טבלת הקודים הנפוצים הוא להבין שקוד שגיאה אומר רק את סוג הכישלון.
- שגיאה 5 (הגישה נדחתה): רשימת הסיבות האפשריות רחבה — חסר ACL ב-NTFS, כתיבה לאזור מוגן בלי הרשאות Administrator, חסימה של אנטי-וירוס או AppLocker, הרשאות חסרות ל-service account וכן הלאה.
- שגיאה 2 (הקובץ לא נמצא): לא בהכרח הקובץ שהמשתמש ציין. DLL תלויה שה-EXE ניסה לטעון במרומז, קובץ הגדרות שנראה במקום הלא נכון בגלל Registry redirect (32-bit/64-bit), נתיב שה-environment variable שלו לא התרחב — “איזה קובץ” לא נמצא לא רואים מהקוד.
- שגיאה 32 (sharing violation): השאלה האמיתית היא “איזה process מחזיק את הקובץ”, אבל הקוד לא אומר את זה.
flowchart TB
accTitle: הסיבה לשגיאה 5 נקבעת לפי ה-context
accDescr: גם לאותה דחיית גישה יש כמה סיבות אפשריות כמו ACL חסר או היעדר הרשאות Administrator, וצריך לזהות איזה API נכשל מול מה
e5["שגיאה 5 (הגישה נדחתה)"] --> c1["ACL חסר"]
e5 --> c2["אין הרשאות Administrator"]
e5 --> c3["חסימה של מוצר אבטחה"]
e5 --> c4["הרשאות service נמוכות"]
c1 --> next["Procmon: היעד שנכשל"]
c2 --> next
c3 --> next
c4 --> next
איור 6: הקוד אומר רק את סוג הכישלון. לשגיאה 5 יש כמה סיבות אפשריות, וצריך לזהות את היעד.
הכלי שמראה בפועל “איזה API, מול איזה object name, החזיר איזו תוצאה” הוא Process Monitor. השימוש בו מפורט ב”מדריך מעשי ל-Process Monitor (ProcMon)”. חיפוש משמעות קוד השגיאה וזיהוי היעד שנכשל הולכים יחד.
4. HRESULT — איך לקרוא את המבנה הארוז ב-32 bit
4.1. פריסת ה-bits
HRESULT הוא פורמט שמכניס success/failure, מקור וקוד פירוט לערך 32-bit אחד. המפרט שפורסם [MS-ERREF] מגדיר אותו בפריסה הבאה.2
| מיקום ה-bit | שם | משמעות |
|---|---|---|
| 31 | S | Severity. 0 = success, 1 = failure |
| 30 | R | reserved (חלק מ-severity כשממפים NTSTATUS) |
| 29 | C | Customer bit. 1 פירושו קוד שהוגדר על ידי מי שאינו Microsoft |
| 28 | N | 1 פירושו ערך NTSTATUS שממופה למרחב HRESULT |
| 27 | X | reserved (0) |
| 26–16 | Facility | קוד facility שמציין את המקור (11 bits) |
| 15–0 | Code | קוד פירוט בתוך ה-facility (16 bits) |
כש-S-bit הגבוה ביותר הוא 1, כלומר HRESULT שה-hex שלו מתחיל ב-0x8 ומעלה הוא כישלון. תצוגה כ-signed 32-bit integer הופכת אותו לשלילי — זה המקור של ה-“214…-“ שהוזכר קודם.
flowchart TB
accTitle: הקשר בין S-bit לתצוגה שלילית
accDescr: HRESULT של כישלון מדליק את S-bit הגבוה ביותר ל-1, ולכן ב-hex הוא מתחיל ב-0x8 ומעלה, וכ-signed 32-bit integer הוא שלילי
s["S-bit = 1 (כישלון)"] --> hex["ה-hex מתחיל ב-0x8 ומעלה"]
hex --> neg["תצוגה עם סימן יוצאת שלילית"]
neg --> back["כשרואים שלילי, ממירים ל-hex וקוראים"]
איור 7: HRESULT של כישלון מתחיל ב-0x8 ומעלה כי S-bit הוא 1, ובתצוגה עם סימן הוא שלילי.
ערכי Facility נפוצים הם אלה.5
| Facility | ערך | מראה ב-hex | משמעות |
|---|---|---|---|
| FACILITY_NULL | 0 | 0x8000xxxx | קודים משותפים רחבים (E_FAIL, E_UNEXPECTED וכדומה) |
| FACILITY_RPC | 1 | 0x8001xxxx | מקור RPC |
| FACILITY_ITF | 4 | 0x8004xxxx | שגיאה מוגדרת-interface (המשמעות תלויה ב-interface) |
| FACILITY_WIN32 | 7 | 0x8007xxxx | קוד שגיאת Win32 עטוף |
| FACILITY_WINDOWS | 8 | 0x8008xxxx | interfaces נוספים שהוגדרו ב-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, מפסיקים לחפור בקוד עצמו, ומעבירים את החקירה ל”איזה רכיב החזיר” ו”יש פירוט ב-Event Log או בלוג האפליקציה באותו זמן”.
עבור 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 הוא הקוד הגנרי E_FAIL של FACILITY_NULL, בלי פירוט, ויש לעבור לחקירת context; 0x80070005 הוא FACILITY_WIN32 ואפשר לקרוא אותו כעטיפה של שגיאת Win32 מספר 5, הגישה נדחתה
a["0x80004005"] --> af["Facility=0 (FACILITY_NULL)"]
af --> ac["Code=0x4005 → E_FAIL"]
ac --> ax["כישלון לא מוגדר. הלאה לחקירת context"]
b["0x80070005"] --> bf["Facility=7 (FACILITY_WIN32)"]
bf --> bc["Code=0x0005 → 5"]
bc --> bx["ERROR_ACCESS_DENIED"]
איור 8: גם אותו “כישלון” — אחרי פירוק כמות המידע שונה. מ-0x80070005 אפשר להגיע לשגיאת Win32 מספר 5.
4.3. ה-pattern החשוב ביותר: 0x8007xxxx = HRESULT_FROM_WIN32
כדי להעביר כישלון משכבה תחתונה שיכולה להחזיר רק קוד שגיאת Win32 לשכבה עליונה שמחזירה HRESULT (מתודת COM או ה-.NET runtime), winerror.h מספק את המאקרו HRESULT_FROM_WIN32.4 ההתנהגות: “לשמור את קוד שגיאת Win32 ב-16 bits הנמוכים, לשים Facility ל-FACILITY_WIN32 (7), ולהדליק את S-bit ל-1”.
flowchart TB
accTitle: איך HRESULT_FROM_WIN32 עובד
accDescr: שומרים את קוד שגיאת Win32 ב-16 bits הנמוכים, שמים Facility ל-7 ואת S-bit ל-1, ומרכיבים HRESULT 0x8007xxxx
win["קוד שגיאת Win32 (דוגמה: 5)"] --> low["שמירה ב-16 bits הנמוכים"]
low --> fac["Facility מוגדר ל-7"]
fac --> sbit["S-bit מוגדר ל-1"]
sbit --> hr["0x80070005"]
איור 9: HRESULT_FROM_WIN32 שומר את שגיאת Win32 ב-16 bits הנמוכים ומגדיר Facility=7 ואת S-bit.
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)
כדי לקרוא לכיוון השני, שולפים את ה-16 bits הנמוכים ב-PowerShell.
0x80070005 -band 0xFFFF # 5 → ERROR_ACCESS_DENIED
0x80072EE7 -band 0xFFFF # 12007 → ERROR_INTERNET_NAME_NOT_RESOLVED (WinINet)
כמו בדוגמה השנייה, גם שגיאות WinINet ו-WinHTTP (טווח הקודים 12000) מוגדרות במרחב קודי שגיאת Win321, ולכן 0x8007xxxx של רשת אפשר לפרק באותו הליך. המיומנות המעשית החשובה ביותר לקחת מהמאמר: ברגע שרואים 0x8007, ממירים את 4 הספרות הנמוכות ל-decimal.
יש אזהרה הפוכה ל-0x8004xxxx (FACILITY_ITF). לקוד FACILITY_ITF מי שמגדיר את המשמעות משתנה לפי interface, ולכן אותו ערך 32-bit יכול לומר משהו אחר אם מי שהחזיר אותו שונה.5 ל-0x8004xxxx לא מוכר מחפשים לא בחיפוש גנרי אלא בתיעוד הרכיב שהחזיר (ספרייה, SDK של driver, מוצר שרת).
flowchart TB
accTitle: איך מחפשים משתנה בין 0x8007 ל-0x8004
accDescr: FACILITY_WIN32 0x8007xxxx נקרא בפירוק מכני של ה-16 bits הנמוכים, אבל ל-FACILITY_ITF 0x8004xxxx מי שמגדיר משמעות משתנה לפי interface, לכן מחפשים בחומרים של הרכיב שהחזיר
hr{"Facility הוא?"} -->|7, WIN32| w["המרת ה-16 bits הנמוכים ל-decimal"]
hr -->|4, ITF| i["המשמעות שונה לפי מי שהחזיר"]
w --> ww["לקרוא כשגיאת Win32"]
i --> ii["לחפש בחומרים של מי שהחזיר"]
איור 10: 0x8007xxxx אפשר לפרק מכנית; 0x8004xxxx מחפשים בחומרים של הרכיב שהחזיר.
5. NTSTATUS — קודי שכבת ה-kernel ועולם הקריסות
5.1. הפריסה ו-Severity
NTSTATUS הוא קוד 32-bit שה-kernel, drivers ו-native APIs של ntdll משתמשים בו, והפריסה דומה ל-HRESULT בלי להיות זהה.3
| מיקום ה-bit | שם | משמעות |
|---|---|---|
| 31–30 | Sev | Severity. 00 = success, 01 = informational, 10 = warning, 11 = error |
| 29 | C | Customer bit |
| 28 | N | reserved (0, כדי שאפשר יהיה למפות ל-HRESULT) |
| 27–16 | Facility | Facility (12 bits) |
| 15–0 | Code | קוד פירוט |
כי severity הוא 2 bits, אפשר לקרוא את הסוג מהספרה הראשונה ב-hex. 0xC… הוא error (11), 0x8… הוא warning (10), 0x4… הוא informational (01), 0x0–0x3… הוא success. breakpoint exception 0x80000003 (STATUS_BREAKPOINT) היא דוגמה מייצגת ל”warning, לא error”.37
flowchart TB
accTitle: NTSTATUS נקרא לפי סוג מהספרה המובילה
accDescr: כי severity הוא 2 bits, NTSTATUS נקרא כ-error אם הספרה הראשונה ב-hex היא 0xC, warning אם 0x8, informational אם 0x4, ו-success אם 0x0 עד 0x3
head{"הספרה הראשונה ב-hex?"} -->|0xC| e["error"]
head -->|0x8| w["warning"]
head -->|0x4| i["informational"]
head -->|0x0–0x3| s["success"]
w -.-> ex["דוגמה: 0x80000003 היא warning"]
איור 11: NTSTATUS נקרא לפי סוג מהספרה הראשונה ב-hex. 0x80000003 הוא “warning, לא error”.
5.2. איפה פוגשים אותו — exception codes, STOP codes ו-Event Log
המצבים שבהם אנשי IT ומפתחים פוגשים NTSTATUS קשורים בעיקר לקריסות.
- Exception code של קריסת אפליקציה: “Exception code: 0xc0000005” שנרשם ב-Event Log תחת “Application Error (event ID 1000)” הוא NTSTATUS. ערכים מייצגים:7
| ערך | symbol | משמעות |
|---|---|---|
| 0xC0000005 | STATUS_ACCESS_VIOLATION | access violation (גישה לא חוקית לזיכרון) |
| 0xC0000135 | STATUS_DLL_NOT_FOUND | DLL נדרשת לא נמצאה ואי אפשר להתחיל |
| 0xC00000FD | STATUS_STACK_OVERFLOW | stack overflow |
| 0xC0000374 | STATUS_HEAP_CORRUPTION | heap corruption |
- STOP code של מסך כחול: במבט ראשון הם נראים דומים, אבל STOP code (bug check code) הוא מערכת מספור משלו, נפרדת מ-NTSTATUS, כמו 0x0000009F (DRIVER_POWER_STATE_FAILURE), ויש לו reference ייעודי.14 מספיק לזכור את ההבחנה: “0xC0000005 הוא NTSTATUS; STOP 0x9F הוא bug check code ואסור לחפש אותו בטבלת NTSTATUS”.
- עמודת Result של Process Monitor: NAME NOT FOUND ו-ACCESS DENIED בעמודת Result של Procmon הם שמות תצוגה של ה-NTSTATUS שה-kernel החזיר (STATUS_OBJECT_NAME_NOT_FOUND, STATUS_ACCESS_DENIED). כאן גם רואים בבירור את ההתאמה בין השכבות: כשל I/O של קובץ נצפה במונחי NTSTATUS, ואז מומר לשגיאת Win32 ומגיע לאפליקציה.
flowchart TB
accTitle: הבחנה בין exception code ל-STOP code
accDescr: קוראים exception code של Event Log כ-NTSTATUS; מחפשים STOP code של מסך כחול ב-reference הייעודי של bug-check codes, מערכת אחרת
q{"איפה הופיע הקוד?"} -->|exception code| nt["לקרוא כ-NTSTATUS"]
q -->|STOP code| bc["לחפש בטבלת bug-check codes"]
nt -.-> n1["דוגמה: 0xC0000005"]
bc -.-> b1["דוגמה: 0x0000009F"]
איור 12: exception code ב-Event Log הוא NTSTATUS; STOP code של מסך כחול הוא מערכת אחרת. לא מחפשים בטבלה הלא נכונה.
חקירה מעבר ל-exception code, כלומר איסוף וניתוח של crash dump, מכוסה ב”מבוא לאיסוף crash dump ב-Windows” וב”קריאת crash dumps עם WinDbg + SOS”.
5.3. הקשר ל-HRESULT — N-bit ו-RtlNtStatusToDosError
הגשר בין NTSTATUS לשתי השכבות האחרות עובר בשני נתיבים.
- מיפוי למרחב HRESULT: הדלקת N-bit של HRESULT (0x10000000) מכניסה ערך NTSTATUS למרחב HRESULT כמו שהוא (המאקרו HRESULT_FROM_NT ב-winerror.h). מיפוי של 0xC0000005 הופך למשל ל-0xD0000005. כשרואים HRESULT שמתחיל ב-0xD, השלב הנכון הוא להוריד את N-bit ולקרוא כ-NTSTATUS.2
- המרה לקוד שגיאת Win32:
RtlNtStatusToDosErrorשל ntdll ממיר 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). כדאי לזכור גם שה-vocabulary העשיר של ה-kernel לפעמים מעוגל בשכבת Win32 להבחנה גסה יותר.
flowchart TB
accTitle: שני גשרים מ-NTSTATUS לשכבות האחרות
accDescr: NTSTATUS עובר לשכבות האחרות בשתי דרכים: מיפוי למרחב HRESULT בהדלקת N-bit, והמרה לקוד שגיאת Win32 על ידי RtlNtStatusToDosError
nt["NTSTATUS (0xC0000005)"] -->|הדלקת N-bit| hr["HRESULT (0xD0000005)"]
nt -->|RtlNtStatusToDosError| win["שגיאת Win32 998 (ERROR_NOACCESS)"]
win -.-> memo["ERROR_MR_MID_NOT_FOUND אם אין התאמה מוגדרת"]
איור 13: יש שני גשרי NTSTATUS. התחלה 0xD נקראת כ-NTSTATUS אחרי שמורידים את N-bit.
6. COM ו-.NET — איך קוד שגיאה ממופה ל-exception
6.1. הגישה של COM — HRESULT + IErrorInfo
מתודת COM מחזירה ביסוד HRESULT, אבל יש גבול למה שאפשר לדחוס ב-32 bit, ולכן כהשלמה מנגנון IErrorInfo יכול להעביר בנפרד מחרוזת תיאור שגיאה ומקור. ב-C++, המחלקה _com_error בתמיכת ה-compiler מטפלת ב-HRESULT וב-IErrorInfo יחד. אפליקציה שתיבת השגיאה שלה מציגה “קוד + תיאור” לרוב מעבירה את התיאור במנגנון הזה.
flowchart TB
accTitle: IErrorInfo שמשלים HRESULT
accDescr: יש גבול למה שאפשר לדחוס ב-HRESULT בן 32 bit, לכן מחרוזת תיאור שגיאה ומקור מועברים בנפרד ב-IErrorInfo, וב-C++ המחלקה _com_error מטפלת בשניהם יחד
hr["HRESULT (רק 32 bit)"] --> lim["יש גבול למה שנכנס"]
lim --> ei["IErrorInfo נושא את התיאור"]
ei --> ce["_com_error מטפל בהם יחד"]
ce -.-> dlg["קוד + תיאור של התיבה"]
איור 14: מחרוזת תיאור שלא נכנסת ל-HRESULT בן 32 bit מגיעה בנפרד דרך IErrorInfo.
6.2. הגישה של .NET — מ-HRESULT לסוג exception
כשה-.NET runtime מקבל כישלון HRESULT ב-COM interop, הוא ממיר אותו ל-exception. HRESULT מוכר ממופה לסוג ה-exception המתאים; לא מוכר הופך ל-COMException.11
flowchart TB
accTitle: מיפוי מ-HRESULT ל-exception של .NET
accDescr: HRESULT כישלון שמתקבל ב-COM interop מומר לסוג ה-exception המתאים אם מוכר, או ל-COMException אם לא, ובכל מקרה הערך המקורי נשמר ב-Exception.HResult
hr["HRESULT כישלון"] --> known{"יש mapping מוכר?"}
known -->|כן| typed["המרה לסוג ה-exception המתאים"]
known -->|לא| comex["המרה ל-COMException"]
typed --> keep["הערך המקורי נשמר ב-Exception.HResult"]
comex --> keep
איור 15: .NET ממפה HRESULT לסוג exception, והערך המקורי נשאר ב-Exception.HResult בכל exception.
| HRESULT | סוג exception ב-.NET |
|---|---|
| E_ACCESSDENIED (0x80070005) | UnauthorizedAccessException |
| E_OUTOFMEMORY (0x8007000E) | OutOfMemoryException |
| E_INVALIDARG (0x80070057) | ArgumentException |
| E_NOTIMPL (0x80004001) | NotImplementedException |
| ערך בלי mapping מוגדר | COMException (הערך המקורי במאפיין ErrorCode) |
בכל exception, ה-HRESULT המקורי נשמר במאפיין Exception.HResult. הסתעפות בטיפול ב-exceptions של file I/O כמו “לנסות שוב רק ב-sharing violation” אפשר לכתוב לפי הערך הזה.
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
כשקוראים ל-Win32 API ישירות דרך P/Invoke, מגדירים SetLastError = true ב-DllImport (או LibraryImport), ואז שולפים ב-Marshal.GetLastWin32Error (מ-.NET 6, המקביל GetLastPInvokeError). להגדיר את GetLastError עצמו כ-P/Invoke ולקרוא לו אינו מדויק, כי קריאת API בתוך ה-runtime יכולה לדרוס את הערך.15
flowchart TB
accTitle: שליפת השגיאה האחרונה ב-P/Invoke
accDescr: לציין SetLastError כ-true ולשלוף ב-Marshal.GetLastWin32Error נכון; לקרוא ל-GetLastError ישירות ב-P/Invoke אינו מדויק בגלל overwrite של ה-runtime
pi["קריאה ל-Win32 API דרך P/Invoke"] --> ok["ציון SetLastError=true"]
ok --> get["שליפה ב-GetLastWin32Error"]
pi --> ng["הגדרה שקוראת ל-GetLastError ישירות"]
ng --> bad["ה-runtime דורס וזה לא מדויק"]
איור 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 ל-exception ואיך לרשום אותה בלוג מכוסה ב”איפה לשים catch ו-logging בטיפול ב-exceptions?”.
7. כלי המרה וחקירה בפועל — cheat sheet להעתקה
7.1. err.exe (Microsoft Error Lookup Tool)
כלי חיפוש שגיאות עצמאי ש-Microsoft מפיצה. הוא עובר על הרבה header files כמו winerror.h ו-ntstatus.h ומציג הגדרות והודעות שתואמות לקוד שצוין.8
err 0x80070005
err 5
err 0xC0000005
מספר אחד יכול לפגוע בכמה headers (למשל “5” תואם הגדרות במקומות שונים מלבד Win32 ERROR_ACCESS_DENIED), ולכן איזה מהמועמדים סביר בוחרים לפי ה-context. שם קובץ ההורדה כולל גרסה (Err_6.4.5.exe בעת הכתיבה), ושימו לב גם שהגדרות הקוד מבוססות על ה-headers בזמן האריזה.8
flowchart TB
accTitle: לבחור תוצאות חיפוש err.exe לפי context
accDescr: err.exe עובר על הרבה header files ומציג הגדרות תואמות, לכן כשכמה מועמדים עולים לאותו מספר, בוחרים את הסביר לפי context
in["הזנת err 5"] --> scan["מעבר על הרבה headers"]
scan --> hits["כמה הגדרות פגעו"]
hits --> pick["בחירת מועמד סביר לפי context"]
איור 17: err.exe הוא חיפוש חוצה-headers, ולכן כמה מועמדים יכולים לעלות, ואת הסביר בוחרים לפי context.
7.2. פקודות מובנות ב-Windows
מה שאפשר להשתמש בו בלי התקנה נוספת הם certutil ו-net helpmsg. האפשרות -error של certutil מציגה את טקסט ההודעה שמתאים לקוד שגיאה, ומקבלת HRESULT ב-hex או decimal.9
certutil -error 0x80070005
certutil -error 5
net helpmsg 5
net helpmsg הוא לקוד שגיאת Win32 ב-decimal בלבד, אבל בסביבה עברית ההודעה חוזרת בעברית, ולכן אפשר להשתמש בה כמו שהיא בהסבר למשתמש.
flowchart TB
accTitle: איך לבחור בין הפקודות הסטנדרטיות
accDescr: קוד שגיאת Win32 עשרוני מחפשים ב-net helpmsg; קוד שכולל hex, כולל HRESULT, מחפשים באפשרות -error של certutil
q{"הקוד שיש לכם הוא?"} -->|Win32 עשרוני| net["net helpmsg"]
q -->|כולל hex| cert["certutil -error"]
net -.-> jp["הודעה עברית חוזרת"]
cert -.-> any["מקבל hex ו-decimal"]
איור 18: איך לבחור בין הפקודות הסטנדרטיות. שגיאת Win32 עשרונית היא net helpmsg; אם יש hex, certutil -error.
7.3. אוסף one-liners ב-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 של WinDbg
כדי לחפש קוד במהלך ניתוח dump, ההרחבה !error של WinDbg מהירה. כברירת מחדל היא מפרשת כקוד שגיאת 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>
ב-crash dump, !analyze -v מציג אוטומטית את ה-exception code (NTSTATUS), ולכן הזרימה היא לאשר משם את המשמעות ב-!error <code> 1.
flowchart TB
accTitle: זרימת אישור exception code ב-WinDbg
accDescr: ב-crash dump פקודת analyze מציגה אוטומטית את ה-exception code; מעבירים את הקוד להרחבת error עם ארגומנט שני 1 ומאשרים את המשמעות כ-NTSTATUS
dump["פתיחת ה-crash dump"] --> an["הרצת !analyze -v"]
an --> exc["ה-exception code מוצג"]
exc --> chk["אישור המשמעות ב-!error code 1"]
איור 19: בניתוח dump מחפשים את ה-exception code ש-!analyze -v הציג ב-!error עם דגל 1.
8. הליך החקירה — מזיהוי השכבה ועד התאמה מול ה-context
מרכיבים את הידע עד כאן להליך לחקירת קוד שגיאה בפועל.
- מיישרים את הייצוג. אם זה decimal שלילי, ממירים ל-hex בן 8 ספרות. ממלאים אפסים ל-hex קצר מ-8 ספרות וקוראים.
- קובעים מאיזו שכבה הקוד. כמו בטבלת הזיהוי למטה, הספרות המובילות כמעט מחליטות.
- מפרקים ומוציאים את הקוד האמיתי. פעולה מכנית: ה-16 bits הנמוכים אם 0x8007xxxx, הורדת N-bit אם 0xDxxxxxxx.
- שולפים שם והגדרה בכלי. מאשרים את שם ה-symbol ואת ההודעה ב-err.exe, certutil או
!error. - מתאימים מול ה-context. מזהים איזו אפליקציה, איזו פעולה, איזה API, נכשל מול מה, מלוג האפליקציה, Event Log ו-Procmon. הקוד הוא “סוג הכישלון”; ה-context הוא “איפה הסיבה”.
flowchart TB
accTitle: הליך חקירת קוד שגיאה
accDescr: תבנית החקירה של יישור הייצוג ל-hex, זיהוי השכבה מהספרות המובילות, פירוק והוצאת הקוד האמיתי, חיפוש שם והגדרה בכלי, ואז התאמה מול ה-context
fix["יישור ל-hex"] --> judge{"ספרות מובילות?"}
judge -->|עשרוני| d1["קריאה כשגיאת Win32"]
judge -->|0x8007| d2["ה-16 bits הנמוכים → decimal"]
judge -->|0xC| d3["קריאה כ-NTSTATUS"]
judge -->|0xD| d4["הורדת N-bit וקריאה"]
d1 --> tool["חיפוש שם/הגדרה"]
d2 --> tool
d3 --> tool
d4 --> tool
tool --> ctx["התאמת context (Procmon)"]
איור 20: תבנית החקירה. מיישרים את הייצוג, מזהים שכבה ומפרקים, שולפים שם, ואז מתאימים מול ה-context.
| מראה | מועמד ראשון | איך לפרק ולהמיר |
|---|---|---|
| decimal בן 1 עד 5 ספרות (5, 1223 וכדומה) | קוד שגיאת Win32 | כמו שהוא ל-net helpmsg או err.exe |
| decimal שלילי (2147024891- וכדומה) | HRESULT | המרה ל-hex בן 8 ספרות, ואז זיהוי לפי השורות למטה |
| 0x8007xxxx | HRESULT (FACILITY_WIN32) | המרת ה-16 bits הנמוכים ל-decimal וקריאה כ-Win32 |
| 0x8004xxxx | HRESULT (FACILITY_ITF) | חיפוש בתיעוד הרכיב שהחזיר |
| 0x8000xxxx | HRESULT (FACILITY_NULL) | קוד גנרי כמו E_FAIL. מעבירים את המשקל לחקירת context |
| 0xCxxxxxxx | NTSTATUS (error) | !error <code> 1; המרה ל-Win32 וקריאה אם צריך |
| 0xDxxxxxxx | מיפוי HRESULT של NTSTATUS | הורדת N-bit (0x10000000) וקריאה כ-NTSTATUS |
| Facility משלו כמו 0x8024xxxx | HRESULT ייחודי לתחום | מזהים את התחום מערך Facility ועוברים לחומרים ייעודיים (0x8024… הוא Windows Update)2 |
מה שעוזר במיוחד בצעד 5 “התאמה מול ה-context” הוא עמודת Result של Process Monitor. גם אם האפליקציה מציגה רק “0x80070002”, Procmon אומר בשורה אחת “איזה process, מול איזה path, קיבל NAME NOT FOUND”. לחיפוש מצד Event Log ראו גם “מבוא ל-Event Log של Windows ול-ETW”.
9. קריאות שגויות נפוצות — תבניות שמאריכות את החקירה
לבסוף, תבניות קריאה שגויה שמופיעות בייעוץ אמיתי.
קריאה שגויה 1: לחשוב ש-0x80004005 הוא “קוד שמצביע על סיבה ספציפית”
E_FAIL הוא “Unspecified failure”, ואותו ערך מופיע ב-Windows Update, ברשת ובמסדי נתונים. לנסות כל תיקון שעולה כשמחפשים את הקוד הזה הוא כמעט בוודאות הדרך הארוכה. מצמצמים לא מהקוד אלא מ”איזו אפליקציה, איזו פעולה, לוגים אחרים באותו זמן”.6
קריאה שגויה 2: לא להבחין ש-decimal שלילי הוא HRESULT
מקרה של חיפוש לוג שאומר “Error -2147467259 occurred” כמו שהוא, או בלבול מ”שגיאה עם מינוס?”. כשרואים שלילי, ממירים ל-hex. זה לבד אומר שזה 0x80004005 (E_FAIL), ומתחבר לידע של קריאה שגויה 1.
קריאה שגויה 3: לחפש את כל 8 הספרות של 0x8007xxxx ולא להסתכל על שגיאת Win32 שמתחת
המהות של 0x80070005 היא “5 = הגישה נדחתה”. אחרי ששולפים את ה-16 bits הנמוכים, לחשוב “מה שגיאת Win32 5 אומרת ב-context של הפעולה הזו” מגיע לליבה מהר יותר מחיפוש כל 8 הספרות.
קריאה שגויה 4: להניח “אותו קוד = אותה סיבה”
אם פעם “שגיאה 5 נגרמה מאנטי-וירוס”, נוטים לקפוץ לאותו תיקון בשגיאה 5 הבאה. גם באותו קוד, אם ה-API שנכשל ו-resource היעד שונים, הסיבה היא דבר אחר. אישור משמעות הקוד וזיהוי היעד ב-Procmon או דומה הם זוג, בכל פעם.
קריאה שגויה 5: לבלבל שגיאת Win32 5 עם 0xC0000005, ו-STOP code עם NTSTATUS
להתייחס ל-ERROR_ACCESS_DENIED ול-STATUS_ACCESS_VIOLATION כאותו דבר בגלל הקשר “5” שולח את החקירה לכיוונים שונים לגמרי — בעיית הרשאות מול באג בתוכנית. גם STOP code של מסך כחול הוא מערכת אחרת מ-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. קודם קובעים איזו שכבה, מי החזיר את הקוד.
- תנודות הייצוג (decimal / hex / שלילי) אפשר ליישר מכנית. ממירים שלילי ל-hex בן 8 ספרות ואז קוראים.
- HRESULT הוא מבנה bits של S/R/C/N/X + Facility (11 bits) + Code (16 bits), ו-0x8007xxxx הוא ה-pattern החשוב ביותר: שגיאת Win32 עטופה. ממירים את ה-16 bits הנמוכים ל-decimal ומוציאים את הקוד האמיתי.
- קוד גנרי כמו 0x80004005 (E_FAIL) אינו מציין סיבה. ההחלטה להפסיק לחפור בקוד ולעבור לחקירת context אפשרית דווקא כי מכירים את המבנה.
- פוגשים NTSTATUS כ-exception code של קריסה או בעמודת Result של Procmon. 0xC0000005 הוא access violation, לא קשור לשגיאת Win32 5. STOP code הוא עוד מערכת.
- ב-.NET, HRESULT ממופה לסוג exception, והערך המקורי נשאר ב-Exception.HResult. ב-P/Invoke משתמשים ב-SetLastError=true וב-Marshal.GetLastWin32Error כזוג.
- כלי החיפוש הם certutil -error ו-net helpmsg (סטנדרט), err.exe (מכונת פיתוח), one-liners ב-PowerShell, ו-!error של WinDbg.
- ההליך הוא “יישור הייצוג → זיהוי השכבה → פירוק → חיפוש שם → התאמה מול ה-context”. מה שהקוד אומר הוא סוג הכישלון; איפה הסיבה אומר ה-context.
בפעם הבאה שפוגשים קוד שגיאה לא מוכר, מסתכלים על הספרות המובילות לפני שמדביקים בתיבת החיפוש. 4 הספרות הנמוכות אם 0x8007, NTSTATUS אם 0xC, המרה ל-hex אם שלילי — הפירוק של 10 השניות הזה קובע במידה רבה את זמן החקירה שאחרי.
מאמרים קשורים
- קריאת crash dumps עם WinDbg + SOS — מדריך מעשי לניתוח אחרי איסוף
- מבוא לאיסוף crash dump ב-Windows - WER, ProcDump, WinDbg
- איפה לשים catch ו-logging בטיפול ב-exceptions?
- מדריך מעשי ל-Process Monitor (ProcMon) — לאתר «ההגדרות לא נקראות» ו-«ACCESS DENIED» ב-10 דקות
- מבוא ל-Event Log של Windows ול-ETW — לשים את הלוגים של אפליקציית העסק על מנגנוני ה-OS הסטנדרטיים
תחומי ייעוץ קשורים
KomuraSoft LLC מטפלת בחקירת תקלות שמתחילה מקוד שגיאה — “לא ברור מה הקוד הזה אומר”, “0x80070005 מופיע רק בסביבה מסוימת” — בתכנון error handling לאפליקציות שמערבבות Win32 API, COM ו-.NET, ובאיתור סיבות עם crash dump ו-Process Monitor. ייעוץ מצילום מסך אחד של תיבת שגיאה מתאים.
מקורות
-
Microsoft Learn, Debug system error codes. מפתח לרשימת קודי שגיאת מערכת Win32 (0–15999); קבלת ההודעה לקוד ש-
GetLastErrorמחזיר ב-FormatMessage ובדגל FORMAT_MESSAGE_FROM_SYSTEM; ששגיאות WinINet/WinHTTP (טווח הקודים 12000) מוגדרות במרחב הזה; ושיטות חקירה עם Microsoft Error Lookup Tool ופקודת !err. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 -
Microsoft Open Specifications, [MS-ERREF]: HRESULT. פריסת ה-bits של HRESULT (ה-bits S, R, C, N ו-X, Facility בן 11 bits, Code בן 16 bits); ש-N-bit מציין ערך NTSTATUS שממופה למרחב HRESULT; ורשימת קודי facility כולל FACILITY_WINDOWS_UPDATE (36). ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Open Specifications, [MS-ERREF]: NTSTATUS. פריסת ה-bits של NTSTATUS (Sev בן 2 bits, C-bit, N-bit, Facility בן 12 bits, Code בן 16 bits); וש-severity מתפצל לארבעה סוגים: success (00), informational (01), warning (10) ו-error (11). ↩ ↩2 ↩3
-
Microsoft Learn, HRESULT_FROM_WIN32 macro. הגדרת מאקרו winerror.h שממפה קוד שגיאת מערכת Win32 לערך HRESULT. ↩ ↩2 ↩3
-
Microsoft Learn, Structure of COM Error Codes. תפקיד ה-severity bit של HRESULT ושדה ה-facility; ערכי FACILITY_NULL, FACILITY_RPC, FACILITY_ITF, FACILITY_WIN32 ו-FACILITY_WINDOWS; ושקוד FACILITY_ITF משמעותו מוגדרת לפי interface ואותו ערך יכול לומר משהו אחר. ↩ ↩2 ↩3
-
Microsoft Learn, Common HRESULT values. ש-E_FAIL (0x80004005) הוא “Unspecified failure”; והגדרות ערכי HRESULT נפוצות כמו E_ACCESSDENIED (0x80070005), E_INVALIDARG (0x80070057) ו-E_OUTOFMEMORY (0x8007000E). ↩ ↩2 ↩3 ↩4
-
Microsoft Open Specifications, [MS-ERREF]: NTSTATUS values. רשימת ערכי NTSTATUS כולל STATUS_ACCESS_VIOLATION (0xC0000005), STATUS_DLL_NOT_FOUND (0xC0000135), STATUS_STACK_OVERFLOW (0xC00000FD), STATUS_HEAP_CORRUPTION (0xC0000374) ו-STATUS_BREAKPOINT (0x80000003). ↩ ↩2 ↩3
-
Microsoft Learn, The Microsoft Error Lookup Tool. שזה כלי עצמאי שמציג את טקסט ההודעה הקשור לקוד מצב hex על פני header files שונים כמו Winerror.h; ששם קובץ ההורדה הוא Err_6.4.5.exe; ושצריך לשים לב שההגדרות הארוזות הן נכון לזמן הקימפול. ↩ ↩2 ↩3
-
Microsoft Learn, certutil. שאפשרות -error של certutil מציגה את טקסט ההודעה הקשור לקוד שגיאה, ושסימון שגיאה שכולל שם symbol משמש בצורה כמו 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND). ↩ ↩2
-
Microsoft Learn, !error. שהרחבת !error של WinDbg מפענחת ומציגה ערכי שגיאה של Win32, Winsock, NTSTATUS ו-NetAPI; ושקביעת 1 כדגל מפרשת כ-NTSTATUS. ↩ ↩2
-
Microsoft Learn, How to: Map HRESULTs and exceptions. מנגנון המיפוי ההדדי בין HRESULT של COM ל-exceptions של .NET; טבלת ההתאמה כמו E_NOTIMPL → NotImplementedException; ש-HRESULT בלי mapping מפורש מומר ל-COMException; וש-Message, Source וכדומה של ה-exception מאותחלים ממידע IErrorInfo. ↩ ↩2
-
Microsoft Learn, RtlNtStatusToDosError function (winternl.h). שזו פונקציה שממירה קוד NTSTATUS לקוד שגיאת מערכת Win32 המתאים; ש-ERROR_MR_MID_NOT_FOUND מוחזר כשאין התאמה מוגדרת; ושפונקציה שמבצעת המרה הפוכה אינה קיימת. ↩ ↩2
-
Microsoft Learn, Last-Error Code. שה-last-error code מוחזק per-thread; שיש לשלוף אותו ב-GetLastError מיד אחרי כישלון; ש-APIs שדורסים את הקוד ב-0 בהצלחה ו-APIs שלא נוגעים מעורבים; וש-bit 29 שמור לקודים שמוגדרים בידי האפליקציה. ↩ ↩2
-
Microsoft Learn, Bug check code reference. רשימת bug check codes (STOP codes) שמוצגים במסך כחול, ואיך להציג מידע על קוד בהרחבת !analyze של WinDbg. שזו מערכת מספור משלה, נפרדת מ-NTSTATUS, אפשר לאשר מהרשימה. ↩ ↩2
-
Microsoft Learn, Marshal.GetLastWin32Error Method. שזו דרך לשלוף את ה-last-error code של קריאת P/Invoke שהציבה את דגל SetLastError; שקריאה ישירה ל-GetLastError ב-P/Invoke אינה אמינה בגלל overwrite בקריאת API בתוך ה-runtime; ושמ-.NET 6 מומלץ GetLastPInvokeError. ↩
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
למה arguments נשברים — כללי command-line arguments ב-Windows
Windows מעביר ל-CreateProcess מחרוזת אחת שהמקבל מפצל. מכסה את כללי CommandLineToArgvW, CRT ו-.NET, ArgumentList, ובניה ב-C++.
מה נשאר אחרי שה-parent מת — מחזיקים child processes ב-Job Object
למה SDK helpers שורדים UI שנהרג ומחזיקים את המצלמה או את ה-COM port? מתכננים משך חיים של child process עם Job Objects, KillOnJobClose ו-c...
Win32 Thread Pool API — מקביליות בלי CreateThread, דרך CreateThreadpoolWork
מפזרים קריאות CreateThread בכל הקוד ה-native? המאמר מסביר את ה-Win32 Thread Pool API שעוצב מחדש ב-Vista: ארבעת האובייקטים work, timer, wa...
Named Pipes בפועל — ה-IPC הסטנדרטי של Windows, מתכנון עד אבטחה
מדריך מעשי ל-Named Pipes, ה-IPC הסטנדרטי ב-Windows. המאמר מסדר לפי מקורות ראשוניים את הבחירה בין byte mode ל-message mode, תכנון server ל...
אפליקציות שנשברות אחרי Sleep — power events ואיך לבנות אפליקציה עסקית ששורדת resume
פתחתם את ה-laptop והתקשורת של האפליקציה העסקית הייתה מתה. הסיבה היא תכנון שלא לקח Sleep בחשבון. המאמר עובר על זרימת WM_POWERBROADCAST, הת...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
יישומים עסקיים, חיבור התקנים וכלי תקשורת, מהגדרת הדרישות ועד הפיתוח.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- מה המשמעות של השגיאה 0x80004005?
- 0x80004005 הוא HRESULT בשם E_FAIL, והמשמעות היא "Unspecified failure" (כישלון לא מוגדר). הוא אומר רק שקרה כישלון שאי אפשר לדווח עליו בפירוט; זה לא קוד שמתאר את הסיבה עצמה. לכן אותו 0x80004005 מופיע במקומות שלא קשורים זה לזה — רשת, Windows Update, VBA, database driver. כשרואים את הקוד הזה, לא חופרים במשמעות שלו. מצמצמים את הסיבה לפי ה-context: איזו אפליקציה, איזו פעולה, ואיזה מידע שגיאה נוסף יש ב-Event Log או בלוג המפורט.
- מהו קוד שגיאה שלילי כמו 2147467259-?
- זה HRESULT של 32 bit שמוצג כ-decimal עם סימן. ב-HRESULT, כשיש כישלון, ה-most significant bit דלוק, ולכן כ-signed integer הערך תמיד שלילי. ב-PowerShell, '0x{0:X8}' -f -2147467259 ממיר אותו חזרה ל-hex (בדוגמה הזו 0x80004005 = E_FAIL). כשרואים בלוג או בהודעת שגיאה של סקריפט מספר שלילי שמתחיל ב-214…-, הצעד הראשון הוא להמיר ל-hex ואז לחפש.
- מה הדרך הכי פשוטה לבדוק מה קוד שגיאה אומר?
- בלי התקנה נוספת אפשר להשתמש ב-net helpmsg 5 בשורת הפקודה (לשגיאת Win32 ב-decimal) וב-certutil -error 0x80070005. certutil מקבל גם HRESULT ב-hex ומציג את שם ה-symbol ואת טקסט ההודעה. ב-PowerShell, [System.ComponentModel.Win32Exception]::new(5).Message מחזיר את ההודעה המקומית. על מכונת פיתוח כדאי לשים את כלי החיפוש הרשמי של Microsoft, err.exe (Microsoft Error Lookup Tool); הוא מחפש ב-Win32, HRESULT ו-NTSTATUS ומציג את כל ההגדרות שתואמות בבת אחת.
- איזה סוג שגיאה הוא 0xC0000005?
- זה NTSTATUS בשם STATUS_ACCESS_VIOLATION, כלומר access violation (גישה לא חוקית לזיכרון). זה הקוד שרואים הכי הרבה כ-Exception code ב-Event Log או ב-crash dump כשאפליקציה קורסת, והוא מצביע על באג בתוכנית כמו dereference של pointer לא תקין או גישה לזיכרון שכבר שוחרר. השם דומה לשגיאת Win32 5 (ERROR_ACCESS_DENIED = הגישה נדחתה), אבל זה קוד לא קשור ממערכת אחרת; לא מבלבלים ביניהם. הדרך האמינה לאתר את הסיבה היא לאסוף crash dump ולנתח אותו ב-WinDbg.
- למה הסיבה משתנה בכל פעם, גם כשזה אותו קוד שגיאה?
- כי קוד שגיאה מתאר רק איזה סוג כישלון זה, ומה נכשל ולמה נקבע לפי ה-context של הקריאה. שגיאה 5 (הגישה נדחתה), למשל, היא אותו קוד לסיבות שונות לגמרי — הרשאות NTFS חסרות, אין הרשאות Administrator, חסימה של אנטי-וירוס וכן הלאה. במצב דומה, אם process אחר עדיין מחזיק את הקובץ פתוח, מקבלים קוד אחר (שגיאה 32 = sharing violation), וקריאה נכונה של הקוד משנה לאן בודקים. גם שגיאה 2 (הקובץ לא נמצא) לעיתים קרובות אינה הקובץ הראשי אלא DLL תלויה או קובץ הגדרות. אחרי שבודקים מה הקוד אומר, הדרך הקצרה לסיבה היא לאשר ב-Process Monitor או כלי דומה איזה API נכשל מול איזה resource.