Windows 오류 코드 읽기 ── Win32·HRESULT·NTSTATUS 삼층 구조

· 업데이트: · · Windows, 오류 코드, HRESULT, NTSTATUS, Win32 API, 장애 조사, 디버깅, Windows 개발

수정 이력(4건, 최종 수정 2026년 08월 22일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

지식 맵의 관계를 전면 점검하고, 설명과 어긋나 있던 관계(방향 반전·지나친 일반화·술어 혼동)를 고쳤습니다. 본문 설명은 바꾸지 않았습니다.
지식 맵의 관계를 전면 점검하고, 설명과 어긋나 있던 관계(방향 반전·지나친 일반화·술어 혼동)를 고쳤습니다. 본문 설명은 바꾸지 않았습니다.
지식 맵의 「RtlNtStatusToDosError는 NTSTATUS를 구현한다」는 관계를 「NTSTATUS에서 Win32 오류 코드로의 변환을 구현한다」로 고쳤습니다. 본문 설명은 바꾸지 않았습니다.
구조를 그림으로 따라갈 수 있도록 Mermaid 그림 21개를 추가했습니다. 세 체계를 넘나드는 변환 흐름, 표기 바꿔 읽기, GetLastError 읽는 법, FormatMessage로 메시지 얻기와 로그 기록, 오류 998과 5의 차이, 오류 5의 원인 분기, HRESULT의 S 비트와 음수의 관계, 0x80004005와 0x80070005의 분해 차이, HRESULT_FROM_WIN32의 동작, 0x8007과 0x8004의 조회 차이, NTSTATUS 종류 판별과 다리, 예외 코드와 STOP 코드 구분, IErrorInfo 보완, .NET 예외 매핑, P/Invoke에서 오류 얻기, err.exe·표준 명령·WinDbg로 조회하기, 오류 코드 조사 절차의 분기, 오류 5와 0xC0000005의 조사 방향 차이를 그림으로 담았습니다. 본문 문장과 코드, 참고 링크는 바꾸지 않았습니다.
최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.22176049)

아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.

Go Komura (2026). 「Windows 오류 코드 읽기 ── Win32·HRESULT·NTSTATUS 삼층 구조」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/windows-error-codes-win32-hresult-ntstatus/

DOI(등록된 아카이브)
10.5281/zenodo.22176049
DOI(마지막 등록 버전)
10.5281/zenodo.22176050

「앱 화면에 0x80004005라는 오류가 나왔습니다. 무슨 뜻인가요」── 장애 조사 상담에서 이런 질문은 단골입니다. 오류 대화상자의 숫자를 그대로 검색 엔진에 넣으면 Windows Update 실패, 공유 폴더에 연결되지 않음, VBA 런타임 오류, 데이터베이스 연결 실패처럼 무관한 글이 대량으로 나와, 오히려 더 혼란스러워진 경험이 있는 분도 많을 것입니다.

이렇게 되는 이유는 0x80004005(E_FAIL)가 「자세한 내용을 알 수 없는 실패」라는 뜻만 가진 범용 코드이기 때문입니다. 같은 코드가 무수한 장면에서 쓰이는 이상, 코드만 검색해서는 원인에 닿지 않습니다. 반면 0x80070005 같은 코드는 구조를 알면 검색 전에 「Win32 오류 번호 5 = 액세스 거부를 HRESULT로 감싼 것」이라고 몇 초 만에 분해할 수 있습니다.

Windows 오류 코드는 역사적 경위로 Win32 오류 코드, HRESULT, NTSTATUS라는 세 체계가 층을 이루고, 층을 넘나들며 서로 변환됩니다. 이 구조를 한번 머리에 넣으면 「이 코드는 어느 층의 누가 반환했는지」「실제 코드는 무엇인지」를 스스로 가릴 수 있게 되어, 조사의 첫 수가 크게 빨라집니다.

이 글에서는 중소기업 IT 담당자와 Windows 앱 개발자를 대상으로, 세 오류 코드 체계를 구분하고 분해하는 방법, .NET 예외와의 관계, err.exe와 PowerShell을 쓰는 실무 조회까지를 2026년 8월 시점의 Microsoft Learn과 공개 사양서 [MS-ERREF]에 근거해 정리합니다.

1. 먼저 결론

  • Windows의 오류 코드는 주로 세 체계입니다. Win32 오류 코드(GetLastError가 반환하는 작은 십진수), HRESULT(COM 이후의 32비트 코드, 0x8로 시작하는 16진 또는 십진 음수), NTSTATUS(커널 계층 코드, 오류는 0xC로 시작)입니다.123
  • 십진과 16진은 같은 코드의 다른 표기입니다. 「오류 5」와 「0x5」와 「0x80070005의 하위 16비트」는 모두 ERROR_ACCESS_DENIED(액세스 거부)를 가리킵니다.1
  • 0x8007xxxx는 「Win32 오류를 감싼 형태」입니다. HRESULT의 FACILITY_WIN32(7)에 Win32 오류 코드를 담은 것이며, 하위 16비트를 십진으로 바꾸면 실제 코드가 나옵니다. 오류 코드 읽기의 가장 중요한 패턴입니다.45
  • 0x80004005(E_FAIL)는 원인 코드가 아닙니다. 의미는 「미지정 실패」이며, 그 이상의 정보는 없습니다. 이 코드를 파고들기보다 나온 문맥과 함께 남은 로그를 찾는 편이 맞습니다.6
  • 십진 음수(-2147467259 등)는 HRESULT입니다. 32비트의 최상위 비트(실패 비트)가 켜져 있어 부호 있는 표시에서는 음수가 됩니다. 16진으로 바꾼 뒤 읽습니다.2
  • 0xC로 시작하는 8자리는 NTSTATUS입니다. 0xC0000005(액세스 위반)와 0xC0000135(DLL을 찾을 수 없음)는 크래시 시점의 이벤트 로그나 덤프에서 자주 나옵니다. Win32 오류 번호 5와는 무관합니다.7
  • 같은 코드라도 의미는 문맥에 따라 달라집니다. 오류 5의 원인은 ACL·권한 상승·백신·점유 등 여러 가지이고, 오류 2의 「찾을 수 없는 파일」이 의존 DLL인 경우도 드물지 않습니다. 코드의 의미와, 어느 API가 무엇에 대해 실패했는지는 반드시 세트로 읽습니다.1
  • 변환·조회 도구는 표준으로 갖춰져 있습니다. certutil -error와 net helpmsg는 Windows 표준이고, PowerShell의 Win32Exception으로 메시지를 얻으며, 개발기에는 err.exe(Microsoft Error Lookup Tool), 덤프 분석에는 WinDbg의 !error를 씁니다.8910
  • .NET에서는 HRESULT가 예외 형에 매핑됩니다. 알려진 HRESULT는 대응하는 예외 형(E_ACCESSDENIED→UnauthorizedAccessException 등)으로, 모르는 것은 COMException으로 변환되고, 원래 값은 Exception.HResult에 남습니다.11

한 문장으로 정리하면, 「표기를 16진으로 맞춘다 → 어느 층의 코드인지 가린다 → 분해해 실제 코드를 꺼낸다 → 문맥과 함께 읽는다」가 Windows 오류 코드 조사의 형입니다.

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 18건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. Windows에는 오류 코드 체계가 세 가지 있다

먼저 전체 지도입니다. Windows 오류 코드는 반환하는 층에 따라 주로 다음 세 체계로 나뉩니다.

체계 주로 반환하는 쪽 전형적인 모습 대표 예
Win32 오류 코드 Win32 API(GetLastError), 명령의 종료 코드 작은 십진수(0〜15999) 5 = ERROR_ACCESS_DENIED
HRESULT COM 구성 요소, 셸, 설치 프로그램, 많은 프레임워크 0x8로 시작하는 8자리 16진, 또는 십진 음수 0x80004005 = E_FAIL
NTSTATUS 커널, 드라이버, 네이티브 API(ntdll) 오류는 0xC로 시작하는 8자리 16진 0xC0000005 = STATUS_ACCESS_VIOLATION

역사적으로는 MS-DOS에서 이어진 오류 번호를 물려받은 Win32 오류 코드, NT 커널이 내부에서 쓰는 NTSTATUS, COM 도입 때 「성공/실패와 출처를 32비트에 담기」 위해 설계된 HRESULT 순으로 쌓여 왔습니다. 현재 Windows에서는 커널이 NTSTATUS를 반환하고, Win32 서브시스템이 이를 Win32 오류 코드로 변환하며, COM 계층이 다시 HRESULT로 감싸는 변환 흐름이 일상적으로 일어납니다.124

세 체계를 넘나드는 변환 흐름커널이 반환한 NTSTATUS를 Win32 서브시스템이 Win32 오류 코드로 변환하고, COM 계층이 다시 HRESULT로 감싼다Win32 서브시스템이 변환COM 계층이 감쌈커널·드라이버NTSTATUS(오류는 0xC…)Win32 오류 코드(5 등)HRESULT(0x8007xxxx)

그림 1: 층을 넘나드는 변환 흐름. 커널의 NTSTATUS가 Win32 오류가 되고, 다시 HRESULT로 감싸입니다.

2.1. 십진과 16진을 바꿔 읽는 습관

세 체계를 가리기 전에, 표기의 흔들림을 흡수해야 합니다. 같은 코드가 장면에 따라 십진으로도 16진으로도 표시되기 때문입니다.

  • 「오류 5」「오류 코드: 0x5」→ 같은 ERROR_ACCESS_DENIED
  • 「오류 1223」「0x4C1」→ 같은 ERROR_CANCELLED
  • 「0x80070005」「-2147024891」→ 같은 HRESULT

PowerShell이면 바꿔 읽기는 한 줄입니다.

# 십진 → 16진
'0x{0:X8}' -f 1223          # 0x000004C1
'0x{0:X8}' -f -2147024891   # 0x80070005 (음수=HRESULT를 16진으로)

# 16진 → 십진
0x4C1                        # 1223

-214…로 시작하는 십진 음수를 보면 반사적으로 16진으로 바꿉니다. 그것만으로 조사 입구에서 길을 잃는 일이 크게 줄어듭니다.

같은 코드의 세 가지 모습십진 오류 5와 16진 0x5와 0x80070005의 하위 16비트는 모두 같은 ERROR_ACCESS_DENIED를 가리킨다십진 표기 「오류 5」ERROR_ACCESS_DENIED16진 표기 「0x5」0x80070005의 하위 16비트표기만 다르고 같은 코드

그림 2: 십진·16진·HRESULT의 하위 16비트는 같은 코드의 다른 표기일 뿐입니다.

3. Win32 오류 코드 ── GetLastError와 FORMAT_MESSAGE

3.1. GetLastError의 기본 동작

CreateFile이나 RegOpenKeyEx처럼 많은 Win32 API는 실패를 반환값(FALSE, NULL, INVALID_HANDLE_VALUE 등)으로 알리고, 자세한 오류 코드는 스레드마다 유지되는 「마지막 오류 코드」에 넣습니다. 호출 쪽은 실패를 확인한 직후 GetLastError로 꺼냅니다.13

실무에서 주의할 점은 두 가지입니다.13

  1. 실패 직후에 읽습니다. 사이에 다른 API 호출(로그 출력 함수 등)을 끼우면, 그 호출이 마지막 오류 코드를 덮어쓸 수 있습니다.
  2. 성공 시의 값에 기대지 않습니다. 성공 때 마지막 오류 코드를 0으로 지우는 API도 있고, 건드리지 않는 API도 있습니다. 반환값으로 실패를 확인한 뒤 읽는 것이 원칙입니다.
GetLastError는 실패 직후에 읽는다반환값으로 실패를 확인하면 다른 API 호출을 끼우지 않고 직후 GetLastError로 마지막 오류 코드를 꺼낸다Win32 API앱Win32 API앱사이에 다른 API를 끼우면 덮어쓸 수 있음CreateFile 호출실패 반환값GetLastError코드 5

그림 3: 마지막 오류 코드는 실패 직후에 읽습니다. 사이에 다른 API 호출을 끼우면 덮어쓸 수 있습니다.

코드에서 메시지 문자열을 얻으려면 FORMAT_MESSAGE_FROM_SYSTEM 플래그를 붙인 FormatMessage를 씁니다.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);
}

자체 앱 로그에는 이렇게 십진과 16진 양쪽과 메시지 본문을 남겨 두면 이후 조사가 한 단계 빨라집니다.

코드에서 메시지를 찾아 로그에 남긴다FormatMessage에 FORMAT_MESSAGE_FROM_SYSTEM 플래그를 지정해 오류 코드의 메시지 문자열을 얻고, 로그에는 십진과 16진과 메시지 본문을 남긴다오류 코드(예: 5)FormatMessage로 문자열 얻기메시지 본문로그에 기록십진과 16진과 본문을 함께 적음

그림 4: 오류 코드는 FormatMessage로 메시지 문자열로 바꾸고, 로그에는 십진·16진·본문을 함께 남깁니다.

3.2. 현장에서 자주 보는 대표 코드

Win32 오류 코드는 0〜15999 범위로 정의되어 있고, Microsoft Learn에 전체 목록이 있습니다.1 그중 장애 조사에서 반복해서 만나는 얼굴은 다음입니다.

십진 16진 심볼 의미
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 상승 대화상자에서 사용자가 「아니요」를 골랐을 때 등에서 나타나며, 오류라기보다 「중단되었다」는 코드입니다.

오류 998과 5는 다른 것998은 NTSTATUS의 액세스 위반이 Win32 층으로 변환된 메모리 액세스 위반이며, 액세스 거부를 나타내는 5와 의미가 다르다Win32 층으로 변환NTSTATUS 0xC0000005오류 998(ERROR_NOACCESS)의미는 메모리 액세스 위반오류 5(액세스 거부)권한 문제. 998과는 다른 것

그림 5: 오류 998은 NTSTATUS의 액세스 위반이 Win32 층으로 변환된 모습이며, 액세스 거부의 5와는 다른 것입니다.

3.3. 같은 코드라도 문맥에 따라 의미가 달라진다

대표 코드 표를 외우는 것보다 중요한 것은, 오류 코드는 「실패의 종류」만 알려 준다는 감각입니다.

  • 오류 5(액세스 거부): NTFS ACL 부족, 관리자 권한 없이 보호 영역에 쓰기, 백신이나 AppLocker의 차단, 서비스 계정의 권한 부족 등, 원인 후보는 넓게 갈립니다.
  • 오류 2(파일을 찾을 수 없음): 사용자가 지정한 파일이라고 단정할 수 없습니다. EXE가 암묵적으로 로드하려던 의존 DLL, 레지스트리 리다이렉션(32비트/64비트) 때문에 다른 곳을 본 설정 파일, 환경 변수 전개가 실패한 경로 등, 「어느 파일」을 못 찾았는지는 코드만으로는 알 수 없습니다.
  • 오류 32(공유 위반): 「어느 프로세스가 잡고 있는지」가 본론이지만, 코드는 그것을 알려 주지 않습니다.
오류 5의 원인은 문맥이 정한다같은 액세스 거부라도 ACL 부족이나 관리자 권한 없음 등 원인 후보는 여러 가지이며, 어느 API가 무엇에 실패했는지 특정해야 한다오류 5(액세스 거부)ACL 부족관리자 권한 없음보안 제품의 차단서비스 권한 부족Procmon으로 실패한 대상을 특정

그림 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, 즉 16진 표기가 0x8 이상으로 시작하는 HRESULT는 실패입니다. 이를 부호 있는 32비트 정수로 표시하면 음수가 됩니다. 앞에서 말한 「-214…」의 정체입니다.

S 비트와 음수 표시의 관계실패 HRESULT는 최상위 S 비트가 1이므로 16진에서는 0x8 이상으로 시작하고, 부호 있는 32비트 정수로 표시하면 음수가 된다S 비트=1(실패)16진은 0x8 이상으로 시작부호 있는 표시에서는 음수가 됨음수를 보면 16진으로 바꿔 읽기

그림 7: 실패 HRESULT는 S 비트가 1이라 0x8 이상으로 시작하고, 부호 있는 표시에서는 음수가 됩니다.

대표적인 Facility 값은 다음과 같습니다.5

Facility 값 16진의 모습 의미
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와는 정보량이 전혀 다릅니다.

0x80004005와 0x80070005의 분해0x80004005는 FACILITY_NULL의 범용 코드 E_FAIL로 상세가 없어 문맥 조사로 옮겨야 하고, 0x80070005는 FACILITY_WIN32라 Win32 오류 번호 5 액세스 거부를 감싼 것임을 알 수 있다0x80004005Facility=0(FACILITY_NULL)Code=0x4005 → E_FAIL미지정 실패. 문맥 조사로0x80070005Facility=7(FACILITY_WIN32)Code=0x0005 → 5ERROR_ACCESS_DENIED

그림 8: 같은 「실패」라도 분해하면 정보량이 다릅니다. 0x80070005는 Win32 오류 번호 5까지 따라갈 수 있습니다.

4.3. 가장 중요한 패턴: 0x8007xxxx = HRESULT_FROM_WIN32

Win32 오류 코드만 반환할 수 있는 하위 층의 실패를, HRESULT를 반환하는 상위 층(COM 메서드나 .NET 런타임)에 전하기 위해, winerror.h에는 HRESULT_FROM_WIN32 매크로가 있습니다.4 동작은 「하위 16비트에 Win32 오류 코드, Facility에 FACILITY_WIN32(7), S 비트에 1을 설정한다」입니다.

HRESULT_FROM_WIN32의 동작Win32 오류 코드를 하위 16비트에 넣고 Facility에 7을 S 비트에 1을 설정해 0x8007xxxx HRESULT를 조립한다Win32 오류 코드(예: 5)하위 16비트에 저장Facility에 7을 설정S 비트에 1을 설정0x80070005

그림 9: HRESULT_FROM_WIN32는 Win32 오류를 하위 16비트에 넣고, Facility=7과 S 비트를 켭니다.

ERROR_ACCESS_DENIED (5)        --HRESULT_FROM_WIN32-->  0x80070005
ERROR_SHARING_VIOLATION (32)   --HRESULT_FROM_WIN32-->  0x80070020
ERROR_INVALID_PARAMETER (87)   --HRESULT_FROM_WIN32-->  0x80070057 (= E_INVALIDARG)
ERROR_OUTOFMEMORY (14)         --HRESULT_FROM_WIN32-->  0x8007000E (= E_OUTOFMEMORY)

반대 방향으로 읽을 때는 PowerShell에서 하위 16비트를 꺼냅니다.

0x80070005 -band 0xFFFF   # 5 → ERROR_ACCESS_DENIED
0x80072EE7 -band 0xFFFF   # 12007 → ERROR_INTERNET_NAME_NOT_RESOLVED (WinINet)

두 번째 예처럼 WinINet이나 WinHTTP 오류(12000번대)도 Win32 오류 코드 공간에 정의되어 있으므로1, 네트워크 계열의 0x8007xxxx도 같은 절차로 분해할 수 있습니다. 「0x8007을 보면 하위 4자리를 십진으로 바꾼다」를 몸에 익히는 것이, 이 글에서 가져가길 바라는 실무 기술 1번입니다.

다만 0x8004xxxx(FACILITY_ITF)에는 반대의 주의가 있습니다. FACILITY_ITF 코드는 의미를 정의하는 주체가 인터페이스마다 다르므로, 같은 32비트 값이라도 반환한 쪽이 다르면 의미가 달라질 수 있습니다.5 익숙하지 않은 0x8004xxxx는 범용 검색이 아니라, 그것을 반환한 구성 요소(라이브러리, 드라이버 SDK, 서버 제품)의 문서에서 찾습니다.

0x8007과 0x8004는 조회 방법이 달라진다FACILITY_WIN32의 0x8007xxxx는 하위 16비트의 기계적 분해로 읽히지만, FACILITY_ITF의 0x8004xxxx는 의미의 정의 주체가 인터페이스마다 다르므로 반환한 구성 요소의 자료에서 찾는다7의 WIN324의 ITFFacility는?하위 16비트를 십진으로의미는 반환한 쪽에 따라 다름Win32 오류로 읽기반환한 쪽의 자료에서 조회

그림 10: 0x8007xxxx는 기계적으로 분해할 수 있지만, 0x8004xxxx는 반환한 구성 요소의 자료에서 찾습니다.

5. NTSTATUS ── 커널 계층 코드와 크래시의 세계

5.1. 레이아웃과 Severity

NTSTATUS는 커널, 장치 드라이버, ntdll의 네이티브 API가 쓰는 32비트 코드이며, 배치는 HRESULT와 비슷하지만 같지는 않습니다.3

비트 위치 이름 의미
31–30 Sev 심각도. 00=성공, 01=정보, 10=경고, 11=오류
29 C Customer 비트
28 N 예약(HRESULT로 매핑할 수 있게 0)
27–16 Facility Facility(12비트)
15–0 Code 상세 코드

심각도가 2비트이므로 앞자리 16진 한 자리로 종류를 읽을 수 있습니다. 0xC…는 오류(11), 0x8…는 경고(10), 0x4…는 정보(01), 0x0〜0x3…는 성공입니다. 중단점 예외 0x80000003(STATUS_BREAKPOINT)이 「오류가 아니라 경고」의 대표 예입니다.37

NTSTATUS는 앞자리 한 자리로 종류를 읽는다심각도가 2비트이므로 NTSTATUS는 16진 앞자리가 0xC이면 오류 0x8이면 경고 0x4이면 정보 0x0부터 0x3이면 성공으로 가른다0xC0x80x40x0〜0x3앞자리 16진 한 자리는?오류경고정보성공예: 0x80000003은 경고

그림 11: NTSTATUS는 앞자리 16진 한 자리로 종류를 읽습니다. 0x80000003은 「오류가 아니라 경고」입니다.

5.2. 어디서 만나는가 ── 예외 코드·STOP 코드·이벤트 로그

IT 담당자나 개발자가 NTSTATUS를 만나는 장면은 주로 크래시와 관련됩니다.

  • 응용 프로그램 크래시의 예외 코드: 이벤트 로그의 「응용 프로그램 오류(이벤트 ID 1000)」에 기록되는 「예외 코드: 0xc0000005」가 NTSTATUS입니다. 대표적인 값은 다음과 같습니다.7
값 심볼 의미
0xC0000005 STATUS_ACCESS_VIOLATION 액세스 위반(잘못된 메모리 접근)
0xC0000135 STATUS_DLL_NOT_FOUND 필요한 DLL을 찾지 못해 시작할 수 없음
0xC00000FD STATUS_STACK_OVERFLOW 스택 오버플로
0xC0000374 STATUS_HEAP_CORRUPTION 힙 손상
  • 블루스크린의 STOP 코드: 얼핏 비슷해 보이지만, STOP 코드(버그 검사 코드)는 0x0000009F(DRIVER_POWER_STATE_FAILURE)처럼 NTSTATUS와는 다른 자체 번호 체계이며, 전용 참조가 있습니다.14 「0xC0000005면 NTSTATUS, STOP 0x9F면 버그 검사 코드이므로 NTSTATUS 표에서 찾으면 안 된다」는 구분만 기억하면 충분합니다.
  • Process Monitor의 Result 열: Procmon의 Result 열에 나오는 NAME NOT FOUND나 ACCESS DENIED는, 커널이 반환한 NTSTATUS(STATUS_OBJECT_NAME_NOT_FOUND나 STATUS_ACCESS_DENIED)의 표시 이름입니다. 파일 I/O 실패를 NTSTATUS 어휘로 관찰하고, 그것이 Win32 오류로 변환되어 앱에 도착하는 층의 대응을 체감할 수 있는 자리이기도 합니다.
예외 코드와 STOP 코드를 가리기이벤트 로그의 예외 코드는 NTSTATUS로 읽고, 블루스크린의 STOP 코드는 다른 체계인 버그 검사 코드의 전용 참조에서 찾는다예외 코드STOP 코드어디에 나온 코드인가?NTSTATUS로 읽기버그 검사 코드 표에서 찾기예: 0xC0000005예: 0x0000009F

그림 12: 이벤트 로그의 예외 코드는 NTSTATUS, 블루스크린의 STOP 코드는 다른 체계입니다. 표를 찾는 곳을 틀리지 않습니다.

예외 코드 너머의 조사, 즉 크래시 덤프의 수집과 분석은 「Windows 앱의 크래시 덤프 수집 입문」과 「WinDbg + SOS로 크래시 덤프 읽기」를 참조합니다.

5.3. HRESULT와의 관계 ── N 비트와 RtlNtStatusToDosError

NTSTATUS와 다른 두 층 사이의 다리는 두 갈래입니다.

  1. HRESULT 공간으로의 매핑: HRESULT의 N 비트(0x10000000)를 켜면 NTSTATUS 값을 그대로 HRESULT 공간으로 가져올 수 있습니다(winerror.h의 HRESULT_FROM_NT 매크로). 0xC0000005를 매핑하면 0xD0000005가 되는 식입니다. 0xD로 시작하는 HRESULT를 보면 N 비트를 벗기고 NTSTATUS로 읽는 것이 올바른 절차입니다.2
  2. Win32 오류 코드로의 변환: ntdll의 RtlNtStatusToDosError가 NTSTATUS를 대응하는 Win32 오류 코드로 변환합니다. 대응이 정의되지 않은 값은 ERROR_MR_MID_NOT_FOUND가 됩니다.12 예를 들어 STATUS_ACCESS_VIOLATION(0xC0000005)은 ERROR_NOACCESS(998)로, STATUS_OBJECT_NAME_NOT_FOUND(0xC0000034)는 ERROR_FILE_NOT_FOUND(2)로 변환됩니다. 커널의 풍부한 어휘가 Win32 층에서는 거친 구분으로 둥글려지는 경우가 있다는 점도 기억해 두면 도움이 됩니다.
NTSTATUS에서 다른 층으로 가는 두 다리NTSTATUS는 N 비트를 켜 HRESULT 공간으로 매핑하는 경로와, RtlNtStatusToDosError로 Win32 오류 코드로 변환하는 경로의 두 갈래로 다른 층에 전달된다N 비트를 켬RtlNtStatusToDosErrorNTSTATUS(0xC0000005)HRESULT(0xD0000005)Win32 오류 998(ERROR_NOACCESS)대응이 없으면 ERROR_MR_MID_NOT_FOUND

그림 13: NTSTATUS의 다리는 두 갈래입니다. 0xD로 시작하면 N 비트를 벗기고 NTSTATUS로 읽습니다.

6. COM과 .NET ── 오류 코드가 예외로 매핑되는 방식

6.1. COM의 방식 ── HRESULT + IErrorInfo

COM 메서드는 HRESULT를 반환하는 것이 기본이지만, 32비트에 담을 수 있는 정보에는 한계가 있어, 보완으로 IErrorInfo라는 메커니즘이 오류 설명 문자열이나 출처를 따로 전할 수 있습니다. C++에서는 컴파일러가 지원하는 _com_error 클래스가 HRESULT와 IErrorInfo를 함께 다룹니다. 오류 대화상자에 「코드+설명문」이 나오는 앱은, 이 메커니즘으로 설명문을 나르는 경우가 많습니다.

HRESULT를 보완하는 IErrorInfo32비트 HRESULT에 담을 수 있는 정보에는 한계가 있어, 오류 설명 문자열이나 출처는 IErrorInfo로 따로 전하고, C++에서는 _com_error 클래스가 둘을 함께 다룬다HRESULT(32비트만)담을 수 있는 정보에 한계가 있음IErrorInfo가 설명문을 나름_com_error가 함께 다룸대화상자의 코드+설명문

그림 14: 32비트 HRESULT에 들어가지 않는 설명 문자열은 IErrorInfo가 따로 나릅니다.

6.2. .NET의 방식 ── HRESULT에서 예외 형으로

.NET 런타임은 COM 상호 운용에서 HRESULT 실패를 받으면 예외로 변환합니다. 알려진 HRESULT는 대응하는 예외 형에 매핑되고, 모르는 것은 COMException이 됩니다.11

HRESULT에서 .NET 예외로의 매핑COM 상호 운용에서 받은 실패 HRESULT는 알려진 매핑이면 대응 예외 형으로, 아니면 COMException으로 변환되고, 어느 쪽이든 원래 값은 Exception.HResult에 남는다예아니요실패 HRESULT알려진 매핑이 있는가?대응하는 예외 형으로 변환COMException으로 변환원래 값은 Exception.HResult에 유지

그림 15: .NET은 HRESULT를 예외 형에 매핑하고, 어느 예외든 원래 값은 Exception.HResult에 남습니다.

HRESULT .NET 예외 형
E_ACCESSDENIED (0x80070005) UnauthorizedAccessException
E_OUTOFMEMORY (0x8007000E) OutOfMemoryException
E_INVALIDARG (0x80070057) ArgumentException
E_NOTIMPL (0x80004001) NotImplementedException
매핑이 정의되지 않은 값 COMException(ErrorCode 속성에 원래 값)

어느 예외든 원래 HRESULT는 Exception.HResult 속성에 유지됩니다. 파일 I/O 예외 처리에서 「공유 위반일 때만 재시도하고 싶다」 같은 분기는 이 값으로 쓸 수 있습니다.

try
{
    using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.None);
}
catch (IOException ex) when (ex.HResult == unchecked((int)0x80070020))
{
    // 0x80070020 = HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION)
    // 다른 프로세스가 파일을 잡고 있음 ── 조금 기다렸다가 재시도하는 식
}

6.3. P/Invoke와 GetLastError

P/Invoke로 Win32 API를 직접 호출할 때는 DllImport(또는 LibraryImport)에 SetLastError = true를 지정한 뒤 Marshal.GetLastWin32Error(.NET 6 이후는 동등한 GetLastPInvokeError)로 꺼냅니다. GetLastError 자체를 P/Invoke로 정의해 호출하는 것은, 런타임 안의 API 호출이 값을 덮어쓸 수 있어 부정확합니다.15

P/Invoke에서 마지막 오류를 꺼내기SetLastError를 true로 두고 Marshal.GetLastWin32Error로 꺼내는 것이 맞고, GetLastError를 직접 P/Invoke하면 런타임이 덮어써 부정확하다P/Invoke로 Win32 API를 호출SetLastError=true를 지정GetLastWin32Error로 꺼내기GetLastError를 직접 호출하는 정의런타임이 덮어써 부정확함

그림 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);

// 반환값은 IntPtr이 아니라 SafeFileHandle로 받고, 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 오류 코드에서 OS 메시지 문자열을 찾아 주므로, 로그에 코드와 메시지를 함께 남기는 용도에 그대로 쓸 수 있습니다. 예외를 어느 층에서 catch해 어떻게 로그에 남길지라는 설계 논의는 「예외 처리에서 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

err.exe 검색 결과는 문맥으로 고른다err.exe는 여러 헤더 파일을 가로질러 일치하는 정의를 나열하므로, 같은 숫자에 후보가 여러 개 나오면 어느 것이 타당한지를 문맥으로 고른다err 5 를 입력여러 헤더를 가로질러 검색여러 정의가 맞음문맥으로 타당한 후보를 고름

그림 17: err.exe는 헤더를 가로지르는 검색이라 후보가 여러 개 나올 수 있고, 타당한 것은 문맥으로 고릅니다.

7.2. Windows 표준 명령

추가 설치 없이 쓸 수 있는 것이 certutil과 net helpmsg입니다. certutil의 -error 옵션은 오류 코드에 대응하는 메시지 텍스트를 표시하며, 16진 HRESULT와 십진 모두 받습니다.9

certutil -error 0x80070005
certutil -error 5
net helpmsg 5

net helpmsg는 Win32 오류 코드의 십진수 전용이지만, 한국어 환경이면 메시지가 한국어로 돌아오므로 사용자 설명에 그대로 쓸 수 있습니다.

표준 명령의 가름십진 Win32 오류 코드는 net helpmsg로 조회하고, 16진 HRESULT를 포함한 코드는 certutil의 -error 옵션으로 조회한다십진 Win3216진을 포함손안의 코드는?net helpmsgcertutil -error한국어 메시지가 돌아옴16진도 십진도 받음

그림 18: 표준 명령의 가름. 십진 Win32 오류는 net helpmsg, 16진이 들어가면 certutil -error입니다.

7.3. PowerShell 한 줄 모음

# Win32 오류 코드 → OS의 메시지 문자열
[System.ComponentModel.Win32Exception]::new(5).Message
# → 액세스가 거부되었습니다.

# 십진 음수 → 16진 표기(HRESULT의 정체를 확인)
'0x{0:X8}' -f -2147467259     # 0x80004005

# 0x8007xxxx → 하위 16비트의 Win32 오류 코드
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. WinDbg의 !error

덤프 분석 중에 코드를 조회하려면 WinDbg의 !error 확장이 빠릅니다. 기본은 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로 의미를 확인하는 흐름이 됩니다.

WinDbg에서 예외 코드를 확인하는 흐름크래시 덤프에서는 analyze 명령이 예외 코드를 자동 표시하고, 그 코드를 error 확장에 두 번째 인수 1을 붙여 넘겨 NTSTATUS로 의미를 확인한다크래시 덤프를 연다!analyze -v 를 실행예외 코드가 표시됨!error 코드 1 로 의미 확인

그림 19: 덤프 분석에서는 !analyze -v가 표시한 예외 코드를, !error에 플래그 1을 붙여 조회합니다.

8. 조사 절차 ── 층 판별에서 문맥과의 대조까지

지금까지의 지식을 실제 오류 코드 조사 절차로 조립합니다.

  1. 표기를 맞춥니다. 십진 음수이면 16진 8자리로 변환합니다. 8자리 미만의 16진은 앞에 0을 채워 읽습니다.
  2. 어느 층의 코드인지 가립니다. 아래 판별표처럼, 앞 몇 자리로 거의 정해집니다.
  3. 분해해 실제 코드를 꺼냅니다. 0x8007xxxx이면 하위 16비트, 0xDxxxxxxx이면 N 비트를 벗기는 기계적인 조작입니다.
  4. 도구로 이름과 정의를 찾습니다. err.exe, certutil, !error 가운데 하나로 심볼 이름과 메시지를 확인합니다.
  5. 문맥과 대조합니다. 어느 앱의·어느 작업에서·어느 API가·무엇에 대해 실패했는지를 앱 로그·이벤트 로그·Procmon으로 특정합니다. 코드는 「실패의 종류」, 문맥이 「원인의 자리」입니다.
오류 코드 조사 절차표기를 16진으로 맞추고 앞 몇 자리로 층을 가린 뒤 분해해 실제 코드를 꺼내고, 도구로 이름과 정의를 찾은 다음 문맥과 대조하는 조사의 형십진 표기0x80070xC0xD표기를 맞춘다(음수는 16진 8자리로)앞 몇 자리는?Win32 오류로 읽기하위 16비트를 십진으로NTSTATUS로 읽기N 비트를 벗기고 읽기도구로 이름과 정의를 찾기문맥과 대조한다(Procmon 등)

그림 20: 조사의 형. 표기를 맞추고 층을 가려 분해한 뒤, 이름을 찾고 문맥과 대조합니다.

모습 첫 후보 분해·변환 방법
1〜5자리 십진수(5, 1223 등) Win32 오류 코드 그대로 net helpmsg나 err.exe로
십진 음수(-2147024891 등) HRESULT 16진 8자리로 바꾼 뒤 아래 행의 판별로
0x8007xxxx HRESULT(FACILITY_WIN32) 하위 16비트를 십진으로 바꿔 Win32로 읽기
0x8004xxxx HRESULT(FACILITY_ITF) 반환한 구성 요소의 문서에서 조회
0x8000xxxx HRESULT(FACILITY_NULL) E_FAIL 등 범용 코드. 문맥 조사로 무게를 옮김
0xCxxxxxxx NTSTATUS(오류) !error <code> 1, 필요하면 Win32로 변환해 읽기
0xDxxxxxxx NTSTATUS의 HRESULT 매핑 N 비트(0x10000000)를 벗기고 NTSTATUS로 읽기
0x8024xxxx 등 독자 Facility 기능 영역 고유 HRESULT Facility 값으로 영역을 특정하고 전용 자료로(0x8024…는 Windows Update)2

5단계 「문맥과의 대조」에서 특히 효과가 큰 것이 Process Monitor의 Result 열입니다. 앱에는 「0x80070002」만 나와도, Procmon으로 보면 「어느 프로세스가·어느 경로에·NAME NOT FOUND를 반환받았는지」가 한 줄로 보입니다. 이벤트 로그 쪽 조회는 「Windows 이벤트 로그·ETW 입문」도 참고합니다.

9. 흔한 오독 ── 조사를 멀리 돌아가게 하는 패턴

마지막으로, 실제 상담에서 보는 오독 패턴을 듭니다.

오독 1: 0x80004005를 「특정 원인을 가리키는 코드」라고 생각하기

E_FAIL은 「미지정 실패」이며, Windows Update에서도 네트워크에서도 데이터베이스에서도 같은 값이 나옵니다. 이 코드로 검색해 나온 대응책을 닥치는 대로 시도하는 것은, 거의 확실히 먼 길입니다. 코드가 아니라 「어느 앱·어느 작업·같은 시각의 다른 로그」로 좁힙니다.6

오독 2: 십진 음수가 HRESULT임을 알아채지 못하기

「오류 -2147467259 가 발생했습니다」라는 로그를 그대로 검색하거나 「마이너스 오류?」라고 혼란스러워하는 경우입니다. 음수를 보면 16진으로 바꿉니다. 그것만으로 0x80004005(E_FAIL)임을 알고, 오독 1의 지식으로 이어집니다.

오독 3: 0x8007xxxx를 8자리 통째로 조회하고, 하위 Win32 오류를 보지 않기

0x80070005의 실제는 「5=액세스 거부」입니다. 8자리 전체를 검색하기보다, 하위 16비트를 꺼내 「Win32 오류 5가 이 작업의 문맥에서 무엇을 의미하는지」를 생각하는 쪽이 핵심에 빨리 닿습니다.

오독 4: 「같은 코드=같은 원인」이라고 단정하기

예전에 「오류 5는 백신이 원인이었다」는 경험이 있으면, 다음 오류 5에도 같은 대응으로 뛰어들기 쉽습니다. 코드가 같아도 실패한 API와 대상 리소스가 다르면 원인은 다른 것입니다. 코드 의미의 확인과, Procmon 등으로 대상을 특정하는 일은 매번 세트로 합니다.

오독 5: Win32 오류 5와 0xC0000005, STOP 코드와 NTSTATUS를 혼동하기

「5」라는 연결로 ERROR_ACCESS_DENIED와 STATUS_ACCESS_VIOLATION을 같다고 보면, 권한 문제와 프로그램 버그라는 전혀 다른 방향으로 조사가 빗나갑니다. 또한 블루스크린의 STOP 코드는 NTSTATUS와는 다른 체계이므로, 0x9F를 NTSTATUS 표에서 찾아도 의미 있는 답은 나오지 않습니다.14

오류 5와 0xC0000005는 조사 방향이 다르다Win32 오류 5는 권한 문제로 조사하고, NTSTATUS 0xC0000005는 프로그램 버그로 조사해야 하며, 같다고 보면 조사가 다른 방향으로 빗나간다Win32의 오류 5권한 문제를 조사NTSTATUS 0xC0000005프로그램 버그를 조사다른 체계의 무관한 코드

그림 21: 「5」라는 연결로 같다고 보지 않습니다. 오류 5는 권한 문제, 0xC0000005는 프로그램 버그 쪽으로 향합니다.

10. 정리

  • Windows 오류 코드는 Win32 오류 코드·HRESULT·NTSTATUS의 삼층 구조입니다. 어느 층의 누가 반환한 코드인지를 먼저 가립니다.
  • 표기의 흔들림(십진/16진/음수)은 기계적으로 맞출 수 있습니다. 음수는 16진 8자리로 바꾼 뒤 읽습니다.
  • HRESULT는 S/R/C/N/X 비트+Facility(11비트)+Code(16비트) 구조이며, 0x8007xxxx는 Win32 오류를 감싼 형태라는 가장 중요한 패턴입니다. 하위 16비트를 십진으로 바꿔 실제 코드를 꺼냅니다.
  • 0x80004005(E_FAIL) 같은 범용 코드는 원인을 가리키지 않습니다. 코드를 파고드는 일을 멈추고 문맥 조사로 전환하는 판단도, 구조를 알기 때문에 가능합니다.
  • NTSTATUS는 크래시의 예외 코드나 Procmon의 Result 열에서 만납니다. 0xC0000005는 액세스 위반이며, Win32 오류 5와는 무관합니다. STOP 코드는 또 다른 체계입니다.
  • .NET에서는 HRESULT가 예외 형에 매핑되고, 원래 값은 Exception.HResult에 남습니다. P/Invoke에서는 SetLastError=true와 Marshal.GetLastWin32Error를 세트로 씁니다.
  • 조회 도구는 certutil -error·net helpmsg(표준), err.exe(개발기), PowerShell 한 줄, WinDbg의 !error로 갖춰집니다.
  • 절차는 「표기를 맞춘다→층을 가린다→분해한다→이름을 찾는다→문맥과 대조한다」입니다. 코드가 알려 주는 것은 실패의 종류까지이고, 원인의 자리는 문맥이 알려 줍니다.

다음에 익숙하지 않은 오류 코드를 만나면, 검색 상자에 붙여 넣기 전에 먼저 앞 몇 자리를 봅니다. 0x8007이면 하위 4자리, 0xC이면 NTSTATUS, 음수이면 16진으로 변환 ── 이 10초의 분해가 그 뒤 조사 시간을 크게 가릅니다.

관련 기사

관련 상담 영역

합동회사 코무라소프트는 「이 오류 코드가 무슨 뜻인지 모르겠다」, 「특정 환경에서만 0x80070005가 나온다」처럼 오류 코드에서 시작하는 장애 조사, Win32 API·COM·.NET이 섞인 앱의 오류 처리 설계, 크래시 덤프나 Process Monitor로 원인을 특정하는 일을 다룹니다. 오류 대화상자 스크린샷 한 장으로 상담하셔도 됩니다.

참고 링크

  1. Microsoft Learn, Debug system error codes. Win32 시스템 오류 코드(0〜15999) 목록으로의 색인과, GetLastError가 반환하는 코드의 메시지를 FORMAT_MESSAGE_FROM_SYSTEM 플래그를 붙인 FormatMessage로 얻는 것, WinINet/WinHTTP 오류(12000번대)가 이 공간에 정의되는 것, Microsoft Error Lookup Tool이나 !err 명령으로 조사하는 방법에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  2. Microsoft Open Specifications, [MS-ERREF]: HRESULT. HRESULT의 비트 배치(S·R·C·N·X 비트, 11비트 Facility, 16비트 Code), N 비트가 NTSTATUS 값을 HRESULT 공간에 매핑했음을 나타내는 것, FACILITY_WINDOWS_UPDATE(36)를 포함한 Facility 코드 목록에 대해. ↩ ↩2 ↩3 ↩4 ↩5

  3. Microsoft Open Specifications, [MS-ERREF]: NTSTATUS. NTSTATUS의 비트 배치(2비트 Sev, C 비트, N 비트, 12비트 Facility, 16비트 Code)와, 심각도가 성공(00)·정보(01)·경고(10)·오류(11)의 네 종류로 나뉘는 것에 대해. ↩ ↩2 ↩3

  4. Microsoft Learn, HRESULT_FROM_WIN32 macro. Win32 시스템 오류 코드를 HRESULT 값에 매핑하는 winerror.h 매크로의 정의에 대해. ↩ ↩2 ↩3

  5. Microsoft Learn, Structure of COM Error Codes. HRESULT의 심각도 비트와 Facility 필드의 역할, FACILITY_NULL·FACILITY_RPC·FACILITY_ITF·FACILITY_WIN32·FACILITY_WINDOWS의 각 값, FACILITY_ITF 코드는 인터페이스마다 의미가 정의되어 같은 값이라도 의미가 달라질 수 있는 것에 대해. ↩ ↩2 ↩3

  6. Microsoft Learn, Common HRESULT values. E_FAIL(0x80004005)이 「Unspecified failure(미지정 실패)」인 것, E_ACCESSDENIED(0x80070005)·E_INVALIDARG(0x80070057)·E_OUTOFMEMORY(0x8007000E) 등 자주 보는 HRESULT 값의 정의에 대해. ↩ ↩2 ↩3 ↩4

  7. Microsoft Open Specifications, [MS-ERREF]: NTSTATUS values. STATUS_ACCESS_VIOLATION(0xC0000005), STATUS_DLL_NOT_FOUND(0xC0000135), STATUS_STACK_OVERFLOW(0xC00000FD), STATUS_HEAP_CORRUPTION(0xC0000374), STATUS_BREAKPOINT(0x80000003)을 포함한 NTSTATUS 값 목록에 대해. ↩ ↩2 ↩3

  8. Microsoft Learn, The Microsoft Error Lookup Tool. 16진 상태 코드에 연결된 메시지 텍스트를 Winerror.h 등 각종 헤더 파일을 가로질러 표시하는 독립 도구인 것, 다운로드 파일 이름이 Err_6.4.5.exe인 것, 수록 정의가 컴파일 시점의 것임을 주의해야 하는 것에 대해. ↩ ↩2 ↩3

  9. Microsoft Learn, certutil. certutil의 -error 옵션이 오류 코드에 연결된 메시지 텍스트를 표시하는 것, 그리고 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND)처럼 심볼 이름을 포함한 오류 표기가 쓰이는 것에 대해. ↩ ↩2

  10. Microsoft Learn, !error. WinDbg의 !error 확장이 Win32·Winsock·NTSTATUS·NetAPI의 오류 값을 디코드해 표시하는 것, 플래그에 1을 지정하면 NTSTATUS로 해석하는 것에 대해. ↩ ↩2

  11. Microsoft Learn, How to: Map HRESULTs and exceptions. COM HRESULT와 .NET 예외의 상호 매핑 메커니즘, E_NOTIMPL→NotImplementedException 등의 대응표, 명시적 매핑이 없는 HRESULT는 COMException으로 변환되는 것, IErrorInfo 정보에서 예외의 Message나 Source 등이 초기화되는 것에 대해. ↩ ↩2

  12. Microsoft Learn, RtlNtStatusToDosError function (winternl.h). NTSTATUS 코드를 대응하는 Win32 시스템 오류 코드로 변환하는 함수인 것, 대응이 정의되지 않은 경우에는 ERROR_MR_MID_NOT_FOUND가 반환되는 것, 역변환을 하는 함수는 존재하지 않는 것에 대해. ↩ ↩2

  13. Microsoft Learn, Last-Error Code. 마지막 오류 코드가 스레드마다 유지되는 것, 실패 직후 GetLastError로 꺼내야 하는 것, 성공 때 코드를 0으로 덮어쓰는 API와 그렇지 않은 API가 섞여 있는 것, 비트 29가 응용 프로그램 정의 코드용으로 예약되어 있는 것에 대해. ↩ ↩2

  14. Microsoft Learn, Bug check code reference. 블루스크린에 표시되는 버그 검사 코드(STOP 코드) 목록과, WinDbg의 !analyze 확장으로 코드 정보를 표시하는 방법에 대해. NTSTATUS와는 다른 자체 번호 체계임은 목록에서 확인할 수 있습니다. ↩ ↩2

  15. Microsoft Learn, Marshal.GetLastWin32Error Method. SetLastError 플래그를 설정한 P/Invoke 호출의 마지막 오류 코드를 꺼내는 방법인 것, GetLastError를 직접 P/Invoke하는 것은 런타임 내부 API 호출에 의한 덮어쓰기 때문에 신뢰할 수 없는 것, .NET 6 이후는 GetLastPInvokeError가 권장되는 것에 대해. ↩

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

오류 0x80004005는 무슨 뜻인가요?
0x80004005는 HRESULT의 E_FAIL이며, 의미는 「미지정 실패(Unspecified failure)」입니다. 즉 「자세한 이유를 보고할 수 없는 실패가 일어났다」는 표시일 뿐, 원인 자체를 나타내는 코드는 아닙니다. 네트워크, Windows Update, VBA, 데이터베이스 드라이버처럼 서로 무관한 장면에서 같은 0x80004005가 나오는 것은 이 때문입니다. 이 코드를 보면 코드 의미를 파고들기보다, 어느 앱의 어느 작업에서 나왔는지라는 문맥과 이벤트 로그·상세 로그에 남은 다른 오류 정보로 원인을 좁힙니다.
-2147467259처럼 음수인 오류 코드는 무엇인가요?
32비트 HRESULT를 부호 있는 십진수로 표시한 것입니다. HRESULT는 실패 시 최상위 비트가 1이 되므로, 부호 있는 정수로 보면 항상 음수입니다. PowerShell에서 '0x{0:X8}' -f -2147467259 를 실행하면 16진 표기(이 예에서는 0x80004005=E_FAIL)로 되돌릴 수 있습니다. 로그나 스크립트 오류 메시지에서 -214…로 시작하는 음수를 보면, 먼저 16진으로 바꾼 뒤 조회하는 것이 정석입니다.
오류 코드의 의미를 가장 쉽게 알아보는 방법은 무엇인가요?
추가 설치 없이 쓸 수 있는 것은 명령 프롬프트의 net helpmsg 5(Win32 오류의 십진수용)와 certutil -error 0x80070005입니다. certutil은 16진 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가 어느 리소스에 대해 실패했는지 확인하는 것이 원인 특정의 지름길입니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기