[OS 개발 5] 부트로더의 개념 및 제작, 실행, 분석


1. 간단한 부트코드 입력


 

준비단계가 길었는데 이제 본격적으로 시작해보겠습니다.

 

우선, 우리는 부트스트랩의 첫 과정으로, 디스크의 첫 512b바이트를 읽어들이는 부분을 구현해야 합니다. 특별히 이 512바이트 영역을 MBR영역이라고 칭한다고 이전 포스트에서 언급했었습니다. 즉, 이번 포스팅에서 할 일은 부트 코드를 입력하고 부트 로더를 실행해 보는 것입니다.

 

그 전에, 제가 앞으로 포스팅할 때 설명하는 방식에 대해 간략하게 소개해 보도록 하겠습니다.

우선 아래 코드와 같이, 전체적인 코드와 함께 될 수 있으면 코드의 설명부분을 주석으로 처리할 것입니다. 만약 다음 포스팅 때 중복되는 내용이 있다면 건너뛸 수도 있습니다. 

 

그리고 뒤에 [설명n]을 붙여놨습니다. 이는 해당 명령어가 무엇을 의미하는지 좀 더 자세한 설명을 아래에서 다시 기술하기 위해 인덱싱해 놓은 부분입니다.

즉, 코드에서의 주석은 어떤 명령을 내리고 있는가에 대한 간략한 설명이고, 이후 이 코드들이 의미하는 바가 무엇인지 구체적인 설명을 늘어놓는 방식입니다.

 

아무튼, 사설이 좀 길었는데, 미리 작성한 아래와 같은 코드를 NotePad++ 같은 편집기에서 작성하고 파일 이름을 boot1.asm으로 저장했습니다. (어셈블리 언어는 주로 확장자 .asm을 사용한다고 한다) 코드에 대한 구체적인 설명은 더 뒤에서 하기로 하고, 우선 실행을 해보도록 하겠습니다.

 

 

OS 만들기 부트로더(Boot Loader) 어셈블리 코드부트로더를 구현한 어셈블리 코드

 

 

 


2. 부트로더 실행


cmd창을 열고 boot1.asm을 저장한 위치에서 다음과 같은 명령어를 입력해 봅니다. 이는 asm파일을 컴파일하고 부팅 가능한 이미지 파일로 제작하기 위한 절차입니다.

 

OS 만들기 부트로더(Boot Loader) 컴파일부트로더 컴파일

 

 

아래와 같이 img 파일이 생성될 것입니다.

 

 

OS 만들기 부트로더(Boot Loader) 생성된 이미지 파일어셈블리 코드 파일과 생성된 이미지 파일

 

 

이제 생성된 img 파일을 아래와 같이 VMWare의 가상 머신에서 부팅을 위해 플로피 디스크에 넣어주면 됩니다. 물론, 상단의 Connect at power on도 체크되어 있어야 합니다. CPU를 작동할 때 가상의 플로피 디스크를 사용하여 부팅하겠다는 것을 의미합니다.

(VMWare에서 가상 머신을 만드는 방법은 따로 소개하지 않겠습니다)

 

 

OS 만들기 부트로더(Boot Loader) VMware 플로피 디스크 셋팅VMWare 부트 로더 셋팅

 


이제 우리의 가상머신을 실행해 봅니다.


 

 

OS 만들기 부트로더(Boot Loader) 컴파일 실행 화면부트로더 실행 화면

 

 

정확히 'GUAVA'가 보이네요. 처음으로 윈도우나 리눅스같은 기존의 운영체제가 아닌 내가 직접 제작한 OS를 만들어서 부팅된 결과입니다. 하지만 아직 OS라고 말하기도 좀 난해한 상황입니다. 고작 부트로더 밖에 없는 상태니까요.

 

이제 제대로 앞서 실행한 프로그램의 코드에 대해 분석해 보도록 하겠습니다.

 


3. 부트 코드 분석


 

설명 1.

 

첫 코드의 시작이 [org 0]으로 시작하는데, 이는 메모리의 어디에서 프로그램을 시작할 것인가를 지정해주는 기준점과 같은 역할을 합니다(org=origin). 아마 값을 바꾸면 오작동할 것입니다. 이를 확인해 보기 위해 디스어셈블링을 해서 확인해보기로 했습니다. [org 0]인 경우와 [org 50]일 경우를 비교해보기 위해 두 개의 파일을 만들어서 다음과 같이 CMD창에서 NASM 명령어를 입력해 보았습니다.

 

 

OS 만들기 부트로더(Boot Loader) 디스어셈블링(DisAssembling) 화면디스어셈블링 컴파일

 

 

 

아래 두 디스어셈블링 된 결과를 보면, 앞부분부터 순서대로 명령어 | 기계어 코드 | 어셈블리 코드를 의미합니다. 앞서 포스팅에서 한번 언급한 적이 있는데, 아래 그림과 같이 기계어와 어셈블리 언어는 1:1매칭 됩니다. dis1.txt는 [org 0]의 경우, dis2.txt는 [org 20]의 경우를 의미합니다. [org 20]은 원래의 오프셋 0x5에 20이 더해진 0x19라는 엉뚱한 주소로 점프하는 것을 볼 수 있습니다. 이를 실행해보니 화면에 아무런 출력도 되지 않았습니다. 

 

OS 만들기 부트로더(Boot Loader) 정상적인 경우정상적인 경우 OS 만들기 부트로더(Boot Loader) 비정상적인 경우비정상적인 경우

 

다시 코드를 보면, 다음으로 나오는 [16bit]는 이후 명령어 처리 과정에서 데이터 처리 단위를 16bit로 처리하겠다는 의미입니다. 이는 나중에 다루게 될 real mode와 protected mode와 관련이 있는데, 이 내용은 그때 가서 자세히 알아보도록 하겠습니다.

 

 

이제 프로그램을 시작하기 위해 메모리 첫 지점으로 점프해야하는데, 이 부분이 바로 jmp 0x07C0:start 입니다. 이게 무엇을 의미하는 걸까요

 

jmp는 뒤에 나오는 주소로 무조건 이동 하라는 의미입니다. 그런데, 위에 보다시피, start 부분이 0x5로 바뀐 것을 볼 수 있습니다. start는 개발자가 지정한 부분이고, 실질적으로 컴파일러가 인식할 때는 0x5로 인식하게 됩니다. 즉, 0x7C0:0x5와 같은 의미인 것이죠. 그렇다면 0x7C0:0x5는 무엇일까요? 이는 곧 이동해야 할 메모리 주소입니다. 여기에서 상대주소라는 개념이 나오는데, 마이크로프로세서가 메모리의 물리 주소를 직접 접근할 수도 있지만, 오프셋을 이용하여 상대적인 주소를 활용하기도 합니다. 물리주소의 한정된 공간을 논리주소로 나누어서 사용하기 위한 것입니다. 그래서 0x7C0 부분을 세그먼트(Segment, 부분, 조각)라고 부릅니다. 0x7C0이라는 메모리의 물리주소에 상대주소인 오프셋 0x5로 접근합니다. 그런데 왜 하필 오프셋 값이 0x5일까요?

 

오프셋이 0x5인 이유는 점프할 다음 메모리 주소를 보면 알 수 있습니다. 위의 dis1.txt 파일 결과를 살펴보면, 처음 메모리 주소 00000000에서 다음은  00000005로 이동한 것을 볼 수 있습니다. 왜냐하면, 처음 실행된 기계어로 EA(00000000), 05(00000001), 00(00000002), C0(00000003), 07(00000004), 5바이트의 주소를 사용했기 때문이입니다. 따라서 다음 점프할 메모리 주소는 00000005가 되며, 여기로 이동하기 위해 jmp 명령을 사용하여 목적지인 물리주소 0x7C0의 상대주소로 0x5를 오프셋으로 설정한 것입니다.(0x7C0:0x5 자체를 물리주소로 바꿀 수 있는데, 이는 뒤의 포스팅 세그먼트:오프셋 포스팅에서 다루도록 하겠다) 그래서 dis2.txt와 같이 오프셋을 0x19로 설정하면 다음 명령어로 이동하지 않고, 엉뚱한 메모리 주소로 이동하여 이후 잘못된 결과를 일으키죠. 이를 활용해서 보면, dis1.txt에서 두번째 줄 8CC8의 메모리 주소는 00000005이며, 다음 명령어의 메모리주소는 00000007로 2만큼 증가했습니다. 각각 오프셋 00000006(8C), 00000007(C8) 2바이트를 차지하고 있으므로 그 만큼 증가한 것입니다. 이 후에도 이런 방식으로 계속 메모리 주소가 옮겨진다고 보면 됩니다.



설명 2.

 

start: 영역의 명령 구문들은 나중에 화면 출력에 필요한 레지스터들을 초기화해주는 부분이라고 보면 알맞을 것입니다. 그런데 ES에 0xB800을 넣는 부분이 보이네요. 이는 일반 메모리 주소와는 달리 미리 사용 목적이 정해져있는 주소인데, 나중에 컬러 텍스트 모드 비디오 메모리를 사용하기 위한 것입니다.

 

아래 표를 보시죠. 이 표는 x86의 메모리 맵에서 1MB 이하의 메모리 영역을 구분한 표입니다. 하드웨어 적으로 화면에 글을 쓰거나 색상을 표현하기 위해서는 여러 메모리 영역 중에서 아래의 보라색으로 표시된 것과 같이 비디오 메모리 영역의 주소를 사용해야 합니다. 그 메모리 주소 영역은 0x000A0000부터 0x000FFFFF까지입니다. 이 가운데 0xB800이 컬러 텍스트 모드 비디오 메모리 영역입니다. 따라서 사용하기 전에 세그먼트 레지스터인 ES에 미리 넣어두는 것입니다. 

 

 

OS 만들기 메모리 맵 사용자 영역과 비디오 메모리 영역메모리 맵의 사용자 영역과 비디오 메모리 영역

 

 

추가로 위 표에서 붉은 표시된 부분도 살펴보겠습니다. 시작 메모리 주소로 0x00007C00으로 나와있는 것을 볼 수 있습니다. 우리가 이전에 0x7C0을 시작주소 지정했던 이유가 바로 여기에 있습니다. 메모리 영역을 개발자가 전부 사용하는게 아니라, 일부 할당된 영역에 한해서 사용 가능하며, 그 사용 가능한 메모리의 시작 부분이 0x7C0인 것입니다. 표에서 뒤의 설명에도 "당신의 OS 부트섹터"라고 나와있듯이 말입니다.

 

음? 그런데 뭔가 이상하네요. 뒤에 위의 표에 기재된 주소에는 0이 하나 더 붙어있습니다. 이는 우리가 처음 세그먼트:오프셋 방식의 논리주소를 썼기 때문이고, 나중에 이를 계산하여 물리주소로 변환하면 0x7C00을 도출할 수 있습니다. 어려운 계산은 아니지만, 세그먼트:오프셋에 대한 설명은 뒤에서 따로 포스팅을 할 계획이니, 이를 보고 이해하면 좋을 것 같습니다. 아무튼 결론은 0x7C00:0x0000 = 0x7C00이며, 이것이 부트로딩하기 위해 사용되는 첫 메모리 시작 주소라는 것만 기억하시면 되겠습니다.

 

 

설명 3.

 

paint: 영역에서는 본격적으로 반복적인 어셈블리 명령을 통해 화면에 출력하기 위한 작업을 실행합니다. 처음 시작과 함께 AX값을 [es:di] 주소에 넣는 작업을 합니다. 처음 시작 주소 0xB800:0x0000에 넣어주겠다는 의미죠. 위에서 설명했던 것 처럼, 0xB800은 비디오 메모리 영역입니다. 즉, 문자 '.'을 화면에 보여주겠다는 의미이다. AX에는 [msgBack] 값이 들어있는 상태이기 때문입니다.

 

그리고 이전에 0으로 초기화 했던 di를 2씩 증가시킴으로서 [es:di]의 오프셋 주소를 증가시키면서 문자 '.'을 넣습니다. 단, CX 값을 1씩 줄여가면서 말입니다. 이때 제로 플래그(ZF)에는 0으로  그리고 jnz paint를 통해 CX값이 1인 동안은 계속 이 작업을 반복합니다. 만약 1이면 다음을 실행합니다.

 

이러한 반복 작업을 통해 위와 같은 무수한 점(.)이 찍힌 화면을 출력할 수 있게 됩니다.

 

 

설명 4.

 

이제는 레지스터 EDI를 사용하여 문자를 넣어봤습니다. 위와 비슷한 방식인데, 이번에는 한 바이트는 문자를, 그 다음 주소에는 0x05와 같은 값을 넣어줍니다. 다음과 같이 컬러 텍스트 모드를 이용하여 글자의 색상을 지정해주기 위함 입니다. 지정 방식은 다음 예를 보시면 되겠습니다. 그리고 색상은 아래 코드 표를 보고 지정하면 됩니다.

 

OS 만들기 컬러 텍스트 모드컬러 텍스트 모드의 비트 별 필드

 ex) 배경색: 녹색, 글자색: 검은색 --> 0x10(첫 바이트: 배경색, 두번째 바이트: 글자색을 넣어준다)

  

 코드

색상 

 0x0(0000) 

검은색 

 0x1(0001) 

파란색 

 0x1(0010) 

 녹색

 0x1(0011)

 하늘색

 0x1(0100)

 빨간색

 0x1 (0101)

 보라색

 0x1(0110)

 갈색

 0x1(0111)

 흰색

 

 

설명 5.

 

이 후 jmp $를 반복하는데, 현재 메모리 주소로 계속 이동함을 의미합니다. 이후 남은 공간은 0으로 채워줍니다.(OS 커널의 구조와 원리 책 저자인 김범준 저자에 의하면, 남은 공간을 0으로 채우는 이유는 실행 파일이 깔끔하고 0x510이상을 넘어가면 NASM이 컴파일 에러를 내줌으로써 에러 탐지에 효율적이기 때문이라고 밝히고 있습니다)

 

0은 현재 메모리 위치부터 마지막 510번지까지만 채우면 됩니다. 왜냐하면, 아래 그림과 같이 마지막 511, 512번지에는 시그내쳐 코드인 0xAA55로 채워주며 각각 0x55, 0xAA가 들어가게 되기 때문입니다. 현재 위치한 메모리 주소($)에서 첫 시작 메모리 주소($$)를 빼고 이를 시그내쳐 코드 영역을 제외한 510으로 빼준 영역들을 0으로 채우면 됩니다. 아래 제가 그린 그림을 참고하시기 바랍니다.

 

OS 만들기 메모리 맵과 MBR의 적재 과정MBR의 메모리 적재 및 주소 계산

 

이로써 부트코드에 대해 분석을 마치겠습니다. 부족한 점은 앞으로도 계속 보완할 예정이며, 일단 이 정도면 어느 정도 부트로더에 대해 이해하는 것에는 무리가 없을 것 같습니다. 이 포스팅 작성하는데 5시간이 넘게 걸렸네요.

 


TAGS.

Comments