1. 유저 모드 세그먼트

다음 코드는 유저 모드 세그먼트영역을 지정하는 디스크립터입니다.

                  dd 0x0000FFFF, 0x00FCFA00

                  dd 0x0000FFFF, 0x00FCF200

 

유저 모드의 코드 세그먼트

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0xFFFF

0x0000

1

11

1

1010

0x00

0x00

1

1

1

1

0xC

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

유저모드의 데이터 세그먼트

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0xFFFF

0x0000

1

11

1

0010

0x00

0x00

1

1

1

1

0xC

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

디스크립터의 셀렉터 번호는 다음과 같이 나타냅니다.

UserCodeSelector equ 0x28+3

UserDataSelector equ 0x30+3

 

3 더하는 것은 세그먼트 셀렉터의 RPL 부분에 유저 모드를 뜻하는 권한 레벨 3 넣어주겠다는 의미입니다.

15

 

 

 

 

 

 

 

 

 

 

 

3

2

1

0

 

 

 

 

 

 

 

 

 

 

 1

0

1

0

1

1

 

15

 

 

 

 

 

 

 

 

 

 

 

3

2

1

0

 

 

 

 

 

 

 

 

 

 

 1

1

0

0

1

1

 

세그먼트 디스크립터는5, 6번째 위치에 있고, TI 모두 0이므로 GDT 존재함을 있습니다.

 

2. 콜게이트 설정

GDT 게이트 디스크립터를 만들었습니다.

descriptor7:

                  dw 0

                  dw SysCodeSelector

                  db 0x02

                  db 0xEC

                  db 0

                  db 0

 

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x00

SysCodeSelector (ox08)

1

11

0

1

1

0

0

 

사용안함

0x2

0x00

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

커널 루틴의 오프셋 부분이 0으로 설정되어 있고, 부분은 Protected Mode 들어오기 전에 다시 채워 넣을 것입니다. 미리 인수의 개수를 2 지정하여 인수를 2 사용합니다.

Printf 오프셋은 0x00AD 입니다. (채워질 주소 값은 0x10000+0x00AD = 0x100AD)

                  xor eax, eax

                  lea eax, [printf]

                  add eax, 0x10000

                  mov [descriptor7], ax

                  shr eax, 16

                  mov [descriptor7+6], al

                  mov [descriptor7+7], ah

 

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x00AD

SysCodeSelector (ox08)

1

11

0

1

1

0

0

 

사용 안함

0x2

0x0001

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

Protected 모드로 들어온 , 임의의 값을 tts_esp0 넣어, 커널이 사용할 스택으로 지정합니다. 여기서는 일단 PM_Start 주소를 지정 합니다. 스택은 거꾸로 자람으로 메모리의 주소 마이너스 방향으로 자라납니다 . PM_Start 위에는 이상 사용하지 않는 루틴이 존재하므로 사용해도 좋습니다.

                  lea esp, [PM_Start]

 

                  mov ax, TSSSelector

                  ltr ax

 

                  mov [tss_esp0], esp

                  lea eax, [PM_Start-256]

                  mov [tss_esp], eax

 

 

3. 유저 모드로의 태스크 스위칭

유저 모드의 태스크를 실행시키겠습니다. Protected Mode 들어와 TR 레지스터, TSS영역을 세팅 , 유저모드 처럼 설정하여 IRET 명령을 통해 유저 모드의 태스크를 실행시킵니다.

(1) 세그먼트 셋팅 유저 데이터 영역 세그먼트로

                  mov ax, UserDataSelector

                  mov ds, ax

                  mov es, ax

                  mov fs, ax

                  mov gs, ax

 

데이터 관련 세그먼트에 UserDataSelector 값을 넣어줍니다.

현재 ESP 레지스터에는 커널 모드의 스택의 시작 주소를 지정하는 PM_Start 저장되어 있으므로 유저 모드 스택으로 바꾸어 주기 위해 PM_Start-256 값을 지정합니다.

Lea esp, [PM_Start-256]

 

(2) 인터럽트에 걸린 처럼 스택을 채우기

인터럽트 루틴에서 유저 모드의 레지스터를 스택에 저장해놓았다가 수행을 마치고, 유저 모드로 돌아갈 다시 복귀 시킵니다. 인터럽트 루틴을 수행한 것은 아니지만, 그런 상황을 다음과 같이 재현하여, 유저 태스크를 실행시킬 것입니다.

UserDataSelector(SS)

Esp(ESP)

0x200(EPLAGS)

UserCodeSelector(CS)

User_process(EIP)

 

 

Push 명령어로 다음과 같이 스택을 꾸며줍니다.

                  push dword UserDataSelector

                  push esp

                  push dword 0x200

                  push dword UserCodeSelector

                  lea eax, [user_process]

                  push eax

                  iretd

 

IRET으로 돌아갈 유저 모드에서 사용하는 SS 복구되지만 DS, ES, Fs, GS 셀렉터들은 값이 복구 되지 않아 미리 값을 채웠습니다.

EIP user_process 함수의 주소(오프셋) 지정되어 있기 때문에 user_process 실행합니다.


4. 콜게이트를 사용한 시스템

커널 모드의 함수인 printf 실행하기 위해 게이트를 이용하여 시스템 콜을 합니다. 다음과 같이 스택이 만들어 집니다.

 

(1) 특권 레벨 3 스택 : 인자2

80*2*7

 

Parameter1

ESP

 

 

(2) 특권 레벨1 스택 :

 

SS

 

ESP

 

80*2*7

EBP+8

Parameter1

 

Cs

EBP

EIP

 

ES

ESP

EAX

 

printf:

                  mov ebp, esp

                  push es

                  push eax

                  mov ax, VideoSelector

                  mov es, ax

                  mov esi, [ebp+8]

                  mov edi, [ebp+12]

 

Printf 함수에서는 먼저 esp(tss esp0) 값을 ebp 지정을 합니다. 프로그램 진행 중에 바뀔 esp 값을 복원하기 위해 백업해 것입니다.

EBP+8 하여 첫번째 인수인 문자열을 접근 있고, 두번째 인수는 EBP+12 사용해 접근 있습니다.

신고

CPU의 보호 레벨은 0~ 3, 4개로 실제로 쓰이는 것은 0과, 3 2개입니다. (숫자가 아닌 보안 수준이)낮은 레벨의 태스크가 높은 레벨의 데이터를 참조(3 레벨의 태스크가 0 레벨 태스크) 하면 일반 보호 에러(#GP)가 발생하게 됩니다.

1. CPU 보호 레벨

 

1) CPL(Current Priviliege level)

현재 실행되고 있는 태스크의 특권 레벨로, CS, SS 셀렉터 레지스터의 0, 1번째 비트에 있는 수 입니다. 프로그램이 다른 특권 레벨의 코드 세그먼트로 제어가 바뀌면 CPU는 CPL을 변경하게 됩니다.

 

2) DPL(Description Privilege Level)

디스크립터를 통해서 세그먼트 영역으로 접근할 때, 항상 CPL과 DPL을 비교하여 접근이 가능한지 불가능한지 판단이 이루어 지게 됩니다.

 

3) RPL(Requested Privilege Level)

콜게이트(낮은 특권 레벨에서 높은 특권 레벨의 루틴을 사용할 수 있게 하는 관문)을 통해 레벨 3인 프로세스가 특권 레벨 0에 해당하는 루틴을 실행할 때, 일시적으로 특권 레벨 0에 해당하는 데이터 영역에 접근할 수 있습니다. 이러한 현상을 사용하여 악용하는 것을 막기 위해 OS는 RPL 값을 사용합니다.

어떤 레벨의 프로세스가 루틴을 불러들였는지 기록하여, 낮은 특권 레벨이 루틴을 불렀을 때 높은 특권 레벨의 데이터 영역을 보호합니다.

 

2. 콜 게이트

콜게이트는 인터럽트, 예외와 마찬가지로 낮은 특권 레벨의 프로그램이 높은 특권 레벨로 변경되는 수단으로 사용됩니다. 하드웨어 인터럽트와 예외는 응용 프로그램과 상관 없이 특권 레벨이 변경되지만, 소프트웨어 인터럽트와 콜게이트는 낮은 특권 레벨의 프로그램에 따라 높은 특권 레벨의 루틴을 사용할 수 있습니다.

컴퓨터의 RAM, 하드디스크에 들어 있는 자료, 그리고 하드웨어 입출력 장치등은 커널에 의해서 관리되고 유저 프로그램은 커널의 루틴의 도움을 받아 이러한 자료들을 잠시 일부분 사용할 수 있습니다.

콜게이트도 GDT 테이블의 디스크립터로 처리되며, 낮은 특권 레벨의 프로그램이 높은 특권 레벨의 프로그램의 일부분을 사용하기 위한 창구입니다.

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

커널 루틴의 오프셋 15 - 0비트

핸들러의 코드 세그먼트 셀렉터

P

DPL

0

1

1

0

0

 

사용안함

인수의 개수

커널 루틴의 오프셋 31 - 16

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

이 디스크립터를 GDT에 지정하고, 유저 프로그램은 코드 셀렉터와 마찬가지로 디스크립터를 셀렉터로 선택하여 JMP나 CALL 명열을 내릴 수 있습니다.

 

3. 코드와 데이터의 특권 레벨 관계

CALL 명령어는 낮은 특권 레벨에서 높은 특권 레벨로, RET 명령은 높은 특권 레벨에서 낮은 특권 레벨로 이동하는 명령어 이기 때문에 이 명령어를 사용합니다. JMP 는 특권 레벨 간의 이동이 불가능 하며 JMP를 이용하여 특권 레벨의 이동을 사용해야 한다면 콜게이트 사용하여야 합니다.

 

(1) CALL 과 RET의 특권 레벨 변화

Q : CALL 명령어로 특권 레벨 0의 루틴을 불러 낼 때

A : CS 셀렉터의 0, 1 비트에 00이 들어가고 CPL은 0이 됩니다.

Q : RET 명령어로 다시 특권 레벨 3으로 돌아갈 때

A : CS 셀렉터의 0, 1비트에 11이 들어가고 CPL은 1이 됩니다.

 

코드 세그먼트 영역의 특권 레벨 DPL과 CPL은 어떤 상황에서 든 기본적으로 값이 같습니다.

예외 >> Conforming 세그먼트

특권 레벨 0의 루틴을 불러내도 CPL의 값은 그대로 3인 채 실행되었다 IRET으로 돌아옵니다. (Call gate와 관련)

 

(2) 특권 레벨과 접근 권한

특권 레벨 0에서는 모든 특권 레벨의 데이터 세그먼트 영역에 접근이 가능하지만 낮은 특권 레벨의 프로세스에서는 높은 특권 레벨의 데이터에 접근이 금지됩니다.

 

4. 특권 레벨 변동 시의 스택의 변화

 

스택 스위칭은 높은 특권 레벨의 루틴이 스택 공간의 부족 때문에 크래시 되지 않게 하기 위해서, 그리고 낮은 특권 레벨의 루틴이 스택을 통해 높은 특권 레벨의 루틴에 간섭하지 못하게 하기 위해 사용 됩니다.

핸들러나 커널 루틴으로 들어가기 전에 스택에 돌아올 주소 등을 넣어 두었다가 핸들러나 커널 루틴이 끝나고 다시 유저 모드 태스크로 돌아오기 위해 스택에 넣어 두었던 값들을 사용합니다.

 

(1) CALL 명령 – 유저 태스크 가 콜 게이트를 이용하여 커널 모드의 루틴

1) 호출 구문에 인수가 없는 경우

이 태스크 TSS 영역의 SS0, ESP0의 값을 참조하여 커널 모드의 스택에 현재 유저 태스크가 사용하고 있는 SS, ESP, 그리고 유저 태스크가 현재 진행중인 루틴의 주소, CS, EIP를 PUSH ( 스택방향 )

SS

 

ESP

CS

 

EIP

 

콜게이트에 지정된 커널 모드의 루틴 주소로 점프하고 실행

루틴을 마치고 RET 명령을 실행하여 커널 모드의 스택에 저장된 SS, ESP, CS, EIP를 POP(CS 를 먼저 검사한 후, 현재 커널 모드 보다 낮은 특권 레벨이면(콜게이트를 통한 특권레벨 이동일 경우???) SS와 ESP를 POP 합니다.)

2) 호출 구문에 인수가 있는 경우

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

커널 루틴의 오프셋 15 - 0비트

핸들러의 코드 세그먼트 셀렉터

P

DPL

0

1

1

0

0

 

사용안함

인수의 개수

커널 루틴의 오프셋 31 - 16

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

콜게이트를 통한 커널 루틴에서 사용할 인수의 개수를 미리 기입함

유저 레벨의 SS, ESP의 주소에 인수들을 PUSH 하고 콜게이트를 통하여 커널 레벨(특권 레벨 0) 루틴으로 들어가도 커널 레벨의 루틴에서는 스택 주소가 SS0, ESP0로 변경되어 pop 하여도 관련 없는 값을 참조하게 될 것입니다.

 

GDT 디스크립터의 인수의 개수 항목에 2가 들어있다면 유저 레벨의 스택에서 2개를 커널 모드의 스택에 복사합니다.

(스택의 방향은 원래 위에서 아래로 자라지만 여기서는 작성자의 착각으로;;; 반대로 표시했습니다. 반대로 뒤집으시면 거꿀로 자라는 스택이 됩니다)

Param1

스택 진행 방향

 

Param2

 

ESP

 

←SS

 
  

 

Param1과 Param2를 복사합니다.

 

SS

 

스택 진행 방향

ESP

Param1

Param2

 

CS

  
 

EIP

 

ESP0

 

←SS0

 
  

 

(2) 인터럽트 예외가 발생하였을 때의 스택

EFLAGS가 추가된 것을 제외하면 콜 게이트와 흡사합니다

1) 커널 모드에서 인터럽트가 걸렸을 때

특권 레벨 0에서 진행 되므로 SS, ESP 그대로 값을 사용하고, 스택에 EFLAGS, CS, EIP를 PUSH 합니다.

 

EFLAGS

↑스택 진행 방향

 

CS

  
 

EIP

 

ESP

 

←SS

 
  

 

2) 유저 레벨 에서 인터럽트가 발생했을 때

유저 레벨 태스크의 TSS 영역에서 SS0, ESP0를 CPU의 SS, ESP 레지스터에 복사하여, 콜게이트 경우에는 유저 모드의 태스크가 사용하던 SS, ESP, CS, EIP등의 레지스터의 값을 PUSH하여 저장합니다. IRET으로 유저 모드로 돌아갈때는 스택에 있는 값을 POP하여 CPU의 각각의 레지스터에 복원합니다.

 

스택 진행 방향

 
 
 
  
 
  

ESP

 

←SS

 
  

 

SS

 

스택 진행 방향

ESP

EFLAGS

 

CS

  
 

EIP

 

ESP0

 

←SS0

 
  

신고

6. 보호 (1) - cpu

Operating Systems/OS 커널 제작 2015.01.28 16:38 Posted by 쌀건빵

1. LIMIT 체크

(1) 세그먼트 디스크립터의 Limit G 비트

세그먼트 디스크립터의 LIMIT 프로그램 또는 프로세스가 세그먼트 외의 메모리 영역을 접근하지 못하게 합니다. 세그먼트 디스크립터의 lmit G 비트에 영향을 받고, G 0 , lmit 세그먼트에 명시된 값과 같으며(1byte 단위), G 비트가 1 경우 명시된 값의 0xFFF 입니다.

 

(2) 보호 예외(#GP)

세그먼트 영역에서 유효 LIMIT 전체 세그먼트 영역의 크기에서 1바이트 같습니다. (주소가 0 부터 시작 하기 때문입니다.)

다음과 같은 경우에는 #GP 일으킵니다.

Byte > limit-1(오프셋이 유효 Limit 보다 바이트 )

Word>limit-2(오프셋이 유효 Limit -1 보다 워드)

Dword>limit-4(오프셋이 유효 Limit-3 바이트 보다 더블 워드)

Qword>limit-8(오프셋이 유효 Limit-7 바이트보다 쿼드 워드)

 

(3) Limit 효과

무효한 포인터 계산등의 프로그래밍 에러 검출

다른 세그먼트에 있는 코드나 데이터 보호

 

(4) GDT, IDT, TSS

CPU GDT IDT 같은 테이블과 TSS 영역의 크기를 체크합니다.

GDT -> GDTR 2바이트에 해당하는 Limit 이용

IDT -> IDTR 2바이트에 해당하는 Limit 이용

TSS -> TR 레지스터에 2 바이트에 해당하는 Limit 이용

 

2. Type 체크

(1) S 비트와 Type 필드

S 비트가 0이면 Type 필드가 시스템 Type이라는 것이고, 1이면 코드나 데이터 세그먼트의 Type이라는 뜻입니다.

S 0일때의 타입입니다.

Type

게이트 종류

0x0

예약됨

0x1

16 비트

0x2

LDT

0x3

Busy 16비트 TSS

0x4

16 비트 콜게이트

0x5

태스크 게이트

0x6

16비트 인터럽트 게이트

0x7

16비트 트랩 게이트

0x8

예약됨

0x9

32비트 TSS

0xA

예약됨

0xB

Busy 32비트 TSS

0xC

32비트 콜게이트

0xD

예약됨

0xE

32비트 인터럽트 게이트

0xF

32비트 트랩 게이트

 

Ex) P비트가 1, DPL 00 S 0이고 32비트 인터럽트 게이트 -> 0x8E

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

Lmit 15 - 0비트

Base Address 15 - 0비트

1

00

0

0xE

Base Address 23 – 16 비트

Base Address 31 – 24 비트

G

D

0

AVL

Lmit 19 - 16비트

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

Ex) P비트가 1, DPL 00 S 0이고 32비트 TTS-> 0x89

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

Lmit 15 - 0비트

Base Address 15 - 0비트

1

00

0

0x9

Base Address 23 – 16 비트

Base Address 31 – 24 비트

G

D

0

AVL

Lmit 19 - 16비트

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

Ex) P비트가 1, DPL 00 S 0이고 32비트 TTS,B비트 1-> 0x8B

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

Lmit 15 - 0비트

Base Address 15 - 0비트

1

00

0

0xB

Base Address 23 – 16 비트

Base Address 31 – 24 비트

G

D

0

AVL

Lmit 19 - 16비트

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

CPU 다음과 같은 경우 type 정보를 조사합니다.

세그먼트 셀렉터가 세그먼트 레지스터에 로드

디스크립터가 세그먼트 레지스터에 이미 로드되어 있는 세그먼트에 명령이 액세스 

CALL JMP 명령의 오퍼랜드에 셀렉터가 있을 셀렉터에 대한 디스크립터의 Type 필드

신고

 

80386 chip 에서 현재 태스크에서 다른 태스크로 스위칭하는 방법은 4가지가 있습니다.

1. TSS descriptor를 참조하여 JMP 혹은 CALL 실행

2. task gate 를 참조하여 JMP 혹은 CALL 실행

3. IDT의 task gate로 인터럽트나 예외 벡터 사용

4. NT flag가 SET 되어 있을 때, IRET 실행

 

이 포스팅에서는 1번의 경우의 태스크 스위칭에 대해서 알아볼 것입니다.

 

1. jmp를 이용한 태스크 스위칭

태스크 스위칭이 일어나 Process2 루틴이 실행되게 해 봅시다.

Tss2의 영역에 있는 EIP에 process2 루틴의 첫 번지를 넣어 process2: 부터 실행 되도록 합니다.

(원래 tss의 EIP영역에는 유저 영역의 스택 주소가 저장되지만, 설명의 편의를 위해 커널 모드의 스택 주소를 넣어 설명하겠습니다.)

 

먼저 다음과 같이 tss 세그먼트 디스크립터가 준비가 되어 있다고 가정합니다.

descriptor 4(TSS 세그먼트 디스크립터) – tss1을 지정, TSS 세그먼트 셀렉터는 TSS1Selector(0x20)

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x68

0x013A

1

00

0

1001

0x01

0x00

0

0

0

0

0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 Descriptor 5 – tss2 를 지정, TSS 세그먼트 셀렉터는 TSS2Selector(0x28)

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x68

0x01A2

1

00

0

1001

0x01

0x00

0

0

0

0

0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

그리고 LTR 명령으로 TSS1이 지정이 되었습니다.

Limit

0x68

Base Address

0x1013A

속성

 

이제 다음 명령을 통해 태스크 스위칭을 시도 합니다.

jmp TSS2Selector:0

 

TR 레지스터를 참조하여, GDT의 Tss!Selector(0x20)에 해당하는 디스크립터를 찾아, tss1영역을 찾은 뒤, 모든 레지스터 값을 저장합니다.

 

Offset은 아무 숫자나 사용해도 태스크 스위칭을 하여 저장되어 있던 EIP 레지스터를 CPU의 EIP 레지스터에 옮겨 전에 실행되던 주소부터 재개하게 때문에 아무 숫자나 사용해도 괜찮으며, 사실 의미가 없습니다.

 

다음과 같이 TSS의 셀렉터를 참조하여 다른 세그먼트 레지스터를 채운것과 같이 GDT에서 해당 세그먼트 디스크립터를 찾은 뒤 TS의 Hidden Part에 복사하여, TSS를 찾습니다.

 

Limit

0x68

Base Address

0x101A2

속성

 

찾은 tss2 영역에 저장되어 있는 레지스터의 값들을 현재 레지스터에 복원시킵니다.

복원 후, EIP 레지스터에 주소가 있다면, 여기서부터 프로그램이 시작되게 됩니다. 처음에 process2: 의 주소를 넣었으므로, process2가 실행 될 것입니다.

 

이 후에 태스크 스위칭이 일어나면 TR에 지정된 TSS 디스크립터에 현재 레지스터의 상태가 저장이 됩니다.

CPU는 TR 레지스터에서TSS 의 base address를 찾고, 여기에 레지스터의 상태를 저장합니다 (EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI, ES, CS, SS, DS, FS, GS, flag register). 이때 TSS의 EIP 필드는 다음에 실행될 명령어를 가리키고 있습니다(EIP 필드가 가리키는 명령어의 전에 실행된 명령어가 task switch를 일으켰다고 볼 수 있습니다).

 

2. CALL 명령에 의한 Task Switching

(1) Task Switching 시점

CALL 명령과 IRET 명령에서 Task Switching이 일어납니다.

(2) EFLAG의 NT 비트와 디스크립터의 B 비트 변화

0 

0 

0 

0 

0 

0 

0 

0 

0 

0 

ID 

VIP 

VIF 

AC 

VM 

RF 

0 

NT

IOPL 

OF 

DF 

IF

TF 

SF 

ZF 

0 

AF 

0 

PF 

1 

CF 

 

EFLAGS 레지스터의 14 비트는 NT 비트로 사용됩니다. Nt는 nested의 약자로, IRET 명령어가 인터럽트 핸들러에서 사용되었는지, 태스크 스위칭에서 사용되었는지 구별할 때 사용합니다.

 

LTR 명령은 해당 TSS 디스크립터의 B 비트를 1로 세트 하며, CPU는 B 비트가 1로 세트되어 있는 디스크립터를 현재 실행되고 있는 태스크로 인식합니다.

CALL 명령이 실행되면, 스위칭 될 태스크의 EFLAGS의 NT 비트와 디스크립터의 B비트가 1로 세트 됩니다.

CALL 명령이 실행될 시점에는 반드시 B 비트가 0이어야 합니다.

IRET명령이 실행될 때, 스위칭 될 태스크의 B 비트가 무조건 1이어야 IRET이 실행이 가능합니다.

IRET을 이용하여 태스크 스위칭 될 때, 현재 태스크의 NT 비트와 디스크립터의 B 비트가 0으로 클리어 됩니다.

 

이러한 B 비트 관리 규약을 사용하는 이유는 이전 태스크로 돌아가지 못하게 하기 위해서 입니다.

 

(3) 이전 주소 저장

CALL 명령이 실행 되면, 새로 태스크 스위칭 될 태스크의 TSS 영역 이전 태스크로의 백링크에 현재 태스크의 TSS 디스크립터 셀렉터를 저장합니다.

IRET 명령이 실행이 되면 현재 태스크의 TSS 영역의 이전 태스크로의 백링크를 사용하여, 이전 태스크를 찾습니다.

 

3. CALL과 JMP

CALL과 IRET 을 이용하여 태스크 스위칭을 하는 경우, 하나의 체인으로 묶여, 그 이전의 태스크는 새로운 태스크의 실행이 끝날 때 까지 다시 실행될 수 없어, 비선점 프로세스 관리에 해당됩니다.

JMP를 이용하여 태스크 스위칭을 하는 경우, 태스크 끼리의 구속성이 없어 자유롭습니다.

신고

1. Task Switching TSS

 

Intel 80286 이상의 CPU Protected Mode에서 CPU  레벨에서 태스크 스위칭을 지원합니다.

태스크 스위칭이 일어나는 동안 프로그램에서 사용하는 레지스터의 값을 저장하고, 복원 시키기 위해서 레지스터를 보관하는 장소가 필요합니다. 이러한 영역을 TSS(Task State Segment)라고 합니다. GDT 처럼  TSS 지정하는 TSS 디스크립터가 GDT 지정되어야 합니다

31

15

I/O 맵의 Base Address

사용안함

T

사용안함

LDT 세그먼트 셀렉터

사용안함

GS

사용안함

FS

사용안함

DS

사용안함

SS

사용안함

CS

사용안함

ES

EDI

ESI

EBP

ESP

EBX

EDX

ECX

EAX

EFLAGS

EIP

CR3(PDBR)

사용안함

SS2

ESP2

사용안함

SS1

ESP1

사용안함

SS0

ESP0

사용안함

이전 태스크로의 링크

 

GDTR, IDTR, CR0, CR1레지스터와 같은 모든 태스크가 공유하는 레지스터를 제외하고 모든 레지스터가 포함되어 있습니다.

다음 코드는 tss 지정하는 코드입니다.

그림은 아래부터 위로 증가하며, 아래 코드는 위에서 아래로 증가합니다.

tss1:

                  dw 0, 0                     ; 이전 태스크로의 back link

                  dd 0                         ; ESP0

                  dw 0, 0                    ; SS0, 사용 안함

                  dd 0                         ; ESP1

                  dw 0, 0                    ; SS1, 사용 안함

                  dd 0                         ; ESP2

                  dw 0, 0                    ; SS2, 사용 안함

                  dd 0, 0, 0                ; CR3, EIP, EFLAGS

                  dd 0, 0, 0, 0           ; EAX, ECX, EDX, EBX

                  dd 0, 0, 0, 0           ; ESP, EBP, ESI, EDI

                  dw 0, 0                    ; ES, 사용 안함

                  dw 0, 0                    ; CS, 사용 안함

                  dw 0, 0                    ; SS, 사용 안함

                  dw 0, 0                    ; DS, 사용 안함

                  dw 0, 0                    ; FS, 사용 안함

                  dw 0, 0                    ; GS, 사용 안함

                  dw 0, 0                    ; LDT, 사용 안함

                  dw 0, 0                    ; 디버그용 T 비트, IO 허가 비트맵

 

항목에 대해서 자세히 알아봅시다.

(1) 이전 태스크로의 back link

이전에 동작하던 프로그램의 TSS 영역의 세그먼트 셀렉터 값이 들어갑니다. 세그먼트 셀렉터를 이용하여 JMP, CALL 명령을 실행하여 태스크 스위칭을 하게 됩니다. CALL 명령어로 태스크 스위칭을 하게 되면, 다음 태스크는 자신의 TSS영역에 이전 태스크 TSS 디스크립터의 셀렉터 값을 저장 두었다가 IRET 명령어 , 이전 태스크로 스위칭을 합니다.

 

(2) ESP0, SS0

시스템이 사용하는 권한 레벨별로 사용할 스택이 따로 존재합니다.  ESP3, SS3 없고, 유저 레벨 스택은 TTS ESP SS 저장 됩니다.

 

(3) CR3

페이지 구현과 관련이 있는 레지스터 입니다.

 

(4) 디버그용 T비트

유저 레벨 태스크를 디버깅 , 태스크 스위칭이 일어나면 디버깅 주잉었다는 것을 표시하기 위해 T 비트에 설정을 합니다.

 

(5) I/O 허가 비트맵

유저레벨태스크는 주변 장치를 제멋대로 사용할 없어, I/O 허가 비트맵을 사용하여 사용할 있는 장비와, 사용할 없는 장비를 구분지어 줍니다.  RAM 영역에 표시하는데, 영역의 시작주소를 TSS I/O 허가 비트맵 칸에 넣어둡니다.

 

TSS 세그먼트 디스크립터

TSS 세그먼트 디스크립터도 GDT 들어가므로 형태가 비슷합니다.

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

Lmit 15 - 0비트

Base Address 15 - 0비트

P

DPL

0

Type

Base Address 23 – 16 비트

Base Address 31 – 24 비트

G

0

0

AVL

Lmit 19 - 16비트

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

다음 코드는 TSS 디스크립터를 기술 것이고, 아래 그림은 표로 나타낸 입니다.

descriptor4:

                  dw 104

                  dw 0

                  db 0

                  db 0x89

                  db 0

                  db 0

 

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

(1) 0x68

(3) 0

1

00

0

(2) 1001

(3) 0

0

0

0

0

0

0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

(1) TSS 디스크립터에서는 항상 limit 값이 0x68 이상이어야 하며, 그렇지 않으면 TSS 예외(#TS) 발생합니다.

 

(2) Type 부분은 다음과 같습니다.

1

0

B

1

1 0 부분은 고정된 부분이며, B비트는 현재 태스크의 상태실행/대기를 나타냅니다. 처음에는 0으로 클리어 합니다.

 

(3) Base address에는 TSS 영역의 시작 주소를 넣습니다.

 

위의 코드에는TSS 세그먼트 디스크립터에  base address 0 기술하였습니다. 부분에는 원래 TSS영역의 시작 주소를 넣어 주어야 됩니다. 따라서 실행 코드에서는 Base Address 넣도록 합니다.

다음 코드가 TSS 세그먼트 디스크립터에 Base address 채워주는 코드 입니다.

tss1 주소(offset)+0x10000 값을 16비트와 8비트와 8비트로 나눠 각각 저장합니다.  3단계로 나눠서 살펴보겠습니다.

                  xor ebx, ebx

                  lea eax, [tss1]                         ; 1) tss1 주소를 구함

                  add eax, 0x10000 ; 2) base address 0x10000 더함

                  mov [descriptor4+2], ax     ; 3) 디스크립터에 알맞게 채워 넣음

                  shr eax, 16                               ;

                  mov [descriptor4+4], al      ;

                  mov [descriptor4+7], ah    ;

 

1) tss 주소 구하기

18 00000009 6631DB                  xor ebx, ebx

    19 0000000C 668D06[3A01]                           lea eax, [tss1]

    20 00000011 660500000100                           add eax, 0x10000

    21 00000017 A3[2C01]                                mov [descriptor4+2], ax

    22 0000001A 66C1E810                              shr eax, 16

    23 0000001E A2[2E01]                                mov [descriptor4+4], al

    24 00000021 8826[3101]                               mov [descriptor4+7], ah

소스 코드를 기계어 코드로 바꿔본 결과, tss1 주소는 0x013A 것을 알아 내었습니다.

 

2) 0x10000 더함

구한 tss 주소에 base address 0x10000 더해줍니다.

(tss1 주소) + (base address) = 0x013A + 0x10000 = 0x1013A

 

3) 디스크립터에 알맞게 채워 넣음

Eax 레지스터에는 다음과 같이 저장이 되어 있습니다.

 

------- ax ---------

 

---ah---

---al----

0x00

0x01

0x01

0x3A

 

Descriptor4+2 주소에 ax, 0x013A 넣어줍니다.

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x68

0x013A

1

00

0

1001

0

0

0

0

0

0

0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

 

다음 명령어는 Shr eax, 16으로 2바이트(16비트) 만큼 eax 레지스터의 값을 오른쪽으로 당겨줍니다 .

결과 eax 레지스터의 내용이 다음과 같이 변했습니다.

 

------- ax ---------

→→

---ah---

---al----

 

 

0x00

0x01


al 값을 Descriptor4+4, Base Address 23 – 16 비트 영역에 넣어줍니다.

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x68

0x013A

1

00

0

1001

0x01

0

0

0

0

0

0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

마지막으로 ah 내용을 Descriptor4+7, Base Address 31-24비트 영역에 넣어 줍니다.

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x68

0x013A

1

00

0

1001

0x01

0x00

0

0

0

0

0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

결과 Base address 영역에 값이 알맞게 나누어져 들어갔습니다.

Tss2 마찬가지로 base address 영역을 채워줄 있습니다.

 

(4) CPU Tss 세그먼트의 위치를 알립니다.

Tss 세그먼트 디스크립터의 위치를 CPU 알려야 사용할 있습니다. 그렇게 하기 위해 TR 레지스터에 TSS 디스크립터의 셀렉터를 넣어줍니다.

mov ax, TSS1Selector

ltr ax

 

Ltr 명령은 CPU TR 레지스터에 TSS 디스크립터 셀렉터 값을 넣는 명령입니다.

이로서 TSS 디스크립터의 설정이 끝났습니다.

신고

5. 예외(Exception)

예외는 프로그램이 프로그램 혹은 시스템에 문제를 일으킬만한 명령을 실행시킬 , CPU 문제 해결을 하기 위해 인터럽트를 일으키는 것입니다.

Protected mode에서 예외는 하드웨어, 인터럽트, 소프트웨어 인터럽트와 같은 IDT 사용합니다.

전체 예외의 종류는 다음과 같습니다.

# Exception Exception handler Signal

0 Divide error divide_error( ) SIGFPE

1 Debug debug( ) SIGTRAP

2 NMI nmi( ) None

3 Breakpoint int3( ) SIGTRAP

4 Overflow overflow( ) SIGSEGV

5 Bounds check bounds( ) SIGSEGV

6 Invalid opcode invalid_op( ) SIGILL

7 Device not available device_not_available( ) None

8 Double fault doublefault_fn( ) None

9 Coprocessor segment overrun coprocessor_segment_overrun( ) SIGFPE

10 Invalid TSS invalid_TSS( ) SIGSEGV

11 Segment not present segment_not_present( ) SIGBUS

12 Stack segment fault stack_segment( ) SIGBUS

13 General protection general_protection( ) SIGSEGV

14 Page Fault page_fault( ) SIGSEGV

15 Intel-reserved None None

16 Floating-point error coprocessor_error( ) SIGFPE

17 Alignment check alignment_check( ) SIGBUS

18 Machine check machine_check( ) None

19 SIMD floating point simd_coprocessor_error( ) SIGFPE

 

다음 코드는 첫번째 인터럽트 디스크립터에 divide error 처리하는 인터럽트 루틴에 관련된 디스크립터를 등록하는 것입니다.

mov edi, 0

lea esi, [idt_zero_divide]

mov cx, 8

rep movsb

 

다음 코드는 인터럽트 디스크립터를 작성한 코드입니다. Isr_zero_divide 인터럽트 서비스 루틴으로 설정하였습니다.

Idt_zero_divide:

dw isr_zero_divide

dw 0x08

db 0

db 0x8E

dw 0x0001

 

isr_zero_divide:

push gs

push fs

push es

push ds

pusad

pushfd

 

mov al, 0x20

out 0x20, al

 

mov ax, VideoSelector

move s, ax

move di, (80*6*2)

lea esi, [msg_isr_zero+divide]

call printf

 

jmp $

 

popfd

popad

pop ds

pop es

pop fs

pop gs

 

신고

3. 타이머 인터럽트 핸들러

타이머는 PIC의 IRQ 0번입니다. 그러나 PIC을 사용하기 위해서 그 전의 포스트에서 초기화 하는 과정에서 시작 번호를 0x20으로 주었으므로, 타이머 인터럽트가 발생하였을 경우 PIC은 CPU에게 0x20을 알릴 것입니다. 따라서 IRQ 0번에 연결된 타이머의 인터럽트를 처리하는 인터럽트 디스크립터는 0x20 번째 디스크립터에 기술 되어야 합니다.

Protected Mode로 넘어갈 때, 모든 인터럽트를 막는 것이 작업에 효율적이었습니다. 따라서 모든 인터럽트를 막았고, 타이머 인터럽트를 받아 들이기 위해서 0x20째 인터럽트를 다시 허용 해야 합니다.

 

lidt[idtr]

 

mov al, 0xFA

out 0x21, al

sti

jmp $

 

Idt를 등록한 뒤에, 타이머에 해당하는 IRQ 0번, 0번 비트를 0으로 클리어 한 뒤, 이것을 0x21 번 포트에 out 명령어로 내주면 해당 인터럽트가 유효하게 됩니다. 마스터 PIC의 데이터 부분의 주소인 0x21에 써줍니다.

7

6

5

4

3

2

1

0

1

리얼타임 클록

1

플로피디스크

컨트롤러

1

프린터

포트1

1

COM1

1

COM2

0

슬레이브

PC

1

키보드

0

타이머

 

Cpu가 PIC으로부터 인터럽트를 받아들이고 /INTA 신호를 돌려주기 위해 sti를 실행 시켜 줍니다.

 

idt_timer:

dw isr_32_timer ;

dw 0x08

db 0

db 0x8E

dw 0x0001

 

     

15

  

  

  

  

  

  

  

  

  

  

  

  

  

  

0

(1)핸들러의 오프셋 15 – 0 (isr_32_timer)

(2)핸들러의 코드 세그먼트 셀렉터 (0x08)

(3)P(1)

(4) DPL(00)

(5) 0

(6)D

(7)1

(8) 1

(9)0

사용안함 ( 0 )

(10) 핸들러의 오프셋 31 – 16(0x0001)

63

  

  

  

  

  

  

  

  

  

  

  

  

  

  

48

 

인터럽트 디스크립터를 보면 0x10000+isr_32_timer 물리 주소로 지정하고 있습니다. 인터럽트 서비스 루틴의 예는 다음과 같습니다.

 

isr_32_timer:

push gs

push fs

push es

push ds

pushad

pushfd

 

mov al, 0x20

out 0x20, al

mov ax, VideoSelector

mov es, ax

mov edi, (80*2*2)

lea esi, [msg_isr_32_timer]

call printf

inc byte [msg_isr_32_timer]

 

popfd

popad

pop ds

pop es

pop fs

pop gs

 

iret

 

핸들러로 들어오면 먼저 모든 레지스터와 EFLAGS를 스택에 저장해 놨다가, 인터럽트 루틴이 종료될 때, 다시 복귀시킵니다.

PIC을 리셋하고 iret 명령어를 실행시켜, 다시 인터럽트가 걸릴 수 있도록 합니다.

 

4. 키보드 인터럽트 핸들러

isr_33_keyboard:

pushad

push gs

push fs

push es

push ds

pushfd

 

in al, 0x60

 

mov al, 0x20

out 0x20, al

 

mov ax, videoSelector

mov es, ax

mov edi, (80*4*2)

lea esi, [msg_isr_33_keyboard]

call printf

inc byte [msg_isr_33_keyboard]

 

popfd

pop ds

pop es

pop fs

pop gs

popad

iret

 

키보드에서 어느 키가 눌렸는지 확인 하기 위해, in al, 0x60 명령어로, 키보드 버퍼에 있는 문자 스캔코드를 가져온 후, PIC을 리셋합니다.

 

신고

1. PIC ( Programmable Interrupt Controller )

PC의 모든 외부로부터의 하드웨어 인터럽트는 8259A라는 칩을 통해서 입력을 받습니다. PIC를 제어 하기 위해, OS에는 PIC을 초기화하고 연결, 인터럽트를 받아들이는 방법, 받아 들인 인터럽트를 CPU에 전달하는 방법이 정의되어 있어야 합니다.

 

   

PIC master –slave 구조로 구성되고, 마스터 PIC IRQ 8 (0 – 7) 슬래이브 PIC IRQ 8 (8 – 15) 핀에 연결된 하드 웨어 장치의 인터럽트를 받아, CPU에게 알리고, CPU 인터럽트의 정보를 PIC으로부터 받아 해당 인터럽트 루틴을 실행합니다.

 

마스터 PIC 동작은 다음과 같습니다.

연결된 장치 , 인터럽트가 발생하면,

(1) INT 핀에 신호를 실어 CPU INT 핀에 신호를

 - CPU Eflags IE 비트를 1 세트하고, 인터럽트를 받을수 있는 상황이면 /INTA 핀으로 신호를 보냄

(2) CPU로부터 /INTA핀으로 신호가 오면, 번째 IRQ 연결된 장치에서 인터럽트가 발생했는지 데이터 버스를 통에 CPU 전달

- CPU PIC 보내온 데이터를 참조하여, Protected Mode 실행 중이면 번호에 맞는 디스크립터를 찾아 인터럽트 핸들러를 실행

슬레이브 PIC 동작은 다음과 같습니다.

(1) 자신의 INT 핀에 신호를 실어 마스터 PIC IRQ 2 핀에 인터럽트 신호를 보냄

- 마스터 PIC 자신의 IRQ 핀에서 인터럽트가 발생하여, CPU에게 INT핀으로 신호 보냄

(2) CPU /INTA 신호를 주면 데이터버스에 숫자를 실어  번째 장치에서 인터럽트가 발생했는지 알려줌

 

마스터 PIC 슬레이브 PIC 사용하기 위해서는 초기화를 해주어야 하며, 동작 등을 프로그래밍 주어야 합니다.

 

PIC 제어 하기 위해 4개의 프로그램을 정의 하겠습니다.

ICW1, ICW2, ICW3, ICW4

 

PIC 사용하는 I/O port 다음과 같은 주소를 사용합니다.

 

Master PIC Command : 0x20

Master PIC Data : 0x21

Slave PIC Command : 0xA0

Slave PIC Data : 0xA1

 

PIC 초기화 – ICW1

7

6

5

4

3

2

1

0

0

0

0

1

L

T

I

M

0

S

N

G

L

I

C

4

 

LTIM – 엣지 트리거링(0), 레벨 트리거링(1)

SNGL – 마스터 슬레이브 구조(0), 마스터만 사용(1)

마스터 PIC 초기화 하기 위해서 다음과 같이 세트 줍니다.

7

6

5

4

3

2

1

0

0

0

0

1

0

0

0

1

16진수로 계산해 보면 0x11 입니다.

 

I/O 명령어인 out 이용하여, 마스터 PIC I/O 주소 0x20 프로그램 합니다.

 

mov al, 0x11

out 0x20, al

 

딜레이를 주기 위해 다음 명령어를 2  실행시킵니다.

jmp $+2

nop

nop

 

명령들은 헥사코드로 0x00eb입니다. 따라서 이렇게 표시합니다

 

dw 0x00eb, 0x00eb

 

슬레이어 PIC I/O주소 0xA0 마찬가지로 세트 하고, 마찬가지로 딜레이를 줍니다.

7

6

5

4

3

2

1

0

0

0

0

1

0

0

0

1

 

 

out 0xA0, al

dw 0x00eb 0x00eb

 

PIC IQR 시작점 설정(IRQ 리맵핑): ICW2                       

7

6

5

4

3

2

1

0

Off7

off6

Off5

Off4

Off3

0

0

0

 

PIC 인터럽트를 받았을 IRQ 번호에 얼마를 해서 CPU 알려줄지 정합니다. 0 – 2 비트가 0 것은 숫자를 8 단위로 기재해야 된다는 뜻입니다.

마스터 PIC 다음과 같이 세트합니다.

7

6

5

4

3

2

1

0

0

0

1

0

0

0

0

0

 

mov al, 0x20

out 0x21, al

dw 0x00eb, 0x00eb

 

슬레이브 PIC 다음과 같이 세트합니다. IRQ 8 부터 시작 함으로, 앞서 마스터 PIC 지정한 것에서 8 더해서 슬레이브 PIC 0x28 세트 합니다.

7

6

5

4

3

2

1