쫑 ` s 개발 이야기 : : ' WDM / Architecture ' 카테고리 의 글 목록 
 쫑 ` s 개발 이야기 
 Total | 78 , 852 
 ' WDM / Architecture ' 에 해당 되 는 글 12 건 
 2016 . 01 . 13 Dynamic - Link Library Best Practices 
 2014 . 04 . 23 HeartBleed 이슈 및 대처 방법 
 2014 . 03 . 31 윈도우 커널 레벨 에서 의 인터럽트 레벨 
 2013 . 03 . 24 Windows 8 x 86 _ EPROCESS 구 조체 & offset 
 2011 . 12 . 14 OB Filtering - OB _ PRE _ CREATE _ HANDLE _ INFORMATION structure 
 2009 . 07 . 15 The AddDevice Routine 
 2009 . 06 . 08 System Boot 
 2009 . 05 . 20 Filter Manager Concepts 
 2009 . 05 . 11 Windows Network Architecture and the OSI Model 
 2009 . 04 . 21 Intel 80386 Protected Mode 
 WDM / Architecture 2016 . 01 . 13 10 : 20 
 코드 를 작성 하 다가 복잡 한 모듈 을 작성 하 게 되 는 경우 아래 현상 을 겪 을 수 있 다 . 
 동기 화 를 처리 하 기 위한 CriticalSection 과 마이크 로 소프트 SDK API 안 에서 내부 적 으로 CriticalSection 이 상호 충돌 이 생길 수 있 다 . 
 최근 Loadlibrary 와 타 기타 CriticalSection 과 의 충돌 이 있 어 사례 를 소개 하 고자 한다 . 
 특정 프로세스 가 행 이 걸렸 을 경우 덤프 툴 에 의해 덤프 를 뜬 후 디버깅 을 해 보 았 다 . 
 00000000 ` 04 e 0 f 038 00000000 ` 74 d 22 bf 1 : 00000000 ` 00000000 00000000 ` 00000000 00000000 ` 00000000 00000000 ` 00000000 : wow 64 cpu ! CpupSyscallStub + 0 x 9 
 00000000 ` 04 e 0 f 040 00000000 ` 74 d 9 d 132 : 00000000 ` 00000000 00000000 ` 74 d 21920 00000000 ` 00000000 00000000 ` 00000000 : wow 64 cpu ! Thunk 0 ArgReloadState + 0 x 23 
 00000000 ` 04 e 0 f 100 00000000 ` 74 d 9 c 54 b : 00000000 ` 00000000 00000000 ` 00000000 00000000 ` 00000000 00000000 ` 00000000 : wow 64 ! RunCpuSimulation + 0 xa 
 00000000 ` 04 e 0 f 150 00000000 ` 772 fd 447 : 00000000 ` 00000000 00000000 ` 7 efdf 000 00000000 ` 7 ef 6 d 000 00000000 ` 00000000 : wow 64 ! Wow 64 LdrpInitialize + 0 x 42 b 
 00000000 ` 04 e 0 f 6 a 0 00000000 ` 772 ac 34 e : 00000000 ` 04 e 0 f 760 00000000 ` 00000000 00000000 ` 7 efdf 000 00000000 ` 00000000 : ntdll ! ? ? : : FNODOBFM : :` string '+ 0 x 29134 
 00000000 ` 04 e 0 f 710 00000000 ` 00000000 : 00000000 ` 00000000 00000000 ` 00000000 00000000 ` 00000000 00000000 ` 00000000 : ntdll ! LdrInitializeThunk + 0 xe 
 역시 64 비트 운영 체제 라서 32 비트 프로세스 에 대한 자세 한 정보 를 얻 을 수 없 다 . 
 이때 는 32 비트 모드 로 변환 시켜 주 어야 한다 . 
 변환 후 콜 스택 을 확인 해 보 자 
 0034 d 5 f 0 77498 e 44 000001 b 8 00000000 00000000 ntdll _ 77460000 ! ZwWaitForSingleObject + 0 x 15 ( FPO : [ 3 , 0 , 0 ] ) 
 0034 d 654 77498 d 28 00000000 00000000 00000001 ntdll _ 77460000 ! RtlpWaitOnCriticalSection + 0 x 13 e ( FPO : [ Non - Fpo ] ) 
 0034 d 67 c 7749 c 401 775620 c 0 7763 d 145 00000000 ntdll _ 77460000 ! RtlEnterCriticalSection + 0 x 150 ( FPO : [ Non - Fpo ] ) 
 0034 d 7 e 8 7749 c 558 0034 d 84 c 0034 d 814 00000000 ntdll _ 77460000 ! LdrpLoadDll + 0 x 287 ( FPO : [ Non - Fpo ] ) 
 0034 d 820 76942 c 95 0034 d 814 0034 d 864 0034 d 84 c ntdll _ 77460000 ! LdrLoadDll + 0 xaa ( FPO : [ Non - Fpo ] ) 
 0034 d 85 c 769 a 4924 00000000 00000000 0167 d 514 KERNELBASE ! LoadLibraryExW + 0 x 1 f 1 ( FPO : [ Non - Fpo ] ) 
 *** ERROR : Symbol file could not be found . Defaulted to export symbols for k _ mcrypto . dll - 
 0034 d 870 7456447 a 0034 d 888 7458 ffe 8 0034 dffc kernel 32 ! LoadLibraryW + 0 x 11 ( FPO : [ Non - Fpo ] ) 
 0034 ddf 4 00300063 00320035 00370035 00360034 xxxx ! Java _ xxxxx + 0 xa 9 a 
 0034 ddf 8 00320035 00370035 00360034 00360037 0 x 300063 
 0034 ddfc 00370035 00360034 00360037 00320034 0 x 320035 
 0034 de 00 00360034 00360037 00320034 00380036 0 x 370035 
 0034 de 04 00360037 00320034 00380036 00380038 0 x 360034 
 0034 de 08 00320034 00380036 00380038 00380046 0 x 360037 
 0034 de 0 c 00380036 00380038 00380046 00330032 0 x 320034 
 0034 de 10 00380038 00380046 00330032 00370042 0 x 380036 
 0034 de 14 00380046 00330032 00370042 00430031 0 x 380038 
 0034 de 18 00330032 00370042 00430031 00380032 0 x 380046 
 0034 de 1 c 00370042 00430031 00380032 00760044 0 x 330032 
 0034 de 20 00430031 00380032 00760044 00640074 0 x 370042 
 0034 e 5 bc 00000000 00000000 00000000 00000000 yyyyyyyy ! xxxxx : : xxxxxxxLog + 0 x 13 a 1 
 x 모듈 과 y 모듈 이 상호 충돌 이 되 는 것 을 확인 할 수 있 으며 , LoadLibraryEx 함수 호출 되 고 CriticalSection 진입 후 Wait 에서 대기 하 고 있 다 . 
 진입 된 CriticalSection 객체 를 조사 해 보 자 
 0 : 000 : x 86 > ! cs 775620 c 0 
 0 x 2228 Thread 에서 잠겨 있 음 이 확인 된다 . 
 해당 객체 의 콜 스택 정보 를 보 기 위해 요청 을 하 였으나 해당 Thread 는 이미 종료 된 Thread 였 다 . 
 0 : 000 : x 86 > ~ 
 . 0 Id : 10 a 8 . 1164 Suspend : 0 Teb : 7 efdb 000 Unfrozen 
 1 Id : 10 a 8 . 17 f 8 Suspend : 0 Teb : 7 efd 8000 Unfrozen 
 2 Id : 10 a 8 . 1404 Suspend : 0 Teb : 7 ef 9 d 000 Unfrozen 
 3 Id : 10 a 8 . 1408 Suspend : 0 Teb : 7 ef 9 a 000 Unfrozen 
 4 Id : 10 a 8 . 17 b 8 Suspend : 0 Teb : 7 ef 97000 Unfrozen 
 5 Id : 10 a 8 . 1728 Suspend : 0 Teb : 7 ef 94000 Unfrozen 
 6 Id : 10 a 8 . 178 c Suspend : 0 Teb : 7 ef 91000 Unfrozen 
 7 Id : 10 a 8 . 1778 Suspend : 0 Teb : 7 ef 8 e 000 Unfrozen 
 8 Id : 10 a 8 . 177 c Suspend : 0 Teb : 7 ef 8 b 000 Unfrozen 
 9 Id : 10 a 8 . 1784 Suspend : 0 Teb : 7 ef 85000 Unfrozen 
 10 Id : 10 a 8 . 1770 Suspend : 0 Teb : 7 ef 82000 Unfrozen 
 11 Id : 10 a 8 . 1774 Suspend : 0 Teb : 7 ef 7 f 000 Unfrozen 
 12 Id : 10 a 8 . 145 c Suspend : 0 Teb : 7 ef 7 c 000 Unfrozen 
 13 Id : 10 a 8 . cc 0 Suspend : 0 Teb : 7 ef 88000 Unfrozen 
 14 Id : 10 a 8 . 1844 Suspend : 0 Teb : 7 ef 79000 Unfrozen 
 15 Id : 10 a 8 . 1 d 58 Suspend : 0 Teb : 7 ef 76000 Unfrozen 
 16 Id : 10 a 8 . 2318 Suspend : 0 Teb : 7 ef 73000 Unfrozen 
 17 Id : 10 a 8 . 1838 Suspend : 0 Teb : 7 efd 5000 Unfrozen 
 18 Id : 10 a 8 . 2170 Suspend : 0 Teb : 7 ef 70000 Unfrozen 
 19 Id : 10 a 8 . 1 c 88 Suspend : 0 Teb : 7 ef 6 d 000 Unfrozen 
 위 와 같이 콜 스택 을 찾아가 서 CriticalSection 객체 를 이용 하 여 찾 을 수 도 있 지만 , 아래 옵션 을 주 게 되 면 lock 이 되 어 있 는 크리티컬 섹션 정보 를 볼 수 있 다 . 
 0 : 000 : x 86 > ! cs - l - o 
 여기 서 주의 할 점 은 Windbg 에서 의 deadlock , lock , cs 는 다른 개념 이 기 때문 에 다르 게 접근 해야 한다 . 
 해당 이슈 로 ! locks , ! deadlock 명령어 를 통해 CriticalSection 에 의해 잠겨 있 는지 확인 해 보 면 " NOT LOCKED " 나온다 . 
 해당 이슈 는 특정 Thread 에서 CriticalSection 진입 후 나오 지 않 고 쓰 래드 가 강제 종료 된 경우 이 며 , 이 때 다른 모듈 이 LoadLibrary 하 는 과정 에서 상호 충돌 하 는 문제 이 다 . 
 아래 글 은 영문 글 은 마이크 로 소프트 MSDN 에서 발췌 하 였으며 , 해당 이슈 에 대해 왜 일어나 고 복잡 한 코딩 을 하 는 경우 주의 해야 될 부분 에 대해 명시 되 어 있 다 . 
 Creating DLLs presents a number of challenges for developers . DLLs do not have system - enforced versioning . When multiple versions of a DLL exist on a system , the ease of being overwritten coupled with the lack of a versioning schema creates dependency and API conflicts . Complexity in the development environment , the loader implementation , and the DLL dependencies has created fragility in load order and application behavior . Lastly , many applications rely on DLLs and have complex sets of dependencies that must be honored for the applications to function properly . This document provides guidelines for DLL developers to help in building more robust , portable , and extensible DLLs . 
 Improper synchronization within DllMain can cause an application to deadlock or access data or code in an uninitialized DLL . Calling certain functions from within DllMain causes such problems . 
 DllMain is called while the loader - lock is held . Therefore , significant restrictions are imposed on the functions that can be called within DllMain . As such , DllMain is designed to perform minimal initialization tasks , by using a small subset of the Microsoft ® Windows ® API . You cannot call any function in DllMain that directly or indirectly tries to acquire the loader lock . Otherwise , you will introduce the possibility that your application deadlocks or crashes . An error in a DllMain implementation can jeopardize the entire process and all of its threads . 
 The following tasks are safe to perform within DllMain : 
 Initialize static data structures and members at compile time . 
 Create and initialize synchronization objects . 
 Allocate memory and initialize dynamic data structures ( avoiding the functions listed above . ) 
 Set up thread local storage ( TLS ) . 
 Open , read from , and write to files . 
 Call functions in Kernel 32 . dll ( except the functions that are listed above ) . 
 Set global pointers to NULL , putting off the initialization of dynamic members . In Microsoft Windows Vista ™, you can use the one - time initialization functions to ensure that a block of code is executed only once in a multithreaded environment . 
 Deadlocks Caused by Lock Order Inversion 
 When you are implementing code that uses multiple synchronization objects such as locks , it is vital to respect lock order . When it is necessary to acquire more than one lock at a time , you must define an explicit precedence that is called a lock hierarchy or lock order . For example , if lock A is acquired before lock B somewhere in the code , and lock B is acquired before lock C elsewhere in the code , then the lock order is A , B , C and this order should be followed throughout the code . Lock order inversion occurs when the locking order is not followed — for example , if lock B is acquired before lock A . Lock order inversion can cause deadlocks that are difficult to debug . To avoid such problems , all threads must acquire locks in the same order . 
 Figure 2 is an example that illustrates lock order inversion . Consider a DLL whose main thread contains DllMain . The library loader acquires the loader lock L and then calls into DllMain . The main thread creates synchronization objects A , B , and G to serialize access to its data structures and then tries to acquire lock G . A worker thread that has already successfully acquired lock G then calls a function such as GetModuleHandle that attempts to acquire the loader lock L . Thus , the worker thread is blocked on L and the main thread is blocked on G , resulting in a deadlock . 
 To prevent deadlocks that are caused by lock order inversion , all threads should attempt to acquire synchronization objects in the defined load order at all times . 
 Best Practices for Synchronization 
 Consider a DLL that creates worker threads as part of its initialization . Upon DLL cleanup , it is necessary to synchronize with all the worker threads to ensure that the data structures are in a consistent state and then terminate the worker threads . Today , there is no straightforward way to completely solve the problem of cleanly synchronizing and shutting down DLLs in a multithreaded environment . This section describes the current best practices for thread synchronizing during DLL shutdown . 
 Thread Synchronization in DllMain during Process Exit 
 By the time DllMain is called at process exit , all the process ’ s threads have been forcibly cleaned up and there is a chance that the address space is inconsistent . Synchronization is not required in this case . In other words , the ideal DLL _ PROCESS _ DETACH handler is empty . 
 Windows Vista ensures that core data structures ( environment variables , current directory , process heap , and so on ) are in a consistent state . However , other data structures can be corrupted , so cleaning memory is not safe . 
 Persistent state that needs to be saved must be flushed to permanent storage . 
 Thread Synchronization in DllMain for DLL _ THREAD _ DETACH during DLL Unload 
 When the DLL is unloaded , the address space is not thrown away . Therefore , the DLL is expected to perform a clean shutdown . This includes thread synchronization , open handles , persistent state , and allocated resources . 
 Thread synchronization is tricky because waiting on threads to exit in DllMain can cause a deadlock . For example , DLL A holds the loader lock . It signals thread T to exit and waits for the thread to exit . Thread T exits and the loader tries to acquire the loader lock to call into DLL A ’ s DllMain with DLL _ THREAD _ DETACH . This causes a deadlock . To minimize the risk of a deadlock : 
 DLL A gets a DLL _ THREAD _ DETACH message in its DllMain and sets an event for thread T , signaling it to exit . 
 Thread T finishes its current task , brings itself to a consistent state , signals DLL A , and waits infinitely . Note that the consistency - checking routines should follow the same restrictions as DllMain to avoid deadlocking . 
 DLL A terminates T , knowing that it is in a consistent state . 
 If a DLL is unloaded after all its threads have been created , but before they begin executing , the threads may crash . If the DLL created threads in its DllMain as part of its initialization , some threads may not have finished initialization and their DLL _ THREAD _ ATTACH message is still waiting to be delivered to the DLL . In this situation , if the DLL is unloaded , it will begin terminating threads . However , some threads may be blocked behind the loader lock . Their DLL _ THREAD _ ATTACH messages are processed after the DLL has been unmapped , causing the process to crash . 
 The following are recommended guidelines : 
 Use Application Verifier to catch the most common errors in DllMain . 
 If using a private lock inside DllMain , define a locking hierarchy and use it consistently . The loader lock must be at the bottom of this hierarchy . 
 Verify that no calls depend on another DLL that may not have been fully loaded yet . 
 Perform simple initializations statically at compile time , rather than in DllMain . 
 Defer any calls in DllMain that can wait until later . 
 Defer initialization tasks that can wait until later . Certain error conditions must be detected early so that the application can handle errors gracefully . However , there are tradeoffs between this early detection and the loss of robustness that can result from it . Deferring initialization is often best . 
 출처 : http : / / greenfishblog . tistory . com / 21 
 DLL 을 만드 는 일 은 개발자 에게 많 은 도전 을 가져다 줍니다 . DLL 은 시스템 을 통한 강제 적 인 버전 관리 가 이뤄지지 않 습니다 . 즉 , 여러 개 의 DLL 이 한 시스템 에 있 을 때 , 이러 한 버전 관리 체크 의 부족 으로 인한 overwrite 는 의존 성 과 API 충돌 을 야기 합니다 . 개발 환경 , 로더 구현 그리고 의존 성 의 복잡 성 은 로드 순서 와 응용 프로그램 행위 에 취약 성 을 만듭니다 . 그래도 많 은 응용 프로그램 들 은 복잡 한 의존 성 을 가지 는 DLL 에 의지 하 고 있 습니다 . 이 문서 는 DLL 개발자 들 을 위해 가이드라인 을 제공 하 여 견고 하 고 이 식성 있 으며 확장 성 있 는 DLL 로 만드 는데 도움 을 줄 것 입니다 . 
 ■ 3 개 의 주요 한 DLL 컴포넌트 개발 모델 은 다음 과 같 습니다 . 
 DLL 은 가끔 복잡 한 내부 의 존성 ( interdependency ) 을 자 니 는데 , 이 는 그 들 이 로드 되 어야 하 는 순서 를 정의 합니다 . Library Loader 는 효과 적 으로 이러 한 의존 성 을 분석 하 고 , 정확 한 로드 순서 를 계산 한 뒤 그 순서 대로 로드 를 합니다 . 
 DLLMain entry - point function 
 이 함수 는 Loader 에 의해 호출 되 며 , 그 시점 은 DLL 의 Load 혹은 Unload 일 때 입니다 . Loader 는 한 시점 에 단 하나 의 DLLMain 만 호출 하 도록 연속 으로 호출 합니다 . 더 많 은 정보 
 Loader Lock 
 Loader 가 순서 대로 로드 할 때 사용 되 는 프로세스 단위 의 동기 화 객체 입니다 . 프로세스 단위 의 Library Loader Data 를 반드시 읽 거나 써야 하 는 함수 는 반드시 이 Lock 을 획득 해야 합니다 . 물론 이러 한 operation 을 수행 하 기 전 에 이뤄져야 합니다 . Loader Lock 은 recursive 이 며 , 이 는 같 은 쓰레드 에서 다시 Lock 의 획득 이 가능 함 을 의미 합니다 . 
 그림 1 . DLL 로드 시 어떤 일 이 이뤄 지 는가 ? 
 DLLMain 에서 의 부적합 한 동기 화 시도 는 응용 프로그램 에게 deadlock 혹은 초기 화 되 지 않 은 DLL 의 data 와 code 의 접근 을 야기 하 게 됩니다 . DLLMain 에서 의 특정 함수 호출 은 이러 한 문제 를 잃 으 킵 니다 . 
 일반 적 인 최고 의 습관 
 DLLMain 은 Loader Lock 이 획득 되 었 을 때 호출 됩니다 . 따라서 , DLLMain 내부 에서 의 호출 은 중요 한 제약 이 강요 됩니다 . DLLMain 은 최소 의 초기 화 작업 을 수행 하 도록 디자인 되 었 는데 , 이 는 Windows API 의 몇몇 함수 군 호출 에 의해서 입니다 . DLLMain 에서 직접 적 이 든 간접 적 이 든 Loader Lock 획득 을 시도 하 는 어떤 함수 도 호출 할 수 없 습니다 . 다시 말 해 , 이 경우 가 발생 하 면 당신 은 deadlock 혹은 crash 를 경험 하 게 됩니다 . DLLMain 구현 에서 의 에러 는 해당 프로세스 와 그 내부 의 쓰레드 를 위험 에 빠트리 게 됩니다 . 
 이상 적 인 DLLMain 은 " 그냥 비우 는 것 " 입니다 . 그러나 , 많 은 응용 프로그램 의 복잡도 를 고려 할 때 이 는 너무 한 제약 이 됩니다 . DLLMain 을 다루 는 좋 은 방법 은 많 은 초기 화 과정 을 가능 한 뒤 로 미뤄 라라 는 것 입니다 . 이러 한 미뤄진 초기 화 는 응용 프로그램 을 더욱더 견고히 해 주 는데 , 그 이유 는 Loader Lock 가 획득 된 동안 의 초기 화 가 이뤄지지 않 았 기 때문 입니다 . 역시 이러 한 방법 은 Windows API 의 사용 에 도 훨씬 많 은 안정 성 을 제공 합니다 . 
 몇몇 초기 화 작업 은 뒤 로 미룰 순 없 을 것 입니다 . 예 를 들 어 , 설정 파일 에 의존 성 이 있 는 DLL 이 있 는데 , 해당 파일 이 좋 지 않 거나 쓰레기 내용 이 포함 되 었 을 때 그 DLL 의 Load 가 실패 되 야 하 는 경우 가 있 을 것 입니다 . 이런 종류 의 초기 화 방식 은 , 다른 작업 의 자원 낭비 를 하 느니 DLL 이 그 행위 를 시도 해 보 고 빨리 실패 하 는 것 이 좋 다고 개념 이 라 보여집니다 . 
 ■ 다음 과 같 은 작업 을 절대로 DLLMain 에서 수행 해서 는 안 됩니다 . 
 LoadLibrary 혹은 LibraryEx 의 직접 적 혹은 간접 적 호출 . 이 는 deadlock 혹은 crash 를 유발 한다 . 
 다른 쓰레드 와 의 동기 화 시도 . 이 는 deadlock 을 유발 한다 . 
 Loader Lock 을 획득 하 기 위해 기다리 는 코드 가 획득 한 다른 사설 동기 화 객 제 를 획득 하 려고 하 는 시도 . 이 는 deadlock 을 유발 한다 . 
 CoInitializeEx 사용 에 의한 COM 쓰레드 초기 화 . 특정 상황 이 되 면 이 함수 는 LoadLibrary 를 호출 한다 . 
 레지스트리 함수 군 의 호출 . 이 함수 는 Advapi 32 . dll 에 구현 되 어 있 는데 , 만약 AdvApi 32 . dll 이 아직 당신 DLL 에서 초기 화 되 지 않 았 다면 , 그 DLL 은 메모리 를 초기 화 해제 하 며 , crash 를 유발 한다 . 
 CreateProces 호출 . 이 는 다른 DLL 을 Load 할 수 있 다 . 
 ExitThread 호출 . DLL Detach 과정 에서 Exit 가 진행 중 인 Thread 는 Loader Lock 을 다시 획득 하 려고 하 는 시도 가 발생 하 여 deadlock 혹은 crash 가 발생 할 수 있 다 . 
 CreateThread 호출 . 다른 Thread 와 동기 화 작업 을 하 지 않 는다면 , 생성 중 인 Thread 가 할 수 있 는데 , 이 는 위험 할 수 있 다 . 
 Named Pipe 혹은 다른 Named Object 의 생성 ( Windows 2000 만 해당 ) . Windows 200 에서 는 Named Object 는 Terminal Service DLL 에 의해 제공 되 는데 , 만일 , 이 DLL 이 초기 화 되 지 않 았 다면 , DLL 을 로드 하 게 되 어 crash 가 유발 될 수 있 다 . 
 메모리 관리 CRT 함수 호출 . 만약 CRT DLL 이 초기 화 되 지 않 았 다면 , crash 가 유발 된다 . 
 User 32 . dll 혹은 Gdi 32 . dll 함수 호출 . 몇몇 함수 들 은 아직 초기 화 되 지 않 은 DLL 을 로드 한다 . 
 관리 코드 의 사용 
 ■ DLLMain 내 에서 안전 한 작업 은 다음 과 같 습니다 . 
 compile time 의 static data 의 초기 화 
 동기 화 객체 의 생 성 과 초기 화 
 메모리 할당 과 dynamic data 의 초기 화 ( 위 금지 함수 이외 ) 
 Thread local storage ( TLS ) 초기 화 
 File 의 열기 / 읽 기 / 쓰 기 
 kernel 32 . dll 함수 의 호출 ( 위 금지 함수 제외 ) 
 전역 포인터 변수 를 NULL 로 할당 
 Lock 순서 의 역 ( Lock order inversion ) 에 의한 deadlock 
 Lock 과 같 은 다중 동기 화 객체 사용 을 구현 할 때 , Lock 순서 를 따르 는 것 은 굉장히 중요 합니다 . 어느 시점 에서 한 개 이상 의 Lock 을 획득 하 는 것 이 필요 할 때 반드시 Lock hierachy 혹은 Lock 순서 라 불리 는 명시 적 인 순서 를 정의 해야 합니다 . 예 를 들 어 , Lock A 가 Lock B 이전 에 획득 되 었 고 , Lock C 이전 에 Lock B 가 획득 되 었 다면 Lock 순서 는 A , B , C 가 되 고 , 이 순서 는 코드 에서 지켜줘야 됩니다 . 만약 Lock 순서 가 역 으로 되 는 경우 가 발생 했 다면 , 예 를 들 어 , Lock A 를 획득 하 기 전 에 Lock B 가 획득 되 었 을 경우 , 이 는 Lock 순서 의 역 에 의한 deadlock 이 발생 하 게 됩니다 . 이렇게 발생 한 deadlock 은 디버깅 하 기 힘든 면 이 있 습니다 . 이것 을 방지 하 기 위해 모든 쓰레드 에서 는 같 은 순서 대로 Lock 을 획득 해야 만 합니다 . 
 Loader 는 이미 획득 한 Loader Lock 으로 DLLMain 을 호출 한다는 사실 은 굉장히 중요 합니다 . 그래서 Loader Lock 은 Locking hierachy 의 가장 높 은 우선순위 가 되 어야 합니다 . 그 와 마찬가지 로 적합 한 동기 화 를 위해 요구 된 Lock 을 획득 하 해야 하 는 것 도 알 아야 합니다 . 물론 hierachy 에 정의 된 모든 단일 Lock 을 획득 해야 할 필요 는 없 습니다 . 예 를 들 어 , A 와 C 를 적합 한 동기 화 를 위해 획득 하 였 다면 , C 를 획득 하 기 이전 에 A 를 획득 해야 하 며 , B 를 획득 할 필요 는 없 습니다 . 더 나아가 설명 하 자면 , 프로그램 코드 에서 는 Loader Lock 을 명시 적 으로 획득 할 수 없 습니다 . 만약 사 적 인 Lock 을 획득 한 상황 에서 Loader Lock 을 간접 적 으로 획득 하 려는 : : GetModuleFileName ( ...) 과 같 은 API 를 호출 해야 한다면 , 사설 Lock 을 획득 하 기 이전 에 : : GetModuleFileName ( ...) 을 획득 해야 만 합니다 . 이 는 Load 순서 를 따르 게 하 기 위함입니다 . 
 그림 2 . Lock 순서 의 역 에 의한 deadlock 
 그림 2 . 는 이러 한 Lock 순서 의 역 을 보여 주 고 있 습니다 . DLLMain 을 포함 하 는 Main 쓰레드 를 가지 는 DLL 을 생각 해 보 십시요 . Library Loader 는 Lock L 을 획득 했으며 , DLLMain 을 호출 하 려고 합니다 . Main 쓰레드 에서 는 동기 화 객체 인 A , B 그리고 공유 데이터 를 접근 하 기 위해 필요 한 G 를 생성 하 고 G 를 획득 하 기 위해 시도 하 려고 합니다 . 그 와 별도 로 , Worker 쓰레드 에서 는 이미 G 를 획득 한 상황 이 며 : : GetModuleHandle ( ...) 를 호출 하 여 Loader Lock 인 L 을 획득 하 려고 시도 할 것 입니다 . 그러면 , Worker 쓰레드 는 L 에 의해 Block 되 며 , Main 쓰레드 는 G 에 의해 Block 되 며 , 이 로 인해 deadlock 이 발생 하 게 됩니다 . 
 이러 한 상황 을 방지 하 기 위해서 는 , 모든 쓰레드 에서 는 항상 순서 에 맞 게 동기 화 객체 를 획득 하 도록 시도 해야 합니다 . 
 동기 화 를 위한 최고 의 습관 
 초기 화 의 한 부분 으로 DLL 이 Worker 쓰레드 를 생성 해야 하 는 경우 를 생각 해 보 십시요 . DLL 이 Cleanup 되 면 data 의 무결성 ( consistent ) 을 확신 하 기 위해 모든 Worker 쓰레드 의 동기 화 가 필요 하 며 , 그 다음 Worker 쓰레드 는 종료 하 게 됩니다 . 오늘날 , 멀티 쓰레드 환경 의 DLL 을 종료 하 고 동기 화 하 는 데 에 는 완벽 하 고 정확 한 방법 은 없 습니다 . 다음 은 DLL 종료 를 하 는 동안 이루어질 쓰레드 동기 화 를 위해 현재 까지 나와 있 는 최고 의 습관 을 설명 하 고 있 습니다 . 
 ■ 프로세스 종료 시 DLLMain 에서 의 쓰레드 동기 화 
 프로세스 종료 시 DLLMain 이 호출 되 었 다면 모든 프로세스 의 쓰레드 들 은 Clean up 이 이뤄지며 주 소 공간 ( Address Space ) 는 더 이상 유지 되 지 않 습니다 . 동기 화 는 이런 경우 에 는 필요 하 지 않 습니다 . 다시 말 해 DLL _ PROCESS _ DETACH 는 비워둬 도 됩니다 . 
 Windows Vista 에서 는 핵심 data 들 ( 환경 변수 , 현재 디렉 토리 , 프로세스 힙 , . ..) 의 유지 가 보장 됩니다 . 그러나 다른 동적 할당 된 사설 data 는 망가져서 더 이상 안 전하 지 않 습니다 . 
 저장 이 필요 한 영구 유지 될 상태 들 은 저장 매체 에 플 러쉬 되 어야 합니다 . 
 ■ DLL UnLoad 시의 DLL _ THREAD _ DETACH 를 위한 DLLMain 의 쓰레드 동기 화 
 DLL 이 UnLoad 될 때 주소 공간 ( Address Space ) 는 사라지 는 것 은 아닙니다 . 따라서 DLL 은 Clean 될 예정 인 상태 입니다 . 이것 은 쓰레드 동기 화 , Open 된 핸들 , 영구 유지 해야 하 는 상태 그리고 할당 된 자원 들 을 포함 합니다 . 
 쓰레드 동기 화 는 종잡 을 수 없 는데 , DLLMain 에서 쓰레드 의 종료 를 기다리 는 것 은 deadlock 을 유발 할 수 있 기 때문 입니다 . 예 를 들 어 , DLL A 가 Loader Lock 을 획득 했 습니다 . 그리고 Thread T 를 종료 시키 기 위해 Signal 을 보냈 고 종료 를 기다리 도록 합니다 . 쓰레드 T 는 종료 가 되 며 , Loader 는 DLL A 의 DLL _ THREAD _ DETACH 호출 을 위해 Loader Lock 획득 을 시도 할 것 입니다 . 이것 이 deadlock 을 유발 시키 게 됩니다 . 이러 한 리스크 를 최소 화 하 는 방법 은 다음 과 같 습니다 . 
 DLL A 는 DLLMain 에서 DLL _ THREAD _ DETACH 메시지 를 받 고 , 쓰레드 T 에게 종료 해 라는 Signal 을 보냅니다 . 
 Thread T 는 현재 의 작업 을 마치 고 스스로 상태 를 유지 하 며 DLL A 에게 Signal 을 보냅니다 . 단 , 이러 한 유지 된 상태 체크 를 위해서 DLLMain 의 deadlock 회피 를 위한 제약 을 지켜야 합니다 . 
 DLL A 가 쓰레드 T 를 종료 시켰으며 , 그것 이 아직 유지 된 상태 임 을 알 수 있 습니다 . 
 만약 DLL 이 그것 의 모든 쓰레드 를 생성 하 고 나 서 UnLoad 되 었 고 실행 이 시작 되 기 전 이 었 다면 , 그 쓰레드 들 은 crash 가 발생 할 수 있 습니다 . 만약 DLL 이 초기 화 의 단계 로 DLLMain 에서 Thread 를 생성 하 였 다면 , 몇몇 쓰레드 들 이 아직 초기 화 가 완료 되 지 못했 고 그 들 의 DLL _ THREAD _ ATTACH 메시지 가 여전히 DLL 에게 전달 되 기 를 기다리 고 있 을 것 입니다 . 이런 상황 에서 DLL 이 UnLoad 된다면 쓰레드 들 의 Terminate 가 시작 될 것 입니다 . 그러나 몇몇 쓰레드 들 은 Loader Lock 에 의해 block 되 어 있 을 겁니다 . 그 들 의 DLL _ THREAD _ ATTACH 메시지 들 은 DLL 이 unmap 된 이후 진행 될 것 이 며 , 이 는 crash 를 유발 할 것 입니다 . 
 ■ 다음 의 가이드라인 을 추천 합니다 . 
 Application Verifier 를 사용 하 며 DLLMain 의 통상 적 인 오류 를 찾 으세요 . 
 DLLMain 에서 의 사설 Lock 을 사용 하 신다면 , Lock hierachy 를 정의 하 고 유지 하 십시요 . Loader Lock 은 가장 마지막 에 있 어야 합니다 . 
 어떤 호출 도 아직 로드 되 지 않 은 다른 DLL 로 의 의존 성 이 없 다는 것 을 확인 하 세요 . 
 간단 한 초기 화 는 DLLMain 보 다는 compile time 에서 수행 하 도록 하 세요 . 
 DLLMain 에서 기다리 는 호출 을 뒤 로 미루 십시요 . 
 초기 화 작업 을 뒤 로 미루 십시요 . 어떤 에러 조건 은 빨리 발견 되 는데 , 이 는 응용 프로그램 의 에러 처리 를 멋지 게 해줍니다 . 그러나 빨리 발견 되 는 것 과 견고 함 의 손실 은 Trade off 가 있 습니다 . 초기 화 를 뒤 로 미루 는 것 이 최고 의 방법 입니다 . 
 구독 하 기 쫑 ` s 개발 이야기 
 Posted by 쫑 경 
 TAG ! cs , Criticalsection , LoaderLock , WinDbg 
 WDM / Architecture 2014 . 04 . 23 14 : 09 
 HeartBleed 이슈 및 대처 방법 
 당신 회사 의 톰캣 은 괜찮 으십니까 ? 
 요즘 화두 가 되 고 있 는 Heartbleed OpenSSL 에 대해 설명 드리 고자 합니다 . 
 취약점 은 톰캣 설치 폴더 의 Bin 폴더 안 에 tcnative - 1 . dll 이 버그 가 발견 되 었 습니다 . 
 버그 에 영향 이 있 는 버전 은 1 . 1 . 24 ~ 29 까지 입니다 . 
 이 버전 을 사용 하 여 인증서 를 발급 할 경우 개인 키가 노출 되 어 SSL 패킷 이 복호화 될 수 있 다는 문제 를 가지 고 있 습니다 . 
 일부 는 톰캣 을 바꿔야 되 는 경우 도 있 습니다 . OpenSSL 을 탑재 하 여 빌드 된 버전 들 은 수정 을 하 셔야 합니다 . 
 해당 정보 는 아래 의 사이트 에서 확인 하 실 수 있 습니다 . 
 http : / / scn . sap . com / community / bi - platform / blog / 2014 / 04 / 11 / testing - for - heartbleed - vulnerability - for - the - bi - platform 
 tcnative - 1 . dll 은 톰캣 가속 엔진 입니다 . 그래서 기능 을 사용 하 지 않 는다면 삭제 하 시 고 사용 하 시 면 됩니다 . 
 삭제 를 하 게 되 면 아파치 로 그 에 
 The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java . library . path 
 라는 로그 가 보일 것 입니다 . 
 삭제 를 안 하 고 사용 하 고 싶 다면 얼마 전 아파치 에서 릴리즈 된 1 . 1 . 30 버전 을 사용 하 시 면 됩니다 . 
 톰캣 6 . 0 에 해당 DLL 만 교체 하 여 사용 해 본 결과 이상 없이 잘 사용 되 고 서버 도 정상 적 으로 동작 합니다 . 톰캣 버전 을 안 올리 셔도 됩니다 . 
 다른 몇몇 의 방법 들 이 있 지만 이 방법 이 가장 적용 하 기 쉬운 방법 인 것 같 아 공유 합니다 . 
 TAG Heartbleed , tcnative - 1 , tcnative - 1 . dll , tomcat , 톰캣 
 WDM / Architecture 2014 . 03 . 31 16 : 38 
 윈도우 커널 레벨 에서 의 인터럽트 레벨 
 하드웨어 또는 소프트웨어 의 인터럽트 가 발생 하 면 프로세서 ( CPU 가 해당 이 되 겠 지요 . . 간혹 프로세스 와 프로세서 를 잘못 보 는 분 이 계셔서 ) 는 현재 의 작업 을 중지 합니다 . 프로세서 는 인터럽트 에 대해 우선 순위 를 부여 하 고 우선 순위 가 높 은 인터럽트 를 먼저 처리 를 합니다 . 
 커널 레벨 에서 의 프로그래밍 을 하 거나 함수 를 사용 할 때 인터럽트 레벨 이 중요 합니다 . 프로그래머 가 사용 할 수 있 는 인터럽트 레벨 은 0 ~ 2 까지 밖 에 사용 할 수 없 습니다 . 상위 권 의 인터럽트 레벨 은 하드웨어 가 사용 하 게 됩니다 . 
 이 는 당연 한 이치 이 지만 , 커널 레벨 에서 의 프로그램 이 드라이버 로 작성 이 되 지만 하드웨어 입장 에서 는 결국 소프트웨어 이 기 때문 에 하드웨어 보다 소프트웨어 가 처리 우선 순위 가 높 다는 것 은 말 이 안 되 기 때문 입니다 . 
 함수 에 대한 중요 성 을 예 를 들 면 
 NTSTATUS ZwCreateFile ( 
 _ In _ opt _ PVOID EaBuffer , 
 ZwCreateFile 함수 의 MSDN 맨 마지막 쪽 을 보 면 
 이 라고 되 어 있 습니다 . 즉 , 이 함수 는 APC _ LEVEL 이상 의 인터럽트 인 상태 에서 사용 하 게 되 면 BSoD 가 발생 하 게 됩니다 . 
 인터럽트 없 음 
 정규 스레드 실행 
 소프트웨어 인터럽트 
 비동기 프로시저 호출 발생 
 스레드 스 케 쥴 링 , 지연 프로시저 호출 실행 
 하드웨어 인터럽트 
 디바이스 인터럽트 요구 레벨 핸들러 실행 
 프로파일링 타이머 
 CLOCK 2 _ LEVEL 
 동기 화 레벨 
 IPI _ LEVEL 
 프로세서 간 인터럽트 레벨 
 POWER ) LEVEL 
 전원 장애 레벨 
 모든 스레드 는 보통 제일 하위 의 PASSIVE _ LEVEL 의 인터럽트 에서 실행 되 며 , 스 케 쥴 링 은 우선 순위 의 값 을 이용 하 여 다음 에 어떤 스레드 가 실행 할지 를 판단 합니다 . 
 보통 드라이버 작성 시 일정 정보 를 저장 하 기 위해 List 에 관리 하 게 되 는데 , 이때 삽입 이나 삭제 를 할 경우 PASSIVE _ LEVEL 에서 작업 을 하 게 되 면 동시 접근 이 되 어서 메모리 오류 가 발생 할 수 있 는 소지 가 다분 합니다 . 이 부분 은 유저 레벨 에서 도 마찬가지 겠 지요 . . 
 그래서 인터럽트 의 레벨 을 올려 , 즉 , IRQL 의 레벨 을 올려서 삽입 , 삭 제시 작업 을 하 게 됩니다 . 이때 드라이버 에서 SpinLock 를 사용 하 여 IRQL 을 DISPATCH _ LEVEL 까지 올려 줍니다 . 
 꼭 드라이버 작성 시 함수 를 쓰 실 때 어느 IRQL 에서 작동 이 되 는 함수 인지 확인 하 고 코드 를 하 는 습관 을 들이 신다면 나중 에 도움 이 많이 될 듯 싶 습니다 . 읽 어 주 셔서 감사 합니다 . 
 TAG IRQL , PASSIVE _ LEVEL , ZwCreateFile , 인터럽트 
 WDM / Architecture 2013 . 03 . 24 22 : 57 
 Windows 8 x 86 _ EPROCESS 구 조체 & offset 
 간 만 에 포스팅 하 는 듯 합니다 . 
 8 에 대해 _ eprocess offset 에 대한 내용 이 검색 해도 없 어서 올립니다 . 
 + 0 x 0 a 0 ProcessLock : _ EX _ PUSH _ LOCK 
 + 0 x 0 a 8 CreateTime : _ LARGE _ INTEGER 
 + 0 x 0 b 0 RundownProtect : _ EX _ RUNDOWN _ REF 
 + 0 x 0 b 4 UniqueProcessId : Ptr 32 Void 
 + 0 x 0 c 0 Flags 2 : Uint 4 B 
 + 0 x 0 c 0 JobNotReallyActive : Pos 0 , 1 Bit 
 + 0 x 0 c 0 AccountingFolded : Pos 1 , 1 Bit 
 + 0 x 0 c 0 NewProcessReported : Pos 2 , 1 Bit 
 + 0 x 0 c 0 ExitProcessReported : Pos 3 , 1 Bit 
 + 0 x 0 c 0 ReportCommitChanges : Pos 4 , 1 Bit 
 + 0 x 0 c 0 LastReportMemory : Pos 5 , 1 Bit 
 + 0 x 0 c 0 NoWakeCharge : Pos 6 , 1 Bit 
 + 0 x 0 c 0 HandleTableRundown : Pos 7 , 1 Bit 
 + 0 x 0 c 0 NeedsHandleRundown : Pos 8 , 1 Bit 
 + 0 x 0 c 0 RefTraceEnabled : Pos 9 , 1 Bit 
 + 0 x 0 c 0 NumaAware : Pos 10 , 1 Bit 
 + 0 x 0 c 0 EmptyJobEvaluated : Pos 11 , 1 Bit 
 + 0 x 0 c 0 DefaultPagePriority : Pos 12 , 3 Bits 
 + 0 x 0 c 0 PrimaryTokenFrozen : Pos 15 , 1 Bit 
 + 0 x 0 c 0 ProcessVerifierTarget : Pos 16 , 1 Bit 
 + 0 x 0 c 0 S tackRandomizationDisabled : Pos 17 , 1 Bit 
 + 0 x 0 c 0 AffinityPermanent : Pos 18 , 1 Bit 
 + 0 x 0 c 0 AffinityUpdateEnable : Pos 19 , 1 Bit 
 + 0 x 0 c 0 PropagateNode : Pos 20 , 1 Bit 
 + 0 x 0 c 0 ExplicitAffinity : Pos 21 , 1 Bit 
 + 0 x 0 c 0 ProcessExecutionState : Pos 22 , 2 Bits 
 + 0 x 0 c 0 DisallowStrippedImages : Pos 24 , 1 Bit 
 + 0 x 0 c 0 HighEntropyASLREnabled : Pos 25 , 1 Bit 
 + 0 x 0 c 0 ExtensionPointDisable : Pos 26 , 1 Bit 
 + 0 x 0 c 0 ForceRelocateImages : Pos 27 , 1 Bit 
 + 0 x 0 c 0 ProcessStateChangeRequest : Pos 28 , 2 Bits 
 + 0 x 0 c 0 P r o cessStateChangeInProgress : Pos 30 , 1 Bit 
 + 0 x 0 c 0 DisallowWin 32 kSystemCalls : Pos 31 , 1 Bit 
 + 0 x 0 c 4 Flags : Uint 4 B 
 + 0 x 0 c 4 CreateReported : Pos 0 , 1 Bit 
 + 0 x 0 c 4 NoDebugInherit : Pos 1 , 1 Bit 
 + 0 x 0 c 4 ProcessExiting : Pos 2 , 1 Bit 
 + 0 x 0 c 4 ProcessDelete : Pos 3 , 1 Bit 
 + 0 x 0 c 4 Wow 64 SplitPages : Pos 4 , 1 Bit 
 + 0 x 0 c 4 VmDeleted : Pos 5 , 1 Bit 
 + 0 x 0 c 4 OutswapEnabled : Pos 6 , 1 Bit 
 + 0 x 0 c 4 Outswapped : Pos 7 , 1 Bit 
 + 0 x 0 c 4 ForkFailed : Pos 8 , 1 Bit 
 + 0 x 0 c 4 Wow 64 VaSpace 4 Gb : Pos 9 , 1 Bit 
 + 0 x 0 c 4 AddressSpaceInitialized : Pos 10 , 2 Bits 
 + 0 x 0 c 4 SetTimerResolution : Pos 12 , 1 Bit 
 + 0 x 0 c 4 BreakOnTermination : Pos 13 , 1 Bit 
 + 0 x 0 c 4 DeprioritizeViews : Pos 14 , 1 Bit 
 + 0 x 0 c 4 WriteWatch : Pos 15 , 1 Bit 
 + 0 x 0 c 4 ProcessInSession : Pos 16 , 1 Bit 
 + 0 x 0 c 4 OverrideAddressSpace : Pos 17 , 1 Bit 
 + 0 x 0 c 4 HasAddressSpace : Pos 18 , 1 Bit 
 + 0 x 0 c 4 LaunchPrefetched : Pos 19 , 1 Bit 
 + 0 x 0 c 4 Background : Pos 20 , 1 Bit 
 + 0 x 0 c 4 VmTopDown : Pos 21 , 1 Bit 
 + 0 x 0 c 4 ImageNotifyDone : Pos 22 , 1 Bit 
 + 0 x 0 c 4 PdeUpdateNeeded : Pos 23 , 1 Bit 
 + 0 x 0 c 4 VdmAllowed : Pos 24 , 1 Bit 
 + 0 x 0 c 4 CrossSessionCreate : Pos 25 , 1 Bit 
 + 0 x 0 c 4 ProcessInserted : Pos 26 , 1 Bit 
 + 0 x 0 c 4 DefaultIoPriority : Pos 27 , 3 Bits 
 + 0 x 0 c 4 ProcessSelfDelete : Pos 30 , 1 Bit 
 + 0 x 0 c 4 SetTimerResolutionLink : Pos 31 , 1 Bit 
 + 0 x 0 c 8 ProcessQuotaUsage : [ 2 ] Uint 4 B 
 + 0 x 0 d 0 ProcessQuotaPeak : [ 2 ] Uint 4 B 
 + 0 x 0 d 8 PeakVirtualSize : Uint 4 B 
 + 0 x 0 dc VirtualSize : Uint 4 B 
 + 0 x 0 e 0 SessionProcessLinks : _ LIST _ ENTRY 
 + 0 x 0 e 8 ExceptionPortData : Ptr 32 Void 
 + 0 x 0 e 8 ExceptionPortValue : Uint 4 B 
 + 0 x 0 e 8 ExceptionPortState : Pos 0 , 3 Bits 
 + 0 x 0 ec Token : _ EX _ FAST _ REF 
 + 0 x 0 f 0 WorkingSetPage : Uint 4 B 
 + 0 x 0 f 4 AddressCreationLock : _ EX _ PUSH _ LOCK 
 + 0 x 0 f 8 RotateInProgress : Ptr 32 _ ETHREAD 
 + 0 x 0 fc ForkInProgress : Ptr 32 _ ETHREAD 
 + 0 x 100 HardwareTrigger : Uint 4 B 
 + 0 x 104 CommitChargeJob : Ptr 32 _ EJOB 
 + 0 x 108 CloneRoot : Ptr 32 _ MM _ AVL _ TABLE 
 + 0 x 10 c NumberOfPrivatePages : Uint 4 B 
 + 0 x 110 NumberOfLockedPages : Uint 4 B 
 + 0 x 114 Win 32 Process : Ptr 32 Void 
 + 0 x 118 Job : Ptr 32 _ EJOB 
 + 0 x 11 c SectionObject : Ptr 32 Void 
 + 0 x 120 SectionBaseAddress : Ptr 32 Void 
 + 0 x 124 Cookie : Uint 4 B 
 + 0 x 128 VdmObjects : Ptr 32 Void 
 + 0 x 12 c WorkingSetWatch : Ptr 32 _ PAGEFAULT _ HISTORY 
 + 0 x 130 Win 32 WindowStation : Ptr 32 Void 
 + 0 x 134 I n h eritedFromUniqueProcessId : Ptr 32 Void 
 + 0 x 138 LdtInformation : Ptr 32 Void 
 + 0 x 13 c CreatorProcess : Ptr 32 _ EPROCESS 
 + 0 x 13 c ConsoleHostProcess : Uint 4 B 
 + 0 x 140 Peb : Ptr 32 _ PEB 
 + 0 x 144 Session : Ptr 32 Void 
 + 0 x 148 AweInfo : Ptr 32 Void 
 + 0 x 14 c QuotaBlock : Ptr 32 _ EPROCESS _ QUOTA _ BLOCK 
 + 0 x 150 ObjectTable : Ptr 32 _ HANDLE _ TABLE 
 + 0 x 154 DebugPort : Ptr 32 Void 
 + 0 x 158 PaeTop : Ptr 32 Void 
 + 0 x 160 EtwDataSource : Ptr 32 Void 
 + 0 x 168 PageDirectoryPte : Uint 8 B 
 + 0 x 170 ImageFileName : [ 15 ] UChar 
 + 0 x 17 f PriorityClass : UChar 
 + 0 x 180 SecurityPort : Ptr 32 Void 
 + 0 x 184 S eAuditProcessCreationInfo : _ SE _ AUDIT _ PROCESS _ CREATION _ INFO 
 + 0 x 188 JobLinks : _ LIST _ ENTRY 
 + 0 x 190 HighestUserAddress : Ptr 32 Void 
 + 0 x 194 ThreadListHead : _ LIST _ ENTRY 
 + 0 x 19 c ActiveThreads : Uint 4 B 
 + 0 x 1 a 0 ImagePathHash : Uint 4 B 
 + 0 x 1 a 4 D efaultHardErrorProcessing : Uint 4 B 
 + 0 x 1 a 8 LastThreadExitStatus : Int 4 B 
 + 0 x 1 ac PrefetchTrace : _ EX _ FAST _ REF 
 + 0 x 1 b 0 LockedPagesList : Ptr 32 _ MM _ AVL _ TABLE 
 + 0 x 1 ec CommitCharge : Uint 4 B 
 + 0 x 1 f 0 CommitChargePeak : Uint 4 B 
 + 0 x 1 f 4 Vm : _ MMSUPPORT 
 + 0 x 264 MmProcessLinks : _ LIST _ ENTRY 
 + 0 x 26 c ModifiedPageCount : Uint 4 B 
 + 0 x 270 ExitStatus : Int 4 B 
 + 0 x 274 VadRoot : _ MM _ AVL _ TABLE 
 + 0 x 28 c VadPhysicalPages : Uint 4 B 
 + 0 x 290 VadPhysicalPagesLimit : Uint 4 B 
 + 0 x 294 AlpcContext : _ ALPC _ PROCESS _ CONTEXT 
 + 0 x 2 a 4 TimerResolutionLink : _ LIST _ ENTRY 
 + 0 x 2 ac T imerResolutionStackRecord : Ptr 32 _ PO _ DIAG _ STACK _ RECORD 
 + 0 x 2 b 4 SmallestTimerResolution : Uint 4 B 
 + 0 x 2 b 8 ExitTime : _ LARGE _ INTEGER 
 + 0 x 2 c 0 A ctiveThreadsHighWatermark : Uint 4 B 
 + 0 x 2 c 4 LargePrivateVadCount : Uint 4 B 
 + 0 x 2 c 8 ThreadListLock : _ EX _ PUSH _ LOCK 
 + 0 x 2 cc WnfContext : Ptr 32 Void 
 + 0 x 2 d 0 SectionMappingSize : Uint 4 B 
 + 0 x 2 d 4 SignatureLevel : UChar 
 + 0 x 2 d 5 SectionSignatureLevel : UChar 
 + 0 x 2 d 6 SpareByte 20 : [ 2 ] UChar 
 + 0 x 2 d 8 KeepAliveCounter : Uint 4 B 
 + 0 x 2 dc DiskCounters : Ptr 32 _ PROCESS _ DISK _ COUNTERS 
 + 0 x 2 e 0 LastFreezeInterruptTime : Uint 8 B 
 WDM / Architecture 2011 . 12 . 14 10 : 38 
 OB Filtering - OB _ PRE _ CREATE _ HANDLE _ INFORMATION structure 
 The OB _ PRE _ CREATE _ HANDLE _ INFORMATION structure provides information to an ObjectPreCallbackroutine about a thread or process handle that is being opened . 
 An ACCESS _ MASK value that specifies the access rights to grant for the handle . By default , this member equals OriginalDesiredAccess , but the ObjectPreCallback routine can modify this value to restrict the access that is granted . 
 Drivers can use the following flags for handles to processes : 
 Create a new child process of the process . 
 Create a new thread in the context of the process . 
 Duplicate handles to or from the context of the process , such as by calling the user - mode DuplicateHandle routine . 
 Set the working set size for the process , such as by calling the user - mode SetProcessWorkingSetSize routine . 
 Modify process settings , such as by calling the user - modeSetPriorityClass routine . 
 Suspend or resume the process . 
 Terminate the process , such as by calling the user - modeTerminateProcess routine . . 
 PROCESS _ VM _ OPERATIONS 
 Modify the address space of the process , such as by calling the user - mode WriteProcessMemory and VirtualProtectExroutines . 
 Write to the address space of the process , such as by calling the user - mode WriteProcessMemory routine . 
 Drivers can use the following flags for handles to threads : 
 Enable a server thread to impersonate one of its clients . 
 Impersonate the operating system ' s anonymous logon token , such as by calling the user - m o d e ImpersonateAnonymousToken routine . 
 Modify the thread ' s execution context , such as by calling the user - mode SetThreadContext routine . 
 Modify thread settings , such as by calling the user - mode SetThreadIdealProcessor routine . The operations that are permitted by this access right are a superset of those that are permitted by the THREAD _ SET _ LIMITED _ INFORMATION access right . 
 Modify a limited set of thread settings , such as by calling the user - mode SetThreadAffinityMask andSetThreadPriorityBoost routines . 
 Modify properties of the thread ' s impersonation token , such as by calling the user - modeSetTokenInformation routine . 
 Suspend or resume the thread , such as by calling the user - mode SuspendThread and ResumeThreadroutines . 
 Terminate the thread , such as by calling the user - mode TerminateThread routine . 
 OriginalDesiredAccess 
 An ACCESS _ MASK value that specifies the original access that was requested for the handle . 
 You can never add access rights beyond what is specified in the DesiredAccess member . If the access right is listed as a modifiable flag , the access right can be removed . 
 TAG OB Filter 
 WDM / Architecture 2009 . 07 . 15 16 : 18 
 The AddDevice Routine 
 In the preceding main section , I showed how you initialize a WDM driver when it ’ s first loaded . In general , though , a driver might be called upon to manage more than one actual device . In the WDM architecture , a driver has a special AddDevice function that the PnP Manager can call for each such device . The function has the following skeleton : 
 NTSTATUS   AddDevice ( PDRIVER _ OBJECT   DriverObject , 
 PDEVICE _ OBJECT   pdo ) 
 The DriverObject argument points to the same driver object that you initialized in your DriverEntry routine . The pdo argument is the address of the physical device object at the bottom of the device stack , even if there are already filter drivers below . 
 The basic responsibility of AddDevice in a function driver is to create a device object and link it into the stack rooted in this PDO . The steps involved are as follows : 
 Call IoCreateDevice to create a device object and an instance of your own device extension object . 
 Register one or more device interfaces so that applications know about the existence of your device . Alternatively , give the device object a name and then create a symbolic link . 
 Next initialize your device extension and the Flags member of the device object . 
 Call I o AttachDeviceToDeviceStack to put your new device object into the stack . 
 Now I ’ ll explain these steps in more detail . I ’ ll show a complete example of AddDevice at the very end of this discussion . 
 In the code snippets that follow , I ’ ve deliberately left out all the error handling that should be there . That ’ s so I could concentrate on the normal control flow through AddDevice . You mustn ’ t imitate this programming style in a production driver ? but of course you already knew that . I ’ ll discuss how to handle errors in the next chapter . Every code sample in the companion content has full error checking in place too . 
 You create a device object by calling IoCreateDevice . For example : 
 sizeof ( DEVICE _ EXTENSION ) ,   NULL ,   FILE _ DEVICE _ UNKNOWN , 
 The first argument ( DriverObject ) is the same value supplied to AddDevice as the first parameter . This argument establishes the connection between your driver and the new device object , thereby allowing the I / O Manager to send you IRPs intended for the device . The second argument is the size of your device extension structure . As I discussed earlier in this chapter , the I / O Manager allocates this much additional memory and sets the DeviceExtension pointer in the device object to point to it . 
 The third argument , which is NULL in this example , can be the address of a UNICODE _ STRING providing a name for the device object . Deciding whether to name your device object and which name to give it requires some thought , and I ’ ll describe these surprisingly complex considerations a bit further on in the section “ Should I Name My Device Object ? ” 
 The fourth argument ( FILE _ DEVICE _ UNKNOWN ) is one of the device types defined in WDM . H . Whatever value you specify here can be overridden by an entry in the device ’ s hardware key or class key . If both keys have an override , the device key has precedence . For devices that fit into one of the established categories , specify the right value in one of these places because some details about the interaction between your driver and the surrounding system depend on it . In fact , the device type is crucial for the correct functioning of a file system driver or a disk or tape driver . Additionally , the default security settings for your device object depend on this device type . 
 The fifth argument ( FILE _ DEVICE _ SECURE _ OPEN ) provides the Characteristics flag for the device object . ( See Table 2 - 3 . ) Most of these flags are relevant for mass storage devices . The flag bit FILE _ AUTOGENERATED _ DEVICE _ NAME is for use by bus and multifunction drivers when creating PDOs . I ’ ll discuss the importance of FILE _ DEVICE _ SECURE _ OPEN later in this chapter in the section “ Should I Name My Device Object ? ” Whatever value you specify here can be overridden by an entry in the device ’ s hardware key or class key . If both keys have an override , the hardware key has precedence . 
 The sixth argument to IoCreateDevice ( FALSE in my example ) indicates whether the device is exclusive . The I / O Manager allows only one handle to be opened by normal means to an exclusive device . Whatever value you specify here can be overridden by an entry in the device ’ s hardware key or class key . If both keys have an override , the hardware key has precedence . 
 The exclusivity attribute matters only for whatever named device object is the target of an open request . If you follow Microsoft ’ s recommended guidelines for WDM drivers , you won ’ t give your device object a name . Open requests will then target the PDO , but the PDO will not usually be marked exclusive because the bus driver generally has no way of knowing whether you need your device to be exclusive . The only time the PDO will be marked exclusive is when there ’ s an Exclusive override in the device ’ s hardware key or the class key ’ s Properties subkey . You ’ re best advised , therefore , to avoid relying on the exclusive attribute altogether . Instead , make your IRP _ MJ _ CREATE handler reject open requests that would violate whatever restriction you require . 
 The last argument ( & fdo ) points to a location where IoCreateDevice will store the address of the device object it creates . 
 If IoCreateDevice fails for some reason , it returns a status code and doesn ’ t alter the PDEVICE _ OBJECT described by the last argument . If it succeeds , it returns a successful status code and sets the PDEVICE _ OBJECT pointer . You can then proceed to initialize your device extension and do the other work associated with creating a new device object . Should you discover an error after this point , you should release the device object and return a status code . The code to accomplish these tasks would be something like this : 
 if   ( ! NT _ SUCCESS ( status ) ) 
 if   ( < some   other   error   discovered >) 
 I ’ ll explain the NTSTATUS status codes and the NT _ SUCCESS macro in the next chapter . 
 Windows XP uses a centralized Object Manager to manage many of its internal data structures , including the driver and device objects I ’ ve been talking about . David Solomon and Mark Russinovich present a fairly complete explanation of the Object Manager and namespace in Chapter 3 , “ System Mechanisms , ” of Inside Windows 2000 , Third Edition ( Microsoft Press , 2000 ) . Objects have names , which the Object Manager maintains in a hierarchical namespace . Figure 2 - 16 is a screen shot of my DEVVIEW application showing the top level of the name hierarchy . The objects displayed as folders in this screen shot are directory objects , which can contain subdirectories and “ regular ” objects . The objects displayed with other icons are examples of these regular objects . ( In this respect , DEVVIEW is similar to the WINOBJ utility that you ’ ll find in the BIN \ WINNT directory of the Platform SDK . WINOBJ can ’ t give you information about device objects and drivers , though , which is why I wrote DEVVIEW in the first place . ) 
 Figure 2 - 16 . Using DEVVIEW to view the namespace . 
 Device objects can have names that conventionally live in the \ Device directory . Names for devices serve two purposes in Windows XP . Giving your device object a name allows other kernel - mode components to find it by calling service functions such as IoGetDeviceObjectPointer . Having found your device object , they can send you IRPs . 
 The other purpose of naming a device object is to allow applications to open handles to the device so they can send you IRPs . An application uses the standard CreateFile API to open a handle , whereupon it can use ReadFile , WriteFile , and DeviceIoControl to talk to you . The pathname an application uses to open a device handle begins with the prefix \\.\ rather than with a standard Universal Naming Convention ( UNC ) name such as C : \ MYFILE . CPP or \\ FRED \ C - Drive \ HISFILE . CPP . Internally , the I / O Manager converts this prefix to \??\ before commencing a name search . To provide a mechanism for connecting names in the \?? directory to objects whose names are elsewhere ( such as in the \ Device directory ) , the Object Manager implements an object called a symbolic link . 
 The name \?? has a special meaning in Windows XP . Confronted with this name , the Object Manager first searches a portion of the kernel namespace that is local to the current user session . To see how this works , establish two or more sessions and start DEVVIEW in one of them . Expand the \ Sessions folder , and you will eventually see folders for each user . Figure 2 - 18 , which appears later in this chapter , provides an example . If the local search isn ’ t successful , the Object Manager then searches the \ GLOBAL ? ? folder . 
 A symbolic link is a little bit like a desktop shortcut in that it points to some other entity that ’ s the real object of attention . One use of symbolic links in Windows XP is to connect the leading portion of MS - DOS - style names to devices . Figure 2 - 17 shows a portion of the \ GLOBAL ? ? directory , which includes a number of symbolic links . Notice , for example , that C and other drive letters in the MS - DOS file - naming scheme are actually links to objects whose names are in the \ Device directory . These links allow the Object Manager to jump somewhere else in the namespace as it parses through a name . So if I call CreateFile with the name C : \ MYFILE . CPP , the Object Manager will take this path to open the file : 
 Figure 2 - 17 . The \ GLOBAL ? ? directory with several symbolic links . 
 Kernel - mode code initially sees the name \??\ C : \ MYFILE . CPP . The Object Manager special - cases the “ ??” name to mean the DosDevices directory for the current session . ( In Figure 2 - 18 , this directory is one of the subdirectories of \ Sessions \ 0 \ DosDevices . ) 
 The Object Manager doesn ’ t find “ C : ” in the session DosDevices directory , so it follows a symbolic link named “ Global ” to the “ GLOBAL ? ?” directory . 
 The Object Manager now looks up “ C : ” in the \ GLOBAL ? ? directory . It finds a symbolic link by that name , so it forms the new kernel - mode pathname \ Device \ HarddiskVolume 1 \ MYFILE . CPP and parses that . 
 Working with the new pathname , the Object Manager looks up “ Device ” in the root directory and finds a directory object . 
 The Object Manager looks up “ HarddiskVolume 1 ” in the \ Device directory . It finds a device object by that name . 
 At this point in the process , the Object Manager will create an IRP that it will send to the driver or drivers for HarddiskVolume 1 . The IRP will eventually cause some file system driver or another to locate and open a disk file . Describing how a file system driver works is beyond the scope of this book , but the sidebar “ Opening a Disk File ” will give you a bit of the flavor . 
 Opening a Disk File 
 The overall process that occurs when an application opens a disk file is complicated almost beyond belief . To continue the example in the text , the driver for HarddiskVolume 1 would be a file system driver such as NTFS . SYS , FASTFAT . SYS , or CDFS . SYS . How a file system realizes that a particular disk volume belongs to it and initializes to handle the volume is itself a saga of Norse proportions . That would have already happened before an application could get far enough to call CreateFile with a particular volume letter in the pathname , though , so we can ignore that process . 
 The file system driver will locate the topmost device object in the storage stack that includes the physical disk drive on which the C volume happens to be mounted . The I / O Manager and file system driver share management of a Volume Parameters Block ( VPB ) that ties the storage stack and the file system ’ s volume stack together . In principle , the file system driver sends IRPs to the storage driver in order to read directory entries that it can search while parsing the pathname specified by the original CreateFile call . In practice , the file system calls the kernel cache manager , which fulfills requests from an in - memory cache if possible and makes recursive calls to the file system driver to fill cache buffers . Deadlock prevention and surprise dismount handling during this process require heroic efforts , including a mechanism whereby a file system driver can stash a pointer to an automatic variable ( that is , one allocated on the call / return stack ) to be used in deeper layers of recursion within the same thread . 
 Luckily , you needn ’ t worry about VPBs and other complications arising from the way file system drivers work in any driver besides one for a storage device . I won ’ t say any more about this in the book . 
 If we were dealing with a device name such as COM 1 , the driver that ended up receiving the IRP would be the driver for \ Device \ Serial 0 . How a device driver handles an open request is definitely within the scope of this book , and I ’ ll be discussing it in this chapter ( in the section “ Should I Name My Device Object ? ” ) and in Chapter 5 , when I ’ ll talk about IRP processing in general . 
 A user - mode program can create a symbolic link in the local ( session ) namespace by calling DefineDosDevice , as in this example ( see Figure 2 - 18 ) : 
 Figure 2 - 18 . Symbolic link created by DefineDosDevice . 
 You can create a symbolic link in a WDM driver by calling IoCreateSymbolicLink , 
 where linkname is the name of the symbolic link you want to create and targname is the name to which you ’ re linking . Incidentally , the Object Manager doesn ’ t care whether targname is the name of any existing object : someone who tries to access an object by using a link that points to an undefined name simply receives an error . If you want to allow user - mode programs to override your link and point it somewhere else , you should call I o C r e a teUnprotectedSymbolicLink instead . 
 The kernel - mode equivalent of the immediately preceding DefineDosDevice call is this : 
 Should I Name My Device Object ? 
 Deciding whether to give your device object a name requires , as I said earlier , a little thought . If you give your object a name , it will be possible for any kernel - mode program to try to open a handle to your device . Furthermore , it will be possible for any kernel - mode or user - mode program to create a symbolic link to your device object and to use the symbolic link to try to open a handle . You might or might not want to allow these actions . 
 The primary consideration in deciding whether to name your device object is security . When someone opens a handle to a named object , the Object Manager verifies that they have permission to do so . When IoCreateDevice creates a device object for you , it assigns a default security descriptor based on the device type you specify as the fourth argument . The I / O Manager uses three basic categories to select a security descriptor : 
 Most file system device objects ( that is , disk , CD - ROM , file , and tape ) receive the “ public default unrestricted ” access control list ( ACL ) . This list gives just SYNCHRONIZE , READ _ CONTROL , FILE _ READ _ ATTRIBUTES , and FILE _ TRAVERSE access to everyone except the System account and all administrators . File system device objects , by the way , exist only so that there can be a target for a CreateFile call that will open a handle to a file managed by the file system . 
 Disk devices and network file system objects receive the same ACL as the file system objects , with some modifications . For example , everyone gets full access to a named floppy disk device object , and administrators get sufficient rights to run ScanDisk . ( User - mode network provider DLLs need greater access to the device object for their corresponding file system driver , which is why network file systems are treated differently from other file systems . ) 
 All other device objects receive the public open unrestricted ACL , which allows anyone with a handle to the device to do pretty much anything . 
 You can see that anyone will be able to access a nondisk device for both reading and writing if the driver gives the device object a name at the time it calls IoCreateDevice . This is because the default security allows nearly full access and because no security check at all is associated with creating a symbolic link ? the security checks happen at open time , based on the named object ’ s security descriptor . This is true even if other device objects in the same stack have more restrictive security . 
 IoCreateDeviceSecure , a function in the . NET DDK , allows you to specify a nondefault security descriptor in situations in which no override is in the registry . This function is too new for us to describe it more fully here . 
 DEVVIEW will show you the security attributes of the device objects it displays . You can see the operation of the default rules I just described by examining a file system , a disk device , and any other random device . 
 The PDO also receives a default security descriptor , but it ’ s possible to override it with a security descriptor stored in the hardware key or in the Properties subkey of the class key . ( The hardware key has precedence if both keys specify a descriptor . ) Even lacking a specific security override , if either the hardware key or the class key ’ s Properties subkey overrides the hardware type or characteristics specification , the I / O Manager constructs a new default security descriptor based on the new type . The I / O Manager does not , however , override the security setting for any of the other device objects above the PDO . Consequently , for the overrides ( and the administrative actions that set them up ) to have any effect , you shouldn ’ t name your device object . Don ’ t despair though ? applications can still access your device by means of a registered interface , which I ’ ll discuss soon . 
 You need to know about one last security concern . As the Object Manager parses its way through an object name , it needs only FILE _ TRAVERSE access to the intermediate components of the name . It performs a full security check only on the object named by the final component . So suppose you have a device object reachable under the name \ Device \ Beep or by the symbolic link \??\ Barf . A user - mode application that tries to open \\.\ Barf for writing will be blocked if the object security has been set up to deny write access . But if the application tries to open a name like \\.\ Barf \ ExtraStuff that has additional name qualifications , the open request will make it all the way to the device driver ( in the form of an IRP _ MJ _ CREATE I / O request ) if the user merely has FILE _ TRAVERSE permission , which is routinely granted . ( In fact , most systems even run with the option to check for traverse permission turned off . ) The I / O Manager expects the device driver to deal with the additional name components and to perform any required security checks with regard to them . 
 To avoid the security concern I just described , you can supply the flag FILE _ DEVICE _ SECURE _ OPEN in the device characteristics argument to IoCreate ­ Device . This flag causes Windows XP to verify that someone has the right to open a handle to a device even if additional name components are present . 
 If you decide to name the device object , you ’ ll normally put the name in the \ Device branch of the namespace . To give it a name , you have to create a UNICODE _ STRING structure to hold the name , and you have to specify that string as an argument to IoCreateDevice : 
 IoCreateDevice ( DriverObject ,   sizeof ( DEVICE _ EXTENSION ) ,   & devname , 
 I ’ ll discuss the use of RtlInitUnicodeString in the next chapter . 
 Starting in Windows XP , device object names are case insensitive . In Windows 98 / Me and in Windows 2000 , they are case sensitive . Be sure to spell \ Device exactly as shown if you want your driver to be portable across all the systems . Note also the spelling of \ DosDevices , particularly if your mother tongue doesn ’ t inflect the plural form of nouns ! 
 Conventionally , drivers assign their device objects a name by concatenating a string naming their device type ( “ Simple ” in this code fragment ) with a 0 - based integer denoting an instance of that type . In general , you don ’ t want to hard - code a name as I just did ? you want to compose it dynamically using string - manipulation functions like the following : 
 _ snwprintf ( name ,   arraysize ( name ) ,   L "\\ Device \\ SIMPLE % 2 . 2 d ", 
 I ’ ll explain the various service functions used in this code fragment in the next couple of chapters . The instance number you derive for private device types might as well be a static variable , as shown in the code fragment . 
 Notes on Device Naming 
 The \ GLOBAL ? ? directory used to be named \ DosDevices . The change was made to move the often - searched directory of user - mode names to the front of the alphabetical list of directories . Windows 98 / Me doesn ’ t recognize the name \?? or \ GLOBAL ? ?. 
 Windows 2000 defines a symbolic link named \ DosDevices that points to the \?? directory . Windows XP treats \ DosDevices differently depending on the process context at the time you create an object . If you create an object , such as a symbolic link , in a system thread , \ DosDevices refers to \ GLOBAL ? ?, and you end up with a global name . If you create an object in a user thread , \ DosDevices refers to \??, and you end up with a session - specific name . In most situations , a device driver creates symbolic links in its AddDevice function , which runs in a system thread , and so ends up with globally named objects in all WDM environments simply by putting the symbolic link in \ DosDevices . If you create a symbolic link at another time , you should use \ GLOBAL ? ? in Windows XP and \ Dos ­ Devices in earlier systems . See Appendix A for a discussion of how to distinguish between WDM platforms . 
 A quick - and - dirty shortcut for testing is to name your device object in the \ DosDevices directory , as many of the sample drivers in the companion content do . A production driver should name the device object in \ Device , however , to avoid the possibility of creating an object that ought to be global in a session - private namespace . 
 In previous versions of Windows NT , drivers for certain classes of devices ( notably disks , tapes , serial ports , and parallel ports ) called I o G e tConfigurationInformation to obtain a pointer to a global table containing counts of devices in each of these special classes . A driver would use the current value of the counter to compose a name like Harddisk 0 , Tape 1 , and so on and would also increment the counter . WDM drivers don ’ t need to use this service function or the table it returns , however . Constructing names for the devices in these classes is now the responsibility of a Microsoft type - specific class driver ( such as DISK . SYS ) . 
 The older method of naming I just discussed ? naming your device object and creating a symbolic link name that applications can use ? has two major problems . We ’ ve already discussed the security implications of giving your device object a name . In addition , the author of an application that wants to access your device has to know the scheme you adopted to name your devices . If you ’ re the only one writing the applications that will be accessing your hardware , that ’ s not much of a problem . But if many different companies will be writing applications for your hardware , and especially if many hardware companies are making similar devices , devising a suitable naming scheme is difficult . 
 To solve these problems , WDM introduces a new naming scheme for devices that is language - neutral , easily extensible , usable in an environment with many hardware and software vendors , and easily documented . The scheme relies on the concept of a device interface , which is basically a specification for how software can access hardware . A device interface is uniquely identified by a 128 - bit GUID . You can generate GUIDs by running the Platform SDK utilities UUIDGEN or GUIDGEN ? both utilities generate the same kind of number , but they output the result in different formats . The idea is that some industry group gets together to define a standard way of accessing a certain kind of hardware . As part of the standard - making process , someone runs GUIDGEN and publishes the resulting GUID as the identifier that will be forever after associated with that interface standard . 
 More About GUIDs 
 The GUIDs used to identify software interfaces are the same kind of unique identifier that ’ s used in the Component Object Model ( COM ) to identify COM interfaces and in the Open Software Foundation ( OSF ) Distributed Computing Environment ( DCE ) to identify the target of a remote procedure call ( RPC ) . For an explanation of how GUIDs are generated so as to be statistically unique , see page 66 of Kraig Brockschmidt ’ s Inside OLE , Second Edition ( Microsoft Press , 1995 ) , which contains a further reference to the original algorithm specification by the OSF . I found the relevant portion of the OSF specification on line at http : / / www . opengroup . org / onlinepubs / 9629399 / apdxa . htm . 
 The mechanics of creating a GUID for use in a device driver involve running either UUIDGEN or GUIDGEN and then capturing the resulting identifier in a header file . GUIDGEN is easier to use because it allows you to choose to format the GUID for use with the DEFINE _ GUID macro and to copy the resulting string onto the Clipboard . Figure 2 - 19 shows the GUIDGEN window . You can paste its output into a header file to end up with this : 
 0 xcaf 53 c 68 ,   0 xa 94 c ,   0 x 11 d 2 ,   0 xbb ,   0 x 4 a ,   0 x 0 ,   0 xc 0 ,   0 x 4 f , 
 You then replace << name >> with something more mnemonic like GUID _ DEVINTERFACE _ SIMPLE and include the definition in your driver and applications . 
 Figure 2 - 19 . Using GUIDGEN to generate a GUID . 
 I think of an interface as being analogous to the protein markers that populate the surface of living cells . An application desiring to access a particular kind of device has its own protein markers that fit like a key into the markers exhibited by conforming device drivers . See Figure 2 - 20 . 
 Figure 2 - 20 . Using device interfaces to match applications and devices . 
 Registering a Device Interface 
 A function driver ’ s AddDevice function should register one or more device interfaces by calling IoRegisterDeviceInterface , as shown here : 
 # include   < initguid . h > 
 # include " guids . h " 
 NTSTATUS   AddDevice ( ...) 
 IoRegisterDeviceInterface ( pdo ,   & GUID _ DEVINTERFACE _ SIMPLE , 
 We ’ re about to include a header ( GUIDS . H ) that contains one or more DEFINE _ GUID macros . DEFINE _ GUID normally declares an external variable . Somewhere in the driver , though , we have to actually reserve initialized storage for every GUID we ’ re going to reference . The system header file INITGUID . H works some preprocessor magic to make DEFINE _ GUID reserve the storage even if the definition of the DEFINE _ GUID macro happens to be in one of the precompiled header files . 
 I ’ m assuming here that I put the GUID definitions I want to reference into a separate header file . This would be a good idea , inasmuch as user - mode code will also need to include these definitions and won ’ t want to include a bunch of extraneous kernel - mode declarations relevant only to our driver . 
 The first argument to IoRegisterDeviceInterface must be the address of the PDO for your device . The second argument identifies the GUID associated with your interface , and the third argument specifies additional qualified names that further subdivide your interface . Only Microsoft code uses this name subdivision scheme . The last argument is the address of a UNICODE _ STRING structure that will receive the name of a symbolic link that resolves to this device object . 
 All that registration actually does is create the symbolic link name and save it in the registry . Later on , in response to the IRP _ MN _ START _ DEVICE Plug and Play request we ’ ll discuss in Chapter 7 , you ’ ll make the following call to IoSetDeviceInterfaceState to enable the interface : 
 Since the interface name ends up pointing to the PDO , the PDO ’ s security descriptor ends up controlling whether people can access your device . That ’ s good because it ’ s the PDO ’ s security that you control in the INF used to install the driver . 
 Enumerating Device Interfaces 
 Both kernel - mode and user - mode code can locate all the devices that happen to support an interface in which they ’ re interested . I ’ m going to explain how to enumerate all the devices for a particular interface in user mode . The enumeration code is so tedious to write that I eventually wrote a C ++ class to make my own life simpler . You ’ ll find this code in the DEVICELIST . CPP and DEVICELIST . H files that are part of the HIDFAKE and DEVPROP samples in Chapter 8 . These files declare and implement a CDeviceList class , which contains an array of CDeviceListEntry objects . These two classes have the following declaration : 
 class   CDeviceListEntry 
 class   CDeviceList 
 The classes rely on the CString class and CArray template class that are part of the Microsoft Foundation Classes ( MFC ) framework . The constructors for these two classes simply copy their arguments into the obvious data members : 
 CDeviceList : : CDeviceList ( const   GUID &   guid ) 
 CDeviceListEntry : : CDeviceListEntry ( LPCTSTR   linkname , 
 LPCTSTR   friendlyname ) 
 All the interesting work occurs in the CDeviceList : : Initialize function . The executive overview of what it does is this : it will enumerate all of the devices that expose the interface whose GUID was supplied to the constructor . For each such device , it will determine a friendly name that we ’ re willing to show to an unsuspecting end user . Finally it will return the number of devices it found . Here ’ s the code for this function : 
 int   CDeviceList : : Initialize ( ) 
 S e tupDiEnumDeviceInterfaces ( info ,   NULL ,   & m _ guid , 
 S e t u p D iGetDeviceInterfaceDetail ( info ,   & ifdata ,   NULL ,   0 , 
 S e t u p D iGetDeviceInterfaceDetail ( info ,   & ifdata ,   detail , 
 if   ( ! S e t u p D i GetDeviceRegistryProperty ( info ,   & did , 
 SPDRP _ FRIENDLYNAME ,   NULL ,   ( PBYTE )   fname , 
 sizeof ( fname ) ,   NULL ) 
 &&   ! S e t u p D i GetDeviceRegistryProperty ( info ,   & did , 
 NULL ,   ( PBYTE )   fname ,   sizeof ( fname ) ,   NULL ) ) 
 This statement opens an enumeration handle that we can use to find all devices that have registered an interface that uses the same GUID . 
 Here we call S e tupDiEnumDeviceInterfaces in a loop to find each device . 
 The only two items of information we need are the detail information about the interface and information about the device instance . The detail is just the symbolic name for the device . Since it ’ s variable in length , we make two calls to S e t u p D iGetDeviceInterfaceDetail . The first call determines the length . The second call retrieves the name . 
 We obtain a friendly name for the device from the registry by asking for either the FriendlyName or the DeviceDesc . 
 We create a temporary instance named e of the CDeviceListEntry class , using the device ’ s symbolic name as both the link name and the friendly name . 
 You might be wondering how the registry comes to have a FriendlyName for a device . The INF file you use to install your device driver ? see Chapter 15 ? can have an HW section that specifies registry parameters for the device . You can provide a FriendlyName as one of these parameters , but bear in mind that every instance of your hardware will have the same name if you do . The MAKENAMES sample describes a DLL - based way of defining a unique friendly name for each instance . You can also write a CoInstaller DLL that will define unique friendly names . 
 If you don ’ t define a FriendlyName , by the way , most system components will use the DeviceDesc string in the registry . This string originates in the INF file and will usually describe your device by manufacturer and model . 
 The DEVINTERFACE sample is a user - mode program that enumerates all instances of all known device interface GUIDs on your system . One way to use this sample is as a way to determine which GUID you need to enumerate to find a particular device . 
 Other Global Device Initialization 
 You need to take some other steps during AddDevice to initialize your device object . I ’ m going to describe these steps in the order you should do them , which isn ’ t exactly the same order as their respective logical importance . I want to emphasize that the code snippets in this section are even more fragmented than usual ? I ’ m going to show only enough of the entire AddDevice routine to establish the surrounding context for the small pieces I ’ m trying to illustrate . 
 Initializing the Device Extension 
 The content and management of the device extension are entirely up to you . The data members you place in this structure will obviously depend on the details of your hardware and on how you go about programming the device . Most drivers would need a few items placed there , however , as illustrated in the following fragment of a declaration : 
 I find it easiest to mimic the pattern of structure declaration used in the official DDK , so I declared this device extension as a structure with a tag as well as a type and pointer - to - type name . 
 You already know that you locate your device extension by following the DeviceExtension pointer from the device object . It ’ s also useful in several situations to be able to go the other way ? to find the device object given a pointer to the extension . The reason is that the logical argument to certain functions is the device extension itself ( since that ’ s where all of the per - instance information about your device resides ) . Hence , I find it useful to have this DeviceObject pointer . 
 I ’ ll mention in a few paragraphs that you need to record the address of the device object immediately below yours when you call I o AttachDeviceToDeviceStack , and LowerDeviceObject is the place to do that . 
 A few service routines require the address of the PDO instead of some higher device object in the same stack . It ’ s very difficult to locate the PDO , so the easiest way to satisfy the requirement of those functions is to record the PDO address in a member of the device extension that you initialize during AddDevice . 
 Whichever method ( symbolic link or device interface ) you use to name your device , you ’ ll want an easy way to remember the name you assign . In this code fragment , I ’ ve declared a Unicode string member named ifname to record a device interface name . If you were going to use a symbolic link name instead of a device interface , it would make sense to give this member a more mnemonic name , such as linkname . 
 I ’ ll discuss in Chapter 6 a synchronization problem affecting how you decide when it ’ s safe to remove this device object by calling IoDeleteDevice . The solution to that problem involves using an IO _ REMOVE _ LOCK object that needs to be allocated in your device extension as shown here . AddDevice needs to initialize that object . 
 You ’ ll probably need a device extension variable to keep track of the current Plug and Play state and current power states of your device . DEVSTATE is an enumeration that I ’ m assuming you ’ ve declared elsewhere in your own header file . I ’ ll discuss the use of all these state variables in later chapters . 
 Another part of power management involves remembering some capability settings that the system initializes by means of an IRP . The devcaps structure in the device extension is where I save those settings in my sample drivers . 
 The initialization statements in AddDevice ( with emphasis on the parts involving the device extension ) would be as follows : 
 In this code snippet , STOPPED and DEVICE _ EXTENSION are things I defined in one of my own header files . 
 Initializing the Default DPC Object 
 Many devices signal completion of operations by means of an interrupt . As you ’ ll learn when I discuss interrupt handling in Chapter 7 , there are strict limits on what your interrupt service routine ( ISR ) can do . In particular , an ISR isn ’ t allowed to call the routine ( IoCompleteRequest ) that signals completion of an IRP , but that ’ s exactly one of the steps you ’ re likely to want to take . You utilize a deferred procedure call ( DPC ) to get around the limitations . Your device object contains a subsidiary DPC object that can be used for scheduling your particular DPC routine , and you need to initialize it shortly after creating the device object : 
 Setting the Buffer Alignment Mask 
 Devices that perform DMA transfers work directly with data buffers in memory . The HAL might require that buffers used for DMA be aligned to some particular boundary , and your device might require still more stringent alignment . The AlignmentRequirement field of the device object expresses the restriction ? it ’ s a bit mask equal to 1 less than the required address boundary . You can round an arbitrary address down to this boundary with this statement : 
 You round an arbitrary address up to the next alignment boundary like this : 
 In these two code fragments , I used SIZE _ T casts to transform the pointer ( which may be 32 bits or 64 bits wide , depending on the platform for which you ’ re compiling ) into an integer wide enough to span the same range as the pointer . 
 IoCreateDevice sets the AlignmentRequirement field of the new device object equal to whatever the HAL requires . For example , the HAL for Intel x 86 chips has no alignment requirement , so AlignmentRequirement is 0 initially . If your device requires a more stringent alignment for the data buffers it works with ( say , because you have bus - mastering DMA capability with a special alignment requirement ) , you want to override the default setting . For example : 
 if   ( MYDEVICE _ ALIGNMENT   -   1   >   fdo -> AlignmentRequirement ) 
 I ’ ve assumed here that elsewhere in your driver is a manifest constant named MYDEVICE _ ALIGNMENT that equals a power of 2 and represents the required alignment of your device ’ s data buffers . 
 Your device might well use other objects that need to be initialized during AddDevice . Such objects might include various synchronization objects , linked list anchors , scatter / gather list buffers , and so on . I ’ ll discuss these objects , and the fact that initialization during AddDevice would be appropriate , in various other parts of this book . 
 Initializing the Device Flags 
 Two of the flag bits in your device object need to be initialized during AddDevice and never changed thereafter : the DO _ BUFFERED _ IO and DO _ DIRECT _ IO flags . You can set one ( but only one ) of these bits to declare once and for all how you want to handle memory buffers coming from user mode as part of read and write requests . ( I ’ ll explain in Chapter 7 what the difference between these two buffering methods is and why you ’ d want to pick one or the other . ) The reason you have to make this important choice during AddDevice is that any upper filter drivers that load afterwards will be copying your flag settings , and it ’ s the setting of the bits in the topmost device object that ’ s actually important . Were you to change your mind after the filter drivers loaded , they probably wouldn ’ t know about the change . 
 Two of the flag bits in the device object pertain to power management . In contrast with the two buffering flags , these two can be changed at any time . I ’ ll discuss them in greater detail in Chapter 8 , but here ’ s a preview . DO _ POWER _ PAGABLE means that the Power Manager must send you IRP _ MJ _ POWER requests at interrupt request level ( IRQL ) PASSIVE _ LEVEL . ( If you don ’ t understand all of the concepts in the preceding sentence , don ’ t worry ? I ’ ll completely explain all of them in later chapters . ) DO _ POWER _ INRUSH means that your device draws a large amount of current when powering on , so the Power Manager should make sure that no other inrush device is powering up simultaneously . 
 Building the Device Stack 
 Each filter and function driver has the responsibility of building up the stack of device objects , starting from the PDO and working upward . You accomplish your part of this work with a call to I o AttachDeviceToDeviceStack : 
 NTSTATUS   AddDevice ( ...,   PDEVICE _ OBJECT   pdo ) 
 The first argument to I o AttachDeviceToDeviceStack ( fdo ) is the address of your own newly created device object . The second argument is the address of the PDO . The second parameter to AddDevice is this address . The return value is the address of whatever device object is immediately underneath yours , which can be the PDO or the address of some lower filter device object . Figure 2 - 21 illustrates the situation when there are three lower filter drivers for your device . By the time your AddDevice function executes , all three of their AddDevice functions have already been called . They have created their respective FiDOs and linked them into the stack rooted at the PDO . When you call I o AttachDeviceToDeviceStack , you get back the address of the topmost FiDO . 
 I o AttachDeviceToDeviceStack might conceivably fail by returning a NULL pointer . For this to occur , someone would have to remove the physical device from the system at just the point in time when your AddDevice function was doing its work , and the PnP Manager would have to process the removal on another CPU . I ’ m not even sure these conditions are enough to trigger a failure . ( Or else the driver under you could have forgotten to clear DO _ DEVICE _ INITIALIZING , I suppose . ) You would deal with the failure by cleaning up and returning STATUS _ DEVICE _ REMOVED from your AddDevice function . 
 Figure 2 - 21 . What I o AttachDeviceToDeviceStack returns . 
 Clear DO _ DEVICE _ INITIALIZING 
 Pretty much the last thing you do in AddDevice should be to clear the DO _ DEVICE _ INITIALIZING flag in your driver object : 
 While this flag is set , the I / O Manager will refuse to attach other device objects to yours or to open a handle to your device . You have to clear the flag because your device object initially arrives in the world with the flag set . In previous releases of Windows NT , most drivers created all of their device objects during DriverEntry . When DriverEntry returns , the I / O Manager automatically traverses the list of device objects linked from the driver object and clears this flag . Since you ’ re creating your device object long after DriverEntry returns , however , this automatic flag clearing won ’ t occur , and you must do it yourself . 
 Here is a complete AddDevice function , presented without error checking or annotations and including all the pieces described in the preceding sections : 
 sizeof ( DEVICE _ EXTENSION ) ,   NULL , 
 TAG AddDevice 
 WDM / Architecture 2009 . 06 . 08 15 : 39 
 시스템 을 부팅 을 하 기 위해서 는 리콜 을 해야 한다 
 0000 : 7 c 00 h 마스터 부트 레코드 ( MBR ) 에 전달 하 고 있 다고 신호 를 보내 면 작업 을 시작 한다 . 이 를 트리거 라고 한다 . . 
 MBR 은 아래 와 같 은 구조 를 가진다 . . 
 TAG MBR , sysboot 
 WDM / Architecture 2009 . 05 . 20 16 : 39 
 The filter manager is installed with Windows , but it becomes active only when a minifilter driver is loaded . The filter manager attaches to the file system stack for a target volume . A minifilter driver attaches to the file system stack indirectly , by registering with the filter manager for the I / O operations the minifilter driver chooses to filter . 
 A legacy filter driver ' s position in the file system I / O stack relative to other filter drivers is determined at system startup by its load order group . For example , an antivirus filter driver should be higher in the stack than a replication filter driver , so it can detect viruses and disinfect files before they are replicated to remote servers . Therefore , filter drivers in the FSFilter Anti - Virus load order group are loaded before filter drivers in the FSFilter Replication group . Each load order group has a corresponding system - defined class and class GUID used in the INF file for the filter driver . 
 Like legacy filter drivers , minifilter drivers attach in a particular order . However , the order of attachment is determined by a unique identifier called an altitude . The attachment of a minifilter driver at a particular altitude on a particular volume is called an instance of the minifilter driver . 
 A minifilter driver ' s altitude ensures that the instance of the minifilter driver is always loaded at the appropriate location relative to other minifilter driver instances , and it determines the order in which the filter manager calls the minifilter driver to handle I / O . Altitudes are allocated and managed by Microsoft . 
 The following figure shows a simplified I / O stack with the filter manager and three minifilter drivers . 
 Simplified I / O Stack with Filter Manager and Minifilter Drivers 
 A minifilter driver can filter IRP - based I / O operations as well as fast I / O and file system filter ( FSFilter ) callback operations . For each of the I / O operations it chooses to filter , a minifilter driver can register a preoperation callback routine , a postoperation callback routine , or both . When handling an I / O operation , the filter manager calls the appropriate callback routine for each minifilter driver that registered for that operation . When that callback routine returns , the filter manager calls the appropriate callback routine for the next minifilter driver that registered for the operation . 
 For example , assuming all three minifilter drivers in the above figure registered for the same I / O operation , the filter manager would call their preoperation callback routines in order of altitude from highest to lowest ( A , B , C ) , then forward the I / O request to the next - lower driver for further processing . When the filter manager receives the I / O request for completion , it calls each minifilter driver ' s postoperation callback routines in reverse order , from lowest to highest ( C , B , A ) . 
 For interoperability with legacy filter drivers , the filter manager can attach filter device objects to a file system I / O stack in more than one location . Each of the filter manager ' s filter device objects is called a frame . From the perspective of a legacy filter driver , each filter manager frame is just another legacy filter driver . 
 Each filter manager frame represents a range of altitudes . If a legacy filter driver is attached to the file system stack , the system attaches a filter manager frame for the range of altitudes above and below the legacy filter driver , to ensure that minifilter drivers attach at the correct location . 
 If a minifilter driver is unloaded and reloaded , it is reloaded at the same altitude in the same frame from which it was unloaded . 
 The following figure shows a simplified I / O stack with a two filter manager frames , minifilter driver instances , and a legacy filter driver . 
 Simplified I / O Stack with Two Filter Manager Frames and a Legacy Filter Driver 
 원문 : http : / / msdn . microsoft . com / en - us / library / aa 488191 . aspx 
 TAG Filter , Filter Manager 
 WDM / Architecture 2009 . 05 . 11 13 : 50 
 The Microsoft Windows operating systems use a network architecture that is based on the seven - layer networking model developed by the International Standards Organization ( ISO ) . Introduced in 1978 , the ISO Open Systems Interconnection ( OSI ) Reference model describes networking as " a series of protocol layers with a specific set of functions allocated to each layer . Each layer offers specific services to higher layers while shielding these layers from the details of how the services are implemented . A well - defined interface between each pair of adjacent layers defines the services offered by the lower layer to the higher one and how those services are accessed . " 
 TAG Network , OSI Model 
 WDM / Architecture 2009 . 04 . 21 09 : 24 
 Intel 80386 Protected Mode 
 0 . 서문 
 현재 Linux 가 돌 고 있 는 시스템 중 의 대부분 은 Intel IA 32 CPU 이 다 . Linux 는 Intel 80386 부터 시작 하 여 80486 , Pentium 계열 의 CPU 에서 실행 이 된다 . 운영 체제 의 기능 을 구현 하 려면 CPU 의 지원 을 필요 로 하 는데 , Linux 는 80386 부터 등장 한 32 - bit 보호 모드 ( protected mode ) 의 지원 을 이용 하 여 , 메모리 관리 , 프로세스 관리 등 을 하 고 있 다 . 여기 서 는 Linux 를 구현 하 기 위해 필요 한 보호 모드 의 기능 들 을 간단히 살펴보 도록 한다 . 
 1 . 실제 모드 ( Real Mode ) 
 실제 모드 는 x 86 계열 로 처음 등장 한 Intel 8086 CPU 와 같 은 동작 모드 를 말 하 는 것 으로 , 16 bit CPU 인 8086 , 80286 에서 뿐 만 아니 라 , 80386 이후 의 모든 32 - bit 프로세서 에서 도 이 를 지원 한다 . x 86 계열 의 모든 CPU 는 처음 시작 할 때 는 실제 모드 로 동작 한다 . 
 실제 모드 에서 는 20 bit address bus 를 사용 하 여 총 1 MB 의 메모리 를 사용 할 수 있 으며 , 16 bit register 를 사용 한다 . 레지스터 의 크기 가 16 bit 이 기 때문 에 , 20 bit 주소 를 나타내 기 위해 segment register 라는 것 을 도입 하 였 다 . 이 는 16 bit segment register 와 16 bit offset 을 중첩 시켜서 20 bit 의 주소 를 만들 어 내 는 것 이 다 . 모든 메모리 접근 에 는 segment 와 offset 이 같이 필요 하 며 , 하나 의 segment 를 사용 하 면 64 K ( 0 x 10000 ) 만큼 의 메모리 를 사용 할 수 있 다 . 실제 모드 에서 는 가상 메모리 라는 개념 이 존재 하 지 않 으며 , segment 와 offset 으로 만들 어 지 는 주소 는 바로 물리 적 인 메모리 주소 이 다 . 또한 이 모드 에서 동작 하 는 모든 프로그램 은 메모리 의 어떤 영역 이 든지 맘대로 접근 할 수 있 으며 , cli ( clear interrupt ) , sti ( set interrupt ) 같 은 명령어 를 포함 하 여 실제 모드 에서 사용 할 수 있 는 모든 명령어 들 을 모두 사용 할 수 있 다 . 
 IBM - PC 는 8086 CPU 를 이용 하 여 만들 어 졌 는데 , 이것 이 처음 등장 하 던 당시 엔 1 MB 의 메모리 는 상당히 큰 것 이 었 다 . 그래서 IBM 은 앞 의 640 KB ( 0 - 0 x 9 ffff ) 만 을 프로그램 이 사용 할 수 있 게 하 고 , 나머지 384 KB ( 0 xa 0000 - 0 xfffff ) 는 BIOS 와 ISA 장치 용 으로 사용 하 게 하 였 는데 , 이 는 실제 모드 로 동작 하 는 각종 프로그램 들 이 640 KB 의 메모리 만 을 사용 할 수 밖 에 없 는 제약 을 만들 었 다 . 일반 적 으로 리눅스 부팅 을 할 때 사용 되 는 LILO 도 실제 모드 로 시작 하 여 리눅스 커널 을 로드 하 기 때문 에 , 마찬가지 로 640 KB 의 제약 을 받 게 된다 . 
 그림 1 - 1 . 실제 모드 에서 의 메모리 변환 과정 ( 주 1 ) 
 그림 1 - 2 . 실제 모드 에서 의 메모리 계산 방법 ( 주 2 ) 
 2 . 보호 모드 ( Protected Mode ) 
 Intel 80286 부터 처음 도입 된 보호 모드 는 80386 에 이르 러서 완성 된 모습 을 보여 지금 에 이르 게 된다 . 80286 의 보호 모드 를 간단히 살펴보 면 , 우선 24 bit address bus 를 사용 하 여 총 16 MB ( 0 x 1000000 ) 의 메모리 를 사용 할 수 있 게 하 였 다 . segment register 는 selector 라는 명칭 으로 바뀌 었 고 , descriptor table 이 라는 것 을 통하 여 16 bit segment 를 24 bit base address 로 바꾸 게 하 였 다 . 이 base address 에 offset 을 더함 으로써 모두 16 MB 의 메모리 를 사용 할 수 있 었 지만 , 80286 역시 16 bit CPU 였 기 때문 에 각 segment 의 크기 는 여전히 64 KB 의 제한 을 가지 게 되 었 다 . 
 이런 제한 을 없애 고 완전 한 32 bit address space 와 32 bit register set 을 제공 하 는 80386 이 등장 하 였 고 , 이후 에 운영 체제 들 은 80386 에서 제공 하 는 보호 모드 의 기능 을 활용 하 게 되 었 다 . 보호 모드 에서 는 CPU 가 제공 하 는 모든 기능 과 명령어 들 을 활용 할 수 있 다 . 보호 모드 에서 는 privilege level 이 라는 것 이 등장 하 는데 , 이 는 프로세서 를 활용 할 수 있 는 권한 정도 를 말 한다 . 이 는 0 부터 3 까지 있 는데 , 0 이 모든 일 을 할 수 있 는 모드 로 일반 적 인 운영 체제 구현 에서 커널 모드 가 이 상태 이 며 , 3 은 응용 프로그램 처럼 사용 자 권한 의 실행 상태 를 나타낸다 . 이 privilege level 을 통하 여 커널 모드 / 사용 자 모드 를 구현 하 게 된다 . 보호 모드 에서 는 32 bit address bus 를 통하 여 4 GB 의 메모리 를 사용 할 수 있 으며 , 메모리 보호 기능 과 , 페이징 ( paging ) 메커니즘 등 을 통해 가상 메모리 를 효율 적 으로 구현 할 수 있 다 . 인터럽트 나 예외 처리 , task switching 등 도 모두 보호 모드 에서 지원 하 는 기능 을 활용 한다 . 
 그림 2 - 1 . 보호 모드 레지스터 와 자료 구조 ( 주 3 ) 
 3 . Intel 80386 Registers 
 일반 목적 의 data register ( 32 bits ) : 
 EAX , EBX , ECX , EDX 
 ESI ( source pointer ) , EDI ( destination pointer ) 
 ESP ( stack pointer ) , EBP ( base pointer ) 
 Segment Register / Selector ( 16 bits ) : 
 CS ( code segment ) , DS ( data segment ) , SS ( stack segment ) 
 ES , FS , GS 
 Flag Register ( 32 bits ) : EFLAGS 
 Instruction pointer : EIP 
 Control Register : 시스템 의 동작 들 을 제어 하 기 위한 레지스터 
 CR 0 : 프로세서 의 상태 와 동작 모드 를 제어 하 는 여러 가지 제어 flag 를 가지 고 있 다 . 대표 적 인 flag 로 는 PG ( Paging . paging 사용 여부 설정 ) , PE ( Protection Enable , 보호 모드 를 사용 하 는지 여부 ) 가 있 다 . 
 CR 1 : reserved 
 CR 2 : page fault 가 발생 하 였을 때 이것 이 발생 한 linear address 를 가지 고 있 다 . 
 CR 3 : page directory 가 시작 하 는 physical address 를 가지 고 있 다 . 
 CR 4 : 아키 텍 쳐 별로 확장 한 여러 가지 flag 들 을 가지 고 있 다 . 
 Descriptor Table Register : 
 GDTR ( Global Descriptor Table Register ) : GDT 의 위치 를 가리키 는 register . LGDT ( Load GDT ) , SGDT ( Store GDT ) 명령 으로 참조 하 고 설정 한다 . 
 LDTR ( Local Descriptor Table Register ) : GDT 안 에 LDT descriptor 를 가리키 는 selector . LLDT ( Load LDT ) , SLDT ( Store LDT ) 명령 으로 참조 하 고 설정 한다 . 내부 적 으로 segment 의 base address 와 segment limit 도 가지 고 있 지만 외부 에서 는 selector 값 만 을 참조 할 수 있 다 . 
 IDTR ( Interrupt Descriptor Table Register ) : IDT 의 위치 를 가리키 는 register LIDT ( Load IDT ) , SIDT ( Store IDT ) 명령 으로 참조 하 고 설정 한다 . 
 Task Register : TR 
 TSS ( Task State Segment ) 가 있 는 위치 를 가리키 는 GDT 내 의 TSS descriptor 를 가리키 는 selector . 내부 적 으로 segment 의 base address 와 segment limit 도 가지 고 있 지만 외부 에서 는 selector 값 만 을 참조 할 수 있 다 . LTR ( Load TR ) , STR ( Save TR ) 명령 으로 참조 하 고 설정 한다 . 
 Test Register : 
 TR 1 : test parity check 
 TR 2 , TR 3 , TR 4 , TR 5 : cache test register 
 TR 6 , TR 7 : TLB ( Translation Look - aside Buffers ) test registers 
 TR 9 , TR 10 , TR 11 : BTB ( Branch Target Buffers ) test registers 
 TR 12 : new feature control 
 Debug Register : 
 DR 0 , DR 1 , DR 2 , DR 3 : debug address register . breakpoint 의 linear address 를 가지 고 있 다 . 
 DR 4 , DR 5 : reserved 
 DR 6 : debug status register 
 DR 7 : debug control register 
 Model Specific Registers ( MSRs ) : 
 4 . 보호 모드 에서 의 메모리 관리 
 보호 모드 에서 는 segmentation 과 paging 메커니즘 을 이용 하 여 메모리 를 관리 한다 . segmentation 은 4 GB 의 메모리 를 segment 라는 단위 로 쪼개 는 것 을 말 한다 . 여기 서 는 16 bit 의 selector 와 32 bit 의 offset 을 이용 하 여 4 GB 범위 안 에 있 는 32 bit 의 선형 주소 ( linear address ) 를 만드 는 일 을 한다 . 이렇게 만들 어 진 선형 주소 가 물리 주소 ( physical address ) 가 되 는 것 이 아니 라 메모리 를 4 KB 단위 로 쪼개 서 관리 하 는 paging mechanism 을 거쳐서 물리 주소 로 변환 된다 . ( control register CR 0 의 PG flag 를 수정 함 으로써 paging 메커니즘 을 사용 하 지 않 을 수 도 있 다 ) 
 그림 4 - 1 . 보호 모드 에서 의 메모리 변환 과정 ( 주 4 ) 
 segment 는 segment descriptor 라는 것 으로 정의 가 된다 . 여기 에 는 segment 의 base address 와 segment limit ( 크기 ) , 그리고 DPL ( descriptor privilege level ) 을 비롯 한 정보 가 들어간다 . ( 여기 에 보 면 segment type 이 라는 것 이 있 다 . 이것 은 이 descriptor 가 나타내 는 것 이 무엇 인지 를 말 하 는 것 이 다 . segment 는 일반 적 인 code / data / stack 일 수 도 있 고 , 뒤 에 나오 는 TSS 일 수도 , gate 일 수 도 있 는데 이 type 을 가지 고 무엇 을 가리키 는 descriptor 인지 구별 할 수 있 으며 , 이 에 따라 descriptor 의 구조 가 달라진다 . ) segment descriptor table 은 이 들 segment descriptor 를 모아 두 고 있 는 것 을 말 하 는데 , 여기 에 는 시스템 전체 에 대한 descriptor table 인 GDT ( global descriptor table ) 과 프로세스 마다 개별 적 으로 정의 하 고 있 는 LDT ( local descriptor table ) 이 있 다 . 이 들 은 모두 메모리 상 에 존재 하 게 되 는데 , GDT 의 위치 는 GDTR ( GDT register ) 가 가리키 고 있 다 . LDT 의 위치 는 LDTR ( LDT register ) 가 가리키 고 있 는데 이 LDTR 은 원래 GDT 에 있 는 한 segment descriptor 를 가리키 고 있 는 selector 이 다 . 즉 GDT 에 는 시스템 에 있 는 모든 LDT 에 대한 segment descriptor 가 들 어 있 으며 , LDTR 은 이 중 현재 프로세스 의 LDT 에 대한 segment descriptor 를 가리키 는 selector 이 다 . 
 16 bit 인 selector 에 는 이 descriptor table 에서 의 index 값 과 , 이것 이 이것 이 GDT 에서 의 index 인지 , LDT 에서 의 index 인지 를 나타내 는 TI ( table indicator ) , 권한 을 나타내 는 RPL ( requestor privilege level ) 이 들 어 있 다 . 이 TI 와 index 를 가지 고 해당 하 는 table 에서 segment descriptor 를 찾 아서 base address 를 구하 게 된다 . 이 값 에 offset 을 합하 면 지정 한 selector 와 offset 에 해당 하 는 linear address 가 만들 어 지 게 된다 . 
 그림 4 - 2 . Selector ( 주 5 ) 
 그림 4 - 3 . Segment Descriptor ( 주 6 ) 
 그림 4 - 4 . descriptor table 참조 ( 주 7 ) 
 그림 4 - 5 . 가상 주소 에서 선형 주소 로 의 변환 ( 주 8 ) 
 이렇게 segmentation 을 거쳐 나온 linear address 는 paging 메커니즘 을 통하 여 physical address 로 변환 이 된다 . paging 은 요즘 의 운영 체제 에서 모두 구현 하 고 있 는 paging 을 지원 하 기 위한 것 이 다 . 이 를 이용 하 여 실제로 하드웨어 적 으로 있 는 메모리 보다 많 은 메모리 를 사용 할 수 있 으며 , 메모리 를 효율 적 으로 사용 할 수 있 으며 , swapping 을 쉽 게 구현 할 수 있 게 된다 . 메모리 는 4 KB 단위 의 page 로 쪼개지 며 , 이 들 은 page directory 와 page table 로 체계 화 된다 . 각 page 들 의 시작 위치 는 page table entry 에 기록 이 되 며 , 각 page table 들 의 위치 는 page directory entry 에 들 어 있 다 . page directory 의 시작 위치 는 control register 중 의 하나 인 CR 3 이 가지 고 있 다 . 앞 에서 넘어온 linear address 는 page directory 에서 의 index , page table 에서 의 index , offset 세 가지 로 쪼개지 며 , 이 들 table 들 을 차례 로 따라가 서 실제 page 의 주소 를 얻 고 , 여기 에 offset 을 더함 으로써 실제 physical address 가 나오 게 된다 . 
 그림 4 - 6 . 선형 주소 에서 물리 주소 로 의 변환 ( 주 9 ) 
 이렇게 가상 주소 가 물리 주소 로 변환 되 는 과정 을 정리 하 면 다음 과 같 다 . 
 그림 4 - 7 . 가상 주소 에서 물리 주소 로 의 변환 ( 주 10 ) 
 5 . 보호 모드 에서 의 태 스 크 관리 
 보호 모드 에서 는 각 task 별 로 TSS ( Task State Segment ) 라는 것 을 관리 한다 . 여기 에 는 하나 의 task 의 상태 - 일반 register , segment register , flag , EIP , stack segment selector , stack pointer , LDT selector , page directory base address 등 - 가 저장 이 되 며 , task switching 이 일어날 때 이전 의 상태 를 자동 으로 여기 에 저장 을 하 며 , 새로운 TSS 에 있 는 상태 가 현재 상태 로 복구 가 된다 . task register 인 TR 은 현재 task 의 TSS 를 가리키 는 selector 이 다 . 각 TSS 는 GDT 에서 TSS descriptor 로 기술 되 며 , TR 은 이 중 에서 현재 TSS 의 descriptor 를 가리키 는 것 이 다 . 
 그림 5 - 1 . Task Register ( 주 11 ) 
 task switching 을 하 는 방법 으로 우선 call 이나 jmp 명령 에 전환 할 TSS selector 를 지정 하 는 것 이 있 다 . 이렇게 하 면 프로세서 는 현재 상태 를 현재 TSS 에 저장 을 하 고 , 새로운 task 의 TSS 로 상태 를 모두 바꾼 후 , 새로운 task 를 실행 한다 . 
 task gate 는 task switching 을 일으키 는 특별 한 descriptor 로서 LDT 나 IDT ( Interrupt Descriptor Table ) 에 들어가 는 descriptor 이 다 . 여기 에 는 TSS descriptor 에 대한 selector 가 들 어 있 다 . task gate 는 interrupt 나 trap 이 발생 하 였을 때 task switching 이 일어나 게 하 는 것 처럼 간접 적 으로 task switching 이 일어나 게 하 거나 , 특정 한 task 들 이 TSS 에 접근 할 수 있 도록 하 기 위해서 사용 한다 . 앞 의 경우 에서 처럼 call 이나 jmp 에 task gate 의 selector 를 지정 을 해 주 면 여기 서 가리키 는 TSS 로 task switching 이 일어난다 . 또한 task gate 가 IDT 에 있 을 때 해당 하 는 interrupt 가 발생 하 면 이 task gate 를 통하 여 해당 하 는 TSS 로 task switching 이 일어나 게 된다 . 
 그림 5 - 1 . Task Gate ( 주 12 ) 
 6 . 보호 모드 에서 의 인터럽트 / 예외 처리 
 인터럽트 나 예외 가 발생 하 면 어떤 것 이 발생 하 였 는지 식별 하 는 번호 가 나오 게 된다 . ( 인터럽트 는 CPU 의 인터럽트 핀 에 의하 여 발생 하 며 , 예외 는 프로세서 가 작업 을 하 는 중 에 잘못 된 일 을 발견 하 거나 - 여기 에 는 fault , trap , abort 가 있 다 -, 프로그래밍 으로 들어가 있 는 코드 에 의하 여 - INT 3 , INT n , BOUND 등 - 에 의하 여 발생 하 는 것 이 다 ) 이 번호 를 vector 라고 부르 는데 이 를 index 삼 아 , IDT ( interrupt descriptor table ) 에 있 는 descriptor 를 찾 아서 이 를 처리 하 게 된다 . IDT 는 interrupt 처리 에 관련 된 descriptor 들 을 모아 둔 것 으로 이 의 위치 는 IDTR ( IDT register ) 가 가지 고 있 다 . 여기 에 들어가 는 descriptor 로 는 task gate , interrupt gate , trap gate 가 있 다 . task gate 는 앞 에서 이야기 한 것 처럼 TSS selector 를 가지 고 있 는 gate 로서 , 해당 하 는 TSS 로 task switching 이 일어나 게 된다 . interrupt gate 와 trap gate 는 GDT / LDT 에 있 는 segment descriptor 에 대한 selector 와 offset 을 가지 고 있 는 gate 로서 , selector 를 가지 고 segment 의 시작 주소 를 얻 고 , 여기 에 offset 을 더 하 여 이 를 처리 할 handler 의 주소 를 얻 게 된다 . 그리고 이 handler 의 위치 로 건너가 서 해당 하 는 처리 를 한 후 이 를 마친 후 에 복귀 하 게 된다 . 이 과정 에서 privilege level 이 바뀔 수 도 있 으며 , 처리 중 에 는 현재 TSS 에 있 는 stack 을 그대로 사용 한다 . interrupt gate 와 trap gate 를 통하 는 경우 의 차이 는 , interrupt gate 를 통하 는 경우 처리 하 는 동안 interrupt 를 금지 하 고 ( flag 의 IF 를 clear ) 처리 를 마치 고 복귀 할 때 다시 이 를 복구 하 지만 , trap gate 를 통하 는 경우 에 는 interrupt 를 금지 하 지 않 는다 . 
 그림 6 - 1 . Interrupt Gate 와 Trap Gate ( 주 13 ) 
 그림 6 - 2 . Interrupt 발생 시 Task Gate ( 주 14 ) 
 7 . 보호 모드 에서 의 시스템 콜 
 시스템 콜 은 사용 자 모드 에서 커널 모드 에서 실행 되 는 코드 를 부르 기 위한 방법 이 다 . 이 를 위해서 위해서 보호 모드 에서 는 call gate 라는 것 을 제공 한다 . call gate 는 다른 privilege level 사이 로 제어 권 을 넘기 는 방법 으로 , segment selector 와 offset 을 가지 고 있 다 . 이 selector 는 GDT / LDT 에 있 는 segment descriptor 를 가리키 고 , 여기 서 얻 어 지 는 base address 에 offset 을 더 하 여 실행 할 함수 의 위치 를 얻 게 된다 . call 이나 jmp 명령 에 목적 하 는 call gate 의 segment selector 를 지정 함 으로써 call gate 를 통하 여 커널 모드 로 진입 하 게 된다 . 이 때 CPL ( current privilege level ) 과 call gate descriptor 의 DPL , call gate selector 의 RPL 을 비교 하 여 권한 을 검사 한다 . 시스템 콜 에 진입 하 였을 때 에 는 현재 task 의 TSS 에 있 는 stack 을 사용 하 며 , ret 명령 을 통해서 결과 값 과 함께 시스템 콜 을 부르 기 이전 의 상태 로 되돌아가 게 된다 . 
 그림 7 - 1 . Call Gate ( 주 15 ) 
 TAG GDT , LDT 
 쫑 경 
 개발 하 면서 도움 되 는 정보 들 을 올릴 생각 입니다 ~ 한 걸음 한 걸음 올라가 는 중 입니다 . . 
 쫑 ` s 개발 이야기 	 ( 88 ) 
 WDM ( 39 ) 
 Blutooth ( 4 ) 
 WDM Tip ( 7 ) 
 책 / 예 제 소스 ( 3 ) 
 개발 에 유용 한 Tip &.. ( 9 ) 
 모바일 보안 가이드 
 모바일 보안 가이드 
 세계 보안 엑스포 
 세계 보안 엑스포 
 CExplorer 1 
 CExplorer 1 컴포넌트 . .. 
 Dynamic - Link Librar . .. 
 보안 시스템 구축 토 . .. 
 모바일 보안 가이드 -... 
 위기 시대 해법 , 보 안 . .. 
 정말 감사 합 니 . .. jin 2013 
 ㄳㄳ . 고맙 습니다 . 2013 
 Thank you very . .. amir 2012 
 감사 합니다 . ㅎ . .. limited 10 2012 
 첨부 에 올려 드 . .. 쫑 경 2012 
 scuttlebutt 360 . .. scuttlebutt 360 . . 2015 
 Belinda Broido . Belinda Broido 2014 
 auriculares bl . .. auriculares bl . . 2014