Как читать коды ошибок Windows — трёхслойная структура Win32, HRESULT и NTSTATUS
· Go Komura · Windows, Коды ошибок, HRESULT, NTSTATUS, Win32 API, Диагностика, Отладка, Разработка Windows
«На экране приложения появилась ошибка 0x80004005. Что это значит?» — В консультациях по расследованию инцидентов такой вопрос классический. Тот, кто вставил число из диалога ошибки прямо в поисковик и получил поток несвязанных статей — сбой Windows Update, общая папка, которая не подключается, ошибка выполнения VBA, сбой подключения к базе — и только ещё больше запутался, не одинок.
Так бывает, потому что 0x80004005 (E_FAIL) — общий код, единственный смысл которого — «неопределённый сбой». Один и тот же код используют в бесчисленных ситуациях, поэтому поиск только по коду не приводит к причине. С другой стороны, код вроде 0x80070005 при знании структуры можно за несколько секунд до поиска разобрать как «номер ошибки Win32 5 = доступ запрещён, обёрнутый в HRESULT».
Коды ошибок Windows по историческим причинам образуют три слоя — коды ошибок Win32, HRESULT и NTSTATUS — и преобразуются между слоями. Когда эта структура в голове, вы сами можете судить, «какой слой, какая сторона вернула этот код» и «какой код существенный», и первый ход расследования становится намного быстрее.
Статья рассчитана на ИТ-сотрудников малых и средних компаний и разработчиков приложений Windows и систематизирует, как различать и разбирать три системы кодов ошибок, связь с исключениями .NET и практический поиск через err.exe и PowerShell — опираясь на Microsoft Learn и опубликованную спецификацию [MS-ERREF] по состоянию на август 2026.
1. Сначала вывод
- Коды ошибок Windows — в основном три системы. Коды ошибок Win32 (маленькое десятичное число, которое возвращает
GetLastError), HRESULT (32-битный код со времён COM, hex, начинающийся с 0x8, или отрицательное десятичное) и NTSTATUS (коды слоя ядра; ошибки начинаются с 0xC).123 - Десятичная и шестнадцатеричная запись — разные обозначения одного кода. «Ошибка 5», «0x5» и «младшие 16 бит 0x80070005» все обозначают ERROR_ACCESS_DENIED (доступ запрещён).1
- 0x8007xxxx — это «обёрнутая ошибка Win32». Это код ошибки Win32, лежащий в HRESULT FACILITY_WIN32 (7); переведите младшие 16 бит в десятичное — и получите существенный код. Это самый важный шаблон чтения кодов ошибок.45
- 0x80004005 (E_FAIL) — не код причины. Он означает «Unspecified failure» и не несёт больше информации. Вместо того чтобы копать этот код, ищите исходный контекст и сопутствующие журналы.6
- Отрицательное десятичное (-2147467259 и подобные) — это HRESULT. Старший бит 32 бит (бит сбоя) установлен, поэтому знаковое отображение отрицательное. Переведите в hex и затем читайте.2
- 8-значное значение, начинающееся с 0xC, — это NTSTATUS. 0xC0000005 (нарушение доступа) и 0xC0000135 (DLL не найдена) постоянно появляются в журнале событий и дампах в момент сбоя. Они не связаны с номером ошибки Win32 5.7
- Один и тот же код меняет смысл с контекстом. Причина ошибки 5 охватывает ACL, повышение прав, антивирус, удерживаемый файл и другое, а «файл не найден» ошибки 2 часто оказывается зависимой DLL. Всегда читайте смысл кода вместе с тем, какой API не сработал против чего.1
- Инструменты преобразования и поиска стандартны.
certutil -errorиnet helpmsgвстроены в Windows;Win32Exceptionв PowerShell даёт сообщение; на машине разработки — err.exe (Microsoft Error Lookup Tool); при анализе дампа —!errorв WinDbg.8910 - В .NET HRESULT отображается на тип исключения. Известный HRESULT идёт в соответствующий тип (E_ACCESSDENIED → UnauthorizedAccessException и т. п.); неизвестный становится COMException; исходное значение остаётся в
Exception.HResult.11
Одной фразой шаблон расследования кода ошибки Windows — «выровнять запись на hex → решить, какой это слой → разобрать и извлечь существенный код → прочитать его вместе с контекстом».
2. В Windows три системы кодов ошибок
Сначала общая карта. Коды ошибок Windows делятся в основном на три системы — по слою, который их возвращает.
| Система | Кто в основном возвращает | Типичный вид | Представительный пример |
|---|---|---|---|
| Код ошибки Win32 | Win32 API (GetLastError), код выхода команды |
Маленькое десятичное (0–15999) | 5 = ERROR_ACCESS_DENIED |
| HRESULT | Компонент COM, оболочка, установщик, многие каркасы | 8-значный hex, начинающийся с 0x8, или отрицательное десятичное | 0x80004005 = E_FAIL |
| NTSTATUS | Ядро, драйвер, нативный API (ntdll) | Ошибки — 8-значный hex, начинающийся с 0xC | 0xC0000005 = STATUS_ACCESS_VIOLATION |
Исторически они наложились в таком порядке: коды ошибок Win32, унаследовавшие номера MS-DOS, NTSTATUS, которым ядро NT пользуется внутри, и HRESULT, спроектированный при появлении COM, чтобы «упаковать успех/сбой и происхождение в 32 бита». В текущем 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. Привыкайте читать десятичное и шестнадцатеричное взаимозаменяемо
Прежде чем различать три системы, нужно усвоить колебания записи. Один и тот же код показывают десятичным или 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
Увидев отрицательное десятичное, начинающееся с «-214…», рефлекторно переведите его в hex. Одно это отсекает много блужданий на входе в расследование.
flowchart TB
accTitle: Три облика одного кода
accDescr: Десятичная ошибка 5, hex 0x5 и младшие 16 бит 0x80070005 все обозначают один ERROR_ACCESS_DENIED
d["Десятичная запись: ошибка 5"] --> same["ERROR_ACCESS_DENIED"]
h["Шестнадцатеричная запись: 0x5"] --> same
l["Младшие 16 бит 0x80070005"] --> same
same -.-> memo["Разная запись, тот же код"]
Рис. 2: Десятичное, hex и младшие 16 бит HRESULT — лишь разные записи одного кода.
3. Коды ошибок Win32 — GetLastError и FORMAT_MESSAGE
3.1. Базовое поведение GetLastError
Многие Win32 API вроде CreateFile и RegOpenKeyEx показывают сбой возвращаемым значением (FALSE, NULL, INVALID_HANDLE_VALUE и т. п.) и кладут подробный код ошибки в «last-error code», хранимый на поток. Вызывающий забирает его через GetLastError сразу после подтверждения сбоя.13
Два практических предостережения.13
- Читайте сразу после сбоя. Если вставить другой вызов API (функцию журналирования, например), этот вызов может перезаписать last-error code.
- Не полагайтесь на значение при успехе. Одни API обнуляют last-error code при успехе; другие не трогают его. Правило — подтвердить сбой по возвращаемому значению и затем читать.
sequenceDiagram
accTitle: Читать GetLastError сразу после сбоя
accDescr: После подтверждения сбоя по возвращаемому значению сразу получить last-error code через 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: Читайте 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);
}
Оставлять в журнале своего приложения и десятичное, и hex, и текст сообщения, как здесь, делает последующее расследование на шаг быстрее.
flowchart TB
accTitle: Получить сообщение по коду и оставить в журнале
accDescr: Передать флаг FORMAT_MESSAGE_FROM_SYSTEM в FormatMessage, чтобы получить строку сообщения кода ошибки, и оставить в журнале десятичное, hex и текст
code["Код ошибки(пример: 5)"] --> fm["Получить строку через FormatMessage"]
fm --> msg["Текст сообщения"]
msg --> log["Записать в журнал"]
log -.-> both["Писать десятичное, hex и текст вместе"]
Рис. 4: Преобразуйте код ошибки в строку сообщения через FormatMessage и оставьте в журнале десятичное, hex и текст вместе.
3.2. Представительные коды, которые постоянно встречаются в поле
Коды ошибок Win32 определены в диапазоне 0–15999, и в Microsoft Learn есть полный список.1 Среди них лица, которые снова и снова встречаются при расследовании инцидентов, следующие.
| Десятичное | Hex | Символ | Смысл |
|---|---|---|---|
| 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 (файл не найден): это не обязательно файл, который указал пользователь. Зависимая DLL, которую EXE пытался загрузить неявно, файл настроек, увиденный не там из-за перенаправления реестра (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 | Зарезервировано (часть severity при отображении NTSTATUS) |
| 29 | C | Customer-бит. 1 означает код, определённый кем-то кроме Microsoft |
| 28 | N | 1 означает значение NTSTATUS, отображённое в пространство HRESULT |
| 27 | X | Зарезервировано (0) |
| 26–16 | Facility | Код facility, указывающий происхождение (11 бит) |
| 15–0 | Code | Код детали внутри facility (16 бит) |
Старший бит S равен 1, то есть HRESULT, чья hex-запись начинается с 0x8 или выше, — сбой. Показ как знакового 32-битного целого делает его отрицательным — вот суть упомянутого «-214…».
flowchart TB
accTitle: Связь бита S и отрицательного отображения
accDescr: HRESULT сбоя имеет старший бит S равным 1, поэтому в hex начинается с 0x8 или выше, а как знаковое 32-битное целое отрицателен
s["Бит S = 1(сбой)"] --> hex["Hex начинается с 0x8 или выше"]
hex --> neg["Знаковое отображение отрицательное"]
neg --> back["Увидев отрицательное, перевести в hex и читать"]
Рис. 7: HRESULT сбоя начинается с 0x8 или выше, потому что бит S равен 1, а знаковое отображение отрицательное.
Представительные значения Facility следующие.5
| Facility | Значение | Вид hex | Смысл |
|---|---|---|---|
| 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 и собрать HRESULT 0x8007xxxx
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)
Чтобы читать в другую сторону, возьмите младшие 16 бит в 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 цифры в десятичное» — практический навык номер один, который эта статья хочет оставить с вами.
Есть обратное предостережение для 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 — 32-битный код, которым пользуются ядро, драйверы устройств и нативные API ntdll, и его раскладка похожа на 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 бита, род читается по ведущей hex-цифре. 0xC… — ошибка (11), 0x8… — предупреждение (10), 0x4… — сведения (01), 0x0–0x3… — успех. Исключение точки останова 0x80000003 (STATUS_BREAKPOINT) — представительный пример «предупреждение, не ошибка».37
flowchart TB
accTitle: NTSTATUS читается по роду из ведущей цифры
accDescr: Поскольку severity — 2 бита, NTSTATUS читается как ошибка, если ведущая hex-цифра 0xC, предупреждение если 0x8, сведения если 0x4 и успех если 0x0–0x3
head{"Ведущая hex-цифра?"} -->|0xC| e["Ошибка"]
head -->|0x8| w["Предупреждение"]
head -->|0x4| i["Сведения"]
head -->|0x0–0x3| s["Успех"]
w -.-> ex["Пример: 0x80000003 — предупреждение"]
Рис. 11: NTSTATUS читается по роду из ведущей hex-цифры. 0x80000003 — «предупреждение, не ошибка».
5.2. Где вы его встречаете — коды исключений, коды STOP и журнал событий
Ситуации, в которых ИТ-сотрудники и разработчики встречают NTSTATUS, в основном связаны со сбоями.
- Код исключения сбоя приложения: «Exception code: 0xc0000005», записанный в журнале событий в «Ошибка приложения (код события 1000)», — это NTSTATUS. Представительные значения следующие.7
| Значение | Символ | Смысл |
|---|---|---|
| 0xC0000005 | STATUS_ACCESS_VIOLATION | Нарушение доступа (незаконное обращение к памяти) |
| 0xC0000135 | STATUS_DLL_NOT_FOUND | Нужная DLL не найдена, запуск невозможен |
| 0xC00000FD | STATUS_STACK_OVERFLOW | Переполнение стека |
| 0xC0000374 | STATUS_HEAP_CORRUPTION | Повреждение кучи |
- Код STOP синего экрана: на первый взгляд похожи, но код STOP (bug check code) — своя система нумерации, отдельная от NTSTATUS, например 0x0000009F (DRIVER_POWER_STATE_FAILURE), и имеет отдельный справочник.14 Достаточно помнить различие: «0xC0000005 — NTSTATUS; STOP 0x9F — код bug check, его нельзя искать в таблице NTSTATUS».
- Столбец Result в Process Monitor: NAME NOT FOUND и ACCESS DENIED в столбце Result Procmon — отображаемые имена NTSTATUS, который вернуло ядро (STATUS_OBJECT_NAME_NOT_FOUND, STATUS_ACCESS_DENIED). Это также место, где чувствуется соответствие слоёв: наблюдать сбой файлового ввода-вывода в словаре NTSTATUS и видеть, как этот сбой преобразуется в ошибку Win32 и приходит в приложение.
flowchart TB
accTitle: Отличить код исключения от кода STOP
accDescr: Код исключения журнала событий читать как NTSTATUS; код STOP синего экрана искать в отдельном справочнике bug-check, другой системе
q{"Где появился код?"} -->|Код исключения| nt["Читать как NTSTATUS"]
q -->|Код STOP| bc["Искать в таблице bug-check"]
nt -.-> n1["Пример: 0xC0000005"]
bc -.-> b1["Пример: 0x0000009F"]
Рис. 12: Код исключения журнала событий — NTSTATUS; код STOP синего экрана — другая система. Не ищите их в неверной таблице.
Расследование за пределами кода исключения, то есть съём и анализ дампа сбоя, разобрано в «Введение в сбор дампов сбоев Windows» и «Чтение крэш-дампов с WinDbg + SOS».
5.3. Связь с HRESULT — бит N и RtlNtStatusToDosError
Мост между NTSTATUS и двумя другими слоями имеет два пути.
- Отображение в пространство HRESULT: установка бита N HRESULT (0x10000000) переносит значение NTSTATUS в пространство HRESULT как есть (макрос HRESULT_FROM_NT в winerror.h). Отображение 0xC0000005 становится, например, 0xD0000005. Увидев HRESULT, начинающийся с 0xD, правильная процедура — снять бит N и читать как 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). Полезно помнить и то, что богатый словарь ядра на слое Win32 иногда округляется до более грубого различия.
flowchart TB
accTitle: Два моста от NTSTATUS к другим слоям
accDescr: NTSTATUS передаётся другим слоям двумя путями: отображается в пространство HRESULT установкой бита N и преобразуется в код ошибки Win32 через RtlNtStatusToDosError
nt["NTSTATUS(0xC0000005)"] -->|Поставить бит N| hr["HRESULT(0xD0000005)"]
nt -->|RtlNtStatusToDosError| win["Ошибка Win32 998(ERROR_NOACCESS)"]
win -.-> memo["ERROR_MR_MID_NOT_FOUND, если соответствие не определено"]
Рис. 13: Мостов NTSTATUS два. Начало 0xD читают как NTSTATUS после снятия бита N.
6. COM и .NET — как код ошибки отображается на исключение
6.1. Стиль COM — HRESULT + IErrorInfo
Метод COM принципиально возвращает HRESULT, но есть предел тому, что умещается в 32 бита, поэтому дополнительно механизм IErrorInfo может передать строку описания ошибки и происхождение отдельно. В C++ поддерживаемый компилятором класс _com_error обрабатывает HRESULT и IErrorInfo вместе. Приложение, чей диалог ошибки показывает «код + описание», часто несёт описание через этот механизм.
flowchart TB
accTitle: IErrorInfo, дополняющий HRESULT
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 interop получает сбой HRESULT, она преобразует его в исключение. Известный HRESULT отображается на соответствующий тип исключения; неизвестный становится COMException.11
flowchart TB
accTitle: Отображение HRESULT на исключение .NET
accDescr: HRESULT сбоя, полученный в COM interop, преобразуется в соответствующий тип исключения, если известен, или в 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. Ветвление в обработке исключений файлового ввода-вывода вроде «повторять только при нарушении совместного доступа» можно написать этим значением.
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 внутри среды может перезаписать значение.15
flowchart TB
accTitle: Получение последней ошибки в P/Invoke
accDescr: Указать SetLastError как true и забрать через Marshal.GetLastWin32Error правильно; вызывать GetLastError напрямую через P/Invoke неточно из-за перезаписи средой
pi["Вызвать Win32 API через P/Invoke"] --> 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. Параметр -error у certutil показывает текст сообщения, соответствующий коду ошибки, и принимает либо шестнадцатеричный HRESULT, либо десятичное.9
certutil -error 0x80070005
certutil -error 5
net helpmsg 5
net helpmsg — только для кода ошибки Win32 в десятичном виде, но в русской среде сообщение возвращается по-русски, поэтому его можно использовать как есть для объяснения пользователю.
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, и десятичное"]
Рис. 18: Как выбирать среди стандартных команд. Десятичная ошибка Win32 — net helpmsg; если есть hex — 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. !error в WinDbg
Чтобы искать код при анализе дампа, расширение !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>
В дампе сбоя !analyze -v автоматически показывает код исключения (NTSTATUS), поэтому поток — подтвердить смысл оттуда через !error <code> 1.
flowchart TB
accTitle: Поток подтверждения кода исключения в WinDbg
accDescr: В дампе сбоя команда analyze автоматически показывает код исключения; передайте этот код расширению error со вторым аргументом 1 и подтвердите смысл как NTSTATUS
dump["Открыть дамп сбоя"] --> an["Выполнить !analyze -v"]
an --> exc["Показан код исключения"]
exc --> chk["Подтвердить смысл через !error code 1"]
Рис. 19: При анализе дампа ищите код исключения, который показал !analyze -v, через !error с флагом 1.
8. Процедура расследования — от суждения о слое к сопоставлению с контекстом
Соберите знание до сих пор в процедуру фактического расследования кода ошибки.
- Нормализуйте запись. Если это отрицательное десятичное, переведите в 8-значный hex. Дополните нулями hex короче 8 цифр и читайте.
- Решите, какой слой этот код. Как в таблице суждения ниже, ведущие несколько цифр почти решают.
- Разберите и извлеките существенный код. Механическая операция: младшие 16 бит, если 0x8007xxxx; снять бит N, если 0xDxxxxxxx.
- Найдите имя и определение инструментом. Подтвердите имя символа и сообщение через err.exe, certutil или
!error. - Сопоставьте с контекстом. Определите, какое приложение, какая операция, какой API не сработал против чего, из журнала приложения, журнала событий и Procmon. Код — «род сбоя»; контекст — «место причины».
flowchart TB
accTitle: Процедура расследования кода ошибки
accDescr: Шаблон расследования: выровнять запись на hex, решить слой по ведущим цифрам, разобрать и извлечь существенный код, найти имя и определение инструментом, затем сопоставить с контекстом
fix["Нормализовать в hex"] --> 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-значный hex, затем суждение строк ниже |
| 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 |
| Своя facility вроде 0x8024xxxx | HRESULT, свой области функции | Определить область по значению Facility и идти в отдельные материалы (0x8024… — Windows Update)2 |
Что особенно эффективно на шаге 5 «сопоставление с контекстом» — столбец Result в Process Monitor. Даже если приложение показывает только «0x80070002», Procmon в одной строке говорит «какой процесс, против какого пути, получил NAME NOT FOUND». Для поиска со стороны журнала событий см. также «Введение в журнал событий Windows и ETW».
9. Частые прочтения вкривь — шаблоны, которые уводят расследование в долгий путь
Наконец, шаблоны неверного прочтения, которые встречаются в реальных консультациях.
Прочтение вкривь 1: считать 0x80004005 «кодом, указывающим конкретную причину»
E_FAIL — «Unspecified failure», и то же значение появляется в Windows Update, сети и базах данных. Пробовать каждое средство, которое всплывает при поиске по этому коду, почти наверняка долгий путь. Сужайте не от кода, а от «какое приложение, какая операция, другие журналы в то же время».6
Прочтение вкривь 2: не заметить, что отрицательное десятичное — HRESULT
Случай искать журнал, где написано «Error -2147467259 occurred», как есть, или путаться от «ошибка с минусом?». Увидев отрицательное, переведите в hex. Одно это скажет, что это 0x80004005 (E_FAIL), и свяжется со знанием прочтения вкривь 1.
Прочтение вкривь 3: искать все 8 цифр 0x8007xxxx и не смотреть на лежащую под ними ошибку Win32
Суть 0x80070005 — «5 = доступ запрещён». После извлечения младших 16 бит думать о «что означает ошибка Win32 5 в контексте этой операции» достигает ядра быстрее, чем искать по всем 8 цифрам.
Прочтение вкривь 4: предполагать «тот же код = та же причина»
Если однажды «ошибку 5 вызвал антивирус», вы склонны прыгать к тому же средству при следующей ошибке 5. Даже при том же коде, если API, который не сработал, и целевой ресурс другие, причина — другая вещь. Подтверждение смысла кода и определение цели через Procmon или аналог — пара, каждый раз.
Прочтение вкривь 5: путать ошибку Win32 5 с 0xC0000005 и код STOP с NTSTATUS
Считать ERROR_ACCESS_DENIED и STATUS_ACCESS_VIOLATION одним и тем же из-за связи «5» уводит расследование в совершенно разные стороны — проблема прав против программной ошибки. Также код 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. Сначала решите, какой слой, какая сторона вернула код.
- Колебания записи (десятичное / hex / отрицательное) можно выровнять механически. Переведите отрицательное в 8-значный hex и затем читайте.
- HRESULT — структура битов S/R/C/N/X + Facility (11 бит) + Code (16 бит), и 0x8007xxxx — самый важный шаблон, обёрнутая ошибка Win32. Переведите младшие 16 бит в десятичное и извлеките существенный код.
- Общий код вроде 0x80004005 (E_FAIL) не указывает причину. Решение остановить копание кода и перейти к расследованию контекста возможно именно потому, что вы знаете структуру.
- NTSTATUS вы встречаете как код исключения сбоя или в столбце Result Procmon. 0xC0000005 — нарушение доступа, не связанное с ошибкой Win32 5. Код STOP — ещё одна система.
- В .NET HRESULT отображается на тип исключения, и исходное значение остаётся в Exception.HResult. В P/Invoke используйте SetLastError=true и Marshal.GetLastWin32Error как пару.
- Инструменты поиска — certutil -error и net helpmsg (стандарт), err.exe (машина разработки), однострочники PowerShell и !error в WinDbg.
- Процедура — «нормализовать запись → решить слой → разобрать → найти имя → сопоставить с контекстом». Что говорит код — род сбоя; место причины говорит контекст.
В следующий раз, встретив незнакомый код ошибки, посмотрите на ведущие несколько цифр, прежде чем вставлять его в поле поиска. Младшие 4 цифры, если 0x8007; NTSTATUS, если 0xC; перевести в hex, если отрицательное — этот 10-секундный разбор во многом решает время последующего расследования.
Похожие статьи
- Чтение крэш-дампов с WinDbg + SOS ── практическое введение в анализ после сбора
- Введение в сбор дампов сбоев Windows — WER/ProcDump/WinDbg
- Где размещать catch и логирование при обработке исключений
- Практическое руководство по Process Monitor (ProcMon) — как за 10 минут выяснить, почему «настройки не читаются» или возникает ACCESS DENIED
- Введение в журнал событий Windows и ETW — как использовать стандартные механизмы ОС для логов бизнес-приложения
Смежные области консультирования
KomuraSoft LLC берёт расследование инцидентов, которое начинается с кода ошибки — «не знаю, что означает этот код ошибки», «0x80070005 появляется только в конкретной среде» —, проектирование обработки ошибок для приложений, которые смешивают Win32 API, COM и .NET, и определение причин через дампы сбоев и Process Monitor. Консультация с одного снимка экрана диалога ошибки подходит.
- Разработка Windows-приложений
- Исследование дефектов и анализ первопричин
- Техническая консультация и ревью проекта
- Связаться с нами
Справочные ссылки
-
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. Раскладка битов HRESULT (биты S, R, C, N и X, 11-битный Facility, 16-битный Code); то, что бит N указывает значение NTSTATUS, отображённое в пространство HRESULT; и список кодов facility, включая FACILITY_WINDOWS_UPDATE (36). ↩ ↩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. Определение макроса winerror.h, который отображает системный код ошибки Win32 на значение HRESULT. ↩ ↩2 ↩3
-
Microsoft Learn, Structure of COM Error Codes. Роль бита severity HRESULT и поля 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»; и определения часто встречающихся значений 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. То, что это автономный инструмент, который показывает текст сообщения, связанный с шестнадцатеричным кодом состояния, по различным заголовочным файлам вроде Winerror.h; что имя файла загрузки — Err_6.4.5.exe; и что нужно учесть: комплект определений — на момент компиляции. ↩ ↩2 ↩3
-
Microsoft Learn, certutil. То, что параметр -error у certutil показывает текст сообщения, связанный с кодом ошибки, и что запись ошибки, включающая имя символа, используется в виде вроде 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 и исключениями .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. То, что last-error code хранится на поток; что его следует забирать через GetLastError сразу после сбоя; что API, которые при успехе перезаписывают код нулём, и API, которые его не трогают, смешаны; и что бит 29 зарезервирован для кодов, определённых приложением. ↩ ↩2
-
Microsoft Learn, Bug check code reference. Список кодов bug check (кодов STOP), показываемых на синем экране, и как показать сведения о коде расширением !analyze в WinDbg. То, что это своя система нумерации, отдельная от NTSTATUS, подтверждается списком. ↩ ↩2
-
Microsoft Learn, Marshal.GetLastWin32Error Method. То, что это способ забрать last-error code вызова P/Invoke, который поставил флаг SetLastError; что вызывать GetLastError напрямую через P/Invoke ненадёжно из-за перезаписи вызовом API внутри среды; и что с .NET 6 рекомендуется GetLastPInvokeError. ↩
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Win32 Thread Pool API — параллелизм без создания потоков через CreateThreadpoolWork
По всему нативному коду разбросаны вызовы CreateThread? Статья разбирает Thread Pool API Win32, переработанный в Vista, — четыре объекта ...
Именованные каналы на практике — стандартный IPC Windows: от проектирования до безопасности
Практическое руководство по именованным каналам — стандартному межпроцессному взаимодействию Windows. Статья по первичным источникам разб...
Приложения, которые ломаются после выхода из сна — как работают события питания Windows и как строить бизнес-приложения, которые это переживают
Открыли ноутбук — и соединения бизнес-приложения мертвы: причина в проекте, который не учитывал сон. Статья разбирает поток уведомлений W...
DllMain и блокировка загрузчика — настоящая причина, почему говорят «ничего не делай в инициализации DLL»
Почему из DllMain нельзя вызывать LoadLibrary и синхронизироваться с другими потоками. Статья по первичным источникам объясняет, как блок...
Что на самом деле значит «Не отвечает» — как Windows решает, что приложение зависло, и как проектировать приложения, которые не зависают
«Не отвечает» в Windows — механизм, в котором ОС судит, что окно не извлекало сообщение 5 секунд, и подменяет его окном-призраком. Статья...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Бизнес-приложения, интеграция оборудования и средства связи — от требований до разработки.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Что означает ошибка 0x80004005?
- 0x80004005 — это HRESULT E_FAIL, и он означает «Unspecified failure» (неопределённый сбой). Иначе говоря, он лишь указывает, что «произошёл сбой, который не может сообщить подробную причину»; это не код, который представляет саму причину. Тот же 0x80004005 появляется в несвязанных местах — сеть, Windows Update, VBA, драйвер базы данных — именно поэтому. Увидев этот код, не копайте смысл кода; сузьте причину по контексту: какое приложение и какая операция его породили, и по другим сведениям об ошибке в журнале событий или подробном журнале.
- Что такое отрицательный код ошибки вроде -2147467259?
- Это 32-битный HRESULT, показанный как знаковое десятичное число. HRESULT при сбое ставит старший бит, поэтому как знаковое целое он всегда отрицательный. В PowerShell '0x{0:X8}' -f -2147467259 переводит его обратно в шестнадцатеричный вид (в этом примере 0x80004005 = E_FAIL). Когда в журнале или сообщении скрипта видно отрицательное число, начинающееся с -214…, стандартный первый шаг — перевести его в hex и затем искать.
- Как проще всего узнать смысл кода ошибки?
- Без дополнительной установки можно использовать 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, то есть нарушение доступа (незаконное обращение к памяти). Это код, который чаще всего виден как «Exception code» в журнале событий или в дампе сбоя при падении приложения, и он указывает на программную ошибку: разыменование неверного указателя или доступ к уже освобождённой памяти. По имени он похож на ошибку Win32 5 (ERROR_ACCESS_DENIED = доступ запрещён), но это несвязанный код другой системы; не путайте их. Надёжный способ найти причину — снять дамп сбоя и разобрать его в WinDbg.
- Почему причина каждый раз разная, даже при одном и том же коде ошибки?
- Потому что код ошибки представляет только «какой род сбоя», а «что не удалось и почему» решает контекст вызова. Ошибка 5 (доступ запрещён), например, — один и тот же код для совершенно разных причин: недостаточных прав NTFS, отсутствия прав администратора, блокировки антивирусом и так далее. В похожей ситуации, если другой процесс ещё держит файл открытым, вы получите другой код (ошибка 32 = нарушение совместного доступа), и правильное чтение кода меняет, куда смотреть. Ошибка 2 (файл не найден) часто тоже не основной файл, а зависимая DLL или файл настроек. Когда смысл кода найден, подтвердить, какой API не сработал против какого ресурса, через Process Monitor или аналог — короткий путь к причине.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.