Windows 가 어떻게 종료 되는가?

"이 문서는 https://blogs.msdn.com/ntdebugging blog 의 번역이며 원래의 자료가 통보 없이 변경될 수 있습니다. 이 자료는 법률적 보증이 없으며 의견을 주시기 위해 원래의 blog 를 방문하실 수 있습니다. (https://blogs.msdn.com/ntdebugging/archive/2007/06/09/how-windows-shuts-down.aspx)"

Windows 가 어떻게 종료 되는가?

저의 이름은 Bryan 이고 Microsoft CPR platforms team 의 Escalation Engineer 입니다. Windows 종료는 많은 문제 시나리오에 포함되어 있습니다. 쉽지는 않지만 종료 문제를 해결할 때는 Winlogon.exe process를 올바르게 이해하고 있어야 합니다.

문제 해결 기술

공통 설정

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon 를 설정하여 Windows 가 어떻게 종료 되는지 설정할 수 있습니다.

자세한 내용은 다음 링크에서 확인할 수 있습니다.

https://www.microsoft.com/technet/prodtechnol/windows2000serv/reskit/regentry/12314.mspx

DisableCAD (REG_DWORD). 1 로 설정한 다면 GINA 는 Ctrl-Alt-Del 을 건너뛰고 바로 logon prompt 로 넘어갑니다. Logon prompt 가 consol 에서 보여진다면 system 은 종료할 수 없습니다.

보안 때문에 다른 설정을 하기도 합니다.

HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management

ClearPageFileAtShutdown(REG_DWORD). 1 로 설정되면 Memory manager 가 종료 될 때 Pagefile 을 0으로 초기화 합니다. 만약 Paging 파일이 클 경우 종료 할 때 많은 시간이 걸릴 수 있습니다.

Winlogon event 통지

Windows Vista/ 2008 server 이전 에서는 Winlonon 통지 패키지를 통해 간단하게 각각의 Event 에 함수를 등록하여 Winlogon 의 상태를 모니터링 할 수 있었습니다. Winlogon 은 event 가 발생하면 마지막에 등록된 함수를 호출하였으며 Winlogon 의 Context 에서 처리 되었습니다. 통지 패키지에 버그가 있다면 Winlogon 에 영향을 주고 System 이 응답 없는 상태에 빠질 수 있으므로 주의 해야 합니다. Winlogon 통지 패키지는 다음에서 좀더 자세히 확인할 수 있습니다.

https://msdn2.microsoft.com/en-us/library/aa380545.aspx.

Windows vista 부터 통지 패키지가 제거되었으며 대부분의 기능은 SCM event 통지가 대신하게 됩니다. 추가 정보는 다음을 통해 확인할 수 있습니다.

https://technet2.microsoft.com/WindowsVista/en/library/6ec4ec6d-6b84-44c9-b3af-116589a42b861033.mspx?mfr=true

어떻게 service 를 만들어야 하는지는 아래 정보를 확인하시면 됩니다.

https://msdn2.microsoft.com/en-us/library/ms685969.aspx

그러나 이런 통지들은 비동기적으로 실행되는 것으로 Winlogon 의 상태를 정확하게 나타내 주지는 않습니다.

Winlogon 의 Log 기록

Winlogon 의 Log 기록은Checked build 의 Winlogon 을 사용하고 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon 레지스트리 키에 값을 설정해야 합니다.

DebugFlags(REG_SZ). 기록하고 싶은 event 에 대한 설정으로 콤마를 구분 자로 사용한다. Error, Warning, Trace, Init, Timeout, Sas, State, MPR, CoolSwitch, Profile, DebugLsa, DebugSpm, DebugMpr, DebugGo, Migrate, DebugServices, Setup, SC, Notify, Job 을 설정할 수 있습니다.

LogFile(REG_SZ). Log 파일이 어디에 저장될지 설정하는 것으로 이 값을 설정하지 않으면 Debug port 로 Log 가 출력됩니다..

이 Option 은 win.ini 파일에 설정할 수 있으며 아래 문서를 통해 Winlogon 에 다른 옵션을 설정하는 방법을 확인할 수 있습니다.

https://support.microsoft.com/default.aspx?scid=kb;EN-US;232575

https://support.microsoft.com/default.aspx?scid=kb;EN-US;221833

Checked build 는 MSDN 을 통해 다운 받을 수 있으며 System 이 사용하고 있는 Service pack 과 동일한 Version 을 사용해야 합니다.

Winlogon 의 Log를 확인할 때 ExitWindowsEx 가 호출되는 3가지 중요 포인트가 있습니다. 아래 로그는 “shutdown –r –f –t” 를 사용하여 종료가 되고 있는 상태 입니다.

 

328.372> Winlogon-Trace: Doing remote-initiated (Administrator) Reboot=true, Force=true

328.372> Winlogon-Trace: Starting user thread for Logoff, flags = 3807

328.764> Winlogon-Trace: Doing immediate shutdown, LastGinaRet = b, Flags = 3804

328.764> Winlogon-Trace: Calling ExitWindowsEx(0x3804, 0)

.

.

.

328.1528> Winlogon-Trace: ExitWindowsEx called to shut down COM processes

.

.

.

328.332> Winlogon-Trace: Starting shutdown

328.332> Winlogon-Trace: Starting user thread for Logoff, flags = 7

.

.

.

328.284> Winlogon-Trace: Calling ExitWindowsEx(0x7, 0)

 

종료가 진행 중이라면 어떤 종료 상태에 있는지 위의 로그를 사용하여 확인할 수 있습니다.

디버깅

Winlogon 을 디버깅 하기 위해 먼저 debugging tools for Windows (https://www.microsoft.com/whdc/devtools/debugging/default.mspx) 를 설치 해야 합니다. 이 곳에는 Winlogon을 디버깅 하기 위해 설정해야 하는 내용이 포함되어 있고 만약 Winlogon 을 위해 디버거가 올바르게 설정되어 있지 않다면 시스템은 사용할 수 없는 상태에 빠질 것이고 재 설치를 해야 할 것 입니다.

SAS 와 Logon prompt 가 보여지고 Winlogon 자체에 명백히 문제가 있을 경우에만 Winlogon 을 디버그 해야 합니다. Winlogon 을 디버깅 할 때 주 process thread 를 확인해야 합니다. 만약 종료(또는 다른 Winlogon 동작) 이 멈추었다면 이 Thread 는 어떤 것이 종료를 막고 있는지 보여줄 것 입니다. 그리고 아래의 Flag 들이 종료 처리 과정이 어떻게 진행되고 있는지 보여줄 것 입니다.

 

0105fe8c winlogon!g_fReadyForShutdown

0105fdf8 winlogon!ShutdownHasBegun

01062b3c winlogon!ShutdownInProgress

01062b30 winlogon!ShutdownTime

 

앞으로 이 변수들이 어떻게 변경되는지 보여드릴 것 입니다.

종료 Event 순서

종료 과정을 디버깅 하고 있다면 종료 과정의 event 순서를 이해해야 합니다.

RPC 호출

Local 에 있거나 remote 에 있는 Application 에서 InitiateSystemShutdownEx (https://msdn2.microsoft.com/en-us/library/aa376874.aspx) 를 사용해서 종료를 Windows 에 알립니다. 이 API 는 initShutdown named pipe 를 사용하여 RPC 호출을 만듭니다. 원격 연결되어 있다면 RPC call 은 아래 network track 와 같이 확인할 수 있습니다.

 

11:08:40.025 CLIENT SERVER SMB SMB: C; Nt Create Andx, FileName = \InitShutdown

11:08:40.027 CLIENT SERVER MSRPC MSRPC: c/o Request: unknown Call=0x1 Opnum=0x2 Context=0x0 Hint=0x20

 

Local 연결에서는 network 을 이용할 필요 없지만 같은 방식으로 처리 합니다.

Server 에서는 winlogon.exe 프로세스의 BaseInitiateShutdownEx함수에서 처리됩니다. RPC 는 아래와 같이 처리 됩니다.

1. client 의 권한 level 을 확인 합니다. Client 권한 level 확인이 실패 한다면 ERROR_ACCESS_DENIED (0x05) 에러가 발생합니다.

2. RPC client 가 보낸 종료 명령을 검사 합니다. 명령이 올바르지 않다면 ERROR_INVALID_PARAMETER (0X57) 에러가 발생합니다.

3. Winlogon flag 인 ShutdownInProgress, ShutdownHasBegun, g_fReadyForShutdown 와 terminal desktop 의 현재 상태가 종료가 가능한지 확인 합니다. Force flag 가 설정되어 있지 않고 desktop 이 잠겨 있다면 종료를 시작하지 않습니다. ShutdownInProgress 또는 ShutdownHasBegun flag 가 설정되어 있다면 ERROR_SHUTDOWN_IN_PROGRESS (0x45B)가 발생하고 g_fReadyForShutdown 가 설정되어 있지 않다면 ERROR_NOT_READY (0x15) 에러가 발생합니다.

Debugger output:

dd winlogon!g_fReadyForShutdown l 1

0105fe8c 00000001

dd winlogon!ShutdownInProgress l 1

01062b3c 00000000

dd winlogon!ShutdownHasBegun l 1

0105fdf8 00000000

 

4. Winlogon 은 ShutdownTime 을 포함해서 종료에 필요한 data 를 초기화 하고 이 값은 아래와 같이 확인할 수 있습니다.

Debugger Output:

dq winlogon!ShutdownTime l 1

01062b30 01c7a859`baee0060

.formats 01c7a859`baee0060

Evaluate expression:

Time: Wed Jun 6 12:42:54.506 2007 (GMT-4)

 

5. 종료를 진행할 수 있다고 판단되면 Winlogon 은 ShutdownInProgress flag 를 설정합니다. 이 Flag 가 설정되어 있다면 3번째 단계에서 실패하였을 것 입니다.

6. 감사가 설정되어 있다면 종료 감사 event 를 기록 합니다. 이후 단계에서 종료가 실패 하더라도 종료 감사 event 는 기록되게 됩니다.

7. 종료를 수행하기 위해 다른 Thread 를 생성 합니다. 이 시점에 RPC 작업 Thread 는 return 됩니다.

이 시점까지 server 에는 종료가 진행되고 있다고 보여지지 않습니다. RPC 작업 Thread 가 어떤 문제로 인해 실패 한다면 client application 은 종료가 실패한 것을 알 수 있습니다. RPC 작업 Thread 가 정상적으로 작업을 수행하고 다른 Thread 가 이후 작업을 처리 한다면 Client application 은 ERROR_SUCCESS(0) 을 return code 로 받습니다. Client application 에서는 이후 작업에 대한 실패를 통지 받을 수 없습니다.

작업 Thread

RPC 작업 Thread 는 LogoffThreadProc Thread 를 처리 합니다. 종료 명령에 제한시간이 설정되어 있지 않다면 ShutdownInProgress Flag 를 0으로 설정하고 ShutdownHasBegun 을 1로 설정합니다. Winlogon 로그에서 “Doing immediate shutdown” 메시지를 통해 확인할 수 있으며 이 시점에는 종료가 진행중인 것을 눈으로 확인할 수 없습니다.

종료 명령에 제한시간을 0 보다 큰 값으로 설정한다면 아래 화면과 같이 종료 시간까지 카운트 다운하는 화면을 볼 수 있습니다.

clip_image001

대화 창이 끝날 때 AbortShutdown Flag 를 확인 하여 설정되어 있다면 종료를 중단 합니다. ShutdownInProgressFlag 가 해제되어 있고 ShutdownHasBegun Flag 가 설정되어 있는 상태에서 System process 가 종료를 시작 하였다면 종료는 즉시 실행됩니다. Flag 는 AbortSystemShutdown (https://msdn2.microsoft.com/en-us/library/aa376630.aspx) API 호출로 설정될 수 있습니다.

Flag 들이 갱신된 후 ExitWindowsInProgress Flag 는 1로 설정됩니다. 그리고 ExitWindowsEx (https://msdn2.microsoft.com/en-us/library/ms893047.aspx) 를 호출 합니다. Winlogon log 에서 “Calling ExitWindowsEx” 로 시작하는 내용을 확인할 수 있습니다.

Debugger Output:

dd winlogon!ExitWindowsInProgress l 1

0105fd84 00000001

Win32 API 인 ExitWindowsEx 는 RPC call 을 만들어 CSRSS.EXE 에 전달합니다. CSRSS 는 WM_QUERYENDSESSION 메시지를 만들어 동기적으로 모든 Window 에 전달하고 application 은 메시지를 받아 종료가 가능함을 알립니다. CSRSS 는 WM_ENDSESSION 메시지를 전달 하고 이후 process 는 종료 됩니다. Application 이 종료 될 수 없음을 알리면 CSRSS 는 종료 작업을 멈추고 사용자가 application 을 종료하기를 기다립니다. ExitWindowsEx 는 ERROR_OPERATION_ABORTED (0x3E3) 을 return 할 것이고 Winlogon flag 는 reset 되어 새로운 종료 요청이 처리될 수 있도록 합니다.

종료과정을 막은 Application 은 desktop 에 window 가 나타나는 것으로 확인할 수 있습니다. 어떤 Application 이 종료를 막았는지를 확인하기 위해 CSRSS 를 live debug 할 수 있습니다. winsrv!ConsoleClientShutdown 또는 winsrv!UserClientShutdown 에서 return 된 값인 3은 Application 이 종료를 막았다는 것을 알려 줍니다.

Debugger Output:

0:002> pc

eax=00000000 ebx=7c81a3ab ecx=7ffdb000 edx=75a58ca0 esi=75a58ca0 edi=00164600

eip=75a564de esp=0052fe40 ebp=0052fe68 iopl=0 nv up ei pl zr na po nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246

CSRSRV!CsrShutdownProcesses+7e:

75a564de ff5740 call dword ptr [edi+0x40]{winsrv!UserClientShutdown (75a9db1f)} ds:0023:00164640=75a9db1f

 

; 호출된 이후로 한 Step 진행

0:002> p

eax=00000003 ebx=7c81a3ab ecx=7ffdb000 edx=75a58ca0 esi=75a58ca0 edi=00164600

eip=75a564e1 esp=0052fe4c ebp=0052fe68 iopl=0 nv up ei pl zr na po nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246

CSRSRV!CsrShutdownProcesses+81:

75a564e1 8bf8 mov edi,eax

 

; 첫번째 파라미터 는 Process ID 를 가지는 구조체

75a564d5 ff75f4 push dword ptr [ebp-0xc]

75a564d8 ff750c push dword ptr [ebp+0xc]

75a564db ff75f8 push dword ptr [ebp-0x8]

75a564de ff5740 call dword ptr [edi+0x40]

 

; 구조체에서 Pointer 를 확인

0:002> dd ebp-8 l 1

dd ebp-8 l 1

0052fe60 0018a530

 

; 첫 번째 DWORD 값이 Process ID, 두 번째DWORD 값이 Thread ID

0:002> dd 0018a530 l 1

dd 0018a530

0018a530 0000066c

 

; 모든 process 의 정보를 확인하기 위해 kernel mode 로 전환

0:002> .breakin

.breakin

Break instruction exception - code 80000003 (first chance)

nt!RtlpBreakWithStatusInstruction:

8081db0e cc int 3

 

; process ID 를 통해 process object 확인

kd> !process 0000066c 0

Searching for Process with Cid == 66c

PROCESS ff62a638 SessionId: 0 Cid: 066c Peb: 7ffdf000 ParentCid: 0108

DirBase: 0390d000 ObjectTable: e1658e38 HandleCount: 51.

Image: test.exe

 

Console (text 기반) application 들은 비 동기적으로 CTRL_SHUTDOWN_EVENT통지를 받게 됩니다. 이것은 ExitWindowEx 가 application 이 이 통지를 어떻게 처리하는지 상관 없이 실행된다는 것입니다.

Services.exe (그리고 모든 windows service) 는 console application 이고 CSRSS로부터 통지를 받습니다. Services.exe 는 ScShutdownNotificationRoutine 제어함수를 등록합니다. ScShutdownAllServices 는 종료 시에 호출 됩니다. 이 함수는 종료되지 않았거나 종료되고 있는 모든 windows service 를 확인하여 SERVICE_CONTROL_SHUTDOWN 통지를 보냅니다. 각 service 는 20 초를 기본 종료 시간으로 가지나 service 가 보다 많은 시간이 필요할 경우 SetServiceStatus 를 호출하여 시간을 연장하여 시간제한을 늘릴 수 있습니다. 그러나 Services 가 이 비 동기 메시지를 CSRSS.EXE로 부터 받았을 경우 종료를 막지 않을 것 입니다.

ExitWindowsEx가 return 된 후 제어는 Winlogon으로 넘어가고 ExitWindowsInProgress flag 는 초기화 되고 LogoffThreadProc 는 종료됩니다.

메인루프

Winlogon 의 상태는 MainLoop 로 불리는 process 의 main thread 에 의해 제어됩니다. LogoffThreadProc 는 ShutdownHasBegan flag 를 설정하고 ExitWindowsEx 를 호출하면 MainLoop 는 상태 변화를 인지하고 종료 코드를 실행합니다. MainLoop 가 사용자와의 상호작용을 책임지고 이 단계는 처음으로 사용자가 Winlogon 으로부터 종료를 인식할 수 있는 단계 입니다.

MainLoop 가 Winlogon 의 상태가 종료로 변경되는 것을 인식하면 아래 동작을 수행 합니다.

1. 종료되고 있음을 shell 에 알림. Explorer shell 이 사라지게 됨

2. 사용자의 profile 에서 update 가 있는지 확인

3. Logoff 통지 event 를 보냄

4. Network connection 삭제

5. Logoff 효과음 발생

6. System 종료 효과음 발생

7. KillComProcesses thread 생성. 이 Thread 는 ExitWindowsEx 를 호출하고 15분까지 완료되기를 기다림

8. 사용자의 profile 을 unload 하고 저장

9. RAS connection 삭제

10. 종료 통지 event 보냄

11. Windows file protection 종료

12. 다른 LogoffThreadProc Thread 생성하여 ExitWindowsEx 다시 호출

13. GINA 에있는 종료 함수 호출. 종료 대화창 표시됨

14. System process 가 종료 되기를 대기. 만약 이 곳에서 멈춘다면 System, smss.exe, csrss.exe 를 확인해 보아야 합니다. 이 중 하나에서 종료가 멈춰 있을 것 입니다.

15. 종료

Winlogon 은 native API 인 NtShutdownSystem 을 15단계에서 사용합니다. 이 단계 이후 시스템이 멈춘다면 Winlogon 의 main thread 가 이 함수를 호출하고 멈추어 있고 system process 의 device driver 에서 문제가 있고 이 함수가 return 되지 않을 것 입니다.