3. 플로피 디스크에서 OS 이미지를 로딩하자

들어가기

기본 부트로더에서 OS 이미지를 로딩하는 기능을 추가하고 테스트용 OS 이미지를 로드해보자.

본론

  • 부트 로더를 사용해 OS 이미지를 로딩하는 방법으로 BIOS 사용하는 방법과 디스크 컨트롤러를 사용하는 방법 존재.
  • 이번 정리에서는 BIOS로 가상 OS 이미지를 로딩.

3.1 BIOS 서비스와 소프트웨어 인터럽트

  • BIOS는 PC 주변기기를 제어하는 거의 모든 기능을 제공.
  • BIOS는 함수의 주소를 인터럽트 벡터 테이블에 넣어두고, 인터럽트를 호출하는 방법을 사용해 기능을 제공.

  • BIOS가 제공하는 디스크 서비스는 0x13 인터럽트를 발생 시켜야 함.

  • 소프트웨어 인터럽트는 int 0x13 형태로 사용.
  • BIOS도 만능은 아니므로 파라미터와 결과 값을 레지스터를 사용해 전달.
  • 기본적으로 범용 레지스터와 ES 세그먼트 레지스터를 사용하지만, BIOS 서비스 마다 사용하는 레지스터의 수와 종류가 다르므로 확인을 해야함.

3.2 OS 이미지 로딩 기능 구현

3.2.1 디스크 읽기 기능 구현

  • 이번 프로젝트에서는 0x10000에 OS 이미지를 로딩할 예정.
  • 플로피 디스크에 첫 번쨰 섹터는 부트 로더로 BIOS가 메모리에 로딩 시킴.
  • 즉, 두번째 섹터부터 OS 이미지 크기만큼 읽음.
    – 순서는 섹터 -> 헤드 -> 트랙 순.
    – 섹터를 다 읽고, 헤드를 1로 바꾸고 다시 같은 섹터를 읽은 뒤, 트랙을 증가 시킴.
  • 앞서 구현했던 환영 메시지 출력 코드를 함수 형태로 작성해 원하는 곳에서 호출할 예정.
  • 이를 위해 스택이 필요.

3.2.2 스택 초기화와 함수 구현

  • x86 프로세서에서 함수를 사용하려면 스택이 꼭 필요.
  • x86에서는 함수 호출 후 call 호출로 되돌아갈 주소를 스택에 저장한뒤 ret 호출로 되돌아옴.
  • 스택은 함수의 파라미터를 저장하는 역할도 함 -> 호출하는 쪽과 호출되는 쪽은 정해진 규칙에 따라 파라미터를 스택에 저장.

  • 스택 생성을 위해서는 스택 관련 레지스터가 필요.
    스택 세그먼트 레지스터(SS), 스택 영역을 사용할 세그먼트의 기준 주소를 지정.
    스택 포인터 레지스터(SP), 데이터를 삽입하고 제거하는 상위를 지정.
    베이스 포인터 레지스터(BP), 스택의 기준 주소를 임시로 지정할 때 사용.

1
2
3
4
mov ax, 0x0000  ; 스택 세그먼트의 시작 어드레스를 레지스터 값으로 변환
mov ss, ax ; SS 설정
mov sp, 0xFFFE ; SP 설정
mov bp, 0xFFFE ; BP 설정
  • OS 이미지가 0x10000에 로딩될 예정으로 그 아래 영역을 사용할 예정.
  • 메시지 출력 함수는 이전에 봤던 출력 핵심 코드와 상당히 유사.
  • but, 함수에서 사용하는 레지스터를 저장하고, 복구하는 코드와 넘겨받은 파라미터를 스택에서 꺼내는 코드를 추가해야 함.
  • 화면에서 원하는 위치에 문자열을 출력하려면 x, y 좌표와 문자열의 주소가 필요.
  • 스택에 삽입하는 순서는 파라미터를 받는 쪽과 같은 순서로 삽입하면 순서는 상관없지만 C언어의 호출 규약을 따를 예정.
1
2
// C 언어 함수 호출
PrintMessage(ix, iy, pcString)
1
2
3
4
5
push word [ pcString ]
push word [ iy ]
push word [ ix ]
call PRINTMESSAGE
add sp, 6
  • 호출된 PRINTMESSAGE는 데이터를 꺼낼 때 C언어 함수 호출 규약과 동일.
  • 함수 호출뒤 add sp, 6을 통해 스택에서 데이터를 제거.
  • 호출되는 함수는 파라미터가 순서대로 삽입되어 있다는 사실을 알고 있음.
  • 즉, 스택의 특정 위치를 기준으로 오프셋을 이용해 접근하며 파라미터를 찾음.
    – but, SP는 push, pop에 따라 계속 변해 불편.
    – 따라서 고정된 BP를 기준으로 파라미터에 접근.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
push bp
mov bp, sp ; BP를 사용해 파라미터 접근

push es
push si
push di ; 모든 레지스터를 스택에 저장
push ax
push cx
push dx

=== 생략 ===
mov ax, word [ bp + 4] ; 파라미터 1 (ix)
mov bx, word [ bp + 6] ; 파라미터 2 (iy)
mov cx, word [ bp + 8] ; 파라미터 3 (pcString)
=== 생략 ===

pop dx
pop cx
pop ax
pop di ; 데이터 복원
pop si
pop es
pop bp

  • 호출되는 함수가 작업을 마치고 원래 주소 코드로 복귀할 때 정상적인 수행을 위해서는 함수 호출 전후의 레지스터 상태가 동일해야 함.
    – 따라서 호출되는 함수에서는 자신이 사용하는 레지스터의 값을 미리 스택에 저장, 수행이 끝나면 이를 복원.

보호 모드에서 사용되는 세 가지 함수 호출 규약

  • stdcall, 파라미터를 스택에 저장하며 호출된 쪽에서 스택을 정리.
  • cdecl, 파라미터를 스택에 저장하며 함수를 호출한 쪽에서 스택을 정리.
  • fastcall, 일부 파라미터를 레지스터에 저장하지만 stdcall과 유사.

3.3 테스트를 위한 가상 OS 이미지 생성

  • 가상 OS 이미지는 세세한 부분까지 구현하지 않고 OS가 제대로 부트 로더로부터 메모리로 복사되는지 확인할 예정.

3.3.1 OS 이미지 통합과 QEMU 실행

  • 1024개의 섹터를 돌며 가상 OS를 출력한 결과.

코드 보기

마치며

포기할 뻔 했다. 역시 어셈블리 많이 어렵다..

Share