2016년 11월 28일 월요일

FLT_STREAM_CONTEXT vs FLT_STREAMHANDLE_CONTEXT

몇년만에 드라이버코드를 작성할 일이 있어서 공부중입니다.
파일시스템이나 미니필터는 바닥부터 작성해본적이 없어서 공부하기가 쉽지 않네요.
FLT_STREAM_CONTEXT 가 FLT_STREAMHANDLE_CONTEXT 뭐가 다른건지 헤깔려서 찾아보다가 정리했습니다.


FLT_STREAM_CONTEXT vs FLT_STREAMHANDLE_CONTEXT

STREAM_CONTEXT


  • file stream 마다 붙일 수 있는 context.
  • FILE_OBJECT.FsContext 를 추적하는데 사용.

STREAMHANDLE_CONTEXT


  • 개개의 file open 시 생성되는 file object (I/O 서브시스템이 생성하는)마다 붙일 수 있는 context.
  • FILE_OBJECT 를 추적하는데 사용.



File Streams, Stream Contexts, and Per-Stream Contexts (MSDN 원문)


File Stream


파일 데이터를 저장하는데 사용되는 바이트 시퀀스이다.
보통 파일은 하나의 file stream 을 가지는데, 요걸 파일의 default data stream 이라고 한다.
그러나 multiple data stream 을 지원하는 파일시스템에서는 각각의 파일은 여러개의 file stream 을 가질 수 있다.
그 중 하나는 default data stream 이고, 얘는 unnamed 이다.
다른 놈들은 alternate data stream 이다. 파일을 열면, 실제로는 해당 파일의 스트림을 여는 것이다.


file system 이 최초로 파일을 열때, file control block(FCB) 이나 stream control block (SCB) 같은
file-system-specific stream context 구조체를 생성하고, 이 구조체의 주소를 file object 의 FsContext 멤버에 저장한다.


로컬 파일시스템에서, 이미 열려있는 file streeam 이 다시 열리면 (shared read access 같은 경우로 인해),
I/O 서브시스템은 새로운 file object 를 생성하지만, file system 은 새로운 stream context 를 생성하지는 않는다.
따라서 로컬 파일시스템에서는 stream context pointer 는 file stream 을 식별하는 유니크한 값으로 사용될 수 있다.


per-stream context 를 지원하는 네트워크 파일시스템에서는 이미 열려있는 file stream 이 동일한 network share name 이나 IP 주소 등을
이용해서 다시 열린다면 로컬 파일시스템과 동일하게 동작한다.
I/O 서브시스템은 새로운 file object 를 생성하지만 file system 은 새로운 stream context 를 생성하지 않고,
두 file object 에 동일한 FsContext 포인터를 할당한다.


그러나 file stream 이 서로 다른 경로로 열리는 경우 (다른 share name 또는 IP 가 다른 경우), file system 은
새로운 file stream context 를 생성한다.
그러므로 per-stream context 를 지원하는 네트워크 파일시스템에서는 FsContext 는 file stream 을 구분하는
유일한 값으로 사용될 수 없다.


per-stream contextFSRTL_PER_STREAM_CONTEXT 구조체를 멤버로 포함하는
filter-defined 구조체이다. 필터드라이버는 이 구조체를 file system 이 열어놓은 각각의 file stream 에 대한 정보를 추적하는데 사용한다.


File Sytem Support for Per-Stream Contexts


Microsoft Windows XP 이후, per-stream context 를 지원하는 파일시스템은 FSRTL_ADVANCED_FCB_HEADER구조체를 포함하는
stream context 구조체를 사용해야 한다.


특정 file stream 에 연관된 per-stream context 의 global list 는 file system 이 관리한다.
file system 이 file stream 을 위한 새 stream context (FSRTL_ADVANCED_FCB_HEADER object)를 생성할 때, 이 list 를 초기화하기 위해
FsRtlSetupAdvancedHeader 를 호출한다.
file system filter 드라이버가 FsRtlInsertPerStreamContext 함수를 호출하면, filter 가 생성한 per-steram context 는
그 global list 에 추가된다.


file system 이 file stream 에 대한 stream context 를 삭제하때, filter 가 가진 file stream 에 연관된 모든 per-stream context 를
제거하기 위해 FsRtlTeardownPerStreamContexts 를 호출한다.
이 루틴은 global list 에 있는 각각의 per-stream context 에 대해서 FreeCallback루틴을 호출한다.
FreeCallback 루틴은 file stream 과 연관된 file object 가 이미 소멸되었음을 인지하고 있어야 한다.


file system 이 file object 로 대표되는 file stream 에 대해서 per-stream context 를 지원하는지 확인하려면, 해당 file object 를 통해서
FsRtlSupportsPerStreamContexts를 호출할 수 있다. file system 은 어떤 파일 타입에 대해서는 per-stream context 를 지원할 수 도 있고,
그렇지 않을 수도 있다. 예를들어 NTFS 와 FAT 는 paging file 에 대해서 per-stream context 를 지원하지 않는다.
따라서 FsRtlSupportsPerStreamContexts 함수는 하나의 file stream 에 대해서는 TRUE 를 리턴하지만, 모든 file stream 에 대해서
TRUE 를 리턴하지는 않을것이다.

2016년 10월 11일 화요일

하이퍼바이저 구현을 위한 공부 방법

howto_study_hypervisor

Hypervisor 구현 방법을 공부하기 위한 가이드


레퍼런스, 메뉴얼


당연히 인텔/AMD 메뉴얼입니다. 무식하게 읽으면 어려워서, 중간에 포기하기 십상입니다만, 어느정도 개념이 잡히면 가장 먼저 보게 되는게 메뉴얼입니다. 경험상 인텔메뉴얼 만큼 좋은 자료는 없었습니다. (전 AMD SVM 은 안봐서 모릅니다. 동작 방식은 거의, 완전히 비슷합니다.)
개인적인 경험으로는 인텔메뉴얼의 Virtual Machine Extension 챕터를 세번정도 읽어보니, 머리속에 들어오기 시작하더군요.


온라인 자료



강추! 개인적으로 가장 추천하는 자료입니다. 엔텔메뉴얼과 같이 보면서 공부하셔야 할것입니다. 최소한 여기 나오는 내용을 확실히 다 숙지해야 합니다.



제가 예전에 만들었던 자료입니다. 마지막에 하이퍼바이저를 통해 안티키로거
제품 우회하는 시연영상도 있습니다.



서승현님 이 정리해두신 글입니다. KVM 코드 분석을 시작하시는 분들께 많이 도움될것 같습니다.



  • Series 1 Xen으로 배우는 가상화 기술의 이해 - CPU 가상화
  • Series 2 Xen으로 배우는 가상화 기술의 이해 - 메모리 가상화
  • Series 3 Xen으로 배우는 가상화 기술의 이해 - I/O 가상화

한빛리얼타임에서 PDF 로 구매하실 수 있습니다. Xen 하이퍼바이저 소스코드를 분석하면서 공부한 내용을 정리해서 책으로 출판했네요.
대단하신분들인듯, 하지만 가상머신의 기본 개념이나 VT-x 스펙에 대한 기초지식이 없으면 이해하기 쉽지 않을 것 같네요.


코드



Windows Interernals 의 저자중 한명인 Alex Ionescue 가 만든 하이퍼바이저입니다. 최소한의 어셈블리코드로 엄청나게 간결하게 작성된 멋진 하이퍼바이저입니다. 공부를 위해서라면 최고의 선택이라고 생각합니다.



또다른 멋진 오픈소스 구현입니다.


  • Xen, KVM, VirtualBox

제품이니 당연히 매우 높은 완성도를 보이죠. 개인적으로는 VirtualBox 소스코드가 가장 읽기 편했습니다만, 기본 개념을 이해하지 못한상태에서는 해독 불가능한 암호로 보일겁니다.


  • 기타

요즘은 다양한 하이퍼바이저 구현코드들을 쉽게 찾을 수 있더군요. 검색검색~~


도움이 될만 한 잔소리


아키텍처(x86, x64 같은)/운영체제를 공부해야 합니다


인터럽트 처리, 가상메모리 구현 같은, 리얼모드/보호모드 같은 운영체제 구현에 대한 기초 지식은 필수입니다. 어차피 하이퍼바이저는 운영체제가 직접 다루는 CPU 나 메모리를 가상화 하는 코드를 구현하는 작업입니다. 따라서 컨트롤 레지스터나 인터럽트들을 운영체제들이 어떻게 다루는지 정확히 알아야 제대로 구현하고, 트러블 슈팅이 가능하겠죠.


공부하기 쉽지는 않지만 꽤 재밌는 것들을 할 수 있습니다.


Full Virtualization vs Para-Virtualzation


가상머신을 구현하는 방법에는 Full virtualization 과 Para-Virtualization 으로 나눌 수 있습니다.
Vmware 나 Qemu 가 대표적인 Full Virtualization 제품에 속하고, Xen 이 대표적인 Para-Virtualization 제품에 속했습니다(과거형입니다).


전자는 모든 명령어를 가상화 하는겁니다. 모든 CPU 명령어를 에뮬레이트하기때문에 완전히 새로운 명령어 체계를 사용하는 아키텍처라 해도 가상화 할 수 있겠죠. 하지만 당연히 느립니다.


후자는 꼭 필요한 명령어만 가상화 합니다. 예를들어 아래와 같은 명령어가 있다고 가정합니다.


xor eax, eax
mov cr0, eax

첫번째 명령어는 그냥 범용 레지스터를 다르는 명령어이기 때문에 가상화 하지 않아도 됩니다. 실제 하드웨어를 그대로 사용하기 때문에 당연히 100% 하드웨어 성능을 냅니다. 하지만 두번째 명령어는 컨트롤 레지스터를 갱신해야 하기 때문에 반드시 가상화 해야 합니다.


첫번째 명령어 처럼 하드웨어를 직접 활용하는 걸 Direct execution 이라고 합니다. 이렇게 꼭 필요한 중요한 명령어만 가상화 하는 것을 Para-Virtualization 이라고 합니다. 당연히 Full Virtualization 보다 성능이 좋아지겠죠?


Para-Virtualization 을 구현하기 위해서는 결국 꼭 가상화해야 하는 명령어와 그렇지 않은 명령어를 구분할 수 있어야 합니다. 그래서 커널을 직접 수정하는 방식을 활용했습니다. mov cr0, eax 같은 명령어를 찾아서 가상머신에게 가상화해달라고 요청하도록 커널코드를 직접 수정했습니다. 응용프로그램은 어차피 반드시 가상화 해야 하는 명령어를 사용하지 않으니 별 문제 없겠죠.


이렇게 가상머신에게 해당 명령어를 가상화해달라고 요청하는것을 하이퍼 콜(hyper call) - vm 과 host 간 호출 이라고 합니다.
그래서 Xen 에서 리눅스 올리려면, 전용 리눅스 커널을 사용해야 했습니다.


그런데 Windows 같은 운영체제는 커널을 직접 수정할 수 없기때문에, VT-x/SVM 기술이 나오기 전까지는 Para-Virtualization 방식의 가상머신 제품은 Windows 운영체제를 지원하지 못했습니다. 재밌는건 Vmware 같은 경우 Full Virtualization 방식임에도 불구하고 Xen 같은 제품보다 더 빨랐다는...


아무튼 옛날엔 그랬고, VT-x/SVM 이 나온 이후 가상머신을 만드는 방식이 매우 간편해졌습니다. 간편해 진게 아니라, 간편하게 만든것이죠.
앞에서 얘기했던 반드시 가상화 해야 하는 명령어와 그렇지 않은 명령어를 CPU, 즉 아키텍처에서 알아서 구분해줍니다. 가상머신 개발자는 CPU 가 알려주는 반드시 가상화하라고 통지하는 이벤트를 상황에 맞게 알아서 잘 처리해주면 됩니다. 이걸 VMExit 이라고 합니다.
저는 하이퍼바이저 구현은 VM Exit handler/callback 작성 이라고 표현하곤 합니다.


따라서 하드웨어 가상화 기술이 나온 이후에는 Para-Virtualization 이니 뭐니 하는게 필요가 없죠. 그렇기 때문에 이제는 Xen 도 Windows 를 가상화할 수 있게 된겁니다. 개발하기도 훨씬 쉬워졌죠. 만세~


Nested VM ?


가상머신 안에 또 가상머신 안에 또 가상머신?


무지막지하게 느릴까요? 당연히 느려지기야 하겠지만, 앞에서 얘기한 direct execution 때문에 그렇게 미친듯이 느려지지는 않을것입니다. 하이퍼바이저 개발자 입장에서는 관리할 자료구조가 훨씬 복잡해지고, 메모리 변환등으로 인해 복잡하긴 하겠지만요.


QEMU 에 대해서


QEMU 는 원래 에뮬레이터입니다.
에뮬레이터이기 때문에 호스트가 x86 이라도 x64, ARM 등의 명령어를 에뮬레이트 할 수 있기때문에 다양한 아키텍처를 지원할 수 있죠. 다양한 CPU를 소프트웨어로 에뮬레이트 할 수 있으니까요. 세상에 있는 모든 cpu 를 지원한다죠? 그렇기 때문에 엄청 느렸습니다.


하드웨어 가상화(VT-x, SVM 같은) 기술이 나온 이후, CPU 에뮬레이트는 하드웨어 가상화 기능을 활용하기 시작했습니다. 그게 바로 KVM 이구요. 나머지 하드웨어들은 소프트웨어로 에뮬레이팅 합니다. 다른 Vmware, Virttual Box 같은 가상머신 제품도 마찬가지 입니다.


만일 QEMU로 호스트의 아키텍처와 다른 아키텍처를 가상머신으로 돌리고자 한다면, VT-x 같은 기술을 활용 할 수 없기때문에 100% 에뮬레이팅에 의존 할 수 없을 겁니다. 느리겠죠?


간략히 얘기하자면, QEMU 는 다양한 디바이스/하드웨어에 대한 에뮬레이팅을 담당하고, KVM 은 CPU/Memory 가상화를 담당한다고 보시면 됩니다. 따라서 VT-x/SVM 구현에 관심있는 분이라면 QEMU 코드를 살펴보기보다는 리눅스 커널에 포함된 KVM 구현 코드를 분석하셔야 합니다.