[OS 개발 10] 32비트 커널 로더(4) - 커널 구현과 분석


1. 부트로더에서 커널 구현부로의 점프


 

 

앞서 언급한대로, 부트로더에서 커널 구현부로 넘어가는 부분을 우선 구현해야할 것입니다. 다음 코드를 보시죠.

 

 

위 코드는 부트로더를 확장하여 작성한 코드입니다.

일부 앞서 포스팅에서 언급했던 내용은 해당 포스팅에서 설명을 참고하도록 하고, 추가된 흰색 구역을 위주로 설명하고자 합니다.

 

 

설명1

 

보호 모드에 진입하겠다는 것을 0번 컨트롤 레지스터(CR0)를 통해 CPU에 알려줘야 합니다. 컨트롤 레지스터에 대해서는 나중에 별도의 포스팅을 통해 제대로 설명해보도록 하겠습니다. 일단, CR0 레지스터를 통해 앞으로 32비트 단위로 보호모드를 사용하겠다는 것을 알린다는 것만 알면 될 것 같습니다.


그런데 or연산을 하는 이유는?

비트중에서 값이 바뀌는 비트 외의 비트 값은 변화를 주지 않고 보호하기 위한 차원입니다. or연산의 특징을 이용한 것이죠. 참고로 or연산에 대해 아래와 같이 간략하게만 언급해 두겠습니다.

0 + 0= 0

0 + 1 = 1

1 + 0 = 1

1 + 1 = 1

 

86번 라인에서 CR0에 or연산된 값이 들어감으로써 이제 보호모드로 작동하고자 함을 CPU에게 알립니다.


 

 

설명2

 

보호 모드로 사용됨을 CR0을 통해 알렸지만, 아직 일부 명령어가 들어있는 레지스터에는 16비트로 들어가 있을 수 있습니다. 이를 건너뛰기 위해 이와 같은 코드를 사용합니다. (음.. 저도 아직까지 이부분 아직 확실히 와닿지 않습니다. 일단 이렇게만 알아둬야겠네요)

 

 

설명3

 

여기에 프리픽스(Address Prefix) 개념이 사용되었습니다. 데이터를 처리하기 위해 오퍼랜드를 사용할 때 처리 단위를 16비트 혹은 32비트로 처리하겠다는 것을 CPU에 알릴 수 있는데, 어셈블리 코드로 각각 [bits 16] [bits 32]로 표현할 수 있습니다. 다음 코드를 보시죠.

 

 

이를 컴파일 하고 다음과 같은 명령어를 사용하여 기계어와 어셈블리 코드가 모두 작성된 텍스트 파일을 추출해 보았습니다.

 

 

 

 

 

그 결과, 위와 같이 명령어와 기계어를 한번에 볼 수 있는 텍스트 파일을 볼 수 있습니다.

이를 통해 알려주고자 하는 점은, 16비트일때 eax나 esi와 같은 32비트 단위의 레지스터를 사용할 경우 그 값의 앞에 0x66이나 0x 67과 같은 prefix가 붙는다는 점입니다. 이를 해석해보면, 16비트 단위의 데이터를 처리하되,  eax같은 확장 레지스터를 사용할 경우에는 프리픽스를 붙여서 32비트단위로 처리하겠다는 것을 CPU에 알리는 것입니다.

이 때, eax와 같이 주소가 아닌 오퍼랜드 자체에 대한 확장 레지스터의 프리픽스를 오퍼렌드 프리픽스(Operand Prefix)라고 하며, 위의 [esi]와 같이 주소 처리에 대한 프리픽스를 어드레스 피리픽스(Address Prefix)라고 부릅니다.

 

그 아래 32비트 단위를 보자. 여기에서는 위의 그림에서 별도로 화살표를 붙이지는 않았는데, 얼핏 봐도 위의 16비트 코드와는 반대임을 알 수 있습니다. 즉, 32비트 단위로 처리하고자 하는데, 일부 16비트 레지스터가 사용될 경우에 프리픽스를 붙이는 것입니다.

 

그런데, 설명3의 코드를 설명한다면서 왜 이런 프리픽스에 대해 설명했는지 궁금할 것입니다.

 

이제 jmp dword SysCodeSelector:0x10000 이 코드를 실행함으로써 커널 코드로 점프할 것인데, 점프하면 바로 [32 bits] 단위의 코드가 실행되기 때문입니다. 즉, 이제까지 위와 같이 16비트 단위로 데이터가 처리되었다면, 이제 커널로 점프하면 바로 [32 bits]가 보일 것이고, 이제 32비트 단위로 데이터를 처리하게 될 것입니다.

 

 

 


2. 32비트 커널 구현부


그런데 위 코드에서 보면 SysCodeSelector의 주소가 보이지 않습니다. 위의 부트로더 코드를 다시 살펴보자! 바로 첫번째 줄에 다음과 같은 명령어가 있죠.

 

 %include "init1.inc" 

 

init1.inc 파일을 참조 파일로 포함하겠다는 의미입니다. init1.inc 파일에는 다음과 같이 단 세줄의 코드가 삽입되어있습니다.

 

 

이 헤더파일의 SysCodeSelector의 값을 참조하여  jmp dword SysCodeSelector:0x10000 를 통해 해당 커널의 메모리 주소로 점프할 수 있게 됩니다.

 




그럼 이제 커널 코드를 살펴보겠습니다.

 

 

32비트 보호모드에서 간단한 문자열 출력을 실행해보기 위해 위와 같이 구현해보았습니다. 꽤 간단한 코드입니다(일단 커널 구현은 처음이므로 문자열의 각 문자를 일일이 저장하였습니다. 마치 C언어에서 printf 노가다와 비슷한....).

 

우선 [bits 32] 를 통해 처리할 코드들이 32비트 단위로 처리됨을 CPU에게 알립니다. 그리고 SysDataSelector와 VideoSelector를 초기화 하였습니다. 그리고 나서 mov edi, 80*2*10+2*10 명령어를 진행합니다. 문자 출력을 위해 썼는데, 각 숫자가 의미하는 바가 있습니다.

 

80: 출력 시 한 줄에 들어갈 글자의 수.

2: 컬러 텍스트 모드에서 1바이트: 배경, 1바이트: 글자, 즉, 2바이트가 필요하므로 이를 곱해줌.

10: 출력할 총 줄의 수.

2*10: 위의 10줄을 수행한 후 문자열을 출력하기 전에 더 추가될 문자의 수. 즉 2바이트씩 10개의 문자를 더 지난다.

 

위의 문자를 지난 후에야 그 다음 prinf를 호출함으로써 "Protected Mode"라는 글자가 출력될 것입니다.

 

 


3. 32비트 보호 모드 커널 실행


이제 위의 부트로더와 커널로더를 부팅파일로 만들기 위해 각각을 컴파일 한 후, 다음과 같이 이미지 파일을 제작합니다.

 

 

 

 

이를 실행해보면 다음과 같은 결과를 볼 수 있습니다.

 

 

 

 

이로써 16비트 리얼모드에서 작동하는 부트로더에서 32비트 보호 모드 커널로 이동하는 과정을 살펴보았습니다. 여기까지만 해도 시간이 꽤 걸렸는데, 아직 앞으로 할게 더 많네요. 다음 포스팅에서는 이 구현되 코드를 좀 더 간결하게 정리해보는 시간을 가지도록 하겠습니다. 안하고 그냥 넘어갈 수도 있는데 일단 이번 포스팅은 여기에서 마무리하고, 나중에 잘못된 점이 있으면 다시 수정해야겠습니다. 포스팅이 길어져서 좀 피곤하네요 자야겠습니다.

 


TAGS.

Comments