IDT.

http://manggong.org/41 여기에서 살짝 언급 했다.

Interrupt descriptor table라고 불리는 놈으로써 Interrupt Vector Table을 구현해 놓은 놈.

이라고 정리했었다. ㅎㅎㅎ 저 글 작성할때, 감정이 다시 돌아 상기 되는듯 하다. 자…

그러면, Interrupt Vector Table 이 무엇인지를 찾아 봐야 겠지??

검색해보자..

….

리얼모드에서는 Interrupt Vector Table(IVT) 이라고 불리우고… 보호모드에서는 Interrupt descriptor table(IDT)이라고 불리우고 .. 또…음???

Interrupt Service Routine(ISR)이 있는 곳을 가르키는 테이블? 이라는데??

흠… 그러면 ISR 은 또 무엇이냐???

이것저것 많은 것들이 검색 되던데 “어떤 인터럽트 발생 시, 발생된 인터럽트가 해야 할 일” 이라고 정리하고 끝내야 겠다. 즉 인터럽트 발생되면 실행되는 애라고 보면 될 것 같다. 그러면 여기서 짚고 넘어가야 할 인터럽트는 정확히 무엇인가??


인터럽트

뭔가 있어 보이는 말이다. ㅋㅋㅋㅋ

위키백과(https://ko.wikipedia.org/wiki/%EC%9D%B8%ED%84%B0%EB%9F%BD%ED%8A%B8)에서는 다음과 같은 정의를 내렸다.

“마이크로프로세서에서 인터럽트(Interrupt:중단, 세치기)란 마이크로프로세서(CPU)가 프로그램을 실행하고 있을 때, 입출력 하드웨어 등의 장치나 또는 예외 상황이 발생하여 처리가 필요할 경우에 마이크로프로세서에게 알려 처리할 수 있도록 하는 것을 말한다.”

인터럽트는 3가지 종류가 있다고 한다.

예외상황 인터럽트, 하드웨어 인터럽트, 시스템 콜.

예외상황 인터럽트는 시스템에 심각한 에러가 생겼을 때 발생되며 예로 어떤 수를 0으로 나누라는 명령어가 실행되었다던지, 페이지 폴트(?) 일어났을 때 발생된다.

인터럽트 번호 발생상황 설명
0

0 으로 나누기 예외

0으로 나누려고 할 경우.

1

디버그

EFLG의 T플래그 설정 및 디버그

2

NMI(Non Maskable Interrupt)

마스킹 불가능한 인터럽트용으로 예약

3

int 3

int 3 명령을 만날경우

4

오버플로우 발생

INTO 명령 실행 시 EFLAGS의 OF 플래그 설정.

5

유효주소 범위 초과

피연산자가 유효주소 범위를 벗어난 경우

6

알수 없는 명령어

잘못된 CPU 명령어 만날 경우.

7

디바이스 사용불가

CR0의 TS설정 상태에서 MMX 명령어를 실행.

8

연속 예외상황 처리 불가능

예외처리 과정중 예외가 또 발생한 경우.

9

Coprocessor 에러

Coprocess 에러 발생

10

무효 TSS 에러

잘못된 TSS를 가지고 태스크전환 할시

11

세그먼트 범위 초과

메모리에 존재하지 않는 세그먼트 참조

12

스택 세그먼트 에러

스택 제한을 벗어나거나 존재하지 않는 메모리 참조

13

일반 보호 에러

보호규약을 위반한 경우

14

페이지 폴트 에러

참조한 페이지가 메모리에 존재 하지 않은 경우

15

정의되지 않음

-

16

X87 부동소수점 에러

부동소수 오버플로우나 0으로 나눈 경우

17

정렬 체크 에러

피연산자의 주소가 정렬되지 않는 경우

18

머신 체크 에러

코드와 소스가 머신과 맞지 않는 경우

19

SIMD 부동소수점 에러

SSE와 SSE2 Floating-point instruction 의해 발생

하드웨어 인터럽트는 주변장치에 뭔가 일이 생겼을 때 발생되며 예로, 키보드가 눌렸다던지 마우스가 조작이 되었을 때 발생된다.

IRQ 설명
0

타이머

1

키보드

2

8259A 슬레이브 컨트롤러

3

두번째 시리얼 포트(COM2/COM4)

4

첫번째 시리얼 포트(COM1/COM3)

5

LPT2

6

플로피 디스크

7

LPT1

8

시스템 클록 RTC

9

할당되어 있지 않음

10

할당되어 있지 않음

11

할당되어 있지 않음

12

PS/2 마우스

13

수치 Coprocessor

14

EIDE Disk 컨트롤러 1

15

EIDE Disk 컨트롤러 2

  하드웨어 인터럽트의 발생을 CPU에 알리는 놈은 8259 고 이전 포스팅에서 적었다.
리얼모드에서는 0~F 까지 (15개 입력라인을 가질 수 있다고 했다.)  IRQ0~IRQ15까지 번호가 지정되었는데 보호모드에서는 IBM 초기 아키텍쳐 설계 실수로 충돌이 난다고 한다.(CPU 인터럽트와 충돌) 그래서 이 IRQ를 옮겨야 한다고 한다.(더 깊은 설명이 있는데… 번역이 대략 난감 ㅡㅡ…) (http://manggong.org/83)
 
또한 위 IRQ와 CPU 기본 인터럽트 번호가 충돌하여 옮겨줘야 한다고 언급 했었다. 영어로 된걸 번역하면서 이해하다 보니 긴글로 오는 피로감으로 넘어갔었는데… 간략하게 이해해 보자면
8259A는 인터럽트 발생 시 IRQ번호 + 내부 Base 로 계산하여 인터럽트 번호를 전달한다고 한다. 즉 내부 Base번호를 설정함으로써 인터럽트 번호 충돌을 회피 한다는 것이다.
 
 

시스템콜은 위 두개와 달리 시스템에서 자동으로 발생되는 것은 아니고 프로그램에서 인위적으로 조작하여 발생시키는 인터럽트이다. 예로 어떤 응용 프로그램이 커널 기능을 사용하기 위하여 커널에게 요청 할때 사용한다.( 지금은 조금 모호하다…^^ )


 

위 3개의 인터럽트들을 IDT 구조에 연속하여 입력하고 이 연속된 테이블 시작 주소를 IDTR 레지스터에 등록하면 인터럽트 처리에 대한 (IVT) 테이블에 정의해 놓고 인터럽트가 발생시 해당 인터럽트 기능을 실행하게 하는게 최종 목표인듯 싶다.

자.. 그러면… IDT 구조를 다시 한번 확인해 보자.

각 항목에 대한 설명은 넘어가고.. 이걸 구조체로 만들면 다음과 같다.

struct _IDT
{
	unsigned short Offset_low;
	unsigned short Selector;
	unsigned char Unused;
	unsigned char Access;
	unsgined short Offset_high;
};

셀렉터는 커널 셀렉터를 입력해 주면 될듯 싶고 인터럽트 번호에 매칭되는 ISR 위치를 Offset에 입력해주면 인터럽트 관련하여 IDT 구성은 끝나는 것 같다.

다음에는 실제 IDT 를 구현해 보는걸로…

 

'프로그래밍 > OS 만들기' 카테고리의 다른 글

OS 만들기 #18 IDT - 3  (0) 2015.08.12
OS 만들기 #17 IDT - 2  (0) 2015.08.10
OS 만들기 #15 커널-키보드 입력  (0) 2015.02.09
OS 만들기 #14 PE 재배치  (0) 2014.03.28
OS 만들기 #13 보호모드 진입  (0) 2014.03.27

요 근래 C# 으로 개발을 집중(?) 하는 탓에 MFC는 손을 놓고 있다가 간단한 윈도우 탐색기를 개발하려고 MFC를 끄집어 들었다.

그러던중, 굉장히 애먹은 작업이 있어서 해결방법이라기는 그렇고 대충 , 유사한 해결방법이 있어서 기록해 놓는다.

CCombobox를 DropDown 방식으로 할 경우, Edit 창이 하나 생기는데 그 창의 크기를 조절하고 싶었다. 그래서 GetWindow(GW_CHILD)로 Edit 창 윈도우 핸들을 얻거나, OnCtlColor함수를 오버로딩 하여 자식창 윈도우 핸들을 얻었다. 그리고 MoveWindow 나, SetWindowPos 함수를 사용하여 윈도우 크기를 조정하였으나, 반응이 없었다. (아… 사소한 작업이 귀찮아 지기 시작하는것이다…)

이리저리 검색해 보니 CCombobox의 폰트 자체를 크게 지정하면 가능하다 하여 지정해봤다.

	comboFont.CreateFont(20, 0, 0, 0, FW_BOLD, FALSE, FALSE, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, DEFAULT_PITCH | FF_SWISS, _T("Arial"));

	SetFont(&comboFont);

에디트 창이 커지고 폰트도 커졌다… 그런데 실제로 원한건 에디트창 영역을 커지기를 원했는데….

그래서 다시 검색…

SetItemHeight(-1, 35);

간단하다 ,

int SetItemHeight(
   int nIndex,
   UINT cyItemHeight 
);

CCombobox 멤버 함수인 SetItemHeight 함수로 그 크기를 조정할 수 있었다. index 인자 부분에 –1, 두번째 인자에는 높이 지정…

원하던 결과는 아니지만, CCombobox 컨트롤 직접 만들지 않은 한은 이 결과에서 만족했다.(귀찮아..)

'프로그래밍 > MFC' 카테고리의 다른 글

WIN32 API프로그래밍에서 MDI에서 자식 생성.  (0) 2013.08.14
MFC 디렉토리 생성  (0) 2013.08.14
쓰레드 생성 (펌글)  (0) 2013.08.14
쓰레드 생성.  (0) 2013.08.14
유니코드  (0) 2013.08.14

보호모드로 진입하고 PE 재배치까지 한 후 메인 함수 호출을 마지막으로 고비는 넘겼다고 생각했다.

그래~ 이제부터 내 세상이닷

…..

화면에 HELLO 라는 문구 하나 출력 뒤에 뭘해야 할지 모르겠다…ㅠㅠ

아~~~휴~~~

뭘 해야 하지???

??? 그런데 왜 키보드 에 타이핑 하면 글자가 안 써지지??

그러고 보니 이제까지 키보드 입력에 관한 건 한번도 처리 안해 봤네???

그래… 키보드 입력 처리 해보자..

자료를 찾아보니 리얼모드에서는 int 21h 로 키보드 입력을 받는다고 한다. 당연히 난 보호모드에 진입되어 있으니 BIOS 함수들은 사용못하겠지.. 그러면 어떻게 해야 하냐??

검색!!!

헉…8259 Programmable Interrupt Controller 이건 모지??

뭐 이건… 뭐만 하려고 하면 굴비 엮듯이 줄줄이 모르는 단어들이 쏟아지는지 ㅡㅡ;;;

흠…

CPU로 인터럽트를 전달하기 위한 칩이란다. Master와 Slave 구조로 되어 있고 Master가 CPU와 연결되어 있단다. Master와 Slave는 각각 8개 입력 라인을 받을 수 있는데 Master 1개 입력 라인이 Slave와 연결되어 있으므로 총 15개 입력 라인을 사용 할수 있다고 한다. CPU가 여러개일 경우 각각 CPU에 연결 할 수 없기 때문에(?) Advanced PIC(APIC)가 나왔고 CPU 자체에 내장된 Local APIC 와 메인보드에 붙어 있는 I/O APIC로 발전 되었다고 하는데… CPU 여러개일 때는 모르겠고 …ㅡㅡ;;;

여튼 키보드에서 키가 입력 되었을 때, 8259 에서는 인터럽트 신호가 발생하고 CPU에서는 하던일을 멈추고 입력된 데이터를 읽는다 라는게 그 흐름 같아 보인다. (뭐 인터럽트에 대한 일반적인….내용).

리얼모드에서는 0~F 까지 (15개 입력라인을 가질 수 있다고 했다.)  IRQ0~IRQ15까지 번호가 지정되었는데 보호모드에서는 IBM 초기 아키텍쳐 설계 실수로 충돌이 난다고 한다.(CPU 인터럽트와 충돌) 그래서 이 IRQ를 옮겨야 한다고 한다.(더 깊은 설명이 있는데… 번역이 대략 난감 ㅡㅡ…)

그럼 어떻게 옮겨???
8259 칩은 0x20 은 Master, 0xA0라는 Slave 명령 IO 포트가 있고 0x21은 master Data IO 포트, 0xA1은 Slave Data IO 포트가 있다.  명령 IO 포트에 명령을 보내고 데이터 IO 포트에 데이터를 보내면 된다고 한다. 그냥 가져다 쓰자 ㅡㅡ;;;;

예제는..

 /* Init master interrupt controller */
outb(0x11, 0x20);       /* Start init sequence */
outb(0x20, 0x21);       /* Vector base */
outb(0x04, 0x21);       
outb(0x01, 0x21);       
outb(0xFF, 0x21);         

/* Init slave interrupt controller */
outb(0x11, 0xA0);       /* Start init sequence */
outb(0x28, 0xA1);       /* Vector base */
outb(0x02, 0xA1);       
outb(0x01, 0xA1);       
outb(0xFF, 0xA1);

이렇다는데… outb 함수 2번째 인자가 IO 포트 이고 1번째 인자가 명령 또는 데이터 값인가 보다. outb 함수가 IO포트에 쓰는(?) 함수인것 같은데 어셈블리어로 구성되어 있고 out 명령어를 사용한다. 이건 또 모지???

out : IO 포트에 데이터를 쓰는 명령어..

예제좀 바꿔야 겠다. outb 함수명 보다는 다른 이름으로~^^

흠… 그냥 인라인 어셈으로 해야 하나??

void Write_port_byte(unsigned short port, unsigned char data)
{
	__asm
	{
        mov		edx, [ebp+8]
        mov		al, [ebp+12]
        out		dx, al
	}
}

void InitPIC()
{
	 /* Init master interrupt controller */
	Write_port_byte(0x20, 0x11);       /* Start init sequence */
	Write_port_byte(0x21, 0x20);       /* Vector base */
	Write_port_byte(0x21, 0x04);       
	Write_port_byte(0x21, 0x01);       
	Write_port_byte(0x21, 0xFF);         

	/* Init slave interrupt controller */
	Write_port_byte(0xA0, 0x11);       /* Start init sequence */
	Write_port_byte(0xA1, 0x28);       /* Vector base */
	Write_port_byte(0xA1, 0x02);       
	Write_port_byte(0xA1, 0x01);       
	Write_port_byte(0xA1, 0xFF);        
}

되는지 안되는지는 모르겠다… 8259 PIC 초기화(?) 는 이정도로 끝마치고 이제.. 키보드 입력을 받는 부분을 만들자…

8259 PIC 의 0x60 포트를 읽으면 키 입력값이 들어온다고 한다.

0x60 포트에서 입력된 키 값은 아스키 코드값이 아니라고 한다.. 이를 다시 아스키 코드로 변환해야 한단다.. 흠.. 아.. 그러고 보니 이제까지 0xB8000 메모리 주소에 아스키 값 써서 화면에 글자 출력 하는 방법을 썼는데 아무래도 printf 함수도 하나 만들어야 겠다. 할게 많네 …갈길이 멀다..

우선 printf 함수 부터…

unsigned char	*Screen = (unsigned char*)0xB8000;

void printf( char *msg )
{

	while(*msg != '\0')
	{
		*Screen++ = *msg++;
		*Screen++ = 7;
	}
}

void putc(char Code)
{
	*Screen++ = Code;
	*Screen++ = 7;
}

전역변수로 Screen 변수를 선언하고 이제 printf() 함수를 호출해서 문자열을 출력하고 putc() 함수를 호출 해서 문자를 호출 하는 함수를 만들었다. 나중에 printf 함수를 손좀 봐서 C Standard library를 구현해야 겠다. 안돼면 가져오는 걸~^^로 하고 지금은 임시…

키보드 스캔코드를 아스키 코드로 변환할 변환 테이블 만들고(만든다긴 보다는 그냥 인터넷 검색.)….

0x60 포트에서 키보드 값 읽어서 화면에 출력… 이걸 무한 반복..

void InitPIC();
unsigned char Read_port_byte(unsigned short port);
void Write_port_byte(unsigned short port, unsigned char data);

unsigned char	*Screen = (unsigned char*)0xB8000;

#define	KEY_F1		0x80
#define	KEY_F2		(KEY_F1 + 1)
#define	KEY_F3		(KEY_F2 + 1)
#define	KEY_F4		(KEY_F3 + 1)
#define	KEY_F5		(KEY_F4 + 1)
#define	KEY_F6		(KEY_F5 + 1)
#define	KEY_F7		(KEY_F6 + 1)
#define	KEY_F8		(KEY_F7 + 1)
#define	KEY_F9		(KEY_F8 + 1)
#define	KEY_F10		(KEY_F9 + 1)
#define	KEY_F11		(KEY_F10 + 1)
#define	KEY_F12		(KEY_F11 + 1)

#define	KEY_INS		0x90
#define	KEY_DEL		(KEY_INS + 1)
#define	KEY_HOME	(KEY_DEL + 1)
#define	KEY_END		(KEY_HOME + 1)
#define	KEY_PGUP	(KEY_END + 1)
#define	KEY_PGDN	(KEY_PGUP + 1)
#define	KEY_LFT		(KEY_PGDN + 1)
#define	KEY_UP		(KEY_LFT + 1)
#define	KEY_DN		(KEY_UP + 1)
#define	KEY_RT		(KEY_DN + 1)
#define	KEY_PRNT	(KEY_RT + 1)
#define	KEY_PAUSE	(KEY_PRNT + 1)
#define	KEY_LWIN	(KEY_PAUSE + 1)
#define	KEY_RWIN	(KEY_LWIN + 1)
#define	KEY_MENU	(KEY_RWIN + 1)

	static const unsigned char kbd_scancode[] =
	{
0,	0x1B,	'1',	'2',	'3',	'4',	'5',	'6',
'7',	'8',	'9',	'0',	'-',	'=',	'\b',	'\t',
'q',	'w',	'e',	'r',	't',	'y',	'u',	'i',

'o',	'p',	'[',	']',	'\n',	0,	'a',	's',
'd',	'f',	'g',	'h',	'j',	'k',	'l',	';',

'\'',	'`',	0,	'\\',	'z',	'x',	'c',	'v',
'b',	'n',	'm',	',',	'.',	'/',	0,	0,
0,	' ',	0,	KEY_F1,	KEY_F2,	KEY_F3,	KEY_F4,	KEY_F5,
KEY_F6,	KEY_F7,	KEY_F8,	KEY_F9,	KEY_F10,0,	0,	KEY_HOME,
KEY_UP,	KEY_PGUP,'-',	KEY_LFT,'5',	KEY_RT,	'+',	KEY_END,
KEY_DN,	KEY_PGDN,KEY_INS,KEY_DEL,0,	0,	0,	KEY_F11,
KEY_F12
	};

void printf( char *msg )
{

	while(*msg != '\0')
	{
		*Screen++ = *msg++;
		*Screen++ = 7;
	}
}

void putc(char Code)
{
	*Screen++ = Code;
	*Screen++ = 7;
}



int  StartKernel(int argc, char *argv[])
{
	char *hello = "Hello OS!";
	unsigned char* vidmem = (unsigned char*)0xB8000;

	printf(hello);

	InitPIC();

	while(1)
	{
		data = Read_port_byte(0x60);
		if( data != 0 )
		{
			putc((char)kbd_scancode[data]);
		}
	}
	return 0;
}

unsigned char Read_port_byte(unsigned short port)
{
	unsigned char KeyCode = 0;

	__asm
	{
		mov		dx, port
		lea		ebx, KeyCode
		in		ax, dx
		mov		[ebx], ax
	}

	return KeyCode;
}

void Write_port_byte(unsigned short port, unsigned char data)
{
	__asm
	{
        mov		dx, [ebp+8]
        mov		al, [ebp+12]
        out		dx, al
	}
}

void InitPIC()
{
	 /* Init master interrupt controller */
	Write_port_byte(0x20, 0x11);       /* Start init sequence */
	Write_port_byte(0x21, 0x20);       /* Vector base */
	Write_port_byte(0x21, 0x04);       
	Write_port_byte(0x21, 0x01);       
	Write_port_byte(0x21, 0xFF);         

	/* Init slave interrupt controller */
	Write_port_byte(0xA0, 0x11);       /* Start init sequence */
	Write_port_byte(0xA1, 0x28);       /* Vector base */
	Write_port_byte(0xA1, 0x02);       
	Write_port_byte(0xA1, 0x01);       
	Write_port_byte(0xA1, 0xFF);        
}

자.. 잘되나 볼까???

뭐지… 그냥 이상한 값 출력 하다가 Virtual PC 창이 닫혀 버린다… 뭐가 잘못 된거지?? 음…계속 키 값이 입력 되는 것인가?? 다시 한번 확인…

음… 값은 키값이 반복되는 모습이다… 그런데 난 키보드 안 눌렀는데?? 키보드 버퍼에 쓰레기 값이 있는건가?? 우선 0x64 에서 현재 키보드 상태값을 읽는 부분이 있다. 출력 버퍼 상태라든지 입력 버퍼 상태 등이 기록된다는데 출력버퍼(출력인지 입력인지 잘 모르겠지만, 키가 입력되었으니 CPU 에게는 출력해야 한다고 가정…)의 상태가 체크 되어 있으면 화면에 출력 하는 루틴으로 수정해 보잣!!

int  StartKernel(int argc, char *argv[])
{
	char *hello = "Hello OS!";
	char *complete = "complete.";
	unsigned char* vidmem = (unsigned char*)0xB8000;
	unsigned char data = 0;

	printf(hello);

	InitPIC();

	while(1)
	{
		while((Read_port_byte(0x64) & 1) == 0 ) ;

		data = Read_port_byte(0x60);
		if( data != 0 )
		{
			putc((char)kbd_scancode[data]);
		}
	}
	return 0;
}

으흐흐흐 잘 된다..

키 하나 입력 될 때마다 하나 하나 화면에 찍힌다…

그런데 키 하나 입력 될때 마다 공백이 생기네??? 음…

아… Key down 과 Key up 이 때문에 key down 때는 글자가 찍히고 Key up은 다른 값으로 찍히나 보다.. 맞나?? 검색…

아.. 키보드에서 a 키를 입력(down)하면 1E 가 발생하고 a 키 UP은 1E | 0x80 = 9E 가 발생한다.. 0x80 비트가 Key down/up 을 나타내는 것 같다. 그러면 저기 코드에서 0x80 비트가 체크 되지 않을 때만 화면에 출력 하는 코드로 수정… 다시 확인.

void InitPIC();
unsigned char Read_port_byte(unsigned short port);
void Write_port_byte(unsigned short port, unsigned char data);

unsigned char	*Screen = (unsigned char*)0xB8000;

#define	KEY_F1		0x80
#define	KEY_F2		(KEY_F1 + 1)
#define	KEY_F3		(KEY_F2 + 1)
#define	KEY_F4		(KEY_F3 + 1)
#define	KEY_F5		(KEY_F4 + 1)
#define	KEY_F6		(KEY_F5 + 1)
#define	KEY_F7		(KEY_F6 + 1)
#define	KEY_F8		(KEY_F7 + 1)
#define	KEY_F9		(KEY_F8 + 1)
#define	KEY_F10		(KEY_F9 + 1)
#define	KEY_F11		(KEY_F10 + 1)
#define	KEY_F12		(KEY_F11 + 1)

#define	KEY_INS		0x90
#define	KEY_DEL		(KEY_INS + 1)
#define	KEY_HOME	(KEY_DEL + 1)
#define	KEY_END		(KEY_HOME + 1)
#define	KEY_PGUP	(KEY_END + 1)
#define	KEY_PGDN	(KEY_PGUP + 1)
#define	KEY_LFT		(KEY_PGDN + 1)
#define	KEY_UP		(KEY_LFT + 1)
#define	KEY_DN		(KEY_UP + 1)
#define	KEY_RT		(KEY_DN + 1)
#define	KEY_PRNT	(KEY_RT + 1)
#define	KEY_PAUSE	(KEY_PRNT + 1)
#define	KEY_LWIN	(KEY_PAUSE + 1)
#define	KEY_RWIN	(KEY_LWIN + 1)
#define	KEY_MENU	(KEY_RWIN + 1)

	static const unsigned char kbd_scancode[] =
	{
0,	0x1B,	'1',	'2',	'3',	'4',	'5',	'6',
'7',	'8',	'9',	'0',	'-',	'=',	'\b',	'\t',
'q',	'w',	'e',	'r',	't',	'y',	'u',	'i',

'o',	'p',	'[',	']',	'\n',	0,	'a',	's',
'd',	'f',	'g',	'h',	'j',	'k',	'l',	';',

'\'',	'`',	0,	'\\',	'z',	'x',	'c',	'v',
'b',	'n',	'm',	',',	'.',	'/',	0,	0,
0,	' ',	0,	KEY_F1,	KEY_F2,	KEY_F3,	KEY_F4,	KEY_F5,
KEY_F6,	KEY_F7,	KEY_F8,	KEY_F9,	KEY_F10,0,	0,	KEY_HOME,
KEY_UP,	KEY_PGUP,'-',	KEY_LFT,'5',	KEY_RT,	'+',	KEY_END,
KEY_DN,	KEY_PGDN,KEY_INS,KEY_DEL,0,	0,	0,	KEY_F11,
KEY_F12
	};

void printf( char *msg )
{

	while(*msg != '\0')
	{
		*Screen++ = *msg++;
		*Screen++ = 7;
	}
}

void putc(char Code)
{
	*Screen++ = Code;
	*Screen++ = 7;
}



int  StartKernel(int argc, char *argv[])
{
	char *hello = "Hello OS!";
	char *complete = "complete.";
	unsigned char* vidmem = (unsigned char*)0xB8000;
	unsigned char data = 0;

	printf(hello);

	InitPIC();

	while(1)
	{
		while((Read_port_byte(0x64) & 1) == 0 ) ;

		data = Read_port_byte(0x60);

		if(data & 0x80)
			continue;
		if( data != 0 )
		{
			putc((char)kbd_scancode[data]);
		}
	}
	return 0;
}

unsigned char Read_port_byte(unsigned short port)
{
	unsigned char KeyCode = 0;

	__asm
	{
		mov		dx, port
		xor		eax, eax
		lea		ebx, KeyCode
		in		ax, dx
		mov		[ebx], ax
	}

	return KeyCode;
}

void Write_port_byte(unsigned short port, unsigned char data)
{
	__asm
	{
                            mov	dx, [ebp+8]
                            mov	al, [ebp+12]
                            out	dx, al
	}
}

void InitPIC()
{
	 /* Init master interrupt controller */
	Write_port_byte(0x20, 0x11);       /* Start init sequence */
	Write_port_byte(0x21, 0x20);       /* Vector base */
	Write_port_byte(0x21, 0x04);       
	Write_port_byte(0x21, 0x01);       
	Write_port_byte(0x21, 0xFF);         

	/* Init slave interrupt controller */
	Write_port_byte(0xA0, 0x11);       /* Start init sequence */
	Write_port_byte(0xA1, 0x28);       /* Vector base */
	Write_port_byte(0xA1, 0x02);       
	Write_port_byte(0xA1, 0x01);       
	Write_port_byte(0xA1, 0xFF);        
}

오케이… 잘된다!!!!

너무 잘되니.. 손이 부들부들… ㅎㅎㅎㅎ

음.. 다 좋은데 키 입력을 이렇게 받을 순 없다.. ㅡㅡ;;;

키입력 관련해서 검색 해 보니 전에 잠깐 언급했던 IDT(Interrupt descriptor table)을 이용해서 키 입력 처리를 하네.. 흠…. 할게 너무 많어 ㅠㅠ

이젠 IDT에 대해서 알아보고 키보드 입력 처리를 해보잣..

'프로그래밍 > OS 만들기' 카테고리의 다른 글

OS 만들기 #17 IDT - 2  (0) 2015.08.10
OS 만들기 #16 IDT - 1  (0) 2015.07.30
OS 만들기 #14 PE 재배치  (0) 2014.03.28
OS 만들기 #13 보호모드 진입  (0) 2014.03.27
OS 만들기 #12 - PE 파일 포맷  (0) 2013.09.10

 

Window class name is not valid.

임베디드 상황에서 C# 으로 개발 했더니, 저런 오류를 토해낸다. 그것도 매번이 아닌가~~끔.

이 에러 메시지의 원인은 폼에서 자식 윈도우(컨트롤)을 생성할 때, 생성 할 수 없을 때 발생한다.

(comctl32.dll 과 관련이 있을지도..)

원인은 글쎄 못 찾겠다..

 

'프로그래밍 > C#' 카테고리의 다른 글

PNG 16bit grayscale 데이터 읽기  (0) 2022.03.15
C# array char to String 변환  (0) 2013.08.14
쓰레드 종료 지연 문제  (0) 2013.08.14
변수 타입  (0) 2013.08.14

어느날 갑자기 노트북(SONY)에 문제가 발생했다.

키보드를 입력만 하면, 인터넷 익스플로어가 얼음 땡!!!!!~~

그래서 크롬을 실행해서 키보드를 아무거나 눌러봤더니 인터넷 익스플로어와 동일한 증상으로 얼음 땡!!~

이에, 키로고 악성 프로그램이 설치된줄 알고 백신이란 백신은 다 설치해서 돌려봐도 검출되지 않았다.


윈도우 서비스 프로그램, 시작 프로그램을 정리를 시작했다. 다~~지웠다..

그래도 동일한 증상이 계속됐다.

Windows를 보호모드로  실행해서 해봤더니, 잘 동작된다…

아~~ 사람 환장하는 줄 알았다. 시원하게 포맷하고 다시 설치할 까도 생각했는데, 기존에 들어 있는 데이터가

지울수 없는 데이터가 많았다.

인터넷창 (MSHTML.DLL) 를 사용하는 프로그램들은 키보드를 입력하면 모두 응답없음. 얼음땡을 토해냈다.

그래서 마이크로소프트사 이놈들이 이번 보안업데이트 관련해서 뭔가를 에러를 내는거라고 생각하고

1달동안 패치한 보안 업데이트들을 다 지웠다.

아~~~ 염병… 보안 패치를 지우고 다시 부팅했다.

작업 취소창.. 어쩌고 저쩌고.. 하면서 보안 패치가 지워지지 않았다. 오히려 더 Windows 가 지저분 해졌다.

아~~~~~~~~~그래서 인터넷 익스플로어를 삭제하려고 시도해봤다.

역시나 안됐다.

 

아~~ 포맷 뿐인가???

라고 생각이 들어서…포기하던차에 인터넷 익스플로어를 관리자 권한 으로 실행해봤다.

 

아~~~~~~~~~~~~~~~~~~~~~악~

키보드 누르면 얼음땡 증상이 없다…

 

이게 뭔가??? 이게 뭔일이야??

 

보안에 신경 쓴다고 Windows UAC 해제 하지 않았더니…

이게 나에게 똥을 던져 줬다..

 

 

현재는 모든 프로그램을 관리자 권한으로 실행한다.

다음 마이크로소프트사의 패치를 기다리며…

 

 

하!~~~ 전 포스트에서 어찌어찌 해서 보호모드 까지 진입 했다. 이제 바람OS 커널을 읽어서 커널로 이동하면 되는데 Visual Studio 툴로 컴파일 하다 보니 PE 포맷이다. ㅠㅠ 아~~~메모리에 커널이미지를 재배치라는 걸 해줘야 커널로 이동 할 수 있다. 젠장 할일 정말 많네…

여튼, 보호모드 까지.. 진입 했으니 커널 이미지를 읽는 작업부터 해야 겠다.

그런데 보호모드에서는 BIOS 인터럽트를 이용 할 수 없으니 , 보호모드에서 파일 읽는 방법을 찾아야 한다.

어디 보자~~~~~~~

음… http://wiki.osdev.org/ATA_read/write_sectors#Read_in_LBA_mode 이 사이트에서 방법을 찾았다. 다만 기존의 만들어 놓은 걸 다시 고쳐서 만들어야 하니.. 그냥… 보호모드 들어가기 전에 커널 이미지를 읽자 ㅡㅡ;;;; 너~~~~~~~~~~~~~~~~~무 귀찮고… 힘들다 ….

그러면.. 기존의 부트로더에서 OS_LDR.SYS 읽는 방법을 그대로 사용…….

;; creator : cyj (mangg@manggong.org)
;; date : 2014. 3. 27
;; desc : 커널 로더.
;;        1. BARAMOS.SYS 파일을 찾아서 읽자.
;;        1. 보호모드로 진입한다.
;;        2. OK 문자 화면에 출력

[bits	16]
org 0x8000

OS_LDR_ENTRY:
	;; CS, DS, ES, & SS을 초기화 하자.
	cli
	xor eax, eax
	mov ds, ax
	mov ss, ax
	mov es, ax
	mov sp, 0xFFFF
	mov bp, sp
	sti

	mov dl,[0x0550]	; 부트 드라이브
	mov [BootDrive], dl

	lea si, [LOADING_MSG]
	call PRINT

	call FAT_INFO

	; RootDirectoryEntry 읽어 첫번째 파일을 읽자.
	mov eax, 0x9000
	mov [TEMP_MEMORY], eax

	mov eax, [RootDirectoryEntry]
	mov [READ_SECTOR], eax
	           
	mov eax, 0x10000000 ; 0x10000 위치
	mov ebx, eax
	mov [FILE_MEMORY], eax

	call READ

	mov ebx, [TEMP_MEMORY]
	add ebx, 0x200
	
	; find file name
	mov ax, [TEMP_MEMORY]
FIND_LOOP:

	cmp eax, ebx
	je NOTFOUND

	add ax, 0x20

	mov si, ax
	mov di, KERNEL_NAME
	mov cx, 11
	repe cmpsb
	jnz FIND_LOOP

	mov ax, [si+9]
	push ax
	mov ax, [si+15]
	push ax
	pop eax
	mov [CurrentCluster], eax

DATA_READ:

	lea si,[DOT]
	call PRINT

	sub eax, 2
	xor ebx, ebx
	mov bl, [BPB_SecPerClus]
	mul ebx
	mov ebx, [RootDirectoryEntry]
	add eax, ebx
	mov [READ_SECTOR], eax
	mov eax, [FILE_MEMORY]
	mov [TEMP_MEMORY], eax

	call READ

	mov eax, [BytsPerCluster]
	mov ebx, [FILE_MEMORY]
	add eax, ebx
	mov [FILE_MEMORY], eax
	xor ebx, ebx

	; FAT 리스트
	mov eax, 0x9000
	mov [TEMP_MEMORY], eax
	mov eax, [CurrentCluster]
	;xor ebx, ebx
	mov bx, [BPB_BytsPerSec]
	shr bx, 2
	xor edx, edx
	div ebx
	push edx
	add eax, [FATsector]
	mov [READ_SECTOR], eax

	call READ

	pop edx

	mov esi, 0x9000
	sal edx, 2
	add esi, edx
	mov eax, [esi]

	mov [CurrentCluster], eax
	cmp eax, [FATEoFMask]
	jz END

	jmp DATA_READ

END:
	lea si, [KERNEL_LOADING]
	call PRINT


    ; GDT 등록
    xor     eax, eax
    lgdt    [GDT_INFO]  

    cli
    mov     eax, cr0
    or      eax, 1
    mov     cr0, eax

    jmp     $+2
    nop
    nop

    jmp     0x08:Entry32

NOTFOUND:
	lea si,[FAIL_MSG]
	call PRINT
	jmp $


; 하드디스크 읽기
READ:
	; Disk Address Packet (DAP)
	xor ax,ax								;INT 13h AH=00h: Reset Disk Drive
	mov dl,BYTE [BootDrive]				;DL = Drive Number
	int 0x13
	jc READ
	mov	al, 0x10 ; 크기
	mov	[0x500], al

	mov al, [BPB_SecPerClus]	; 읽을 섹터 수
	mov	[0x502], ax
	mov eax, [TEMP_MEMORY] ; 데이터가 들어갈 주소
	mov [0x504], eax ; Segment:offset
	mov eax, [READ_SECTOR] ; 접근 할 LBA 번호
	mov	[0x508], eax

	mov ax, 0x500 ; DAP의 Segment:offset
	mov	si, ax
	mov	ax, 0
	mov ds, ax
	mov ah, 0x42 ; LBA 읽기 명령
	mov dl, [BootDrive] ; 장치

	int 0x13

	jc READ_ERROR
	ret

READ_ERROR:
	lea si,[READ_ERR]
	call PRINT

	jmp $

PRINT:
	pusha				; 모든 레지스터를 스택에 넣는다.
PRINT_LOOP:
	mov al, [si]		; al에 글자 하나 저장한다.
	inc si				; si 주소를 증가한다.
	or al, al			; or 연산. 
	jz PRINT_END		; 문자열 끝이 or 연산이 0 이라면. 종료
	mov ah,0x0E			; ah = 0xE
	int 0x10			; 인터럽트 0x10h
	jmp PRINT_LOOP		; 다음 문자 출력 루프	

PRINT_END:
	popa				;모든 레지스터 스택에서 뺀다.
	ret					;호출 한곳으로 리턴.

FAT_INFO:
	pusha

	xor ebx, ebx
	mov eax, 0x9000
	mov [TEMP_MEMORY], eax
	
	xor eax, eax
	mov eax, 0
	mov [READ_SECTOR], eax

	call READ

	xor ebx, ebx
	mov si, [TEMP_MEMORY]
	mov bl, [si+454]
	mov bl, 0x80

	mov eax, 0x9000
	mov [TEMP_MEMORY], eax
	
	mov eax, ebx
	mov [READ_SECTOR], eax

	call READ

	mov si, [TEMP_MEMORY]
	mov bx, [si]

	mov bx, [si + 11]		; BytePerSec
	mov [BPB_BytsPerSec], bx
	xor ebx, ebx
	mov bl, [si + 13]		; SecPerCluster
	mov [BPB_SecPerClus], bl

	xor ecx, ecx
	xor ebx, ebx
	xor eax, eax
	mov cl, [si + 16]    ;  FAT NUM
	mov ebx, [si + 28]	;  Hidden Sector
	mov eax, [si + 36]	;  FAT 32

	mul ecx
	mov cx, [si + 14]		; Reserved Sector

	add eax, ecx
	add eax, ebx
	mov [RootDirectoryEntry], eax

	xor eax, eax
	mov eax, ebx
	add eax, ecx
	mov [FATsector], eax

	xor eax, eax
	xor ebx, ebx
	mov bx, [si + 11]
	mov al, [si + 13]
	mul ebx
	mov [BytsPerCluster], eax

	lea si,[FAT32_OK]
	call PRINT

	popa
	ret


DOT:
	db ".", 0x0D, 0x0A,0
FAT32_OK:
	db "Loading FAT's Info...", 0x0D, 0x0A, 0
KERNEL_LOADING:
	db "Loading BARAMOS.SYS...", 0x0D, 0x0A, 0
READ_ERR:
	db "Can't Read Disk..", 0x0D, 0x0A, 0

KERNEL_NAME:
	db "BARAMOS SYS",0

LOADING_MSG			db 0x0D, 0x0A, "Searching for BARAMOS.SYS...", 0x0D, 0x0A, 0x00
FAIL_MSG			db 0x0D, 0x0A, "ERROR.... ", 0x0D, 0x0A, 0x0A, 0x00
 
BPB_BytsPerSec		dw 0
BPB_SecPerClus		db 1
BootDrive			db 0x00
RootDirectoryEntry	dd 0
FATsector			dd 0
BytsPerCluster		dd 0
FATClusterMask		dd 0
FATEoFMask			dd 0x0FFFFFFF
CurrentCluster		dd 2
TEMP_MEMORY			dd 0x9000
READ_SECTOR			dd 0
FILE_MEMORY			dd 0x10000

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 보호모드 GDT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GDT_INFO:
			dw     GDT_TABLE_END - GDT_TABLE - 1
		    dd     GDT_TABLE

GDT_TABLE:
			dw 0x0000 ; null
			dw 0x0000
			db 0x00
			db 0x00
			db 0x00
			db 0x00
            dw 0xFFFF    ; Code
            dw 0x0000
            db 0x00
            db 0x9A
            db 0xCF
            db 0x00
            dw 0xFFFF    ; Data
            dw 0x0000
            db 0x00
            db 0x92
            db 0xCF
            db 0x00
GDT_TABLE_END:

[bits 32]
Entry32:
	xor		eax, eax
    mov     ax, 0x10
	mov		ds, ax
	mov		es, ax
	mov		ss, ax
    xor     esp, esp
    mov     esp, 0xFFFF

    mov     BYTE [0xB8000], 'O'
    mov     BYTE [0xB8002], 'K'

 hang:
	jmp hang

위와 같이 작성..

이제 BARAMOS.SYS 파일을 만들어야 하는데~~~

음..

동작 까진 필요 없는 테스트이니깐....

Visual Studio 에서 콘솔에 프로그램 임의로 하나 작성..

// testKernel.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//

#include "stdafx.h"


int _tmain(int argc, _TCHAR* argv[])
{
	return 0;
}

Visual Studio 에서 기본 으로 생성해주는 파일 그대로 컴파일 해서 BARAMOS.SYS 로 변경!!!

자 테스트 해보잣!!!

 

os14-1

흐흐흐흐 잘된다…

자~~~~~ 이제 읽은 이미지 데이터를 이제 메모리에 재배치 하잣!!!!

메모리 0x10000위치에 커널 이미지(BARAMOS.SYS) 데이터를 섹션별로 재배치~~~

..

……

…………..

PE포맷은 ImageBase + AddressOfEntryPoint 가 실제 주소란다.. 그렇겠지..ImageBase는 기본 메모리 시작점이라고 했고, AddressOfEntryPoint가 RVA 가상 위치 오프셋 이라고 하니..

근데 … 진짜 근데…보통 실행파일은 0x400000라는데….(?)

그래서 PCVIEW 프로그램으로 내가 예제로 만든 프로그램을 분석해 봤다.

os14-2

역쉬 4M 위치다. 리얼모드 모드에서는 1M 이상은 접근 안된다고 했는데????

아~~~~ 맞다… ㅡㅡ;;; 보호 모드로 넘어가서 재배치 해야 하는거군 … 에휴!!

음. 근데 초기 메모리 레이아웃에는 0x10000 위치에 커널 이미지 올린다고 했는데 … 0x400000으로 바꿔야 하나??? 흠…

구분

위치

부트로더 0x7C00
커널 로더 0x8000
커널 임시 0x10000
커널 재배치 0x400000

자… 이제 커널 재배치를 해보잣!!!!

0x10000 커널 데이터에서 ImageBase를 우선 구해야 하니, 요넘은 IMAGE_NT_HEADER 구조체 IMAGE_OPTIONAL_HEADER32 구조체 안에 있으니… 우선 IMAGE_OPTIONAL_HEADER32 구조체 위치를 찾아야겠다.

IMAGE_DOS_HEADER의 e_lfanew 에 IMAGE_NT_HEADER위치가 기록 되어 있고, IMAGE_OPTIONAL_HEADER32 위치는 e_lfanew + 0x18 에 있네.. 여기서 다시 ImageBase 위치는 아~~~ 그냥 e_lfanew + 0x34 구만..

ImageBase = e_lfanew(IMAGE_DOS_HEADER) + 0x34

PCVIEW 프로그램으로 찾아보니 쉽네 ㅋㅋㅋㅋ

ImageBase를 찾았으니, 섹션만 ImageBase 위치에 순차적으로 복사하면 되겠군..

섹션 첫 위치는 IMAGE_OPTIONAL_HEADER32 구조체의 SizeOfHeaders에 기록되니까 ImageBase 찾는거랑 마찬가지로…

Section = e_lfanew +  0x54

아.. 그런데 섹션위치는 알았는데 얼마만큼 복사하는지 섹션 크기가 알아야 하네… 그리고 섹션이 하나가 아니고 여러개 존재 하니깐…. Section 첫번째 위치는 별 의미가 없다… 흠… 그러면 섹션들 헤더를 열고.. 섹션 크기, 섹션 실제 위치, ImageBase + 섹션 RVA를 구한후 복사해줘야 할듯… 그러자면 섹션이 몇개 있는지에 대한 정보도 필요하다..섹션갯수는 elfanew 에서 …

SectionCount = e_lfanew + 0x06

그리고 각 섹션헤더 위치는 IMAGE_OPTION_HEADER 다음 위치에 있으니깐 IMAGE_FILE_HEADER구조체 SizeOfOptionalHeader가 IMAGE_OPTION_HEADER 크기니깐 ㅡㅡ;;;

SizeOfOptionHeader = e_lfanew + 0x14 구하고,

SectionHeader = e_lfanew + SizeOfOptionHeader + IMAGE_FILE_HEADER크기

IMAGE_FILE_HEADER크기는 0x14 크기니깐..

SectionHeader = e_lfanew + SizeOfOptionHeader + 0x14

에다가 다시 Signature 4바이트 더하면…

SectionHeader = e_lfanew + SizeOfOptionHeader + 0x14 + 0x04

만큼 있다.

아고 힘들어…

이제 섹션 갯수 만큼루프 돌면서 섹션 헤더를 보고 섹션의 실제 저장된 데이터를 ImageBase + 섹션 RVA만큼 위치에다가 복사해주면 된다!!!!

그럼 코딩해 보잣!!!!

;; creator : cyj (mangg@manggong.org)
;; date : 2014. 3. 27
;; desc : 커널 로더.
;;        1. BARAMOS.SYS 파일을 찾아서 읽자.
;;        1. 보호모드로 진입한다.
;;        2. OK 문자 화면에 출력

[bits	16]
org 0x8000

OS_LDR_ENTRY:
	;; CS, DS, ES, & SS을 초기화 하자.
	cli
	xor eax, eax
	mov ds, ax
	mov ss, ax
	mov es, ax
	mov sp, 0xFFFF
	mov bp, sp
	sti

	mov dl,[0x0550]	; 부트 드라이브
	mov [BootDrive], dl

	lea si, [LOADING_MSG]
	call PRINT

	call FAT_INFO

	; RootDirectoryEntry 읽어 첫번째 파일을 읽자.
	mov eax, 0x9000
	mov [TEMP_MEMORY], eax

	mov eax, [RootDirectoryEntry]
	mov [READ_SECTOR], eax
	           
	mov eax, 0x10000000
	mov ebx, eax
	mov [FILE_MEMORY], eax

	call READ

	mov ebx, [TEMP_MEMORY]
	add ebx, 0x200
	
	; find file name
	mov ax, [TEMP_MEMORY]
FIND_LOOP:

	cmp eax, ebx
	je NOTFOUND

	add ax, 0x20

	mov si, ax
	mov di, KERNEL_NAME
	mov cx, 11
	repe cmpsb
	jnz FIND_LOOP

	mov ax, [si+9]
	push ax
	mov ax, [si+15]
	push ax
	pop eax
	mov [CurrentCluster], eax

DATA_READ:

	lea si,[DOT]
	call PRINT

	sub eax, 2
	xor ebx, ebx
	mov bl, [BPB_SecPerClus]
	mul ebx
	mov ebx, [RootDirectoryEntry]
	add eax, ebx
	mov [READ_SECTOR], eax
	mov eax, [FILE_MEMORY]
	mov [TEMP_MEMORY], eax

	call READ

	mov eax, [BytsPerCluster]
	mov ebx, [FILE_MEMORY]
	add eax, ebx
	mov [FILE_MEMORY], eax
	xor ebx, ebx

	; FAT 리스트
	mov eax, 0x9000
	mov [TEMP_MEMORY], eax
	mov eax, [CurrentCluster]
	;xor ebx, ebx
	mov bx, [BPB_BytsPerSec]
	shr bx, 2
	xor edx, edx
	div ebx
	push edx
	add eax, [FATsector]
	mov [READ_SECTOR], eax

	call READ

	pop edx

	mov esi, 0x9000
	sal edx, 2
	add esi, edx
	mov eax, [esi]

	mov [CurrentCluster], eax
	cmp eax, [FATEoFMask]
	jz END

	jmp DATA_READ

END:
	lea si, [KERNEL_LOADING]
	call PRINT


    ; GDT 등록
    xor     eax, eax
    lgdt    [GDT_INFO]  

    cli
    mov     eax, cr0
    or      eax, 1
    mov     cr0, eax

    jmp     $+2
    nop
    nop

    jmp     0x08:Entry32

NOTFOUND:
	lea si,[FAIL_MSG]
	call PRINT
	jmp $


; 하드디스크 읽기
READ:
	; Disk Address Packet (DAP)
	xor ax,ax								;INT 13h AH=00h: Reset Disk Drive
	mov dl,BYTE [BootDrive]				;DL = Drive Number
	int 0x13
	jc READ
	mov	al, 0x10 ; 크기
	mov	[0x500], al

	mov al, [BPB_SecPerClus]	; 읽을 섹터 수
	mov	[0x502], ax
	mov eax, [TEMP_MEMORY] ; 데이터가 들어갈 주소
	mov [0x504], eax ; Segment:offset
	mov eax, [READ_SECTOR] ; 접근 할 LBA 번호
	mov	[0x508], eax

	mov ax, 0x500 ; DAP의 Segment:offset
	mov	si, ax
	mov	ax, 0
	mov ds, ax
	mov ah, 0x42 ; LBA 읽기 명령
	mov dl, [BootDrive] ; 장치

	int 0x13

	jc READ_ERROR
	ret

READ_ERROR:
	lea si,[READ_ERR]
	call PRINT

	jmp $

PRINT:
	pusha				; 모든 레지스터를 스택에 넣는다.
PRINT_LOOP:
	mov al, [si]		; al에 글자 하나 저장한다.
	inc si				; si 주소를 증가한다.
	or al, al			; or 연산. 
	jz PRINT_END		; 문자열 끝이 or 연산이 0 이라면. 종료
	mov ah,0x0E			; ah = 0xE
	int 0x10			; 인터럽트 0x10h
	jmp PRINT_LOOP		; 다음 문자 출력 루프	

PRINT_END:
	popa				;모든 레지스터 스택에서 뺀다.
	ret					;호출 한곳으로 리턴.

FAT_INFO:
	pusha

	xor ebx, ebx
	mov eax, 0x9000
	mov [TEMP_MEMORY], eax
	
	xor eax, eax
	mov eax, 0
	mov [READ_SECTOR], eax

	call READ

	xor ebx, ebx
	mov si, [TEMP_MEMORY]
	mov bl, [si+454]
	mov bl, 0x80

	mov eax, 0x9000
	mov [TEMP_MEMORY], eax
	
	mov eax, ebx
	mov [READ_SECTOR], eax

	call READ

	mov si, [TEMP_MEMORY]
	mov bx, [si]

	mov bx, [si + 11]		; BytePerSec
	mov [BPB_BytsPerSec], bx
	xor ebx, ebx
	mov bl, [si + 13]		; SecPerCluster
	mov [BPB_SecPerClus], bl

	xor ecx, ecx
	xor ebx, ebx
	xor eax, eax
	mov cl, [si + 16]    ;  FAT NUM
	mov ebx, [si + 28]	;  Hidden Sector
	mov eax, [si + 36]	;  FAT 32

	mul ecx
	mov cx, [si + 14]		; Reserved Sector

	add eax, ecx
	add eax, ebx
	mov [RootDirectoryEntry], eax

	xor eax, eax
	mov eax, ebx
	add eax, ecx
	mov [FATsector], eax

	xor eax, eax
	xor ebx, ebx
	mov bx, [si + 11]
	mov al, [si + 13]
	mul ebx
	mov [BytsPerCluster], eax

	lea si,[FAT32_OK]
	call PRINT

	popa
	ret


DOT:
	db ".", 0x0D, 0x0A,0
FAT32_OK:
	db "Loading FAT's Info...", 0x0D, 0x0A, 0
KERNEL_LOADING:
	db "Loading BARAMOS.SYS...", 0x0D, 0x0A, 0
READ_ERR:
	db "Can't Read Disk..", 0x0D, 0x0A, 0

KERNEL_NAME:
	db "BARAMOS SYS",0

LOADING_MSG			db 0x0D, 0x0A, "Searching for BARAMOS.SYS...", 0x0D, 0x0A, 0x00
FAIL_MSG			db 0x0D, 0x0A, "ERROR.... ", 0x0D, 0x0A, 0x0A, 0x00
 
BPB_BytsPerSec		dw 0
BPB_SecPerClus		db 1
BootDrive			db 0x00
RootDirectoryEntry	dd 0
FATsector			dd 0
BytsPerCluster		dd 0
FATClusterMask		dd 0
FATEoFMask			dd 0x0FFFFFFF
CurrentCluster		dd 2
TEMP_MEMORY			dd 0x9000
READ_SECTOR			dd 0
FILE_MEMORY			dd 0x10000

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 보호모드 GDT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GDT_INFO:
			dw     GDT_TABLE_END - GDT_TABLE - 1
		    dd     GDT_TABLE

GDT_TABLE:
			dw 0x0000 ; null
			dw 0x0000
			db 0x00
			db 0x00
			db 0x00
			db 0x00
            dw 0xFFFF    ; Code
            dw 0x0000
            db 0x00
            db 0x9A
            db 0xCF
            db 0x00
            dw 0xFFFF    ; Data
            dw 0x0000
            db 0x00
            db 0x92
            db 0xCF
            db 0x00
GDT_TABLE_END:

[bits 32]
Entry32:
	xor		eax, eax
    mov     ax, 0x10
	mov		ds, ax
	mov		es, ax
	mov		ss, ax
    xor     esp, esp
    mov     esp, 0xFFFF

	; 메모리 재배치 해보자
	mov		esi, 0x10000     ; 커널 이미지 저장된곳
	xor		edx, edx
	xor		eax, eax

	add		edx, 0x3C
	mov		eax, [esi+edx]
	mov		[NT_POINTER], eax ; e_lfanew 위치 구하기(IMAGE_NT_HEADER위치)
	mov		ebx, eax
	mov		edx, 0x34
	add		edx, ebx
	mov		eax, [esi+edx]
	mov		[IMAGE_BASE], eax	; IMAGE_BASE 위치 구하기
	mov		edx, 0x28
	add		edx, ebx
	mov		eax, [esi+edx]
	mov		[ENTRY_POINT], eax		; ENTRY_POINT 위치 구하기
	mov		edx, 0x06
	add		edx, ebx
	mov		ax, WORD [esi+edx]
	mov		[SECTION_COUNT], ax		; 섹션 갯수 구하기
	mov		edx, 0x14
	add		edx, ebx
	mov		ax, WORD [esi+edx]
	mov		[OPTIONHEADER_SIZE], ax		;IMAGE_OPTION_HEADER크기 구하기
	mov		edx, 0x18
	add		dx, WORD[OPTIONHEADER_SIZE]
	add		edx, ebx
	mov		eax, edx
	mov		[SECTION_HEADER], eax	;섹션 헤더 위치 구하기

	xor		ecx, ecx
	mov		cx, [SECTION_COUNT]		; 섹션 갯수 만큼 반복
SECTION_LOOP:
	mov		esi, 0x10000
	mov		ebx, [SECTION_HEADER]		; 섹션 헤더 위치

	mov		edx, 0x0C				; 섹션 RVA = 섹션 헤더 위치 + 0x0C
	add		edx, ebx
	mov		eax, [esi+edx]
	mov		[SECTION_RVA], eax
	mov		edx, 0x10				; 섹션 크기 = 섹션 헤더 위치 + 0x10
	add		edx, ebx
	mov		eax, [esi+edx]
	mov		[SECTION_SIZE], eax
	mov		edx, 0x14				; 섹션 데이터 위치 = 섹션 헤더 위치 + 14
	add		edx, ebx
	mov		eax, [esi+edx]
	mov		[SECTION_POINTER], eax

	; 복사할 원본 위치
	add		esi, [SECTION_POINTER]	; 섹션 데이터가 있는 위치 .

	mov		edi, [IMAGE_BASE]		; 복사될 메모리 위치
	add		edi, [SECTION_RVA]		; IMAGE_BASE 에서 섹션 RVA 만큼 위치

	push	ecx
	cld
	mov		ecx, [SECTION_SIZE]		; 섹션 사이즈 만큼
	repz	movsb					; 복사
	pop		ecx

	mov		eax, [SECTION_HEADER]	; 섹션 헤더 위치를 다음 섹션 헤더 위치로 변경
	add		eax, 40
	mov		[SECTION_HEADER], eax
	loop	SECTION_LOOP

    	mov     BYTE [0xB8000], 'O'
	mov     BYTE [0xB8002], 'K'


	mov		eax, [IMAGE_BASE]
	add		eax, [ENTRY_POINT]
	call	eax


 hang:
	jmp hang


IMAGE_BASE			dd	0
ENTRY_POINT			dd	0
NT_POINTER			dd	0
OPTIONHEADER_SIZE	dw	0
SECTION_COUNT		dw	0
SECTION_HEADER		dd	0
SECTION_RVA			dd	0
SECTION_SIZE		dd	0
SECTION_POINTER		dd	0

PE 메모리 재배치 하는 코드 찾다가 http://1228.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EC%96%B4%EC%85%88%EB%B8%94%EB%A6%AC%EC%BD%94%EB%93%9C%EC%97%90%EC%84%9C-C%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%A0%90%ED%94%84%ED%95%98%EA%B8%B0-%EB%A1%9C%EB%8D%94-%EC%9E%91%EC%84%B1 블로그 꼬꼬넷님 코드를 훔쳤다(?) … 정말 감사드린다……똭!!!

이제 테스트…!!!

헐… 또 재부팅이다… 음…

코드를 몇번을 확인해도 문제는 없어 보인다.

커널이 메모리상에 재배치 되는건 문제가 없어 보인다. 그래서 Visual Studio에서 컴파일 할때 옵션이 잘못된듯 해 보여 또 부랴부랴 검색질.

http://wiki.osdev.org/Visual_Studio

요기 이 사이트에서 하라는대로 컴파일 옵션을 수정했다.

그리고 커널 코드도 바꿨다…

int StartKernel(int argc, char* argv[])
{
    char* hello = "Hello OS!";

    unsigned char* vidmem = (unsigned char*)0xB8000;

    while( *hello != '\0' )        
    {
        *vidmem++ = *hello++;
        *vidmem++ = 7;        // 문자 기본 속성
    }   

	return 0;
}

그리고 컴파일 옵션->고급 에서 진입점을 StartKernel 입력하고 컴파일…

아직까진 간단한 코드니 에러 없이 컴파일 잘 된다!!

자 이제!! 테스트

os14-3

ㅠㅠ

된다… 와 ! 된다 ㅠㅠ

부트로더, 커널로더 까지 많은 시행 착오 및 병행 학습을 많이 했는데 막상 이렇게 기능상 구현이 된 모습을 보니 간단하다… 누가 이걸 보고 많은 지식이 포함되어 있다는걸 알겠누.. 흠..

이제 어려운 과정(?)은 다 끝난것 같다. 물론 남은 일이 더 남아 있지만, 어셈블리어 메뉴얼 뒤져 보면서 한자 한자 출력 해보면서 테스트 하는 작업은 끝난 듯….

OS의 핵심인 커널은 이것보다 험난 할 듯 보이지만.. 적어도 문법적으로 검증해야 하는 난관이 없다는데 행복함을 느낀다.

커널은 부트로더, 커널 로더 처럼 단일 기능이 아니고 필요할 때마다 학습해서 될 문제는 아닌듯 해서 커널 까지는 다시 학습이 필요 할 것 같다…

학 ………………..습.

 


너무 많은 시간을 블로그에 신경을 못썼다. 바쁘다는 핑계도 있고 어쩌면 OS를 만들다가 지쳐 버린 것인지도 모르겠다. 내일 해야지.. 하고 미뤘던게 벌써 몇달이 지나 버렸다. ㅎㅎㅎ

지난 포스트 읽고 다시 복습중….



초기 바람OS는 부트로더에서 바로 커널을 호출 하는 방법을 사용하려고 했었다. OS 이미지 파일에서 부트로더 다음 섹터를 읽는 방법으로 커널을 호출했다. 여기에서 좀 더 확장해서 파일 시스템(FAT32)를 추가해서 부트로더가 KERNEL.SYS 파일을 찾아서 로딩 하는 형태로 구현하려고 했다. 그런데 부트로더의 512바이트 한계에서 파일시스템에서 KERNEL.SYS를 찾아서 로딩 하는 구현부까지는 무리라고 생각되었다. 또 보호모드 진입을 위한 처리를 하기 위해선 부트로더와 커널간 이 처리를 해줘야 하는 무언가가 필요하다고 판단하여 OS_LDR.SYS 라는 커널 로더를 추가 하기로 했다.

부트로더->커널 로더->커널

자… 커널 로더에서 보호 모드를 진입하고 커널을 읽어 주면 된다.

말만 쉬웠다.

보호 모드 진입이 이렇게 어려울 줄은 몰랐다. 또한 커널을 Visual Studio 툴로 개발 하려고 한 난 , 컴파일이 된 생성 파일이 PE포맷 이라는 사실에 좌절하게 되었다. 그래서 부랴 부랴 보호모드와 PE 포맷을 열공 했다. (~~라고 쓰고 그냥 이론서들을 읽는 수준…)

우선, 커널 로더에서 보호 모드로 진입을 해 봐야 겠다.

그전에, 커널 로더와 커널이 메모리 상에 어디에 적재 되는지 정의부터 해야 겠다.

기존 포스트를 보니

부트로더가 0x7C00에 적재되고 FAT 관련 TEMP 데이터는 0x9000을 이용했다. 그리고 커널로더를 0x8000에 저장해 놓은 형태이다. 그러면 음.. 커널을 0x100000 에 놓으면 될까??

구분 메모리 주소
부트로더 0x7C00
커널 로더 0x8000
커널 0x10000

이렇게 대충(?) 성의 없고 계획없는 정의는 해 놓고 커널 로더를 작성하잣!!!!!!!

 

 

- 보호 모드 진입

이제 보호모드 진입하자!!~~ 보호 모드 진입은 CR0 레지스터를 이용하면 진입 할 수 있다고 한다. 흠 CR0 레지스터 넌 또 뭐하는 놈이냐!!!

CR0 레지스터 : 32비트 레지스터로 운영 모드를 변경, 제어하는 컨트롤 레지스터 (X86 프로세서에는 CR0~CR4 등의 5개의 컨트롤 레지스터가 있으며 그중 하나)

Bit 설명 엑세스
31 페이징 활성화 R/W
30 캐시 비활성화 R/W
29 NOT-WriteThrough  이라는데 모르겠음. R/W
28~19 예약  
18 Alignment Mask 몰라. R/W
17 예약  
16 Write Protect (몰라) R/W
15~6 예약  
5 Numeric Error R/W
4 Extension Type R
3 Task Switched R/W
2 Emulation R/W
1 Monitor Coprocessor R/W
0 Protection Enabled (보호 모드 활성화) R/W

요렇게 구성된다는 데 아직 다~~~ 모르겠다. 다만, 0비트 Protection Enabled 여기를 1로 바꾸면 보호 모드로 진입한다는데… 쉽네?

or CR0, 1 하면 되나???

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

CR0은 컨트롤 레지스터로 요케 하면 안되고!! 다른 레지스터를 이용해서 하라는 삽질이 필요하단다 ㅋㅋ

mov EAX, CR0 ; CR0 을 eax 레지스터로 옮기고!
or EAX, 1         ; EAX 값에다가 1을 OR 연산 시킨다.
mov CR0, EAX ; 다시 CR0으로 복사.

 

그러면 한번 해볼까???

;; creator : cyj (mangg@manggong.org)
;; date : 2014. 3. 27
;; desc : 커널 로더.
;;        1. 보호모드로 진입한다.

org 0x8000
bits 16

OS_LDR_ENTRY:

	cli
	;; CS, DS, ES, & SS을 초기화 하자.
	xor eax, eax
	mov ds, ax
	mov ss, ax
	mov es, ax
	mov sp, 0xFFFF
	mov bp, sp
	sti

	mov EAX, CR0
	or  EAX, 1
	mov CR0, EAX

자, 이걸 가상 이미지 파일에 넣고!!!

 

짜잔~~~~~~~!!!

무한 재부팅이다! ㅋㅋㅋㅋ

아~ 재미없어…..

왜 안되지???

아~~~~~~~

GDT 테이블을 우선 GDT 레지스터에 등록해야 한다고 한다.

그럼 GDT 테이블 먼저 만들어야 겠네?.. 지난 포스트(http://manggong.org/41)에서 GDT레지스터에 테이블 위치 넘기는 부분에 대해서 간단히 언급한적이 있다.

struct
{
   unsigned short GDT_SIZE;
   unsigned int GDT_ADDRESS;
} GDT_INFO

LGDT GDT_INFO

GDT_INFO는 GDT 테이블의 크기와 GDT 테이블의 주소를 가지고 있는 구조체.

그러면 GDT_INFO는 이렇게 어셈으로 코딩하고…

LGDT GDT_INFO

GDT_INFO:

GDTSIZE      dw     GDT테이블 크기
GDTADDRESS   dd     GDT_TABLE

GDT 테이블만 만들어 주면 되겠네… GDT 테이블은 GDT 디스크립터 모음이라고 한다고 하니 디스크립터 배열인듯 하다. 전 포스트에 열심히 표로 그려놓은 디스크립터….

os11-1

8바이트로 구성 된 이상한 구조체…

막상 값을 넣으려고 봤더니 뭘 넣어 줘야 할지 막막하다…

그냥 예전에 설명 해 놓은 글을 참고로

Segment Limit(0:15)라는 놈은 세그먼트 크기를 나타낸다고 하고 4GB까지 가능하다고 하니 다 0xFFFF

Base Address(15:0) 라는 놈은 세그먼트 시작 주소라고 하니 음…음…뭘 넣어주지?? 커널 로더가 0x8000에서 시작하니까 그냥 세그먼트 시작 주소를 0x8000 넣어주면 되나??? 음… 아 모르겠다.. 그냥 0x0

Base Address(23:16) 이놈도 그냥 모르겠으니 0x0

Type : 이놈… 코드 세그먼트 인지 데이터 세그먼트 인지 접근 권한 뭐 이런거 있는데 이놈은 뭘 넣어줘야 할지 모르겠다. 실행되고 읽기 지원 하는 놈을 선택했다.(잘한것 같다. 검색해보니 이렇게 하더라 ㅡㅡ ) 0xA 그러고 보니 4비트군 ㅡㅡ;;

S : 이놈 모르겠다…시스템 디스크립터면 0, 걍 디스크립터(코드,데이터) 구분자면 1이라면 넌 그냥 1

DPL : 커널과 유저 접근권한 .. 그래도 너도 0이다.

P : 유효??? 하면 1 유효하지 않으면 0  …넌…넌… 너 모르는데…너도 그냥 1 모르겠다. 그냥 1주자…

그래서 Type,S,DPL, P 요넘들이 1바이트 이니까 전체 설정한 값을 보면 0x9A가 된다.

Segment Limit(19:16) 요놈..그냥 너도 0xF

AVL: 임의의 용도?? 그럼 0

L 요건 64비트면 1 32비트면 0 이라니 난 32비트 0

D/B 이것도 난 32비트 1

G 이건 내가 아까 4G까지 Segment Limit를 쓴다고 했으니 1로 설정해야 한다.

그러면 이것들(AVL, L, D/B, G,SegmentLimit) 조합해보면 11001111 = 0xCF

Base Address(31:24) 이것도 그냥 0

휴~~~ 값 넣기 힘들다…

그럼 다시 정리 하면

Segment Limit : 0xFFFF
Base Address(0~15) : 0x0000
Base Address(15~23) : 0x00
Type,S,DPL,P : 0x9A
Segment Limit, AVL, L, D/B, G : 0xCF
Base Address(24~32) : 0x00

S값을 1로 줬기 때문에 이 디스크립터는 코드 디스크립터 이고 실행과 읽기만 가능하다.

주소는 0~4G까지이다.

코드 디스크립터 이므로 CS 세그먼트에서 참조될 꺼다…..CS 외에 DS,ES 세그먼트에서 사용할 데이터 디스크립터도 만들다…

다 똑같고..

Segment Limit : 0xFFFF
Base Address(0~15) : 0x0000
Base Address(15~23) : 0x00
Type,S,DPL,P : 0x92
Segment Limit, AVL, L, D/B, G : 0xCF
Base Address(24~32) : 0x00

Type만 데이터 디스크립터임을 나타내고 읽고/쓰기 가능으로 바꿨다.

자~~ 이제 GDT 테이블을 다 완성했닷!!!!!!!!!!!!!

그러면..

GDT_TABLE:
                  dw 0xFFFF    ; Code
                  dw 0x0000
                  db  0x00
                  db  0x9A
                  db  0xCF
                  db  0x00
                  dw 0xFFFF    ; Data
                  dw 0x0000
                  db  0x00
                  db  0x9A
                  db  0xCF
                  db  0x00
GDT_TABLE_END

이렇게 GDT_TABLE은 구성될수 있겄다. 하지만… GDT_TABLE 구조체 첫번째 인덱스는 모두 NULL로 채워라고 했다.. (이게 이제 기억이 나네 ..흠…) 그래서 0으로 채우고 전체 소스는

;; creator : cyj (mangg@manggong.org)
;; date : 2014. 3. 27
;; desc : 커널 로더.
;;        1. 보호모드로 진입한다.

org 0x8000
bits 16

OS_LDR_ENTRY:

	;; CS, DS, ES, & SS을 초기화 하자.
	xor eax, eax
	mov ds, ax
	mov ss, ax
	mov es, ax
	mov sp, 0xFFFF
	mov bp, sp
	
    ; GDT 등록
    xor     eax, eax
    lgdt    [GDT_INFO]  
    cli
    mov     eax, cr0
    or      eax, 1
    mov     cr0, eax
	
	jmp		$    ; 무한루프.
	

GDT_INFO:

GDTSIZE      dw     GDT_TABLE_END-GDT_TABLE-1
GDTADDRESS   dd     GDT_TABLE

GDT_TABLE:
			dw 0x0000 ; null
			dw 0x0000
			db 0x00
			db 0x00
			db 0x00
			db 0x00
            dw 0xFFFF    ; Code
            dw 0x0000
            db 0x00
            db 0x9A
            db 0xCF
            db 0x00
            dw 0xFFFF    ; Data
            dw 0x0000
            db 0x00
            db 0x9A
            db 0xCF
            db 0x00
GDT_TABLE_END:

잘 되나 한번 확인!!!

반응이 없다…되었는지 안되었는지 모르겠지만 재부팅은 안된다..ㅎㅎㅎ

보호모드가 성공하면 화면에 출력하는 루틴을 넣어야 겠다.

어랏 , 보호모드에서는 BIOS 인터럽트를 호출 못하네?? 이제까지 int 0x10으로 처리했는데 안된단다.. 그럼 어떻게?? 흠… 전 포스트에서 리얼모드 설명하면 1M 가 다 예약 되어 있다는 글을 쓴적이 있다. 거기에 비디오 메모리 라고 해서.. 컬러, 흑백 주소가 있다고 했는데 거기 0xB8000 컬러 텍스트 비디모 메모리 영역에다가 알파벳 쓰면 출력된다고 한다. 그래?? 쉽겠군 .. 메모리 번지에 아스키 값만 던져주면 되니..

음….

32비트 보호모드로 바꿔 주고 나서 해줘야 하는게 있단다.

CPU 파이프 라인 때문에 mov cr0, eax 다음 명령어를 의미 없는 코드로 바꿔란다.. nop 요걸로..

왜?? CPU가 명령어를 받을 때 mov cr0, eax 명령 처리 한다음 다음 명령어를 읽는게 아니고 미리 명령어를 읽어 두기 때문에.. 그래서 보통

jmp $+2
nop
nop

요렇게 쓴단다.. 뭐 별수 있나.. 남들이 하니 해야지..

그리고 이제까지 리얼 모드에서 돌아가는 코드니깐 [bits 16] 이렇게 컴파일러 한테 알려줬는데 보호모드로 들어가면 32비트 이므로 [bits 32] 라고 해줘야 한단다.. 뭐 그까짓거 하면 되지 ~~~

그리고 제일 중요한데… 주소 어드레싱이 보호모드로 가면 틀려진단다..

리얼모드 일 때는 그냥 선형 주소라서 그냥 쓰면 되는데.. 이제 세그먼트(CS,DS,ES등등) 라고 부른던 놈을 셀렉터 라고 부르고 그 셀렉터는 위에서 정의한 GDT 테이블의 디스크립터를 가리킨다고 한다. 그럼 어떻게 가리킬까 생각 해 보다 GDT 테이블이 배열이고 그 배열 인덱스 번호를???

mov ax, 0x01
mov cs, ax

요렇게 말이지… ㅎㅎ 비슷한데 0x01이 아니란다…

os11-3

요게 셀렉터 모습인데 Index가 3~15비트 부터 이다.. 그래 0x01은 RPL 0비트에 1이라고 설정하는 것 뿐이다. 그러므로 GDT 테이블의 코드 디스크립터를 가리키려면

mov ax, 0x08
mov cs, ax

라고 위와 같이 해줘야 한단다..

os13-1

그리고 주소공간에는 위 그림과 같이 세그먼테이션 작업을 거치고 페이징 작업을 거치면 물리 주소 공간에 읽고/쓴다는데.. ㅎㅎㅎ 복잡하네..

그러니까 세그먼트 셀렉터는 GDT 테이블의 디스크립터를 가르키고 디스크립터는 선형 주소 공간에서 Base Address를 결정하고 다시 여기에서 Offset 만큼 떨어진 위치를 가르킨다. 맞나?? 맞나?? 맞겠지??

페이징은 패스… 아직 모르겠다.. 필요 할때 다시 찾아 봐야 할것 같다. 페이징은 다음에 하고 보호모드 진입하고 “OK” 단어 출력해 보는걸로 오늘은 마무리 해야겠다…

지친다…

;; creator : cyj (mangg@manggong.org)
;; date : 2014. 3. 27
;; desc : 커널 로더.
;;        1. 보호모드로 진입한다.
;;        2. OK 문자 화면에 출력

[bits	16]
org 0x8000

OS_LDR_ENTRY:
	;; CS, DS, ES, & SS을 초기화 하자.
	xor eax, eax
	mov ds, ax
	mov ss, ax
	mov es, ax
	mov sp, 0xFFFF
	mov bp, sp

    ; GDT 등록
    xor     eax, eax
    lgdt    [GDT_INFO]  

    cli
    mov     eax, cr0
    or      eax, 1
    mov     cr0, eax

    jmp     $+2
    nop
    nop

    jmp     0x08:Entry32


GDT_INFO:
			dw     GDT_TABLE_END - GDT_TABLE - 1
		    dd     GDT_TABLE

GDT_TABLE:
			dw 0x0000 ; null
			dw 0x0000
			db 0x00
			db 0x00
			db 0x00
			db 0x00
            dw 0xFFFF    ; Code
            dw 0x0000
            db 0x00
            db 0x9A
            db 0xCF
            db 0x00
            dw 0xFFFF    ; Data
            dw 0x0000
            db 0x00
            db 0x92
            db 0xCF
            db 0x00
GDT_TABLE_END:

[bits 32]
Entry32:
	xor		eax, eax
    mov     ax, 0x10
	mov		ds, ax
	mov		es, ax
	mov		ss, ax
    xor     esp, esp
    mov     esp, 0xFFFF

    mov     BYTE [0xB8000], 'O'
    mov     BYTE [0xB8002], 'K'

 hang:
	jmp hang

대충 이렇게 만들어 주고…

되나???

오~~~~~~~~~~~~잘된다..

 

 

'프로그래밍 > OS 만들기' 카테고리의 다른 글

OS 만들기 #15 커널-키보드 입력  (0) 2015.02.09
OS 만들기 #14 PE 재배치  (0) 2014.03.28
OS 만들기 #12 - PE 파일 포맷  (0) 2013.09.10
OS 만들기 #11 - 리얼모드 & 보호모드  (0) 2013.08.14
OS 만들기 #10  (0) 2013.08.14

아놔!~

업데이트 한다고 한 20분 정도를 기다리게(?) 해 놓더니 인터넷 익스플로어만 실행시키면 죽는다. (바로 죽진 않고 검색하다가 랜덤하게…)

뭐 나야, 크롬을 쓰기 때문에 인터넷 익스플로어 에 의한 블루 스크린이 뭐 대수롭지 않다고 생각했지만….

인터넷 뱅킹 그게 문제였다.. 아무튼

 

IGDPMD64.SYS 에서 블루스크린이 발생한다고 하길래..

구글에 검색들어갔다.

윈도우즈 업데이트 KB2670838 설치후 INTEL 그래픽 가속기 관련해서 문제가 발생하는 거란다.

MSDN 도 사이트를 가보니 SONY 노트북에서 이 호환성 문제가 발생한다고 하네?

아무튼 이 문제를 해결 하기 위해선 KB2670838 을 삭제하라고 한다.

제어판 -> 소프트웨어 -> 업데이트 항목 클릭하면

KB2670838 이게 있을 거란다. 이걸 삭제하면 해결된다고 하는데…..

내 컴퓨터에서는 이게 없었다….

헉… 다른 문제인 것이다….

내가 잘못된 드라이버 설치를 하다가 발생한 문제인지 알고 다 지웠다.

그래도 발생한다… ActiveX 도 지웠다. 똑같다. 바이러스 체크를 해도 검출 안된다…

반 포기 상태로 이것 저것 뒤지다가 인터넷 익스플러어 옵션에서 이상한 항목을 발견했다.

 

ie_err

인터넷 익스플러어 옵션창 고급탭에서

가속그래픽 -> GPU 렌더링 대신 소프트웨어 랜더링 사용

이 부분…인텔 그래픽 가속기 문제로 인해서 IGDPMD64.SYS 블루스크린이 발생했다고 했는데

인터넷 익스플로어에서 가속 기능을 쓴다고 한다… 그래서 촉이 왔다.

GPU 렌더링을 소프트웨어 랜더링 으로 선택하고 확인을 눌렀다.

헐…

블루 스크린이 발생 안한다…

요.. GPU 랜더링 관련이 문제가 발생한 원인 인가 보다….

이번 인터넷 익스플로어 업데이트 후 발생되는 문제인것 같은데

아직, 나랑 동일한 문제가 발생한 컴퓨터는 없는 듯 하지만..(내 컴이 구린가? ㅋ)

윈도우즈 업데이트 되면… 두려워 지네…흠.. Windows 8로 넘어가야 하나…

PE 파일 포맷

지금 BaramOS는 커널을 Visual studio로 컴파일 하기 때문에 파일 포맷이 PE 포맷으로 생성된다. 그.래.서 또 PE 포맷이 어떻게 생겼는지를 확인해 봐야 한다. 역시나 쉬운게 없다. OS 만들기가 이리 힘들줄이야~ 여차 하면 컴파일러도(허~~세 작렬) 만들어 볼 생각인데 아마 더 죽어 버릴 듯하다. 그냥 컴파일러는 포기!!! 이거 하나 끝내기 힘드네..

여튼. PE 포맷 요놈이 어떻게 생겼는지를 알아야 한다. 그럼 PE 파일이 뭐냐~?~~

PE(Portable Executable) 포맷은 Windows 에서 만든 포맷으로 유닉스의 COFF(Common Object file format)을 기반으로 나왔다. 다양한 운영체제에서의 이식성을 보여준다는 뜻에서 이식이 가능한 실행 형식(Portable Executable)이라는 이름을 붙였다.                           - 위키백과

이식이 가능한 실행 형식이라는데 뭘 이식 한다는 건진 모르겠다. 찾아보니 파일을 다른곳에서 실행해도 실행이 될 수 있도록 규정한 형식이란다. 예전에는 파일이 만든 곳에서만 실행 되었나??? 흠.. 모르겠다. 패스~

갑자기 실행파일 이란 무엇일까 라는 의문이 들었다. 그냥 막연하게 프로그램을 실행시킬 수 있는 파일 이라고 머리속에서 떠올랐다. 그런데 이게 어떤 원리로?? (또 생각에 꼬리를 물기 시작한다 ㅡㅡ;;;)

나름 실행파일 이란 정의를 내려봤다. CPU가 연산, 제어 할수 있는 코드들을 파일안에 모아 놓고 또 연산, 제어만 하다 보니 데이터들이 필요 했을 것이다. 이 데이터들도 파일 안에 모아 놓았을 것이고.. 그럼 실행파일은 CPU 실행 코드와 데이터 코드들을 모아 놓은 파일. 이게 실행파일 이지 않을까 하고 생각해 봤다… 그런데 왜 굳이 PE포맷으로 실행파일들을 구성해 놨을까??? 음..동적 라이브러리 때문에??? 흠.. 실행파일 포맷이 만들어진 이유는 다음에 좀더 찾아 봐야 겠다. 너무 멀리 가는것 같으니깐…

PE 파일 포맷이라고 인터넷에서 검색해 보니 많은 글들이 찾아 진다. 그리고 자세히 분석 해 놓은 곳도 많다. PE파일에 관한 자료 찾는건 어렵지 않아 보인다. (아싸~) ㅎㅎ

파일 구조는

IMAGE_DOS_HEADER

IMAGE_NT_HEADER

IMAGE_SECTION_HEADER

.TEXT SECTION

DATA SECTION

IMAGE_DOS_HEADER 도스에서 쓰던 구조체란다. 총 64바이트로 구성되어 있단다.

typedef struct _IMAGE_DOS_HEADER {     // DOS .EXE header
    WORD   e_magic;                  // Magic number
    WORD   e_cblp;                    // Bytes on last page of file
    WORD   e_cp;                       // Pages in file
    WORD   e_crlc;                     // Relocations
    WORD   e_cparhdr;               // Size of header in paragraphs
    WORD   e_minalloc;              // Minimum extra paragraphs needed
    WORD   e_maxalloc;             // Maximum extra paragraphs needed
    WORD   e_ss;                      // Initial (relative) SS value
    WORD   e_sp;                      // Initial SP value
    WORD   e_csum;                 // Checksum
    WORD   e_ip;                       // Initial IP value
    WORD   e_cs;                      // Initial (relative) CS value
    WORD   e_lfarlc;                   // File address of relocation table
    WORD   e_ovno;                   // Overlay number
    WORD   e_res[4];                 // Reserved words
    WORD   e_oemid;                 // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;              // OEM information; e_oemid specific
    WORD   e_res2[10];             // Reserved words
    LONG   e_lfanew;                // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

이 실행파일 첫부분에 64바이트를 위와 같은 구조로 저장되어 있다.

여기서 필요한건 e_magic 과 e_lfanew 이 두 변수만 필요하단다.

e_magic 은 Dos Signature로 4D5A 기본값이 저장되어 있다. 이게 아스키 값으로 “MZ” 이라는데 만든 사람 Mark zbikowski 이니셜이라고 한다.

e_lfanew는 IMAGE_NT_HEADER 구조체 위치란다. ???  IMAGE_DOS_HEADER 64바이트 다음에 IMAGE_NT_HEADER 아닌가?? 라고 생각해서 검색해 봤더니 IMAGE_DOS_HEADER와 IMAGE_NT_HEADER 사이에 도스에서 실행되는 코드가 있다고 한다..DOS Stub 란다. 16비트 명령어로 구성되어 있어서 32비트 윈도우즈에서는 실행되지 않는단다. (윈도우즈 프로그램을 도스에서 실행시킬 때 “This is program cannot be run in DOS mode” 이렇게 화면에 출력하는 코드) e_lfanew는 파일에서 IMAGE_NT_HEADER가 있는 절대 위치를 가르킨단다.

윈도우즈 실행파일 로더는 프로그램의 첫 2바이트의 MZ 이니셜을 찾고 IMAGE_NT_HEADER로 건너 뛴단다.(물론 도스모드에서 실행하면 바로 에러 메시지 출력하고 종료…)

자 그럼 IMAGE_NT_HEADER 구조체..

이 구조체가 본격적으로 PE 구조체란다.. (아…귀찮다..)

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32

Signature는 단어 그대로 올바른 PE 포맷인지 확인하기 위한 Signature란다. 위에 IMAGE_DOS_HEADER의 첫 MZ와 마찬가지로 PE 포맷을 나타내는 PE00 (0x00004550)이 기록되어 있어야 한단다. 이게 없으면 실행한된단다.

IMAGE_FILE_HEADER는 현재 이 파일이 DLL(동적 라이브러리)인지 EXE 실행 파일인지 구분 여부와 실행될 플랫폼(CPU), 섹션등이 저장된 구조체 란다. (점점… 복잡하게 추적해야 할 구조체가 많아진다.. ㅡㅡ)

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

Machine 은 어떤 CPU 에서 실행되는지 정보. 뭐 Intel 386인지, Intel64,MIPS, ALPHA 에 관한 정보가 기록된단다.

NumberOfSections은 단어 그대로 섹션의 갯수가 기록되어 있단다. 자. 그럼 섹션은 무엇일까??? 실행파일은 적어도 코드 와 데이터 이렇게 구분된단다. 이걸 코드와 데이터로 분류해서 섹션?이라는 이름으로 저장하는걸 말한단다. 기본적으로 코드(text), 데이터(data), 리소스(.rsrs) 이렇게 이름이 된다고 한다. 이게 프로그램이 크면 섹션이 많이 존재하게 되는데 이 섹션의 갯수를 말한단다.

TimeDataStamp는 파일이 생성된 날짜와 시간이 기록된단다.

PointerToSymbolTable, NumberOfSymbols 은 유닉스 시스템에서 사용하던 것으로 지금은 사용 안한단다.

SizeOfOptionHeader요놈은 IMAGE_OPTIONAL_HEADER32구조체의 크기이다. 배열도 아니고 포인터도 아닌데 왜 크기를 명시해야 한단다. 왜? 그냥 찾아보긴 했는데 명확한 곳은 없고 64비트 일 경우에 IMAGE_OPTIONAL_HEADER32구조체가 크기가 다르단다. (뭐, 64비트 일 경우 IMAGE_OPTIONAL_HEADER64 구조체를 사용할텐데….) 그래서 크기를 명시해 놔야 한다는데 맞는건진 모르겠다.

Characteristics 파일 속성 값으로 DLL 인지 실행파일인지 등을 정의 해 놓은 속성이란다.

자… IMAGE_FILE_HEADER 했으니 IMAGE_OPTIONAL_HEADER32 해야 겠네… 이 구조체는 PE 헤더 구조체중 제일 크기가 크단다. 이 말은 알아야 할게 많다는 말이지..흠..

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

많다….….

Magic 이 구조체가 32비트인지(IMAGE_OPTIONAL_HEADER32) 64비트인지 (IMAGE_OPTIONAL_HEADER64) 구분자란다. 32비트는 10Bh, 64비트는 20Bh .

MajorLinkerVersion, MinorLinkerVersion 컴파일러 버전 정보.

sizeOfCode는 코드 영역에 전체 크기를 말한단다.

sizeOfInitializedData 초기화 된 데이터 영역 전체 크기를 말한단다.

sizeOfUnInitializedData 초기화 되지 않은 데이터 영역 전체 크기를 말한다.

AddressOfEntryPoint는 Entry Point는 프로그램의 시작 위치이다. 그 시작 위치의 RVA값이란다. RVA(Relative Virtual Address)로 상대적 가상 주소를 말한다. 상대적 가상 주소.. 단어 어렵네… 가상의 주소 인데 상대적이란다. 뭐에 대한 상대적??? 요 밑에서 언급 될 ImageBase의 상대적 가상 주소 란다. 즉 ImageBase에서 AddressOfEntryPoint값 만큼 떨어진 위치에 Entry Point가 있다는 말이다. ImageBase에서 RVA만큼 떨어진.. 그럼 Offset 과 동일한 개념이라고 보면 되는건가? 음.

BaseOfCode 또한 RVA 값이란다. 이것도 ImageBase에서 RVA 값 만큼 위치에 Code의 시작 위치가 있다는 말인가 보다.

BaseOfData 위에 코드랑 똑같이 RVA 값.

ImageBase는 프로그램이 메모리에 적재될 위치를 가르킨단다. 보통 0x400000은 실행파일, 0x10000000은 DLL이란다. 물론 다른 값으로 지정될 수 있단다. 그러니까 메모리 로딩 시작 위치에서 RVA로 각 Entry Point, Code, Data위치를 기록하나 보다. (오프셋 개념인듯…). 아. DLL은 0x10000000인데 만약 여러 DLL들이 0x10000000로 ImageBase가 기록되어 있으면(미리 하나의 DLL이 메모리에 선점되어 있으면) 다른 메모리 주소로 PE 로더가 재배치 한단다.

SectionAlignment는 메모리에서 섹션(.TEXT, .DATA)의 최소 단위 크기란다. 이 크기보다 섹션의 크기가 커지면 이 섹션 크기 배수만큼 크기여야 한단다.

FileAlignment는 PE 파일 내에서 섹션의 최소 단위 크기.

뭐 기타 Version 정보는 건너 뛰고~~~ ^^

SizeOfImage는 PE 파일을 메모리에 올렸을 때 전체 크기를 말한단다. 파일 크기가 달라 질수 있는데 이건 SectionAlignment의 영향을 받는단다. (섹션 다음에 패딩이 붙는 다든지.. 등등)

SizeOfHeaders는 PE 포맷의 모든 헤더의 크기를 말한단다. 즉 파일 시작 위치에서 SizeOfHeaders 오프셋 뒤에 첫번째 섹션이 존재한단다. 이 SizeOfHeaders의 값은 FileAlignment값의 배수가 되어야 한단다.

Checksum 파일의 변경 유무 판단할 때 사용.

SubSystem은 파일이 드라이버 파일(1)인지 GUI 프로그램인지(2), CUI 프로그램인지를 나타내는 값.

SizeOfStackReserve 프로그램에서 사용할 스택 예약 크기.

SizeOfStackCommit 프로그램에서 사용할 스택 크기.

SizeOfHeapReserve 프로그램에서 사용할 힙 예약 크기.

SizeOfHeapCommit 프로그램에서 사용할 힙 크기.

LoaderFlags 사용하지 않음.

NumberOfRvaAndSizes 밑에 IMAGE_DATA_DIRECTORY구조체 배열 DataDirectory 크기.

DataDirectory 는…음.. IMAGE_DATA_DIRECTORY 구조체의 배열이다.

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD VirtualAddress;
    DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

IMAGE_DATA_DIRECTORY구조체에는 VirtualAddress변수와 Size가 있다. 이게 Export, Import, Resource, Exception 등등의 위치와 크기를 정의하는 것이라는데.. 별로 커널쪽에선 중요하진 않을 것 같아 패쓰~

휴~~~

IMAGE_NT_HEADER 파일 뒤에는 섹션에 관련된 IMAGE_SECTION_HEADER가 따라 온다. 뭐 섹션 테이블이라고도 불리운다고 한단다.

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

IMAGE_SECTION_HEADER 구조체 데이터가 IMAGE_FILE_HEADER구조체의 NumberOfSections값만큼 있단다..(섹션의 갯수만큼..)

Name은 섹셕의 이름인데 NULL도 채워도 되나 보통 .text, .data, .rdata, .bss, .idata, .edata 등이 보통 관례대로 사용된단다.

VirtualSize 섹션이 메모리에 올라갈 때 섹션 크기. (SectionAlignment 배수)

VirtualAddress 섹션이 메모리에 올라 갈 메모리 위치. RVA 값이다. 즉 ImageBase + VirtualAddress .

SizeOfRawData 메모리가 아닌 실제 PE 파일내에서 섹션의 크기. (FileAlignment 배수)

PointerToRawData PE 파일내에서 섹션이 시작하는 위치(파일 오프셋).

PointerToRelocation obj 파일에서만 사용되고 실제 실행파일에서는 0 이 된단다.

PointerToLinenumbers COFF 스타일의 라인번호를 위한 파일 오프셋(??) . 모르겠다 이건.

NumberOfRelocation 요것도 0.

PointerToLinenumbers COFF 라인번호가 PE 에 첨부되었을 때만 사용.(???) 진짜 모르겠다.

Characteristics 섹션의 속성. (CODE 인지 DATA, 실행, 읽기, 쓰기) OR 연산.

     0x00000020 코드 섹션
     0x00000040 초기화 된 데이터 섹션
     0x00000080 초기화 되지 않은 데이터 섹션
     0x20000000 실행 가능한 섹션
     0x40000000 읽기 가능한 섹션
     0x80000000 쓰기 가능한 섹션.


자… 이제 필요한건 다 했다. IAT, EAT 이런것들은 아직… 필요 없다고 판단되었다. 필요 할때 다시 살펴볼 생각… IMAGE_SECTION_HEADER 다음에 실제 섹션 데이터들이 온단다. 뭐 이제 PE 포맷 구조에 대충 훑어 봤다. 휴~~~ 시간이 많이 걸렸다..(노느니라~~~)

정리가 필요하다….정리…

실행파일은 PE 포맷으로 구성되어 있다. 뭐 이런 저런 잡다한 것 다 빼고…

코드, 데이터 섹션 이런식의 섹션별로 실행 코드, 데이터를 분류해 놓았다.

이걸, IMAGE_NT_HEADER 구조체 안에 있는 IMAGE_OPTIONAL_HEADER 구조체의 ImageBase 값. 메모리에 실행파일을 로딩해 놓는다.

이 때, 각 섹션은 ImageBase + VirtualAddress(IMAGE_SECTION_HEADER 구조체 내) 의 RVA 위치에 섹션을 배치한다.

다시… 정리하면..

실행파일에서 ImageBase 값을 구하고 섹션의 갯수를 구한 다음. 각 섹션을 ImageBase + RVA 위치에 메모리에 올린다.

라고 정리가 된다. 흠…

 

'프로그래밍 > OS 만들기' 카테고리의 다른 글

OS 만들기 #14 PE 재배치  (0) 2014.03.28
OS 만들기 #13 보호모드 진입  (0) 2014.03.27
OS 만들기 #11 - 리얼모드 & 보호모드  (0) 2013.08.14
OS 만들기 #10  (0) 2013.08.14
OS 만들기 #9 - 부트로더  (0) 2013.08.14



DLNA에 대해서 좀 알아 보려고 했더니만 뜬금없는 uPNP가 나오네??? DLNA가 uPNP에서 파생되었다고 하는데...어쩐지 miniDLNA소스를 봐도 uPNP 밖에 안 나온다… 그래서 uPNP에 대해서 먼저 학습을 하고 DLNA로 넘어가야 겠다.


uPNP

Universal Plug and Play 로 마이크로소프트사에 의해 제안된 기술이란다. PnP랑 이름이 같길래, 난 그건줄 알았더니 거의 유사하다. PC에 연결해서 주변기기를 알아서 등록, 재생 하는 그 기술의 확장판이랄까?? 요건 네트워크에 딱 등록되면 네트워크 연동해서 !!! 사용이 가능 하게 해주는 기술이랄까???

여튼, 요즘같이 스마트 시대에 아주 적당한 기술이라 할 수 있겠다. 프린트, 라디오, 비디오, 스마트폰이 네트워크로 연결되서 서로 사용이 가능 하다면 얼마나 편하겠나~~~ 좋은 세상이야..

uPNP는 TCP/IP, SSDP, SOAP, GENA, HTTP, XML같은 통신 표준과 기술을 바탕으로 한다고 한다.

TCP/IP는 알겠는데 SSDP, SOAP, GENA는 또 뭐냐??? 점점 꼬리에 꼬리를 물고 알아야 할게 늘어난다 ㅡㅡ;;;




SSDP
Simple Service Discovery Protocol로 네트워크상 장치들을 찾기 위한 프로토콜.

이라는데, 이게 뭔 소린지…흠..그냥 프로토콜 인데(?)

네트워크 상에서 1900 포트로 브로드캐스트 를 하면 같이 연결된 장치들이 그에 응답하여 서로 장치의 IP주소를 획득. 이렇게 해서 서로 같은 네트워크 상에서 장치를 인식하게 해주는 프로토콜(?) 인가 보다.



SOAP

Simple Object Access Protocol로 HTTP, HTTPS, SMTP등을 사용하여 XML 기반의 메시지를 컴퓨터 네트워크 상에서 교환하는 형태의 프로토콜이다.

요건 모.. 그냥 XML 데이터를 HTTP, HTTPS, SMTP 프로토콜로 서로 전송, 수신 한다는 말이고…



GENA

General Event Notification Architecture는 이벤트를 HTTP로 송.수신 한다.

뭔말인지… 그냥 이벤트를 HTTP를 통해서 전달한다는 말이겠지… 이벤트 전용 프로토콜인건가??



정리하믄 SSDP로 각 장치를 찾고, SOAP으로 데이터를 송수신 하고 GENA로 이벤트를 송수신 한다는 말인건가?

 



uPNP는 검색, 서술???(Description), 제어, 이벤트?(Eventing), 프리젠테이션  요런 단계로 나뉘어 진단다..




검색

Control Point 라는 놈(장치를 제어하는 놈)이 동일 네트워크 상에 장치(서비스 하는 기기)를 검색할 때 사용하나 보다. (사용할려면 어떤놈들이 있는지 부터 알아야 하니… ㅋㅋㅋ) 근데 Control Point 놈만 이걸 보내는 게 아니라, 장치 들도 네트워에 연결되면 알아서 ㅋ “나 여기 있어요~~” 라고 통지 메시지를 보낸다고 한다. 제법 기특해!~

Control Point는 M-SEARCH 메시지를 보내는데 “이 네트워크 있는 기기 들 있으면 모두 손!!!” 과 같다고 봐야 할 것 같다. 손 들라고 했으니 이 메시지를 수신 한 기기들은 Control Point로 응답 메시지(“저 여기 있어요~~~”)를 보낸다.

M-SEARCH * HTTP/1.1
HOST: 239.255.255.255:1900
MAN: “ssdp:discover”
MX: 30
ST: ssdp:all


M-SEARCH * HTTP/1.1 기기찾는 다는 M-SEARCH와 HTTP/1.1 버젼 정보
HOST: 239.255.255.255:1900 uPNP에서 약속된 브로드캐스팅 IP와 포트.
MAN: “ssdp:discover” 요건 필수!
MX: 30 Control Point라는 놈이 검색 한다고 하고 무한정 기다릴 수 없으니… 초 단위로 1에서 120 사이에 값.
ST: ssdp:all 네트워크에 있는 모든 장치 그리고 서비스를 찾는다. 문서를 보면 ssdp:all이 아닌 특정 장치, 특정 서비스(?)등을 찾는 세부적으로 나눠서 검색 할 수 있는 것 같은데.. 세부적인 내용에 대해선 뭔지 모르겠다…

M-SEARCH * HTTP/1.1 : HOST: 239.255.255.255:1900 uPNP에서 약속된 브로드캐스팅 IP와 포트 요건 안 바뀐다.

Control Point는 요런 형태로 네트워크 상에 있는 기기를 찾는다고 메시지를 보낸다.


HTTP/1.1 200 OK
CACHE-CONTROL: max-age=100
DATE: Tue, 18 Jun 2013 03:05:18 GMT
EXT:
LOCATION: http://192.168.10.100:8080/api/test.xml
SERVER: Linux/2.6.18 UPnP/1.0 Test Device/2.0
ST: ssdp:all
USN:



HTTP/1.1 200 OK HTTP 버전 및 응답
CACHE-CONTROL: max-age=100 장치가 Control Point 한테 본인이 유효한 시간(초 단위로 최대 1800)을 표기한다. 뭔말이냐 .ㅡㅡ;;; 이 시간 만큼 Control Point가 요청하는 내용에 대해서 응답을 할 수 있다는 말인가?? 흠.
DATE:Tue, 18 Jun 2013 03:05:18 GMT 응답 메시지가 만들어진 날짜/시간.
EXT: 요건 나도 몰라.
LOCATION: http://192.168.10.100:8080/api/test.xml 요 밑에서 언급될 서술? 설명? (Description)에 대한 장치의 정보를 담고 있는 위치.
SERVER:Linux/2.6.18 UPnP/1.0 Test Device/2.0 OS/버전, 장치/버전등의 정보.
ST: ssdp:all M-SEARCH의 ST 값에 의해 달라진다. 위에서 ssdp:all 이라고 했으니 ssdp:all 이라고 했다.
USN 서비스 이름. 식별자 인것 같기도 하고.. 몰겠어..

그러면 각 장치들은 요렇게 메시지를 만들어서 Control Point에 응답한다.

 

위에서 장치들이 네트워크에 연결이 되면 알.아.서 본인들이 통지 메시지를 보낼 수도 있다고 했다.

NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=100
LOCATION: http://192.168.10.100:8080/api/test/xml
NT: upnp:rootdevice
NTS: ssdp:alive
SERVER: Linux/2.6.18 UPnp/1.0 Test Device/2.0
USN: ~~~


NOTIFY * HTTP/1.1 장치가 알아서 응답한다.
HOST: 239.255.255.250:1900
NT: upnp:rootdevice upnp:rootdevice라는 놈이 보냈다 정도???
NTS: ssdp:alive 요렇게 명시해 줘야 한단다.

그래, “나 여기 있어요” 라고 보내는 참으로 기특하다. 근데 네트워크에서 제거 될 때도 알아서 통지 메시지를 보낸다.

NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
NT: upnp:rootdevice
NTS: ssdp:byebye
USN: ~~~


NTS: ssdp:byebye 제거 될때 요렇게 해줘야 한단다.




서술? 설명? (Description)

Control Point 가 이제 네트워크 상에서 장치들이 어디(IP)에 있는지 알았다. 근데 이놈들이 정확히 어떤 놈들인지에 대한 확실한 정보는 없다. 그래서 검색 시에 장치들이 응답한 데이터 중 LOCATION 위치에 있는 정보(UTF-8 로 인코딩 되어 있단다)를 요청한다. 그냥 HTTP 프로토콜 GET 이다.

근데 이게 ㅡㅡ;;; XML 로 많은 내용이 있다.

<?xml version="1.0"?> 
 <root xmlns="urn:schemas-upnp-org:device-1-0"> 
 <specVersion> 
 <major>1</major> 
 <minor>0</minor> 
 </specVersion> 
 <URLBase>base URL for all relative URLs</URLBase> 
 <device> 
 <deviceType>urn:schemas-upnp-org:device:deviceType:v</deviceType> 
 <friendlyName>short user-friendly title</friendlyName> 
 <manufacturer>manufacturer name</manufacturer> 
 <manufacturerURL>URL to manufacturer site</manufacturerURL> 
 <modelDescription>long user-friendly title</modelDescription> 
 <modelName>model name</modelName> 
 <modelNumber>model number</modelNumber> 
 <modelURL>URL to model site</modelURL> 
 <serialNumber>manufacturer's serial number</serialNumber> 
 <UDN>uuid:UUID</UDN> 
 <UPC>Universal Product Code</UPC> 
 <iconList> 
 <icon> 
 <mimetype>image/format</mimetype> 
 <width>horizontal pixels</width> 
 <height>vertical pixels</height> 
 <depth>color depth</depth> 
 <url>URL to icon</url> 
 </icon> 
XML to declare other icons, if any, go here
 </iconList> 
 <serviceList> 
 <service> 
 <serviceType>urn:schemas-upnp-org:service:serviceType:v</serviceType> 
 <serviceId>urn:upnp-org:serviceId:serviceID</serviceId> 
 <SCPDURL>URL to service description</SCPDURL> 
 <controlURL>URL for control</controlURL> 
 <eventSubURL>URL for eventing</eventSubURL> 
 </service> 
Declarations for other services defined by a UPnP Forum working committee (if any)
go here
Declarations for other services added by UPnP vendor (if any) go here
 </serviceList> 
 <deviceList> 
Description of embedded devices defined by a UPnP Forum working committee (if any)
go here
Description of embedded devices added by UPnP vendor (if any) go here
 </deviceList> 
 <presentationURL>URL for presentation</presentationURL> 
 </device> 
 </root> 
 

복잡하다… ㅡㅡ;; 이제 각 속성들을 살펴 봐야 겠다.


root xmlns ”run:schemas-upnp-org:device-1-0 같이 xmlns이 반드시 있어야 한단다.
specVersion major, minor major , minor 버전. major 는 반드시 1이어야 한다고 하네?
URLBase   Optional.
device   장치
deviceType   장치 타입이라는데 “urn:schemas-upnp-org:device:” 로 시작해야 한다고 한다.  타입이 뭔지 모르겠다. devicetype:v 이렇게 있는데 v는 버전 정보란다.
friendlyName   그냥, 최종 사용자를 위한 짧은 이름?
manufacturer   제조사
manufacturerURL   제조사 URL
modelDescription   모델 설명
modelName   모델 이름
modelNumber   모델 넘버
modelURL   모델 웹 사이트.
serialNumber   시리얼 넘버
UDN   유일한 장치 이름. 장치 식별자.
UPC   제품 코드
iconList   장치에서 사용하는 아이콘 리스트
  icon Control point UI에서 표시될 아이콘
  mimetype 아이콘 MIME TYPE “image/png”.
  width 가로
  height 세로
  depth depth
  url 아이콘 이미지 파일일 있는 URL 주소
serviceList   장치에서 제공하는 서비스들(?)^^
  service 무슨 서비스를 할 것인가를 설명한 건가?? 참 어려다잉.
  serviceType 말 그대로 서비스 타입. “urn:schemas-upnp-org:service:”로 시작 되어야 한다고 한다. devicetype처럼 servicetype:버전 이렇게 설정한다.
  serviceId 서비스 식별자. “urn:upnp-org:serviceId:serviceID” 요렇게 설정.
  SPCDURL 서비스 설명을 위한 URL
  controlURL 컨트롤을 위한 URL
  eventSubURL 이벤팅을 위한 URL
devicelist   장치 안에 장치가 또 있나 보다. 또 디바이스 리스트.
presentationURL   presentation을 위한 URL

 

휴 많다… 이제까지 root device라는 용어가 나오는데 root device가 메인 장비?? 장치를 말하나 보다. 그 장치안에 또 장치가 있는 모양. ㅎㅎㅎ

이제, 장치에 대한 정보를 알았으니, 장치가 가지고 있는 서비스 정보에 대해서 알아야 한다. 요게 장치에서 제공하는 서비스의 액션, 변수, 데이터, 범위, 이벤트등의 정보를 가지고 있다고 한다. 그러니까 장치를 제어 하기 위해 필요한 정보 모음집으로 봐야 하나?? 하~~ 망할 영어…

Control Point는 장치 기술, 설명(description)을 HTTP GET으로 요청하고 다시 서비스 Description을 요청(HTTP GET)한단다.

 

<?xml version="1.0"?> 
 <scpd xmlns="urn:schemas-upnp-org:service-1-0"> 
 <specVersion> 
 <major>1</major> 
 <minor>0</minor> 
 </specVersion> 
 <actionList> 
 <action> 
 <name>actionName</name> 
 <argumentList> 
 <argument> 
 <name>formalParameterName</name> 
 <direction>in xor out</direction> 
 <retval /> 
 <relatedStateVariable>stateVariableName</relatedStateVariable> 
 </argument> 
Declarations for other arguments defined by UPnP Forum working committee (if any)
go here
 </argumentList> 
 </action> 
Declarations for other actions defined by UPnP Forum working committee (if any)
go here
Declarations for other actions added by UPnP vendor (if any) go here
 </actionList> 
 <serviceStateTable> 
 <stateVariable sendEvents="yes"> 
 <name>variableName</name> 
 <dataType>variable data type</dataType> 
 <defaultValue>default value</defaultValue> 
 <allowedValueList> 
 <allowedValue>enumerated value</allowedValue> 
Other allowed values defined by UPnP Forum working committee (if any) go here
 </allowedValueList> 
 </stateVariable> 
 <stateVariable sendEvents="yes"> 
 <name>variableName</name> 
 <dataType>variable data type</dataType> 
 <defaultValue>default value</defaultValue> 
 <allowedValueRange> 
 <minimum>minimum value</minimum> 
 <maximum>maximum value</maximum> 
 <step>increment value</step> 
 </allowedValueRange> 
 </stateVariable> 
Declarations for other state variables defined by UPnP Forum working committee
(if any) go here
Declarations for other state variables added by UPnP vendor (if any) go here
 </serviceStateTable> 
 </scpd> 

 


요놈도 장난이 아니다. 그냥 하나 하나 뜯어서 살펴 볼려니, 심신이 괴롭다. 아니 정확히 머리가 아프다..

 

scpd   반드시 urn:schemas-upnp-org:service-1-0으로 시작해야 한단다.
specVersion major, minor major, minor 버전 정보. major는 반드시 1이어야 한단다.
actionList   액션(?) 리스트?
action name 액션 이름. 동작 함수 인듯.
argumentList   액션이 함수라면 인자도 있겠지~~ 그 인자 리스트 정의.
argument name 인자 이름.
  direction 인자가 IN 타입인지 OUT 타입인지를 말하나 보다. IN, OUT 개념도 있네..흠.
  retval 액션 리턴 값.
  relatedStateVariable 액션 내부에서 사용할 StateVariable 이름??? 아직 모르겠다.
serviceStateTable   변수 테이블?
stateVariable name 변수 이름
  dateType 데이터 타입
  defaultValue 기본 값
allowedValueList allowedValue. 문자열에서 쓰나 본데 모르겠다.
allowValueRange minimum, maximum,step 변수값에 최소, 최대 값 정의.


휴~ 요렇게 해서 Description 마쳤다.

 

Control Point는 장치를 제어 하기 위한 정보를 Description을 통해서 다 얻었다. 이제 장치를 제어해 봐야 겠지~~




제어

장치의 제어는 서비스 Description에서 얻은 액션이름(함수명??)으로 가능하단다. 근데 위에서 언급했듯이 SOAP이라는 놈으로 구성되어 있다.(아~~ 망할…)

POST path of control URL HTTP/1.1 
 HOST: host of control URL:port of control URL
 CONTENT-LENGTH: bytes in body
 CONTENT-TYPE: text/xml; charset="utf-8" 
SOAPACTION: "urn:schemas-upnp-org:service:serviceType:v#actionName" 




---------body ----------
 <?xml version="1.0"?> 
 <s:Envelope
 xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 
 <s:Body> 
 <u:actionName xmlns:u="urn:schemas-upnp-org:service:serviceType:v"> 
 <argumentName>in arg value</argumentName> 
other in args and their values go here, if any
 </u:actionName> 
 </s:Body> 
 </s:Envelope>

장치 제어는 HTTP 헤더와 body 요렇게 두 부분으로 구성되어 있다.

POST control URL HTTP/1.1 장치 Description에서 설정된 control URL.
HOST IP 주소, 도메인.
CONTENT-LENGTH body 크기
CONTENT-TYPE text/xml; charset=”utf-8” 로 사용하란다.
SOAPACTION urn:schemas-upnp-org:service:serviceType:번호#액션이름 이렇게 사용하란다. 뭔말인건지.. 제어할 액션이름.

헤더는 이렇고 body는…

 

Envelope   이건 그대로 쓰면 되겠다.
body   제어 부분
actionName   제어할 액션 이름을 쓴다.
xmlns:u=”urn:schemas-upnp-org:service:serviceType:v” 요건 모르겠다. 아놔 환장하겠네..
  argumentName 인자 이름. 인자 이름을 쓰고 해당 인자에 값을 표기.

근데 이렇게 장치로 POST 했는데 “405 Method Not allowed” 라는 메시지를 리턴 받으면 다시 M-POST로 메시지를 보내야 한다고 하네?  뭐이리 복잡해.

 

M-POST path of control URL HTTP/1.1 
 HOST: host of control URL:port of control URL
 CONTENT-LENGTH: bytes in body
 CONTENT-TYPE: text/xml; charset="utf-8" 
 MAN: "http://schemas.xmlsoap.org/soap/envelope/"; ns=01
01-SOAPACTION: "urn:schemas-upnp-org:service:serviceType:v#actionName" 

 

자.. 이제 제어 명령을 보냈으니, 장치가 응답을 보내겠지. 주는게 있으니 받는것도 있는 법이니까..

 

 HTTP/1.1 200 OK 
 CONTENT-LENGTH: bytes in body
 CONTENT-TYPE: text/xml; charset="utf-8" 
 DATE: when response was generated
 EXT: 
 SERVER: OS/version UPnP/1.0 product/version


--------- body ----------

 <?xml version="1.0"?> 
 <s:Envelope
 xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> 
 <s:Body> 
 <u:actionNameResponse xmlns:u="urn:schemas-upnp-org:service:serviceType:v"> 
 <argumentName>out arg value</argumentName> 
other out args and their values go here, if any
 </u:actionNameResponse> 
 </s:Body> 
 </s:Envelope> 

여기서 actionNameResponse는 actionName + Response 가 더해서 만들어진 단어인가 보다.  그렇다고 하니 그렇겠지.

 

제어는 대충 이런식인가 보다. 액션을 호출 하는 것만 알아 볼련다. 각 변수값들도 요청하는 게 있나 본데 솔직히 귀찮다. DLNA 수정해 보려다 uPNP때문에 미치겠다. 그래서 더 미치기 전에 간단하게 넘어 갈련다.

 



이벤트

Control Point가 장치의 서비스 변수의 상태가 변경이 되었을 때 통지 받고자 하면 이벤트를 요청한다고 한다. 장치 서비스는 변수의 값이 변경이 될 때마다 Control Point에게 이 값을 전달 하는게 이벤트 라고 한다.

Subscriber, publisher 뭐 이런놈들이 있는데 이건 그냥 무시하겠다… 깊게는 들어가지는 들어가지 말아야지^^

그럼 Control Point가 장치에게 이벤트 요청 메시지를 어떻게 보내냐면..

SUBSCRIBE publisher path HTTP/1.1 
 HOST: publisher host:publisher port
CALLBACK: <delivery URL> 
NT: upnp:event
 TIMEOUT: Second-requested subscription duration 

이렇게 보낸단다.

SUBSCRIBE publisher path HTTP/1.1 SUBSCRIBE 명령에 장비 Description에서 설정되었던 이벤트 URL 을 설정.
HOST 장치 IP, 포트
CALLBACK 이벤트를 전달받을 URL로 설정. 이때 이 URL은 여러개가 될 수 있다. 이벤트 메시지를 보낼곳들을 설정한다.
NT upnp:event
TIMEOUT 이벤트 발생시 한번 보내고 끝내는게 아닌가 보다. 이 timeout 값에 의해 보내는 시간이 결정된단다. Second-XX 이렇게 설정하던지 아니면 infinite 무한 설정 이렇게 한단다.

이벤트 설정? 메시지 수신 후 장치는 응답 메시지를 보낸다. 뭐~ 잘 받았다는 메시지 겠지..

HTTP/1.1 200 OK 
 DATE: when response was generated
 SERVER: OS/version UPnP/1.0 product/version
SID: uuid:subscription-UUID
 TIMEOUT: Second-actual subscription duration 

이 메시지중 SID 이 필드값 이건 아마도 이벤트 요청을 한 Control Point의 식별자를 장치가 하나 발급하나 보다. 이 SID 필드 값으로 관리하는듯… Control Point가 이 메시지를 수신 후 SID 값을 저장해 놔야 하나 보다.. 왜? 그건 renewal 메시지에서 사용하니까~~

SUBSCRIBE publisher path HTTP/1.1 
 HOST: publisher host:publisher port
SID: uuid:subscription UUID
 TIMEOUT: Second-requested subscription duration 

renewal 메시지는 이벤트 요청 메시지랑 명령은 같다. 다만 NT와 CALLBACK만 빠져 있다. 아~ SID 가 추가 되어 있다. 그럼 renewal은 뭐냐?? 단순히 메세지만 봐서는 TIMEOUT 값만 다시 변경 할 때 사용하나 보다. 추가된 SID는 장치에서 송신한 SID 필드값이겠지?? ㅋ 그러므로 Control Point도 SID값을 저장해 놔야 하는듯.

이벤트를 보내 달라고 했으니 보내지 말라는 메시지도 있겠지? 당연히~~ 있네…

UNSUBSCRIBE publisher path HTTP/1.1 
 HOST: publisher host:publisher port
SID: uuid:subscription UUID

이렇게 보내면 장치는 이벤트 메시지를 보내지 않는다고 한다.


이벤트를 요청하고 갱신?하고 이벤트 중지 하는 메시지 까지 했으니 이제 실제 이벤트 발생시 보내지는 메시지만 남았다.

NOTIFY delivery path HTTP/1.1 
 HOST: delivery host:delivery port
 CONTENT-TYPE: text/xml 
 CONTENT-LENGTH: Bytes in body
NT: upnp:event
NTS: upnp:propchange
SID: uuid:subscription-UUID
SEQ: event key


---------- body ----------------------

 <?xml version="1.0"?> 
 <e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0"> 
 <e:property> 
 <variableName>new value</variableName> 
 </e:property> 
</e:propertyset>

생김새는 제어 쪽에서 본 메시지랑 비슷하다.

NOTIFY delivery path HTTP/1.1   NOTIFY 요건 통지 관련 메시지다는 의미일테고 delivery path는 이벤트 요청시 설정한 CALLBACK url이란다.
CONTENT-TYPE   text/xml
CONTENT-LENGTH   body 크기
NT   upnp:event
NTS   upnp:propchange
SID   SID
SEQ   0에서 시작해서 1씩 증가한다고 한다.
propertyset   urn:schemas-upnp-org:event-1-0. 이렇게 써주고.
property variableName variableName변수의 변경된 값.

이벤트 메시지를 받았으니 메시지 잘 받았다고 응답 보내줘야지?

 HTTP/1.1 200 OK 

휴~~~

이 이벤트를 끝으로 uPNP는 그만 볼련다. DLNA를 시작으로 uPNP까지 머리도 터지겠고 보기도 싫다. 이거 뭐 되지도 않는 영어 문서 붙잡고.. 하나 하나 볼려니 오역80%에 개념 상실 20%.

그래도 uPNP 어느정도 개념은 잡았으니 miniDLNA 소스 코드는 어느정도 분석은 가능하겠군.. 근데 DLNA 프로토콜도 찾아서 봐야 하는데. 이 정도라면 그만 둘까 라는 생각이 든다.

 

참고문서:

http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf

 

  

+ Recent posts