「앱 화면에 오류 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)는 원인 코드가 아닙니다. 뜻은 「Unspecified failure」이며 그 이상의 정보가 없습니다. 이 코드를 파기보다 출현 문맥과 함께 남은 로그를 찾아야 합니다.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
한 문장으로 말하면, Windows 오류 코드 조사의 형은 「표기를 16진으로 맞추고 → 어느 층의 코드인지 판정하고 → 분해해 본질 코드를 꺼내고 → 문맥과 함께 읽는다」입니다.
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
flowchart TB
accTitle: 세 체계를 넘나드는 변환 흐름
accDescr: 커널이 돌려준 NTSTATUS를 Win32 서브시스템이 Win32 오류 코드로 변환하고, COM 계층이 다시 HRESULT로 감싼다
kernel["커널과 드라이버"] --> nt["NTSTATUS(오류는 0xC…)"]
nt -->|Win32 서브시스템이 변환| win["Win32 오류 코드(5 등)"]
win -->|COM 계층이 감쌈| hr["HRESULT(0x8007xxxx)"]
그림 1: 층을 넘나드는 변환 흐름. 커널 NTSTATUS가 Win32 오류가 되고, 다시 HRESULT로 감싸인다.
2.1. 십진과 16진을 서로 바꿔 읽는 습관
세 체계를 가리기 전에, 표기 흔들림을 몸에 익혀야 합니다. 같은 코드가 상황에 따라 십진 또는 16진으로 표시됩니다.
- 「오류 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…로 시작하는 음수 십진을 보면 반사적으로 16진으로 바꾸십시오. 그것만으로 조사 입구에서 길을 잃는 일이 크게 줄어듭니다.
flowchart TB
accTitle: 같은 코드의 세 가지 모습
accDescr: 십진 오류 5, 16진 0x5, 0x80070005의 하위 16비트는 모두 같은 ERROR_ACCESS_DENIED를 가리킨다
d["십진 표기: 오류 5"] --> same["ERROR_ACCESS_DENIED"]
h["16진 표기: 0x5"] --> same
l["0x80070005의 하위 16비트"] --> same
same -.-> memo["표기만 다르고 같은 코드"]
그림 2: 십진, 16진, HRESULT의 하위 16비트는 같은 코드의 다른 표기일 뿐입니다.
3. Win32 오류 코드 ── GetLastError와 FORMAT_MESSAGE
3.1. GetLastError의 기본 동작
CreateFile과 RegOpenKeyEx 같은 많은 Win32 API는 반환값(FALSE, NULL, INVALID_HANDLE_VALUE 등)으로 실패를 알리고, 자세한 오류 코드는 스레드마다 가진 「마지막 오류 코드」에 넣습니다. 호출 측은 실패를 확인한 직후 GetLastError로 꺼냅니다.13
실무에서 주의할 점은 두 가지입니다.13
- 실패 직후에 읽으십시오. 그 사이에 다른 API 호출(로그 함수 등)을 넣으면 그 호출이 마지막 오류 코드를 덮어쓸 수 있습니다.
- 성공 시의 값에 기대지 마십시오. 어떤 API는 성공 시 마지막 오류 코드를 0으로 지우고, 어떤 API는 건드리지 않습니다. 규칙은 반환값으로 실패를 확인한 뒤 읽는 것입니다.
sequenceDiagram
accTitle: 실패 직후 GetLastError를 읽기
accDescr: 반환값으로 실패를 확인한 뒤, 다른 API 호출을 끼워 넣지 않고 바로 GetLastError로 마지막 오류 코드를 꺼낸다
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: 실패 직후에 마지막 오류 코드를 읽으십시오. 사이에 다른 API를 넣으면 덮어쓸 수 있습니다.
코드에서 메시지 문자열을 얻으려면 FORMAT_MESSAGE_FROM_SYSTEM 플래그를 주고 FormatMessage를 씁니다.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);
}
이렇게 앱 로그에 십진과 16진, 메시지 본문을 함께 남기면 이후 조사가 한 단계 빨라집니다.
flowchart TB
accTitle: 코드에서 메시지를 찾아 로그에 남기기
accDescr: FormatMessage에 FORMAT_MESSAGE_FROM_SYSTEM 플래그를 주어 오류 코드의 메시지 문자열을 얻고, 십진·16진·메시지 본문을 로그에 남긴다
code["오류 코드(예: 5)"] --> fm["FormatMessage로 문자열 얻기"]
fm --> msg["메시지 본문"]
msg --> log["로그에 기록"]
log -.-> both["십진·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 권한 상승 대화상자에서 「아니요」를 고를 때 나오는 코드처럼, 오류라기보다 「취소되었다」에 가깝습니다.
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(파일을 찾을 수 없음): 사용자가 지정한 파일이 아닐 수 있습니다. EXE가 암묵적으로 로드하려던 의존 DLL, 레지스트리 리다이렉션(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 | 예약(NTSTATUS를 매핑할 때 severity의 일부) |
| 29 | C | Customer 비트. 1이면 Microsoft가 아닌 쪽이 정의한 코드 |
| 28 | N | 1이면 HRESULT 공간으로 매핑된 NTSTATUS 값 |
| 27 | X | 예약(0) |
| 26–16 | Facility | 출처를 나타내는 facility 코드(11비트) |
| 15–0 | Code | facility 안의 상세 코드(16비트) |
최상위 S 비트가 1이면, 즉 16진 표기가 0x8 이상으로 시작하는 HRESULT는 실패입니다. 이를 부호 있는 32비트 정수로 표시하면 음수가 됩니다. 앞에서 말한 「-214…」의 정체입니다.
flowchart TB
accTitle: S 비트와 음수 표시의 관계
accDescr: 실패 HRESULT는 최상위 S 비트가 1이므로 16진에서는 0x8 이상으로 시작하고, 부호 있는 32비트 정수로는 음수가 된다
s["S 비트 = 1(실패)"] --> hex["16진이 0x8 이상으로 시작"]
hex --> neg["부호 있는 표시는 음수"]
neg --> back["음수를 보면 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와 완전히 다릅니다.
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로 두어 0x8007xxxx HRESULT를 조립한다
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)
반대 방향으로 읽으려면 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, 서버 제품)의 문서에서 찾으십시오.
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는 커널, 장치 드라이버, ntdll 네이티브 API가 쓰는 32비트 코드이며, 배치는 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비트이므로 앞자리 16진 숫자로 종류를 읽을 수 있습니다. 0xC…는 오류(11), 0x8…는 경고(10), 0x4…는 정보(01), 0x0–0x3…는 성공입니다. 중단점 예외 0x80000003(STATUS_BREAKPOINT)은 「오류가 아닌 경고」의 대표 예입니다.37
flowchart TB
accTitle: NTSTATUS는 앞자리로 종류를 읽는다
accDescr: Severity가 2비트이므로 NTSTATUS는 앞자리 16진이 0xC이면 오류, 0x8이면 경고, 0x4이면 정보, 0x0부터 0x3이면 성공으로 읽는다
head{"앞자리 16진은?"} -->|0xC| e["오류"]
head -->|0x8| w["경고"]
head -->|0x4| i["정보"]
head -->|0x0–0x3| s["성공"]
w -.-> ex["예: 0x80000003은 경고"]
그림 11: NTSTATUS는 앞자리 16진으로 종류를 읽습니다. 0x80000003은 「오류가 아닌 경고」입니다.
5.2. 만나는 곳 ── 예외 코드, STOP 코드, 이벤트 로그
IT 담당자와 개발자가 NTSTATUS를 만나는 상황은 주로 크래시와 관련됩니다.
- 응용 프로그램 크래시의 예외 코드: 이벤트 로그의 「응용 프로그램 오류(이벤트 ID 1000)」에 기록되는 「Exception code: 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 오류로 변환되어 앱에 도착하는 층 대응을 느낄 수 있는 자리이기도 합니다.
flowchart TB
accTitle: 예외 코드와 STOP 코드를 가리기
accDescr: 이벤트 로그의 예외 코드는 NTSTATUS로 읽고, 블루스크린 STOP 코드는 다른 체계인 전용 버그 검사 코드 참조에서 찾는다
q{"코드가 어디에 나타났는가?"} -->|예외 코드| nt["NTSTATUS로 읽기"]
q -->|STOP 코드| bc["버그 검사 코드 표에서 조회"]
nt -.-> n1["예: 0xC0000005"]
bc -.-> b1["예: 0x0000009F"]
그림 12: 이벤트 로그의 예외 코드는 NTSTATUS이고, 블루스크린 STOP 코드는 다른 체계입니다. 잘못된 표에서 찾지 마십시오.
예외 코드 너머의 조사, 즉 크래시 덤프를 모아 분석하는 일은 「Windows 앱의 크래시 덤프 수집 입문」과 「WinDbg + SOS로 크래시 덤프 읽기」에서 다룹니다.
5.3. HRESULT와의 관계 ── N 비트와 RtlNtStatusToDosError
NTSTATUS와 다른 두 층 사이의 다리는 두 갈래입니다.
- HRESULT 공간으로의 매핑: HRESULT N 비트(0x10000000)를 켜면 NTSTATUS 값을 그대로 HRESULT 공간으로 가져옵니다(winerror.h의 HRESULT_FROM_NT 매크로). 예를 들어 0xC0000005를 매핑하면 0xD0000005가 됩니다. 0xD로 시작하는 HRESULT를 보면 N 비트를 벗기고 NTSTATUS로 읽는 것이 올바른 절차입니다.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 층에서 더 거친 구분으로 둥글려진다는 점도 기억해 두면 좋습니다.
flowchart TB
accTitle: NTSTATUS에서 다른 층으로 가는 두 다리
accDescr: NTSTATUS는 N 비트를 켜 HRESULT 공간으로 매핑하는 길과, RtlNtStatusToDosError로 Win32 오류 코드로 변환하는 두 길로 다른 층에 전달된다
nt["NTSTATUS(0xC0000005)"] -->|N 비트를 켬| hr["HRESULT(0xD0000005)"]
nt -->|RtlNtStatusToDosError| win["Win32 오류 998(ERROR_NOACCESS)"]
win -.-> memo["대응이 없으면 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를 함께 다룹니다. 오류 대화상자에 「코드 + 설명」이 나오는 앱은 이 메커니즘으로 설명을 나르는 경우가 많습니다.
flowchart TB
accTitle: HRESULT를 보완하는 IErrorInfo
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 상호 운용에서 HRESULT 실패를 받으면 예외로 변환합니다. 알려진 HRESULT는 대응하는 예외 형에 매핑되고, 모르는 것은 COMException이 됩니다.11
flowchart TB
accTitle: HRESULT에서 .NET 예외로의 매핑
accDescr: COM 상호 운용에서 받은 실패 HRESULT는 알려진 매핑이면 대응 예외 형으로, 아니면 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 속성에 남습니다. 파일 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)
// Another process is holding the file — wait a little and retry, for example
}
6.3. P/Invoke와 GetLastError
P/Invoke로 Win32 API를 직접 호출할 때는 DllImport(또는 LibraryImport)에 SetLastError = true를 지정한 뒤 Marshal.GetLastWin32Error로 꺼냅니다(.NET 6부터는 동등한 GetLastPInvokeError). GetLastError 자체를 P/Invoke로 정의해 호출하는 것은 부정확합니다. 런타임 안의 API 호출이 값을 덮어쓸 수 있기 때문입니다.15
flowchart TB
accTitle: P/Invoke에서 마지막 오류를 꺼내기
accDescr: SetLastError를 true로 두고 Marshal.GetLastWin32Error로 꺼내는 것이 맞고, GetLastError를 직접 P/Invoke하면 런타임이 덮어써 부정확하다
pi["P/Invoke로 Win32 API 호출"] --> 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 오류 코드에서 OS 메시지 문자열을 찾으므로, 코드와 메시지를 로그에 남기는 데 그대로 쓸 수 있습니다. 어느 층에서 예외를 잡고 어떻게 로그에 남길지라는 설계 질문은 「예외 처리에서 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입니다. certutil의 -error 옵션은 오류 코드에 대응하는 메시지 본문을 표시하며, 16진 HRESULT와 십진 모두 받습니다.9
certutil -error 0x80070005
certutil -error 5
net helpmsg 5
net helpmsg는 십진 Win32 오류 코드 전용이지만, 한국어 환경에서는 메시지가 한국어로 돌아오므로 사용자 설명에 그대로 쓸 수 있습니다.
flowchart TB
accTitle: 표준 명령 고르는 법
accDescr: 십진 Win32 오류 코드는 net helpmsg로 조회하고, HRESULT를 포함한 16진이 들어간 코드는 certutil의 -error 옵션으로 조회한다
q{"가진 코드는?"} -->|십진 Win32| net["net helpmsg"]
q -->|16진 포함| cert["certutil -error"]
net -.-> jp["한국어 메시지가 돌아옴"]
cert -.-> any["16진과 십진을 모두 받음"]
그림 18: 표준 명령 고르는 법. 십진 Win32 오류는 net helpmsg, 16진이 들어가면 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. WinDbg의 !error
덤프 분석 중에 코드를 조회하려면 WinDbg의 !error 확장이 빠릅니다. 기본은 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 명령이 예외 코드를 자동 표시하고, 그 코드를 두 번째 인수 1과 함께 error 확장에 넘겨 NTSTATUS로 의미를 확인한다
dump["크래시 덤프를 연다"] --> an["!analyze -v를 실행"]
an --> exc["예외 코드가 표시됨"]
exc --> chk["!error code 1로 의미 확인"]
그림 19: 덤프 분석에서는 !analyze -v가 표시한 예외 코드를 플래그 1과 함께 !error로 조회하십시오.
8. 조사 절차 ── 계층 판정부터 문맥 대조까지
지금까지의 지식을 실제로 오류 코드를 조사하는 절차로 모읍니다.
- 표기를 정규화하십시오. 음수 십진이면 8자리 16진으로 바꿉니다. 8자리보다 짧은 16진은 앞에 0을 채워 읽습니다.
- 어느 층의 코드인지 판정하십시오. 아래 판정표처럼 앞 몇 자리가 거의 결정합니다.
- 분해해 본질 코드를 꺼냅니다. 0x8007xxxx이면 하위 16비트, 0xDxxxxxxx이면 N 비트를 벗기는 기계적 작업입니다.
- 도구로 이름과 정의를 조회하십시오. err.exe, certutil,
!error로 심볼 이름과 메시지를 확인합니다. - 문맥과 대조하십시오. 앱 로그, 이벤트 로그, Procmon으로 어느 앱의 어느 작업, 어느 API가 무엇에 대해 실패했는지 특정합니다. 코드는 「실패의 종류」이고, 문맥은 「원인의 자리」입니다.
flowchart TB
accTitle: 오류 코드 조사 절차
accDescr: 표기를 16진으로 맞추고 앞자리로 층을 판정한 뒤 분해해 본질 코드를 꺼내고, 도구로 이름과 정의를 조회한 다음 문맥과 대조하는 조사 형
fix["16진으로 정규화"] --> 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자리 16진으로 바꾼 뒤 아래 행의 판정 |
| 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은 「Unspecified failure」이며, 같은 값이 Windows Update, 네트워킹, 데이터베이스에 나타납니다. 이 코드로 검색해 나오는 대응책을 모두 시도하는 것은 거의 확실히 먼 길입니다. 코드가 아니라 「어느 앱, 어느 작업, 같은 시각의 다른 로그」로 좁히십시오.6
오독 2: 음수 십진이 HRESULT임을 알아채지 못하기
「Error -2147467259 occurred」라는 로그를 그대로 검색하거나 「마이너스 오류?」에 혼란스러워하는 경우입니다. 음수를 보면 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를 혼동하기
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의 삼층 구조입니다. 먼저 어느 층의 누가 코드를 돌려주었는지 판정하십시오.
- 표기 흔들림(십진 / 16진 / 음수)은 기계적으로 맞출 수 있습니다. 음수는 8자리 16진으로 바꾼 뒤 읽으십시오.
- 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초 분해가 이어지는 조사 시간을 크게 가릅니다.
관련 기사
- WinDbg + SOS로 크래시 덤프 읽기 ── 수집 이후 실무 분석 입문
- Windows 앱의 크래시 덤프 수집 입문 - 우선 WER / ProcDump / WinDbg를 어떻게 구분해서 쓸까
- 예외 처리에서 catch와 로그는 어디에 두어야 하는가
- Process Monitor(ProcMon) 실전 가이드 ── 「설정이 반영되지 않는다」「ACCESS DENIED」를 10분 만에 특정하기
- Windows 이벤트 로그·ETW 입문 ── 업무 앱의 로그를 OS 표준 체계에 올리기
관련 상담 영역
합동회사 코무라소프트는 「이 오류 코드가 무슨 뜻인지 모르겠다」, 「0x80070005가 특정 환경에서만 나온다」처럼 오류 코드에서 시작하는 장애 조사, Win32 API·COM·.NET이 섞인 앱의 오류 처리 설계, 크래시 덤프와 Process Monitor로 원인을 특정하는 일을 다룹니다. 오류 대화상자 스크린샷 한 장으로 상담하셔도 됩니다.
참고 링크
-
Microsoft Learn, Debug system error codes. Win32 시스템 오류 코드(0–15999) 목록의 색인,
GetLastError가 돌려주는 코드를 FormatMessage와 FORMAT_MESSAGE_FROM_SYSTEM 플래그로 메시지로 얻는 방법, WinINet/WinHTTP 오류(12000대)가 이 공간에 정의되어 있다는 점, Microsoft Error Lookup Tool과 !err 명령으로 조사하는 방법에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 -
Microsoft Open Specifications, [MS-ERREF]: HRESULT. HRESULT 비트 배치(S, R, C, N, X 비트, 11비트 Facility, 16비트 Code), N 비트가 HRESULT 공간으로 매핑된 NTSTATUS 값을 나타낸다는 점, FACILITY_WINDOWS_UPDATE(36)를 포함한 facility 코드 목록에 대해. ↩ ↩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. Win32 시스템 오류 코드를 HRESULT 값으로 매핑하는 winerror.h 매크로의 정의에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, Structure of COM Error Codes. HRESULT severity 비트와 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」라는 점, E_ACCESSDENIED(0x80070005), E_INVALIDARG(0x80070057), E_OUTOFMEMORY(0x8007000E) 등 자주 보는 HRESULT 값의 정의에 대해. ↩ ↩2 ↩3 ↩4
-
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
-
Microsoft Learn, The Microsoft Error Lookup Tool. Winerror.h 등 여러 헤더를 가로질러 16진 상태 코드에 연결된 메시지 본문을 표시하는 독립 도구라는 점, 다운로드 파일 이름이 Err_6.4.5.exe라는 점, 묶인 정의가 컴파일 시점 기준임을 알아 두어야 한다는 점에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, certutil. certutil의 -error 옵션이 오류 코드에 연결된 메시지 본문을 표시한다는 점, 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND)처럼 심볼 이름을 포함한 오류 표기를 쓴다는 점에 대해. ↩ ↩2
-
Microsoft Learn, !error. WinDbg의 !error 확장이 Win32, Winsock, NTSTATUS, NetAPI 오류 값을 디코드해 표시한다는 점, 플래그로 1을 지정하면 NTSTATUS로 해석한다는 점에 대해. ↩ ↩2
-
Microsoft Learn, How to: Map HRESULTs and exceptions. COM HRESULT와 .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로 꺼내야 한다는 점, 성공 시 코드를 0으로 덮어쓰는 API와 그러지 않는 API가 섞여 있다는 점, 비트 29가 응용 프로그램 정의 코드용으로 예약되어 있다는 점에 대해. ↩ ↩2
-
Microsoft Learn, Bug check code reference. 블루스크린에 표시되는 버그 검사 코드(STOP 코드) 목록과 WinDbg의 !analyze 확장으로 코드 정보를 표시하는 방법. NTSTATUS와 별개인 자체 번호 체계임은 목록에서 확인할 수 있습니다. ↩ ↩2
-
Microsoft Learn, Marshal.GetLastWin32Error Method. SetLastError 플래그를 켠 P/Invoke 호출의 마지막 오류 코드를 꺼내는 방법이라는 점, GetLastError를 직접 P/Invoke하면 런타임 내부 API 호출의 덮어쓰기 때문에 신뢰할 수 없다는 점, .NET 6부터는 GetLastPInvokeError가 권장된다는 점에 대해. ↩
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
Win32 스레드 풀 API ── CreateThreadpoolWork로 「스레드를 만들지 않는」병렬 처리
네이티브 코드에서 CreateThread를 여기저기 만들고 있지는 않은가요. Vista에서 개편된 Win32 스레드 풀 API의 work·timer·wait·io 네 객체, 클린업 그룹, 콜백에서 해서는 안 되는 일까지 1차 정보를 바탕으로 해설...
네임드 파이프의 실무 ── Windows 프로세스 간 통신의 정석을 설계부터 보안까지
Windows 프로세스 간 통신의 정석인 네임드 파이프를 실무 관점에서 정리합니다. 바이트/메시지 모드 선택, 여러 클라이언트를 받는 서버 설계, ACL과 위장 보안, .NET NamedPipeStream까지 1차 정보를 바탕으로 설명합니다.
절전에서 재개하면 깨지는 앱 ── Windows 전원 이벤트의 구조와 살아남는 업무 앱
노트북을 열었더니 업무 앱의 연결이 죽어 있었다 ── 원인은 절전을 전제하지 않은 설계입니다. WM_POWERBROADCAST 알림의 흐름, Modern Standby의 동작, 끊김/재연결 설계, 절전 억제, 조사 명령을 1차 정보로 정리합니다.
DllMain과 로더 락 ── 「DLL 초기화에서는 아무것도 하지 말라」는 말의 진짜 이유
DllMain에서 LoadLibrary를 호출하거나 다른 스레드와 동기화해서는 안 되는 이유를 설명합니다. 로더 락이 모든 DLL 알림을 직렬화하는 구조, 데드락이 구조적으로 확정되는 고전적 시나리오, 초기화를 미루는 올바른 설계, 행(hang)...
「응답 없음」의 정체 ── Windows가 앱의 행을 판단하는 방식과 멈추지 않는 설계
Windows의 「응답 없음」은 창이 5초 동안 메시지를 꺼내지 않았다고 OS가 판단해 고스트 창으로 바꾸는 메커니즘입니다. 그 판단의 내부, 행의 고전적 원인, 무거운 작업을 UI 스레드에서 떼어 내는 설계, 행 조사 절차를 정리합니다.
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
장애 조사 & 장기 가동 장애
간헐적 장애, 통신 진단, 장기 가동 크래시, 실패 경로 테스트 기반을 정리한 토픽 페이지입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
장애 조사 & 원인 분석
간헐적 장애, 장기 가동 중 크래시, 누수, 통신 중단 등 까다로운 프로덕션 이슈를 조사합니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- 오류 0x80004005는 무슨 뜻인가요?
- 0x80004005는 HRESULT E_FAIL이며, 의미는 "Unspecified failure(미지정 실패)"입니다. 즉 "자세한 이유를 보고할 수 없는 실패가 일어났다"는 표시일 뿐, 원인 자체를 나타내는 코드가 아닙니다. 네트워크, Windows Update, VBA, 데이터베이스 드라이버처럼 서로 무관한 곳에서 같은 0x80004005가 나오는 것은 이 때문입니다. 이 코드를 보면 코드의 의미를 파고들기보다, 어느 앱의 어느 작업에서 나왔는지라는 문맥과 이벤트 로그·상세 로그에 남은 다른 오류 정보로 원인을 좁히십시오.
- -2147467259처럼 음수인 오류 코드는 무엇인가요?
- 32비트 HRESULT를 부호 있는 십진수로 표시한 것입니다. HRESULT는 실패 시 최상위 비트가 켜지므로, 부호 있는 정수로 보면 항상 음수입니다. 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가 어느 리소스에 대해 실패했는지 확인하는 것이 원인 특정의 지름길입니다.