Как читать коды ошибок Windows — Win32, HRESULT и NTSTATUS
· Обновлено: · Go Komura · Windows, Коды ошибок, HRESULT, NTSTATUS, Win32 API, Диагностика, Отладка, Разработка Windows
История изменений (1 обновлений, последнее 31 Aug 2026)
Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.
- Русский текст переписан по текущему навыку технического перевода как полный перевод японского оригинала.
- Первая публикация
Цитирование статьи(DOI (зарегистрированный архив): 10.5281/zenodo.22176051)
Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.
Go Komura (2026). Как читать коды ошибок Windows — Win32, HRESULT и NTSTATUS. KomuraSoft LLC. https://comcomponent.com/ru/blog/windows-error-codes-win32-hresult-ntstatus/
- DOI (зарегистрированный архив)
- 10.5281/zenodo.22176051
- DOI (последняя зарегистрированная версия)
- 10.5281/zenodo.22176052
«На экране приложения появилась ошибка 0x80004005. Что это значит?» — в разборе сбоев такой вопрос встречается постоянно. Если вбить в поиск число из диалога как есть, вываливается куча несвязанных статей — сбой Windows Update, общая папка не открывается, ошибка выполнения VBA, сбой подключения к базе — и путаница только растёт. С этим сталкивались многие.
Так выходит потому, что 0x80004005 (E_FAIL) — универсальный код, у которого нет другого смысла, кроме «сбой без подробностей». Раз один и тот же код используют в бесчисленных ситуациях, поиск только по коду к причине не приводит. Зато код вроде 0x80070005 при знании структуры за несколько секунд, ещё до поиска, разбирается так: «ошибка Win32 номер 5 = отказ в доступе, упакованная в HRESULT».
Коды ошибок Windows исторически сложились в три системы — коды ошибок Win32, HRESULT и NTSTATUS, — и между этими слоями они ещё и преобразуются друг в друга. Если держать эту структуру в голове, вы сами определите, какой слой и кто вернул код и какой код стоит за записью, — и первый шаг расследования заметно ускоряется.
Статья рассчитана на системных администраторов малого и среднего бизнеса и разработчиков Windows-приложений. По Microsoft Learn и открытой спецификации [MS-ERREF] по состоянию на август 2026 года здесь собрано, как различать и разбирать три системы кодов ошибок, как они связаны с исключениями .NET и как искать значения на практике через err.exe и PowerShell.
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
- Восьмизначное значение, которое начинается с 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 → определить слой → разобрать и извлечь исходный код → прочитать его вместе с контекстом».
На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 18, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle
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 перевод — одна строка.
# десятичное → hex
'0x{0:X8}' -f 1223 # 0x000004C1
'0x{0:X8}' -f -2147024891 # 0x80070005 (отрицательное = HRESULT в hex)
# hex → десятичное
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 и т. п.) и кладут подробный код ошибки в код последней ошибки, который хранится отдельно для каждого потока. Вызывающий забирает его через GetLastError сразу после того, как убедился в сбое.13
Два практических предостережения.13
- Читайте сразу после сбоя. Если вставить другой вызов API (например, функцию журналирования), этот вызов может перезаписать код последней ошибки.
- Не полагайтесь на значение при успехе. Одни API обнуляют код последней ошибки при успехе, другие его не трогают. Правило — сначала подтвердить сбой по возвращаемому значению и только потом читать.
sequenceDiagram
accTitle: Читать GetLastError сразу после сбоя
accDescr: После подтверждения сбоя по возвращаемому значению сразу получить код последней ошибки через GetLastError, не вставляя другой вызов API
participant app as Приложение
participant api as Win32 API
app->>api: Вызов CreateFile
api-->>app: Возвращаемое значение сбоя
app->>api: GetLastError
api-->>app: Код 5
Note over app: Другой API между ними может перезаписать значение
Рис. 3: Код последней ошибки читайте сразу после сбоя. Другой вызов API между ними может его перезаписать.
Чтобы получить строку сообщения из кода, используйте FormatMessage с флагом FORMAT_MESSAGE_FROM_SYSTEM.1
#include <windows.h>
#include <stdio.h>
void PrintLastError(const wchar_t* apiName)
{
DWORD code = GetLastError(); // сразу после сбоя (другой 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 (отказано в доступе): кандидаты причины широки — не хватает ACL NTFS, запись в защищённую область без прав администратора, блокировка антивирусом или 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 | Серьёзность. 0 = успех, 1 = сбой |
| 30 | R | Зарезервировано (часть серьёзности при отображении 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 | Серьёзность. 00 = успех, 01 = сведения, 10 = предупреждение, 11 = ошибка |
| 29 | C | Бит Customer |
| 28 | N | Зарезервировано (0, чтобы отображение в HRESULT было возможно) |
| 27–16 | Facility | Facility (12 бит) |
| 15–0 | Code | Подробный код |
Поскольку серьёзность занимает 2 бита, тип читается по старшей hex-цифре. 0xC… — ошибка (11), 0x8… — предупреждение (10), 0x4… — сведения (01), 0x0–0x3… — успех. Исключение точки останова 0x80000003 (STATUS_BREAKPOINT) — характерный пример «предупреждение, не ошибка».37
flowchart TB
accTitle: NTSTATUS читается по типу из старшей цифры
accDescr: Поскольку серьёзность — 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, в основном связаны со сбоями.
- Код исключения при падении приложения: «Код исключения: 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["В 32 бита умещается не всё"]
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 -->|Да| typed["Преобразовать в соответствующий тип исключения"]
known -->|Нет| 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)
// Файл держит другой процесс — подождать и повторить попытку и т. п.
}
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);
// Возвращаемое значение принимать как SafeFileHandle, не IntPtr, и закрывать через using
// (если оставить IntPtr, утечёт дескриптор ядра)
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(); // например: 5
var message = new Win32Exception(code).Message; // например: Отказано в доступе.
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 → строка сообщения ОС
[System.ComponentModel.Win32Exception]::new(5).Message
# → Отказано в доступе.
# отрицательное десятичное → hex (подтвердить, что это HRESULT)
'0x{0:X8}' -f -2147467259 # 0x80004005
# 0x8007xxxx → код ошибки Win32 в младших 16 битах
0x80070005 -band 0xFFFF # 5
# HRESULT → какое исключение сопоставляет .NET
[System.Runtime.InteropServices.Marshal]::GetExceptionForHR(-2147024891)
# → UnauthorizedAccessException (0x80070005)
# код ошибки Win32 → HRESULT (воспроизвести упаковку)
'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) - Отказано в доступе.
0:000> !error 0xc0000005 1
Error code: (NTSTATUS) 0xc0000005 - <нарушение доступа>
В дампе сбоя !analyze -v автоматически показывает код исключения (NTSTATUS), поэтому поток такой: оттуда подтвердить смысл через !error <code> 1.
flowchart TB
accTitle: Поток подтверждения кода исключения в WinDbg
accDescr: В дампе сбоя команда analyze автоматически показывает код исключения; передайте этот код расширению error со вторым аргументом 1 и подтвердите смысл как NTSTATUS
dump["Открыть дамп сбоя"] --> an["Выполнить !analyze -v"]
an --> exc["Показан код исключения"]
exc --> chk["Подтвердить смысл через !error код 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
Случай искать журнал, где написано «Произошла ошибка -2147467259», как есть, или путаться от «ошибка с минусом?». Увидев отрицательное, переведите в 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 одним и тем же из-за «пятёрки» уводит расследование в совершенно разные стороны — проблема прав против программной ошибки. Также код 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 идёт к проблеме прав; 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); и то, что серьёзность делится на четыре рода: успех (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. Роль бита серьёзности 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. То, что код последней ошибки хранится отдельно для каждого потока; что его следует забирать через GetLastError сразу после сбоя; что API, которые при успехе перезаписывают код нулём, и API, которые его не трогают, смешаны; и что бит 29 зарезервирован для кодов, определённых приложением. ↩ ↩2
-
Microsoft Learn, Bug check code reference. Список кодов bug check (кодов STOP), показываемых на синем экране, и как показать сведения о коде расширением !analyze в WinDbg. То, что это своя система нумерации, отдельная от NTSTATUS, подтверждается списком. ↩ ↩2
-
Microsoft Learn, Marshal.GetLastWin32Error Method. То, что это способ забрать код последней ошибки вызова P/Invoke, который поставил флаг SetLastError; что вызывать GetLastError напрямую через P/Invoke ненадёжно из-за перезаписи вызовом API внутри среды; и что с .NET 6 рекомендуется GetLastPInvokeError. ↩
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Time Travel Debugging — записывать и перематывать ошибки, которые не воспроизводятся в долгоживущих приложениях
Ошибка раз в месяц оставляет в дампе только результат. Записывайте и перематывайте исполнение через WinDbg Time Travel Debugging (TTD): T...
Почему ломаются аргументы ── правила аргументов командной строки Windows
В Windows массива аргументов нет: в CreateProcess уходит одна строка, делит её принимающая сторона. Правила деления CommandLineToArgvW, C...
Что остаётся после падения родителя ── дерево дочерних процессов в Job Object
Почему после принудительного завершения UI остаётся помощник SDK и держит камеру или COM-порт. Как Job Object делает дерево процессов одн...
Пул потоков Win32 — параллелизм через CreateThreadpoolWork без своих потоков
Не плодите ли вы CreateThread по всему нативному коду? Разбираем API пула потоков Win32, переработанный в Vista: четыре объекта work, tim...
Именованные каналы на практике — от проектирования до безопасности IPC в Windows
Практический разбор именованных каналов — стандартного IPC в Windows. По первоисточникам: выбор байтового режима и режима сообщений, серв...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы 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, то есть нарушение доступа (некорректное обращение к памяти). Этот код чаще всего встречается как «код исключения» в журнале событий или в дампе при падении приложения и указывает на программную ошибку: разыменование неверного указателя или обращение к уже освобождённой памяти. По имени он похож на ошибку Win32 5 (ERROR_ACCESS_DENIED = отказано в доступе), но это несвязанный код другой системы; их нельзя путать. Надёжный способ найти причину — снять дамп сбоя и разобрать его в WinDbg.
- Почему при одном и том же коде ошибки причина каждый раз разная?
- Потому что код ошибки говорит только, какого рода сбой, а что именно не удалось и почему, определяет контекст вызова. Ошибка 5 (отказано в доступе), например, — один и тот же код при совершенно разных причинах: не хватает прав NTFS, нет прав администратора, антивирус блокирует доступ и так далее. В похожей ситуации, если другой процесс ещё держит файл открытым, придёт уже другой код (ошибка 32 = нарушение совместного доступа), и от того, как вы прочитаете код, зависит, куда смотреть. Ошибка 2 (файл не найден) часто тоже относится не к основному файлу, а к зависимой DLL или файлу настроек. Когда смысл кода понятен, краткий путь к причине — через Process Monitor или аналог проверить, какой API не сработал и против какого ресурса.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.