예이 성공했다.

이제 64비트에서 C언어 엔트리로 진입한다.

잘 된다!

이번에 구글 IO 같은 일정이 있어서 24시간 카페에서 사람만나서 밤을 보내기로 했었다.

물론 모두 안와서 혼자서 밤을 보냈지만.

아무튼간 오기로했던 사람들이 다 안와서, 나홀로 카페에서

구석에 처박혀서 코딩을 했다.

이런모습

물론 개발하면서 보던 책은 무거워서 안들고 갔기에, 책없이 OS 개발을 시작했다.

물론, 마지막 남은 64비트 진입 및, C언어 엔트리 진입까지 만들어 보겠다고 시작했다.

즉, 부팅을 완성해보자고 시작했다.

아무튼간, 16비트에서 32비트로 전환하는거를 책보면서 하나하나 이해해가며

했기 때문에, 구조는 비슷할것이라고 생각했고,

이전에, 64비트 관련된 페이지 자료구조 및, 기타등등을 전에 어느정도 완성해놨기 때문에, 간단할거라고 시작했다.

일단 가장 처음 해야할건, 저번에 점프했던 2MB부터의 영역을 엔트리포인트로

두도록 해야하는것이다. 즉, 링크스크립트를 수정해야한다.

뭐 저번에는 시작지점이 0x10200이였으니 그걸 0x200000 으로 변경하면 되겠지

게다가 저번껀 32비트 전용이였으니 x86-64를 지원하게끔 해줘야 할것이다.

처음부터 작성하기보단 기존에 있는걸 좀 뜯어보기로 했다.

/usr/lib/ldscript/elf_x86_64 파일이 64비트를 대상으로하는 링크 스크립트다.

보니까, 지금 쓰고있는 LinkScript32.x 과 그렇게 큰 차이가 나지않아

수정해야할 부분을 찾기위해

diff를 걸었다. 물론, 보기 편하라고 colordiff를 걸었다.

ColorDiff

오? 그렇게 바꿔야 할게 많지 않다.

시작점을 0x200000 으로 두고, 링크스크립트를 작성했다.

책이 없었기 때문에, 감으로 하고 있었는데,

막상 링크스크립트를 작성해 0x200000을 엔트리로 두니까

어??? 64비트 커널은 어셈 엔트리 포인트가 없는건가???

이런생각을 가지게 됬었다.

왜냐면 32비트때는, 0x10000에 EntryPoint.s 를 올리고, 0x10200 으로

호출해 C언어로 진입했기 때문인데,

둘중 하나는 0x200000에서 시작할탠데… 로 고민을 하고

연구를 했다.

책이 없어서 이런거 하나하나를 확인하면서 넘어갔는데,

뭔가 링커에 비밀이 있을것이라 판단했고, 테스트를 진행했다.

makefile를 확인해본결과 역시나,

EntryPoint의 바이너리가 링킹의 가장 첫번째 대상으로 들어가고, 0x200000을 기점으로 시작해 아래에 있는 커널 함수나, 기타등등을 활용하게 된다.

이게 일단, 어느정도 이해가 끝나 실질적으로 개발을 시작했는데, 이제 수정했던건 LinkScript, makefile 등등이였다.

또한, OS 책 커뮤니티에서 보이고 구현된 소스코드에서 보였던 메모리 로드 방식을 가져왔다.

일단 부트로더에 의해 모든 데이터를 불러온다. 차곡차곡 쌓는 느낌. 그 단위는 512Byt

지금 있는 Kernel32의 경우 5섹터가 Kernel64의 경우 1섹터가 나오는데,

이걸 부트로더는 0x10000 위치에 읽어들인다.

아직 책을 못 읽어봐서 왜 이렇게 하는지는 모른다. 곧 돌아가면 읽어보려고

기대하는 중이다.

추측하건데, 남는 공간을 활용하기 위해서이거나, 속도의 이점을 위하기 떄문이라고 생각한다.

이걸 구현하기 위해선 디스크 이미지 앞에 정보를 가지고 있어야 한다.

총 섹터 수 혹은, kernel64 섹터 수

Kernel32 섹터의 수 가 필요하다. 그뒤 그 정보를 이용해 메모리에 올리게 된다.

아무튼, 64Bit커널의 경우 2MB가 되는 지점에 복사해놓는다, 그 후 IA-32e 모드 스위치 후 2메가바이트의 위치로 점프한다.

점프했을때 정상적으로 동작했다면 바로 성공했을 텐데,

이전에 작성했던 코드중에서 오타나, 발견하지 못한 오류로 인해

디버깅에 조금 힘을 썼다.

일단, 페이지 디렉터리 포인터 테이블의 시작 위치가 잘못됬었다.

0x10200 이 아니라 0x102000 임.

//Page.c

	for(int i = 0; i < 64; i++)
	{
		SetPageEntryData(&pdptentry[i], 0, 0x10200 /*0x102000*/
        + i * PAGE_TABLE_SIZE, PAGE_FLAG_DEFAULT, 0);
	}


그리고 상위 어드레스를 구하는 식에

PAGE_DEFAULT_SIZE가 아니라 PAGE_FLAG_DEFAULT 가 들어가 있었다.

젠장

    //Page.c
	DWORD high = (i * (PAGE_FLAG_DEFAULT >> 20) ) >> 12;
	//위 코드는 오류 코드임
    //아래가 올바른 코드
    DWORD high = (i * (PAGE_DEFAULT_SIZE) >> 20) ) >> 12;
	

페이지 관련 설정이 이상하니까 ModeSwitchAndJumpKernel64 함수를 통해 r0 레지스터에서 페이징을 활성화 시키니까 부팅도 이상하고 계속 깜빡깜빡 거렸던것 같다.

아무튼 고치니까 이상 없음.

문제는 아무리 해도 2MB 위치에 있는 엔트리에 점프해도 아무일도 안일어났던것이였다.


ModeSwitchAndJumpKernel64:
	
	
	; Set 1 CR4 Register PAE Bit
	mov eax, cr4
	or eax, 0x20
	mov cr4, eax

	mov eax, 0x100000
	mov cr3, eax
	
	mov ecx, 0xC0000080
	rdmsr
	or eax, 0x0100
	wrmsr
	

	
	;Write Table
	mov eax, cr0
	or  eax, 0xE0000000
	xor eax, 0x60000000
	mov cr0, eax
	
	jmp 0x08:0x200000
	
	;Not Entry
	jmp $
	
	

이코드중 jmp 0x08:0x200000 위치로 점프하는 구문이 있는데,

점프해도 아무일이 안일났었다. 혹시 어디서 문제있나 해서

해당 라인까지 코드가 진행이 안되는지도 테스트해보고

이거저거 의심하며 찾았다.

그중 정확한 단서를 찾아내는데, qemu에서 컨트롤 + R + 2번을 누르면 커멘드쉘이 등장한다.

거기서 주소내의 값 여부를 알 수 있는데

x 0x200000

항상 아무값도 안들어왔다 따라서 복사쪽혹은 데이터 자체가 잘못되었을거라고 생각했다.

혹시, 디스크 이미지에 넣은 데이터가 잘못된건 아닐까 싶어, vim에서 hex 값을 봤는데 정상이였다.

%!xxd

정상임

그리고, qemu를 이용헤 데이터가 복사되기 전까지 잘 올라 오나 보기위해

세그먼트에 512 * 5를 한

0x10A00 부터 차래차래 확인헀는데 데이터는 잘 불러오고 있었다.

copy 함수를 들여다 보았다.



void LoadKernel64ImageToMemory(DWORD _address)
{
	WORD TotalKernelSector 	= *( (WORD*) 0x7C05);
	WORD Kernel32Sector 	= *( (WORD*) 0x7C07);
	
	DWORD* SourceAddress = (DWORD*) 0x10000 + Kernel32Sector * 512;
	DWORD* DestAddress	= (DWORD*) 0x200000; 
	
	for(int i=0; i< 512 * (TotalKernelSector -Kernel32Sector) / 4; i++)
	{
		*DestAddress = *SourceAddress;
		DestAddress++;
		SourceAddress++;
	}

	
}

아 저거구나.

	DWORD* Source = ((DWORD*) (0x10000 + Kernel32Sector * 512));

연산자 우선순위.

젠장, 초보적인 실수를 범했다.

아무튼 저렇게 고치니까.

2MB 지점에 엔트리포인트인 다음 코드를 올려 놓게 되고,


[BITS 64]

SECTION .text

extern __KERNEL_ENTRY

START:

	mov ax, 0x10
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax

	mov ss,  ax
	mov rsp, 0x6FFFF8
	mov rbp, 0x6FFFF8

	call __KERNEL_ENTRY

	jmp $

해당 엔트리는 C 엔트리 함수를 호출하면서


#include "Types.h"

void PrintVideoMemory(int _x, int _y, BYTE _Attribute, const char* _str);

void __KERNEL_ENTRY()
{
	PrintVideoMemory(5,12, 0x0F,"64 bit C Language Kernel.");	
}




void PrintVideoMemory(int _x, int _y, BYTE _Attribute ,const char* _str)
{
	CHARACTER_MEMORY*  = ( CHARACTER_MEMORY* ) 0xB8000;
	
	int i = 0;
	
	Address+= ( _y * 80 ) + _x;
	
	for ( i = 0; _str[i] != 0; i++)
	{
		Address[i].bCharactor = _str[i];
		Address[i].bAttribute = _Attribute;
		
	}
}


위 이미지같이 정상적으로 동작하게 된다!

뭐, 코딩도 코딩 나르대로 오래 했지만 디버깅 시간도 만만찮았다.

아무래도 GDB같은걸 익숙하게 써버릇 해야겟다.

이제 부팅 과정이 다 끝난거 같다.

밤을 꼴딱 새고

이동하면서 글을 이어 이어 이어 쓴거라

문맥흐름도 이상하고 중간중간 끊긴 느낌일 수 도 있다.. 양해 바람.

어떨진 모르겠지만

내가 해둔거 책하고 비교해서 볼생각하니까 좋네.

좀 쉬었다가 이전 포스팅에서 잘못된 값 올려놓은 코드 수정해야지

이제, 시작이다!