오늘 약간 시간이 남아서 Visual Studio 로 커널 만들기에 대해서 생각 해 봤다.
1. 왜 우리는 GCC를 쓰는가? CYGWIN은 뭔데?
왜 GCC를 쓰는지는 아직 이해하지 못했다. 무료라서 그런가? 아니면 LD의 디폴트 링킹이 아니라
사용자 정의 스크립트를 이용한 링킹을 하기 위해서?
CYGWIN은 윈도우에서 돌아가는 리눅스가 아니다.
윈도우에서 리눅스 코드를 컴파일 할 수 있도록 POSIX호환 API를 제공하는것이다.
2. ELF, PE, COFF
이제 우리는 CYGWIN을 이용해서 윈도우에서 윈도우용 코드, 리눅스용 코드를 모두 컴파일 할 수 있게 되었다.
그리고 이러한 코드를 컴파일하면 Object File, 링크 하면 Executable File이 나오게 되는데.
윈도우와 리눅스는 서로 다른 형태의 포맷을 사용한다.
윈도우에서 오브젝트 파일과 실행파일의 포맷은 PE/COFF(Portable-Executable / Common Object File Fomat)
리눅스에서는 ELF, Executable and Linking Format 를 사용한다.
[COFF, ELF, PE/COFF 의 역사]
본래 Object File 포맷의 기원은 Unix의 a.out이었다.
근데 이 포맷은 shared libraries(윈도우에서 DLL), foreign format identification, explicit address linkage를 지원하기에 부족했다.
그리고 Unix-like 시스템들이 AT&T 밖과 안에서 발전함에 따라 이 문제에 대한 해결책이 여러 갈래로 나뉘었다.
AT&T의 경우 이 문제를 해결하기 위해 Unix System V 에서 COFF를 처음으로 도입했다.
근데, 이 방식이 너무도 제한되었고(too limited), 불완전했다.
이를테면 섹션 수에 제한이 있었고, 섹션 이름 길이에도 제한이 있었다.
또한 이 포맷에서 포함된 소스 파일이나 사용하는 디버깅 인포메이션이
C와 같은 실제 프로그래밍 언어를 지원하기 어려웠다. 그래서 실제적으로 구현된 COFF는 표준을 위반할 수 밖에 없었다.
SVR4가 AT&T에서 나오면서, AT&T는 COFF를 ELF로 교환했다.
PE/COFF는 Windows NT용으로 개발되었다. PE/COFF라 불리는 이유는,
Object File을 위해 COFF 헤더를 이용하며, Executable File을 위한 PE 헤더의 구성요소로 COFF를 이용하기 때문이다.
Developed for Windows NT, the PE format (sometimes written as PE/COFF) uses a COFF header for object files, and as a component of the PE header for executable files.
(참조 - http://en.wikipedia.org/wiki/COFF)
(참조 - http://en.wikipedia.org/wiki/Portable_Executable)
(참조 - http://en.wikipedia.org/wiki/Executable_and_Linkable_Format )
사실 이런 포맷들이 하는 일은 각 섹션에 대한 자세한 정보를 포함하고 있는 것 뿐이다.
따라서 PE와 ELF는 서로 기능적으로 비슷할 수 밖에 없다. (참조 - 1http://www.theparticle.com/cs/bc/os/elfpecoff.html)
[이진 호환성과 ABI, Application Binary Interface]
이렇게 운영체제(플랫폼) 마다 상이한 Executable Binary Format을 ABI, Application Binary Interface 라고 한다.
다시말해서, 운영체제에 탑재된 (실행파일을 위한) 로더가 다르다는 것이다.
동일한 마이크로 프로세서의 바이트코드(기계어 코드)라고 해도 로더를 위한 부가정보가 각 포맷에 포함되기 때문에
로더가 어떤 ABI를 지원하느냐에 따라 실행파일의 상호 호환성이 결정된다.
참고로, 바이너리 호환성에 있어서 포맷 뿐만 아니라 System Call 또한 문제가 될 수 있다.
특정 ABI를 위한 로더와 대상 OS를 위한 System call Wapper를 새로 만들었다 하더라도,
System call 의 Protocal이 OS에 따라 다르기 때문에 문제가 된다.
예를 들어 Linux에서는 시스템 콜 인터럽트가 80h고, Win2K는 시스템 콜 인터럽트가 2Eh다.
결국 인터럽트 후킹까지 해야하는 것이다. (인터럽트 후킹 이야기 존나 이해 안됌.) 2
(참조 - http://kldp.org/node/70098)
[ELF, Executable and Linking Format]
ELF에 대해서는 다음을 참조하라.
- http://recipes.egloos.com/5010841 (친절한 임베디드 시스템 개발자 되기, ELF)
- http://recipes.egloos.com/5011946 (ELF 부터 링크까지)
- ELF.pdf
[링크]
추가적으로 링크에 대해서 설명하자면
- 1. 컴파일을 통해 만들어진 오브젝트 파일들의 섹션을 통합하고
- 2. 실제 함수 정의부의 위치와 전역변수들의 위치를 라이브러리 파일과 오브젝트 파일에서
조사한 후 Table로 간직하고 있다가, 사용할때 주소를 삽입해 주는 역할을 한다.
(참조 - http://www.iamroot.org/xe/index.php?document_srl=19360&mid=wiki&act=dispWikiHistory)
(참조 - http://cs.lmu.edu/~ray/notes/x86assembly/)
3. 그래서 ELF가 뭐 어쨌다고? VS 로 커널을 어찌 만들건데?
(참조 - http://wiki.osdev.org/Visual_Studio)
우리가 7장에서 공부 했듯이 사실 OBJCOPY 가 GCC로 컴파일되어 나온
ELF 에서 뽑아내는건 실행에 필요한 데이터들이다.
.TEXT, .DATA .RODATA(= .RDATA) 섹션이 그 3가지다.
Visual Studuio 에서도 컴파일 옵션을 잘 주면 이 3가지 섹션만 나오도록 할 수 있고.
단지 앞부분에 섹션 헤더, 도스 스텁과 같은 쓰잘데기 없는 것들이 붙을 뿐이다.
(Visual Studio는 PE와 COFF만 생성할 수 있다는것을 기억해라.)
그리고 이 부분을 뛰어 넘어서 텍스트 섹션으로 바로 점프할 수 있다면
우리는 Pure Binary, 즉 기계어 코드 부분을 곧바로 실행시킬 수 있다.
'개발자를 위한 나만의 운영체제 만들기' 란 책에서는
이 방법을 사용한다. BootLoader 가 커널을 0x20000에 로드하되, PE부분의 쓸데 없는 정보가
0x0FFF 까지를 차지하므로, 0x21000로 점프하는 것이다.
난 항상 이 책을 보면서
아 시발 0x20000를 엔트리 포인트로 설정하는데, 왜 BootLoader에서는 0x2000:1000, 즉 0x21000으로 점프하는거야?
라고 생각했는데, 이유가 여기 있었던 것이다.(개객기! 책에 설명도 안해놓고)
2013.01.08 수정
[ PE Format의 Address of Entry Point 와도 관련 있다. Text 섹션이 아무리 0x200 시작한다고 해서 0x20200으로 점프 해 봐야
코드 실행 안된다. 링크할때 엔트리 포인트 시작 주소를 0x1000(RVA)로 정했기 때문이다.
1. 따라서 텍스트 섹션을 찾아 내서 0x21000 에 복사 해 주던지(이건 사실 PE를 읽는 커널을 만드는것과 다름 없다. elf-new 찾아가고..시발..)
2. 아니면 코드 섹션의 시작을 0x1000으로 맞추는 수 밖에 없다. 어떻게? 파일 사이즈를 늘려서.
내가 찾아 낸 해답은 전역변수로 const int g[10000]; 하기다. 이거 하면 코드 섹션이 커지더라.
재수정 : 링커 옵션에서 /filealign:0x1000 하면 그냥 됌.
안되면 더 넣고 별 짓 다 하면된다. 코드 섹션의 시작은 파일 사이즈에 따라 다르긴 한데
0x200 부터 시작해서 512bytes, 즉 0x200 단위로 커지는것 같고 맥시멈이 0x1000인것 같다.]
재수정 : 미니멈, 맥시멈은 filealign 링커옵션 값에 따라 달라질 수 있음.
위에 보면, 부트 로더(512bytes 짜리)에서 뒷부분을 읽어서 0x20000 에 올리고
LDRSEG:1000h 로 점프하는데, 위에 매크로에 보면 알겠지만 LDRSEG 는 0x2000, 즉
세그먼테이션 된 값으로 0x20000이다. 다시 말해서 0x21000 으로 점프한다는 것이다.
자, 이제 링커가 0x20000에 뭘 위치하게 하는지 확인 해 보자.
EntryPoint16? 어디서 온거당가요?
entry.asm 에서 PUBLIC 으로 왔다. 이걸 참조하게 한 적이 없는데?
(참고로, PUBLIC은 외부에서 사용할 수 있게 노출하는거고, EXTERN 은 외부에 정의된 내용을 선언하는거다.
C에서의 EXTERN은 두가지 모두로 사용 가능하지만 MASM에서는 구분 해 논 듯 하다.)
참고 -
어셈블리 언어
- 저자
- KIP R. IRVINE 지음
- 출판사
- Pearson Education Korea | 2007-11-22 출간
- 카테고리
- 컴퓨터/IT
- 책소개
- 이 책은 인텔 IA-32 프로세서를 위한 어셈블리 언어 프로그래...
이미 링커 인풋으로 해놨었다. 시발럼아.
그럼 빌드는 언제 해쓰까?
아.. Pre-Build Event 였쿠나!!!.
어쨌든, 이런 방식으로 Entry16(entry.asm내의) 를 0x20000에 로드하고, PE부분의 쓰레기 0x1000을 뛰어 넘어서
Bootloader.asm 에서 0x21000 으로 점프 했으므로. 이 뒷부분 코드를 살펴보자.
GDT 세팅, CR0 세팅 하고
신박하게 $+2 로 jmp 한다. jmp 명령어가 2바이트니까, FJMP16 08h, EntryPoint32 이걸 실행한다는건데
NASM 에서 jmp dword 0x08:EntryPoint32 쯤 되시겠다.
왜 그냥 냅둬도 실행할걸 점프하냐는 독자들의 질문에, 저자는 "파이프라이닝" 을 역 이용해서 RealMode 에서 탑재된 명령어를 싸그리 실행시키고 넘어가기 위함이라고 말한다.
다시 말하자면, 파이프라이닝을 이용하면 미리 실행할 명령어를 가져와서 어찌어찌 처리함으로써 성능을 높일 수 있다.
근데, jmp를 이용하면 어디로 분기할지 알 수 없으므로 파이프 라이닝을 이용할 수 없고, 리얼모드에서 탑재된 명령을 모두 실행한 후에야 jmp 명령어 부분을 실행한다는 것이다. ( 이사람은 실행시점을 말하지만, 내생각에는 파이프라이닝 될 수 있도록 컴파일 되지 못하게, 즉, EntryPoint32가 어디 올라갈지 알 수 없으므로 파이프라이닝을 이용할수 없게끔 바이트코드가 구성되는것이지 않을까 생각해봄.)
그리고 나서 존나 특이한점이 두 개 있는데 MASM은 FAR JMP와 LGDT 명령어를 지원하지 못해서
이 책의 저자가 스스로 바이트코드를 찾아서 매크로로 만들었다. (NASM이 더 우월한듯 ㅎ_ㅎ)
그리고 EntryPoint32 의 마지막 부분에서, C언어로 작성된 첫번째 함수인 chobits_init으로 점프한다. 위에서 언급했듯이
EXTERN 되어 있다.
[Name Mangling]
이때 앞에 '_' 하이픈이 붙는 이유는, 컴파일러가 C파일에는 하이픈을 붙여서 오브젝트 파일에 저장하기(심볼로 노출하고)
때문이다. 이렇게 컴파일러가 함수의 이름을 바꾸는걸 Name Decoration 또는 Name Magling 이라 부르른다.
더 정확히 말하면, chobits_init 함수가 cdecl 방식이기 때문에 하이픈 하나만 붙는거다.
stdcall 을 함수 호출 규약으로 사용하면 다른 형태로 변한다.
(참조 - http://thepassion.tistory.com/61)
(참조 - http://no1rogue.blog.me/30095521394)
(참조 - http://en.wikipedia.org/wiki/Name_mangling 위키)
cdecl 방식은 호출하는 쪽이 스택에 삽입된 인수를 제거하고
stdcall 은 호출 받는 쪽이 스택을 정리한다.
MASM에서는 이를 위해 언어 지정자를 사용할 수 있다.
.MODEL FLAT, C
.MODEL FLAT, STDCALL
와 같이 사용할 언어 지정자를 사용하면, 다른 모듈을 위해 해당 언어가 이해할 수 있는 심볼 형태로 어셈블리 함수를 Export한다.
또한 다른 모듈에서 노출하여 어셈블리 코드에서 사용할 함수를 링커에게 올바르게 알려줄 수 있다.
그리고, 어셈블리 언어에서 C, C++ 함수를 호출하려면 Extern "C" 를 통해 네임 맹글링을 제한해야 한다.
(근데 chobits_init엔 그런거 없던데.. 아마도 그냥 cdecl이라 그런가 봄.)
(참조 - 인텔 어셈블리언어 챕터8 330p, 514p, 528p)
존나 존나 존나 존나 빙글빙글빙글 돌아왔다.
아니 시발 그럼 처음부터 LGDT, FJMP 같은거 없으면
NASM 쓰고 OUTPUT을 COFF로 출력한다음에 C랑 같이 Visual Studio에서 링크하면 되잖아
이 씨발!!!!!!!!!!!!!!!
참고로 NASM 로 어셈블 할때 OBJ 포맷은 16비트 윈도우, 즉 DOS를 위한 OMF 포맷이고
WIN32 가 PE/COFF 포맷이다.
일반 COFF 포맷은 djgpp 를 위한 것.
(참조 - http://forum.nasm.us/index.php?topic=113.0 실패하고 질문 올린 사람)
(참조 - http://www.nasm.us/doc/nasmdoc7.html : NASM 매뉴얼)
그리고 NASM과 CL, LINK를 같이 쓰는 방법은
http://stackoverflow.com/questions/1023593/how-to-write-hello-world-in-assembler-under-windows
NASM 에서 C함수를 호출하는 방법은
- http://cs.lmu.edu/~ray/notes/x86assembly/ 이거 가 젤 잘나와있음
- http://www.nasm.us/xdoc/2.09.08/html/nasmdoc9.html#section-9.1 (NASM 매뉴얼)
- http://www.cs.uaf.edu/2011/fall/cs301/visual2010/ (NASM with VS2010, 어셈에서 C함수 부르기)
- - http://www.cs.uaf.edu/2010/fall/cs301/lecture/10_13_linker.html
- http://forum.nasm.us/index.php?topic=1029.0 (맨 밑에꺼 읽어보기)
4. Writing multiboot PE Kernels using Visual C++
사실 이 방법을 피하려면, PE 포맷을 읽을 수 있는 커널을 만들어야 한다.
국내 블로그중에 있었던거 같은데, 지금은 찾질 못하겠고
http://www.brokenthorn.com/Resources/OSDevMSVC.html
: 여기서 Executing PE Kernel 을 읽어라
아님 아래 두개의 글을 참고해도 좋겠다.
http://ksrenevasan.blogspot.kr/2005/10/writing-multiboot-pe-kernels-using.html (1)
http://ksrenevasan.blogspot.kr/2005/10/writing-multiboot-pe-kernels-using_03.html (2)
5. C++ 로 커널 만들기
익히 알고 있듯이(위에서 네임 데코레이션이라고 이미 언급했었다.)
오버로딩으로 인해 C++ 에서는 함수 심볼 명이 abc@xyz1234 와 같이 변한다.
이를 방지 하기 위해 Extern C 를 함수 앞에 붙여주면 된다.
Virtual Function을 안쓰고 클래스만 사용할 것이라면 프로젝트 속성을 바꾸지 않아도 된다.
만약 가상함수를 사용한다면, 기본 라이브러리 무시를 선택해야 한다. 3
(참조 -http://charsyam.tistory.com/entry/CharSyam-OS-Kernel-With-C)
(참조 - http://blog.naver.com/PostView.nhn?blogId=yoosy35&logNo=110035983414)
(참조 - http://kldp.org/node/29882 : 리눅스에서 C++로 커널 개발?)
6. 기타 참고
윈도우와 리눅스에서 x86 어셈블리 프로그래밍 하기 NASM, MASM, GAS 차이
- http://cs.lmu.edu/~ray/notes/x86assembly/ (존나 두번세번 읽기)
터보 C의 깊은 곳 : 존나 꼭 두번 세번 읽기
근거리, 원거리, 거대 포인터 - http://www.hackerschool.org/HS_Boards/data/Lib_prog/Tc1.txt
메모리 모델 및 구조 - http://www.hackerschool.org/HS_Boards/data/Lib_prog/Tc2.txt
함수 호출 규약 - http://www.hackerschool.org/HS_Boards/data/Lib_prog/Tc3.txt
어셈블리어 기초 - http://www.hackerschool.org/HS_Boards/data/Lib_prog/Tc4.txt
어셈블리어 기초 2- http://www.hackerschool.org/HS_Boards/data/Lib_prog/Tc5.txt
세그먼트 디렉터 - http://www.hackerschool.org/HS_Boards/data/Lib_prog/Tc6.txt
MASM 디렉티브 및 메모리 모델
- http://www.c-jump.com/CIS77/ASM/Directives/lecture.html
MASM 사용법 및 메모리 모델 등
- http://www.hackerschool.org/HS_Boards/data/Lib_prog/MASM.txt
16비트 MASM, LINK.EXE
- http://www.asmcommunity.net/board/index.php?topic=25659.0
: 16비트에서 어셈블리 코드를 컴파일할때는 /coff 스위치 대신 /omf 를 써야한다는데.. 이유는..?
MSVC 링커 옵션 - http://msdn.microsoft.com/en-us/library/y0zzbyt4(v=vs.80).aspx
MSVC 링커옵션 : 섹션지정 - http://msdn.microsoft.com/ko-kr/library/sf9b18xk(v=vs.80).aspx
MSVC 링커 옵션 서브 시스템 - http://msdn.microsoft.com/en-us/library/fcc1zstk(v=vs.80).aspx
OSDev MASM 위키 - http://wiki.osdev.org/MASM#Microsoft_Reference_For_ML.EXE
NASM Output Format - http://cs.nyu.edu/courses/fall02/V22.0201-001/nasm_doc_html/nasmdoc6.html
MSVC 로 OS 만들기 - http://www.brokenthorn.com/Resources/OSDevMSVC.html
OSDev Visual Studio 위키 - http://wiki.osdev.org/Visual_Studio
[기타 잡동사니]
어셈블리어로 PE분석기 제작
별 영양가 없는 글. IDE만 Visual Studio고 컴파일러로 gcc 사용.
http://gusc.lv/2012/11/im-writing-my-own-os/
http://gusc.lv/2012/11/im-writing-my-own-os-p2/
http://gusc.lv/2012/11/my-os-part-3/
http://gusc.lv/2012/11/im-writing-my-own-os-intermission/
http://gusc.lv/2012/12/myos-mingw-cross-compiler/
yasm + Visual Studio, 뭔가 하긴 하는데 이해할수 엄슴. 별 영양가 없음.
- http://dc0d32.blogspot.kr/2012/02/writing-kernel-in-windows-with-visual.html
-'기타 자료' 카테고리의 다른 글
NASM, C, C++ 로 커널 만들기 (0) | 2013.01.29 |
---|---|
Windbg 정리 (0) | 2013.01.27 |
Windbg + Vmware, Websymbol 세팅 (0) | 2013.01.22 |
각종 디바이스 드라이버 데이터 시트 및 코딩 (0) | 2013.01.22 |
Visual Studio 로 커널 만들기 2 (0) | 2013.01.08 |