22 Jul
2014
Posted in: 코드
By    No Comments

BSOD: 신호와 소음


BSOD: 신호와 소음
by 신영진(YoungJin Shin), codewiz at gmail.com, @codemaru, http://www.jiniya.net

#0

2008 베이징 올림픽 개막식을 장식한 화려한 블루스크린

드라이버 개발자 10명을 불러다 놓고 어떤 말을 제일 싫어하십니까, 라고 물어보면 10명 모두 BSOD를 제일 싫어한다고 대답할 것이다. 그만큼 BSOD는 드라이버 개발자에게는 제일 듣기 싫은 말이다. 자다가도 경기를 일으키고, 소리만 나와도 소스라치게 놀라며, 화면에 파란 도트만 보여도 커널 개발자를 겁에 질리게 만들어 버리는 녀석이 바로 BSOD다. BSOD는 Blue Screen Of Death의 약자로 우리말로 번역하면 죽음의 블루스크린 정도 되겠다. 2008년 베이징 올림픽 개막식에서 발생한 블루스크린이 전세계 공중파를 타면서 이제는 일반인들도 블루스크린이 뭔지를 알게 되었다.

블루스크린이 출력된 섹시한 티, 하지만 섹시하기는 커녕 두렵기만 하다.

BSOD에 대한 두려움을 경계한 개발자들은 BSOD 화면 보호기를 만들어서 담력을 키우기도 하고, 여자친구에게 블루스크린이 출력된 티셔츠를 선물해 보기도 하지만 큰 소용은 없다. 급기야 MS 개발자들은 윈도우 8부터는 블루스크린 이미지를 아주 친근한 형태로 변경했다. 색상을 파스텔톤의 파란색으로 바꾸었고, 화면에는 이모티콘도 넣었다. 블루스크린이 떴지만 쫄지말고 웃으라는 MS 개발자의 패기를 보여준다고 할 수 있겠다. 이런 노력에도 불구하고 커널 개발자들은 여전히 BSOD를 무서워 한다.

윈도우 8은 블루스크린 이미지를 친근하게 개선했다.

이렇게 설명하니 블루스크린이 뭔가 대단한 것처럼 보이는데 사실은 그냥 커널 영역에서 발생하는 프로그램 오류에 불과하다. 일반 응용 프로그램이 오류를 발생시키면 “잘못된 연산을 수행하였습니다”같은 아주 친절한 메시지를 뿜꼬는 프로그램이 종료되는 정도로 끝이 난다. 그런 똑같은 일이 커널 영역에서 발생하면 드라이버 하나가 죽고 끝나는 것이 아니라 블루스크린이 발생하고 시스템은 정지된다. 동일한 오류지만 파급 효과가 다른 것이다. 하나는 프로그램만 죽고 끝나고, 다른 하나는 시스템 전체가 죽는…

윈도우 9x 시절에는 블루스크린이 너무나 자주 발생해서 사람들은 블루스크린이 발생하는 줄도 몰랐다. 그저 또 껐다 켜야 하는구나라고 생각했다. 하지만 윈도우 2000이상부터 NT 커널이 도입되면서 블루스크린은 생각보다 보기 힘든 일이 되었다. 이후에도 다양한 보안 프로그램에서 많은 블루스크린을 발생시키곤 했지만 이 또한 MS의 꾸준한 노력으로 요즘은 정말 블루스크린 보기가 쉽지 않아졌다.

#1
거창하게 썰을 풀었는데, 요는 이렇게 마주하기 싫은 블루스크린을 최근 다시 마주하게 되었다는 이야기를 하고 싶었던 것이었다. 따끈따끈한 신작 게임에 적용을 진행하면서 발생한 문제인데 당.연.히. 원인은 나의 어처구니 없는 멍청함에 있었다. 나의 멍청함을 인식하는데 다소 시간이 걸렸는데, 그 과정에 문제 해결을 방해하는 소음이 한 몫 했다.

첫번째는 어째서 그 게임에서만 밥 먹듯이 블루스크린이 발생하냐는 점이었다. 그런 문제라면 적어도 이전에 수도 없이 적용된 게임에서 응당 발견되었어야 하지 않았을까라는 생각이었다. 결과론적으로 그 게임에서만 발생하는 이유가 있었고, 그 문제가 그 전에는 전혀 발견될 수 없는 종류의 문제였다. 둘째는 블루스크린에 출력된 함수 파라미터와 관련한 데이터들이 우리의 생각을 오염시켰다. 실제로 우리가 참고한 글에 나온대로 파라미터를 수정하면 블루스크린이 발생하지 않았기 때문이었다. 하지만 이는 최종적으로 함정같은 존재였다.

이런 이유로 크래시 덤프 문제를 해결하는 과정이 생각보다 복잡 다단했다. 최종적으로는 정말 아주 사소한 문제에서 이 모든 것이 비롯됐다는 것을 알게 되었고, 그 도화선이 된 부분은 정말 실소를 금할 수 없을 정도로 어처구니 없는 내용이었다. 그래서 여기에 그 내용을 소개하면 다소 도움이 되지 않을까라는 생각에 글을 써본다.

덤프의 내용은 다음과 같다. 논페이지드 영역에서 페이지 폴트가 발생해서 블루스크린이 발생한 것이다. 콜스택을 살펴보면 MmProbeAndLockPages라는 함수에서 참조하려고 한 논페이지드 영역이 존재하지 않는 영역이라 발생했다는 것을 알 수 있다. 이 문제와 관련된 내용을 다음 몇 차례의 글을 통해서 살펴볼 생각이다. 혹시 관심있는 독자 분들이 있다면 여기에서 관련 파일을 받을 수 있다. 64비트 시스템에서 conan_ldr을 관리자 권한으로 실행하면 블루스크린이 발생하는 것을 재현할 수 있다. conan.dmp는 그렇게 생성한 미니덤프 파일이다.

kd> !analyze -v
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced.  This cannot be protected by try-except,
it must be protected by a Probe.  Typically the address is just plain bad or it
is pointing at freed memory.
Arguments:
Arg1: ffffffff825f0000, memory referenced.
Arg2: 0000000000000000, value 0 = read operation, 1 = write operation.
Arg3: fffff80002af3798, If non-zero, the instruction address which referenced the bad memory
	address.
Arg4: 0000000000000005, (reserved)

Debugging Details:
------------------


Could not read faulting driver name

READ_ADDRESS: GetPointerFromAddress: unable to read from fffff80002d0f100
 ffffffff825f0000 

FAULTING_IP: 
nt!MmProbeAndLockPages+118
fffff800`02af3798 410fb601        movzx   eax,byte ptr [r9]

MM_INTERNAL_CODE:  5

DEFAULT_BUCKET_ID:  VISTA_DRIVER_FAULT

BUGCHECK_STR:  0x50

PROCESS_NAME:  conan_ldr.exe

CURRENT_IRQL:  0

TRAP_FRAME:  fffff8800454f6d0 -- (.trap 0xfffff8800454f6d0)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=0000000000000001 rbx=0000000000000000 rcx=0000000000000000
rdx=0000000000000001 rsi=0000000000000000 rdi=0000000000000000
rip=fffff80002af3798 rsp=fffff8800454f860 rbp=fffff8800454fca0
 r8=fffffa8001cd5cf0  r9=ffffffff825f0000 r10=0000000000000001
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei ng nz ac pe nc
nt!MmProbeAndLockPages+0x118:
fffff800`02af3798 410fb601        movzx   eax,byte ptr [r9] ds:5480:ffffffff`825f0000=??
Resetting default scope

LAST_CONTROL_TRANSFER:  from fffff80002b545b3 to fffff80002ad7bc0

STACK_TEXT:  
fffff880`0454f568 fffff800`02b545b3 : 00000000`... : nt!KeBugCheckEx
fffff880`0454f570 fffff800`02ad5cee : 00000000`... : nt! ?? ::FNODOBFM::`string'+0x43801
fffff880`0454f6d0 fffff800`02af3798 : fffffa80`... : nt!KiPageFault+0x16e
fffff880`0454f860 fffff880`04baf04f : fffffa80`... : nt!MmProbeAndLockPages+0x118
fffff880`0454f970 fffffa80`01cd5cc0 : 00000000`... : conan+0x104f
fffff880`0454f978 00000000`00000000 : 00000000`... : 0xfffffa80`01cd5cc0

#2
덤프를 살펴보면 알겠지만 크래시 시점에 주요하게 관여된 함수로 MmProbeAndLockPages, MmMapLockedPages가 있다. 덤프를 제일 먼저 분석한 엔지니어는 아래 내용을 토대로 드라이버의 코드의 함수 파라미터를 고쳐야 한다는 이야기를 했었다. 덤프 분석에 떡밥 내지는 힌트 정도로 살펴보면 좋을 것 같다. 앞으로 차차 살펴보겠지만 여러분이 MmProbeAndLockPages와 MmMapLockedPages 함수에 사용되는 AccessMode의 용도에 관한 아래 글들의 내용에 대해서 충분히 상세히 알고 있다면 그것만으로도 자부심을 가져도 될 것 같다.

The better idea would be to pass Irp->RequestorMode as the second parameter to MmProbeAndLockPages. In this case, if the requestor is UserMode and the buffer is a kernel address the API will immediately raise an exception instead of potentially crashing. Calling ProbeForWrite yourself would basically give you the same effect (it also checks to make sure the buffer supplied is a user address), which is why I asked if you were making this call earlier as it would point to this being a different type of bug.

http://osronline.com/ShowThread.cfm?link=236232

If AccessMode is KernelMode and MmMapLockedPages cannot map the specified pages, the system issues a bug check. (For this reason, drivers should use MmMapLockedPagesSpecifyCache when available; that routine returns NULL on failure, rather than causing a bug check.) If AccessMode is UserMode and the specified pages cannot be mapped, the routine raises an exception. Callers that specify UserMode must wrap the call to MmMapLockedPages in a try/except block. For more information, see Handling Exceptions.

http://msdn.microsoft.com/en-us/library/windows/hardware/ff554622(v=vs.85).aspx

#3
앞으로 추가적인 몇 개의 글을 통해서 이 덤프의 정확한 원인을 추적하는 작업을 해 볼 생각입니다. 그전에 덤프에 대한 나름의 의미 있는 해석을 내렸다고 생각하는 분들은 codewiz at gmail.com으로 메일 주세요. 함께 할만한 일들이 있을지도 모르잖아요 ㅎㅎ~


  • 트랙백 주소: http://www.jiniya.net/wp/archives/14056/trackback

관련 글