1. 1024 섹터 크기의 이미지를 메모리로 복사하는 코드
(1) C언어
#include <stdio.h>
int main(){
int iTotalSectorCount = 1024;
int iSectorNumber = 2;
int iHeadMumber = 0;
int iTrackNumber = 0;
// 실제 이미지를 복사할 address(물리주소)
char* pcTargetAddress = (char*) 0x1000;
while(1){
//전체 섹터 수를 하나씩 감소시키면서 0이 될때까지 섹터를 복사
if(iTotalSectorCount == 0){
break;
}
iTotalSectorCount = iTotalSectorCount - 1;
// 1섹터를 읽어들여서 메모리 어드레스에 복사
// BIOSReadOneSector: BIOS의 섹터 읽기 기능을 호출하는 임의의 함수
if(BIOSReadOneSector(iSectorNumber, iHeadNumber, iTrackNumber, pcTargetAddress) == ERROR){
HandleDiskError();
}
// 1섹터는 512(0x200) 바이트이므로, 복사한 섹터 수만큼 어드레스 중가
pcTargetAddress = pcTargetAddress + 0x200;
// 섹터 -> 헤드 -> 트랙 순으로 번호 증가
iSectorNumber = iSectorNumber + 1;
if(iSectorNumber < 19){
continue;
}
// 헤드번호는 0과 1을 반복하므로, XOR연산을 사용함
// 아래 코드는 iHeadNumber = (iHeadNumber == 0x00)? 0x01:0x00; 과 같은 의미
iHeadNumber = iHeadNumber ^ 0x01;
iSectorNumber = 1;
if(iHeadNumber != 0){
continue;
}
iTrackNumber = iTrackNumber + 1;
}
}
void HandleDiskError(){
printf("Disk Error-!!");
while( 1 );
}
(2) asm
- 메모리 계산: https://heeyamsec.tistory.com/22
- ES:BX = ES * 0x10 + BX
TOTALSECTORCONT: dw 1024 ;부트로더를 제외한 MINT64 OS 이미지의 크기. 최대 1152 섹터(0x90000byte)까지 가능
SECTORNUMBER: db 0x02 ;OS 이미지가 시작하는 섹터 번호를 저장하는 영역
HEADNUMBER: db 0x00 ;OS 이미지가 시작하는 헤드 번호를 저장하는 영역
TRACKNUMBER: db 0x00 ;OS 이미지가 시작하는 트랙 번호를 저장하는 영역
;디스크의 내용을 메모리로 복사할 어드레스(ES:BX)를 0x10000으로 설정
mov si, 0x1000 ;OS 이미지를 복사할 어드레스(0x10000)를 세그먼트 레지스터 값으로 변환
mov es, si ;ES 세그먼트 레지스터에 값 설정
mov bx, 0x0000 ;BX 레지스터에 0x0000을 설정하여 복사할 어드레스를 0x1000:0000(0x10000)으로 최종 설정
mob di, word [TOTALSECTORCOUNT] ;복사할 os이미지의 섹터 수를 di레지스터에 설정
;디스크를 읽는 코드의 시작
READDATA: ;모든 섹터를 다 읽었는지 확인
cmp di, 0 ;복사할 OS 이미지의 섹터 수를 0과 비교
je READEND ;복사할 섹터수가 0이라면 다 복사했으므로 READEND로 이동
sub di, 0x1 ;복사할 섹터 수 1 감소
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BIOS READ Function 호출
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov ah, 0x02 ;BIOS 서비스 번호 2 (Read Sector)
mov al, 0x1 ;읽을 섹터 수는 1
mov ch, byte [TRACKNUMBER] ;읽을 트랙 번호 설정
mov cl, byte [SECTORNUMBER] ;읽을 섹터 번호 설정
mov dh, byte [HEADNUMBER] ;읽을 헤드 번호 설정
mov dl, 0x00 ;읽을 드라이브 번호(0==Floppy) 설정
int 0x13 ;인터럽트 서비스 수행
jc HANDLEDISKERROR ;에러가 발생했다면 HANDELEDISKERROR로 이동
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 복사할 어드레스, 트랙, 헤드, 섹터 어드레스 계산
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
add si, 0x0020 ;512(0x200)바이트만큼 읽었으므로, 이를 세그먼트 레지스터 값으로 변환
mov es, si ;ES 세그먼트 레지스터에 더해서 어드레스를 한 섹터만큼 증가
;한 섹터를 읽었으므로 섹터 번호를 증가시키고 마지막 섹터(18)까지 읽었는지 판단
;마지막 섹터가 아니면 섹터 읽기로 이동해서 다시 섹터 읽기 수행
mov al, byte [SECTORNUMBER] ;섹터 번호를 AL레지스터 설정
add al, 0x1 ;섹터 번호를 1증가
mov byte [SECTORNUMBER], al ;증가시킨 섹터 번호를 SECTORNUMBER에 다시 설정
cmp al, 19 ;증가시킨 섹터 번호를 19와 비교
jl READDATA ;섹터 번호가 19미만이라면 READDATA로 이동
;마지막 섹터까지 읽었으면(섹터 번호가 19일 경우) 헤드를 토글 (0->1, 1->0)하고, 섹터 번호를 1로 설정
xor byte [HEADNUMBER], 0x1 ;헤드 번호를 0x01과 xor하여 토글(0->1, 1->0)
mov byte [SECTORNUMBER], 0x01 ;섹터 번호를 다시 1로 설정
;만약 헤드가 1->0으로 바뀌었다면 양쪽 헤드를 모두 읽은 것이므로 아래로 이동하여 트랙번호를 1 증가
cmp byte [HEADNUMBER], 0x00 ;헤드 번호를 0x00과 비교
jne READDATA ;헤드 번호가 0이 아니면 READDATA로 이동
;트랙을 1 증가시킨 후, 다시 섹터 읽기로 이동
add byte [TRACKNUMBER], 0x01 ;트랙 번호를 1 증가
READEND:
HANDELEDISKERROR: ;에러 처리하는 코드
```생략```
2. 스택 초기화와 함수 구현
(1) 스택 사용할 영역
- 0x010000(64KB) 주소부터 OS이미지가 로딩되므로 0x01000이하로 지정함
- 본 실습에서는 0x0000:0000 ~ 0x0000:FFFF 영역으로 지정함
- 스택 세그먼트 레지스터(SS)의 값은 0x0000로 설정하였으며, SP와 BP를 0xFFFF로 설정하여 스택 영역의 크기는 세그먼트의 최대 크기로 지정하였음
(2) 스택 초기화 코드
- 부트 로더 앞부분에 추가될 코드
;stack을 0x0000:0000~0x0000:FFFF 영역에 64KB 크기로 생성
mov ax, 0x0000 ;스택 세그먼트의 시작 어드레스(0x0000)를 세그먼트 레지스터 값으로 변환
mov ss, ax ;SS 세그먼트 레지스터에 설정
mov sp, 0xFFFE ;SP 레지스터의 어드레스를 0xFFFE로 설정
mov bp, 0xFFFE ;BP 레지스터의 어드레스를 0xFFFE로 설정
(3) 메시지 출력 함수
- 화면에서 원하는 위치에 문자열을 출력하려면 X좌표, Y좌표, 출력할 문자열 어드레스가 필요함
- 필요한 3가지를 함수 파라미터로 정의하고 스택에 삽입하는 순서를 지정해야함
- C언어 경우, 파라미터의 역순(오른쪽 → 왼쪽)으로 삽입하고 스택에서 꺼낸 순서가 파라미터 순서와 같게 함
- Example
- C언어
PrintMessage( iX, iY, pcString );
- 어셈블리어
push word [pcString] ;문자열 어드레스를 스택에 삽입 push word [iY] ;화면의 Y좌표를 스택에 삽입 push word [iX] ;화면의 X좌표를 스택에 삽입 call PRINTMESSAGE ;PRINTMESSAGE 함수 호출 add sp, 6 ;2byte*3하여, 6만큼 sp 증가
- 어셈블리 함수의 일반적인 형식
push bp ;베이스 포인터 레지스터(BP)를 스택에 삽입 mov bp, sp ;베이스 포인터 레지스터(BP)에 스택 포인터 레지스터(SP)의 값 설정 ;베이스 포인터 레지스터(BP)를 통해 파라미터에 접근할 목적 push es ;ES 세그먼트 레지스터부터 DX레지스터까지 스택에 삽입 push si ;함수에서 임시로 사용하는 레지스터로 함수의 마지막 부분에서 push di ;스택에 삽입된 값을 꺼내 원래 값으로 복원 push ax push cx push dx ```생략``` mov ax, word [bp+4] ;파라미터 1(iX, 화면 X좌표) mov bx, word [bp+6] ;파라미터 2(iY, 화면 Y좌표) mov cx, word [bp+8] ;파라미터 3(pcString, 출력할 문자열의 어드레스) ```생략``` pop dx ;함수에서 사용이 끝난 DX 레지스터부터 ES레지스터까지 스택에 삽입된 값을 이용해서 복원 pop cx ;스택은 가장 마지막에 들어간 데이터가 가장 먼저 나오는 자료구조이므로 pop ax ;삽입의 역순으로 제거해야함 pop di pop si pop es pop bp ;베이스 포인터 레지스터(BP) 복원 ret ;함수를 호출한 다음 코드의 위치로 복원
- PRINTMESSAGE함수의 코드
;메시지 출력하는 함수 (PARAM: x좌표, y좌표, 문자열)
PRINTMESSAGE:
push bp ;베이스 포인터 레지스터(BP)를 스택에 삽입
mov bp, sp ;베이스 포인터 레지스터(BP)에 스택 포인터 레지스터(SP)의 값 설정
;베이스 포인터 레지스터(BP)를 통해 파라미터에 접근할 목적
push es ;ES 세그먼트 레지스터부터 DX레지스터까지 스택에 삽입
push si ;함수에서 임시로 사용하는 레지스터로 함수의 마지막 부분에서
push di ;스택에 삽입된 값을 꺼내 원래 값으로 복원
push ax
push cx
push dx
; ES 세그먼트 레지스터에 비디오 모드 어드레스 설정
mov ax, 0xB800 ;비디오 메모리 시작 어드레스(0x0B8000)를 세그먼트 레지스터 값으로 변환
mov es, ax ;ES 세그먼트 레지스터에 설정
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; X, Y의 좌표로 비디오 메모리의 어드레스를 계산함
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Y좌표를 이용해서 라인 어드레스를 먼저 구함
mov ax, word [bp+6] ;파라미터 2(화면 Y좌표)를 AX레지스터에 설정
mov si, 160 ;한 라인의 바이트 수(2*80 컬럼)을 SI레지스터에 설정
mul si ;AX레지스터와 SI레지스터를 곱하여, 화면 Y어드레스 계산
mov di, ax ;계산된 화면 Y어드레스를 DI레지스터에 설정
;X좌표를 이용해서 2를 곱한 후, 최종 어드레스 구함
mov ax, word [bp+4] ;파라미터 1(화면 X좌표)를 AX레지스터에 설정
mov si, 2 ;한 문자를 나타내는 바이트수(2)를 SI레지스터에 설정
mul si ;AX레지스터와 SI레지스터를 곱하여, 화면 X어드레스를 계산
add di, ax ;화면 y어드레스와 계산된 X 어드레스를 더해서 실제 비디오 메모리 어드레스 계산
; 출력한 문자열의 어드레스
mov si, word [bp+8] ;파라미터 3(출력한 문자열의 어드레스)
.MESSAGELOOP: ;메시지를 출력하는 루프
mov cl, byte [si] ;SI레지스터가 가르키는 문자열 위치에 한 문자를 CL레지스터에 복사.
;CL레지스터는 CX레지스터의 하위 1byte를 의미
cmp cl, 0 ;복사된 문자와 0을 비교
je .MESSAGEEND ;복사한 문자의 값이 0이면 문자열이 종료되었음을 의미하며, .MESSAGEEND로 이동하여 문자 출력 종료
mov byte [es:di], cl ;0이 아니라면 비디오 메모리 어드레스 0xB800:di에 문자를 출력
add si, 1 ;SI레지스터에 1을 더하여, 다음 문자열로 이동
add di, 2 ;DI레지스터에 2을 더하여, 비디오 메모리의 다음 문자 위치로 이동
;비디오 메모리는 (문자, 속성)의 쌍으로 구성되므로 문자만 출력하려면 2를 더해야함
jmp .MESSAGELOOP ;메시지 출력 루프로 이동하여 다음 문자 출력
.MESSAGEEND:
pop dx ;함수에서 사용이 끝난 DX 레지스터부터 ES레지스터까지 스택에 삽입된 값을 이용해서 복원
pop cx ;스택은 가장 마지막에 들어간 데이터가 가장 먼저 나오는 자료구조이므로
pop ax ;삽입의 역순으로 제거해야함
pop di
pop si
pop es
pop bp ;베이스 포인터 레지스터(BP) 복원
ret ;함수를 호출한 다음 코드의 위치로 복원
3. 보호 모드에서 사용되는 3가지 함수 호출 규약
- 이전까지는 리얼모드인 16비트 모드 기준이었음. 보호모드는 32bit 이므로 레지스터&스택 크기가 리얼 모드의 2배임
- 함수 규약은 함수를 호출할 때, 파라미터와 복귀 어드레스 등을 지정하는 규칙을 의미함
- 보호 모드에서 사용하는 대표적 호출 규약: stdcall, cdecl, fastcall
- stdcall: 파라미터를 스택에 저장하며, 호출된 쪽에서 스택을 정리함
- cdecl: 파라미터를 스택에 저장하지만, 함수를 호출한 쪽에서 스택을 정리함
- fastcall: 일부 파라미터를 레지스터에 저장하는 것을 제외하면, stdcall방식과 같음
4. 최종 부트 로더 소스코드_BootLoader.asm
- OS이미지가 정상적으로 로딩되었다면, 0x1000위치로 이동해서 보호모드 커널 코드를 실행하도록 진행되었음
- 현재 로딩할 커널 이미지가 없으므로 이후 과정에서 구현해야함
- 부트 로더를 제외한 나머지 섹터 크기를 1024로 설정하였으므로 부트 로더를 포함해서 최소한 1025개 이상의 섹터로 구성된 OS이미지가 필요함 (but, 부트 로더 테스트 단계로 가상 OS이미지로 대체할 예정)
[ORG 0x00] ; 코드의 시작 어드레스를 0x00으로 설정
[BITS 16] ; 이하의 코드는 16비트 코드로 설정
SECTION .text ; text 섹션(세그먼트 정의)
jmp 0x07C0:START
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; MINT64 OS에 관련된 환경설정 값
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TOTALSECTORCOUNT: dw 1024 ;부트로더를 제외한 MINT64 OS 이미지의 크기. 최대 1152 섹터(0x90000byte)까지 가능
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 코드 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
START:
mov ax, 0x07C0 ;부트 로더의 시작 어드레스(0x7C00)를 세그먼트 레지스터 값으로 변환
mov ds, ax ;DS 세그먼트 레지스터에 설정
mov ax, 0xB800 ;비디오 메모리 시작 어드레스(0x0B8000)를 세그먼트 레지스터 값으로 변환
mov es, ax ;ES 세그먼트 레지스터에 설정
;stack을 0x0000:0000~0x0000:FFFF 영역에 64KB 크기로 생성
mov ax, 0x0000 ;스택 세그먼트의 시작 어드레스(0x0000)를 세그먼트 레지스터 값으로 변환
mov ss, ax ;SS 세그먼트 레지스터에 설정
mov sp, 0xFFFE ;SP 레지스터의 어드레스를 0xFFFE로 설정
mov bp, 0xFFFE ;BP 레지스터의 어드레스를 0xFFFE로 설정
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 화면 모두 지우고, 배경 녹색 & 글자 검은색으로 설정
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov si, 0
.SCREENCLEARLOOP:
mov byte [es:si], 0
mov byte [es:si+1], 0x20
add si, 2
cmp si, 80*25*2
jl .SCREENCLEARLOOP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 화면 상단에 시작 메시지 출력
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
push MESSAGE1 ;출력할 메시지의 어드레스를 스택에 삽입
push 0 ;화면 Y좌표(0)을 스택에 삽입
push 0 ;화면 X좌표(0)을 스택에 삽입
call PRINTMESSAGE
add sp, 6 ;삽입한 파라미터 제거
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; OS 이미지를 로딩한다는 메시지 출력
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
push IMAGELOADINGMESSAGE
push 1 ;화면 Y좌표(1)을 스택에 삽입
push 0 ;화면 X좌표(0)을 스택에 삽입
call PRINTMESSAGE
add sp, 6
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 디스크에서 OS이미지를 로딩
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 디스크를 읽기 전에 먼저 리셋
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RESETDISK: ;디스크를 리셋하는 코드의 시작
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BIOS Reset Function 호출
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;서비스 번호 0, 드라이브 번호(0=Floppy)
mov ax, 0
mov dl, 0 ;읽을 드라이브 번호(0==Floppy) 설정
int 0x13 ;인터럽트 서비스 수행
jc HANDLEDISKERROR ;에러가 발생했다면 HANDELEDISKERROR로 이동
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 디스크에서 섹터를 읽음
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;디스크의 내용을 메모리로 복사할 어드레스(ES:BX)를 0x10000으로 설정
mov si, 0x1000 ;OS 이미지를 복사할 어드레스(0x10000)를 세그먼트 레지스터 값으로 변환
mov es, si ;ES 세그먼트 레지스터에 값 설정
mov bx, 0x0000 ;BX 레지스터에 0x0000을 설정하여 복사할 어드레스를 0x1000:0000(0x10000)으로 최종 설정
mov di, word [TOTALSECTORCOUNT] ;복사할 os이미지의 섹터 수를 di레지스터에 설정
READDATA: ;디스크 읽는 코드 시작 & 모든 섹터를 다 읽었는지 확인
cmp di, 0 ;복사할 OS 이미지의 섹터 수를 0과 비교
je READEND ;복사할 섹터수가 0이라면 다 복사했으므로 READEND로 이동
sub di, 0x1 ;복사할 섹터 수 1 감소
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BIOS READ Function 호출
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov ah, 0x02 ;BIOS 서비스 번호 2 (Read Sector)
mov al, 0x1 ;읽을 섹터 수는 1
mov ch, byte [TRACKNUMBER] ;읽을 트랙 번호 설정
mov cl, byte [SECTORNUMBER] ;읽을 섹터 번호 설정
mov dh, byte [HEADNUMBER] ;읽을 헤드 번호 설정
mov dl, 0x00 ;읽을 드라이브 번호(0==Floppy) 설정
int 0x13 ;인터럽트 서비스 수행
jc HANDLEDISKERROR ;에러가 발생했다면 HANDELEDISKERROR로 이동
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 복사할 어드레스, 트랙, 헤드, 섹터 어드레스 계산
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
add si, 0x0020 ;512(0x200)바이트만큼 읽었으므로, 이를 세그먼트 레지스터 값으로 변환
mov es, si ;ES 세그먼트 레지스터에 더해서 어드레스를 한 섹터만큼 증가
;한 섹터를 읽었으므로 섹터 번호를 증가시키고 마지막 섹터(18)까지 읽었는지 판단
;마지막 섹터가 아니면 섹터 읽기로 이동해서 다시 섹터 읽기 수행
mov al, byte [SECTORNUMBER] ;섹터 번호를 AL레지스터 설정
add al, 0x1 ;섹터 번호를 1증가
mov byte [SECTORNUMBER], al ;증가시킨 섹터 번호를 SECTORNUMBER에 다시 설정
cmp al, 19 ;증가시킨 섹터 번호를 19와 비교
jl READDATA ;섹터 번호가 19미만이라면 READDATA로 이동
;마지막 섹터까지 읽었으면(섹터 번호가 19일 경우) 헤드를 토글 (0->1, 1->0)하고, 섹터 번호를 1로 설정
xor byte [HEADNUMBER], 0x1 ;헤드 번호를 0x01과 xor하여 토글(0->1, 1->0)
mov byte [SECTORNUMBER], 0x01 ;섹터 번호를 다시 1로 설정
;만약 헤드가 1->0으로 바뀌었다면 양쪽 헤드를 모두 읽은 것이므로 아래로 이동하여 트랙번호를 1 증가
cmp byte [HEADNUMBER], 0x00 ;헤드 번호를 0x00과 비교
jne READDATA ;헤드 번호가 0이 아니면 READDATA로 이동
;트랙을 1 증가시킨 후, 다시 섹터 읽기로 이동
add byte [TRACKNUMBER], 0x01 ;트랙 번호를 1 증가
jmp READDATA ;READDATA로 이동
READEND:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; OS 이미지가 완료되었다는 메시지 출력
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
push LOADINGCOMPLETEMESSAGE ;출력할 메시지의 어드레스를 스택에 삽입
push 1 ;화면 Y좌표(1)를 스택에 삽입
push 20 ;화면 X좌표(20)를 스택에 삽입
call PRINTMESSAGE
add sp, 6
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 로딩한 가상 OS 이미지 실행
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
jmp 0x1000:0x0000
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 함수 코드 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
HANDLEDISKERROR: ;에러 처리하는 코드
push DISKERRORMESSAGE
push 1 ;화면 Y좌표(1)를 스택에 삽입
push 20 ;화면 X좌표(20)를 스택에 삽입
call PRINTMESSAGE
jmp $ ;현재 위치에서 무한 루프 수행
;메시지 출력하는 함수 (PARAM: x좌표, y좌표, 문자열)
PRINTMESSAGE:
push bp ;베이스 포인터 레지스터(BP)를 스택에 삽입
mov bp, sp ;베이스 포인터 레지스터(BP)에 스택 포인터 레지스터(SP)의 값 설정
;베이스 포인터 레지스터(BP)를 통해 파라미터에 접근할 목적
push es ;ES 세그먼트 레지스터부터 DX레지스터까지 스택에 삽입
push si ;함수에서 임시로 사용하는 레지스터로 함수의 마지막 부분에서
push di ;스택에 삽입된 값을 꺼내 원래 값으로 복원
push ax
push cx
push dx
; ES 세그먼트 레지스터에 비디오 모드 어드레스 설정
mov ax, 0xB800 ;비디오 메모리 시작 어드레스(0x0B8000)를 세그먼트 레지스터 값으로 변환
mov es, ax ;ES 세그먼트 레지스터에 설정
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; X, Y의 좌표로 비디오 메모리의 어드레스를 계산함
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Y좌표를 이용해서 라인 어드레스를 먼저 구함
mov ax, word [bp+6] ;파라미터 2(화면 Y좌표)를 AX레지스터에 설정
mov si, 160 ;한 라인의 바이트 수(2*80 컬럼)을 SI레지스터에 설정
mul si ;AX레지스터와 SI레지스터를 곱하여, 화면 Y어드레스 계산
mov di, ax ;계산된 화면 Y어드레스를 DI레지스터에 설정
;X좌표를 이용해서 2를 곱한 후, 최종 어드레스 구함
mov ax, word [bp+4] ;파라미터 1(화면 X좌표)를 AX레지스터에 설정
mov si, 2 ;한 문자를 나타내는 바이트수(2)를 SI레지스터에 설정
mul si ;AX레지스터와 SI레지스터를 곱하여, 화면 X어드레스를 계산
add di, ax ;화면 y어드레스와 계산된 X 어드레스를 더해서 실제 비디오 메모리 어드레스 계산
; 출력한 문자열의 어드레스
mov si, word [bp+8] ;파라미터 3(출력한 문자열의 어드레스)
.MESSAGELOOP: ;메시지를 출력하는 루프
mov cl, byte [si] ;SI레지스터가 가르키는 문자열 위치에 한 문자를 CL레지스터에 복사.
;CL레지스터는 CX레지스터의 하위 1byte를 의미
cmp cl, 0 ;복사된 문자와 0을 비교
je .MESSAGEEND ;복사한 문자의 값이 0이면 문자열이 종료되었음을 의미하며, .MESSAGEEND로 이동하여 문자 출력 종료
mov byte [es:di], cl ;0이 아니라면 비디오 메모리 어드레스 0xB800:di에 문자를 출력
add si, 1 ;SI레지스터에 1을 더하여, 다음 문자열로 이동
add di, 2 ;DI레지스터에 2을 더하여, 비디오 메모리의 다음 문자 위치로 이동
;비디오 메모리는 (문자, 속성)의 쌍으로 구성되므로 문자만 출력하려면 2를 더해야함
jmp .MESSAGELOOP ;메시지 출력 루프로 이동하여 다음 문자 출력
.MESSAGEEND:
pop dx ;함수에서 사용이 끝난 DX 레지스터부터 ES레지스터까지 스택에 삽입된 값을 이용해서 복원
pop cx ;스택은 가장 마지막에 들어간 데이터가 가장 먼저 나오는 자료구조이므로
pop ax ;삽입의 역순으로 제거해야함
pop di
pop si
pop es
pop bp ;베이스 포인터 레지스터(BP) 복원
ret ;함수를 호출한 다음 코드의 위치로 복원
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 데이터 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 부트 로더 시작 메시지
MESSAGE1: db 'MINT64 OS Boot Loader Start~!!', 0 ;마지막 0은 .MESSAGELOOP에서 문자열이 종료되었음을 알 수 있도록 함
DISKERRORMESSAGE: db 'DISK Error~!!', 0
IMAGELOADINGMESSAGE: db 'OS Image Loading...', 0
LOADINGCOMPLETEMESSAGE: db 'Complete~!!', 0
;디스크 읽기에 관련된 변수들
SECTORNUMBER: db 0x02 ;OS 이미지가 시작하는 섹터 번호를 저장하는 위치
HEADNUMBER: db 0x00 ;OS 이미지가 시작하는 헤드 번호를 저장하는 위치
TRACKNUMBER: db 0x00 ;OS 이미지가 시작하는 트랙 번호를 저장하는 위치
times 510 - ($ - $$) db 0x00
db 0x55
db 0xAA ; 1byte를 선언하고 값은 0xAA
; 어드레스 511, 512에 0x55, 0xAA를 써서 섹터로 표기함
- 결과
5. 테스트를 위한 가상 OS 이미지 생성
- 테스트를 위해, 잠깐 사용하므로 세세한 부분까지 구현하지 않아도 됨
- 따라서, 부트 로더 코드를 기반으로 기능을 간소화하여 OS가 실행되었음을 표시하는 기능만 생성함
- 자신의 섹터 번호를 화면 위치에 대응시켜 0~9까지 번호를 출력한다면 화면에 출력된 문자의 위치와 수를 확인하여 정상여부를 판단할 수 있음
- 가상 OS이미지는 거의 같은 코드를 반복하는 구조이므로, 첫번째 섹터에 삽입할 코드를 구현하고 이를 반복하여 나머지 섹터도 생성하였음
- 본 코드는 “01.Kernel32 디렉터리에 VirtualOS.asm”파일로 생성하였음
- VirtualOS.asm파일은 임의로 지정한 가상 OS 소스 파일임
- 해당 코드는 부트로더 코드와 동일한 방식으로 레지스터 초기화를 수행 후, 화면 2번째 라인의 가장 왼쪽 위치에 0을 출력하도록 하였음
- SECTORCOUNT라는 메모리 어드레스는 정상적으로 처리된 섹터의 수를 기록하고 이를 이용하여 화면에 출력될 좌표를 계산할 용도록 추가하였음
(1) 1섹터 크기의 가상 OS 소스코드(VirtualOS.asm)
[ORG 0x00] ;코드의 시작 어드레스는 0x00으로 설정
[BITS 16] ;이하의 코드는 16비트 코드로 설정
SECTION .text ;text 섹션(세그먼트)을 정의
jmp 0x1000:START ;CS 세그먼트 레지스터에 0x1000을 복사하면서, START레이블로 이동
SECTORCOUNT: dw 0x0000 ;현재 실행 중인 섹터 번호를 저장
START:
mov ax, cs ;CS 세그먼트 레지스터의 값을 AX레지스터에 설정
mov ds, ax ;AX 레지스터의 값을 DS 세그먼트 레지스터에 저장
mov ax, 0xB800 ;비디오 메모리 어드레스인 0xB8000을 세그먼트 레지스터 값으로 변환
mov es, ax
mov ax, 2 ;한 문자를 나타내는 바이트 수(2)를 AX레지스터에 설정
mul word [SECTORCOUNT] ;AX레지스터와 섹터 수를 곱함
mov si, ax ;곱한 결과를 si레지스터에 설정
mov byte [es:si + (160*2)], '0' ;계산한 결과를 비디오 메모리에 오프셋으로 삼아 세번째 라인부터 화면에 0을 출력
add word [SECTORCOUNT], 1 ;섹터 수를 1 증가
jmp $ ;현재 위치에서 무한루프 수행
times 512 - ($-$$) db 0x00 ;512 - ($-$$): 현재부터 어드레스 512까지
;현재 위치에서 어드레스 512까지 0x00으로 채움
(2) 1024섹터를 만드는 방법
- 1024섹터 중 마지막 섹터를 제외한 1023섹터의 코드는 화면에 자신을 출력하는 코드 및 다음 섹터의 어드레스로 이동하는 코드를 반복하면 됨
- 마지막 섹터의 경우, 더 이상 섹터가 없으므로 자신을 출력하고 무한 루프를 수행하도록 만들면 됨
- NASM 어셈블러는 반복되는 구문을 위해, %rep라는 전처리문을 제공함
- times와 동일하게 반복 횟수와 반복할 대상을 입력하여 사용할 수 있음
- %endrep사이의 코드를 반복횟수만큼 확장해줌
- 이 외에도 변수할당, 대입문(%assign), 조건문(%if-%elif-%else-%endif)도 지원함
- NASM어셈블러 문서 웹페이지: http://www.nasm.us/doc
- Code
- TOTALSECTORCOUNT라는 상수를 1024로 정의하여, 1024 섹터의 os이미지가 생성되도록 구현하였음 (equ명령은 상수를 정의하는 명령어임)
- times (512 - ($-$$) %512) db 0x00
- 각 섹터의 상위에 화면 출력 코드를 삽입하고, 512byte단위로 정렬하기 위해 남은 영역을 0으로 채움
[ORG 0x00] ;코드의 시작 어드레스는 0x00으로 설정 [BITS 16] ;이하의 코드는 16비트 코드로 설정 SECTION .text ;text 섹션(세그먼트)을 정의 jmp 0x1000:START ;CS 세그먼트 레지스터에 0x1000을 복사하면서, START레이블로 이동 SECTORCOUNT: dw 0x0000 ;현재 실행 중인 섹터 번호를 저장 TOTALSECTORCOUNT equ 1024 ;가상OS의 총 섹터 수 (최대 1152섹터인 0x90000byte까지 가능) START: mov ax, cs ;CS 세그먼트 레지스터의 값을 AX레지스터에 설정 mov ds, ax ;AX 레지스터의 값을 DS 세그먼트 레지스터에 저장 mov ax, 0xB800 ;비디오 메모리 어드레스인 0xB8000을 세그먼트 레지스터 값으로 변환 mov es, ax %assign i 0 ;i라는 변수를 지정하고 0으로 초기화 %rep TOTALSECTORCOUNT ;TOTALSECTORCOUNT에 저장된 값만큼 아래 코드를 반복 %assign i i+1 ;i를 1증가 ;현재 실행 중인 코드가 포함된 섹터의 위치를 화면 좌표로 변환 mov ax, 2 ;한 문자를 나타내는 바이트 수(2)를 AX레지스터에 설정 mul word [SECTORCOUNT] ;AX레지스터와 섹터 수를 곱함 mov si, ax ;곱한 결과를 si레지스터에 설정 mov byte [es:si + (160*2)], '0' + (i % 10) ;계산한 결과를 비디오 메모리에 오프셋으로 삼아 세번째 라인부터 화면에 0을 출력 add word [SECTORCOUNT], 1 ;섹터 수를 1 증가 %if i == TOTALSECTORCOUNT ;마지막 섹터이면 현재 위치에서 무한 루프 수행 jmp $ ;현재 위치에서 무한루프 수행 %else jmp (0x1000 + i * 0x20): 0x0000 ;다음 섹터 오프셋으로 이동 %endif ;if문 끝 times (512 - ($-$$) %512) db 0x00 ;512 - ($-$$): 현재부터 어드레스 512까지 ;현재 위치에서 어드레스 512까지 0x00으로 채움 %endrep ;반복문의 끝
(3) 이미지 생성을 위해, Makefile 생성
- 이전에 만든 소스코드를 통해, 이미지 파일 생성
- 1.Kernel32 디렉토리에 makefile 생성
all: VirtualOS.bin
VirtualOS.bin: VirtualOS.asm
nasm -o VirtualOS.bin VirtualOS.asm
clean:
rm -f VirtualOS.bin
(4) OS이미지 통합
- 가상 OS이미지까지 만들었으므로, 파일들을 하나로 합쳐서 부팅 이미지를 만들어줌
- 부팅 이미지를 만드는 작업이므로, 최상위 디렉토리에 존재하는 makefile을 수정
- 추가 내용
- 먼저, 1.Kernel32 디렉터리에서 make를 수행하여, 가상의 OS이미지 빌드 진행
- 두번째로, 부트로더와 가상 OS이미지를 하나의 파일로 합치도록 수정
- all 레이블에서 부트 로더 → 가상 OS이미지 → 부팅 이미지 순서로 진행하도록 작성
- makefile
all: BootLoader Kernel32 Disk.img
BootLoader:
@echo
@echo =================== Build Boot Loader ===================
@echo
make -C 0.BootLoader
@echo
@echo ==================== Build Complete ====================
@echo
Kernel32:
@echo
@echo =================== Build 32bit Kernel ===================
@echo
make -C 1.Kernel32
@echo
@echo ==================== Build Complete ====================
@echo
Disk.img: BootLoader Kernel32
@echo
@echo =================== Disk Image Build Start ===================
@echo
cat 0.BootLoader/BootLoader.bin 1.Kernel32/VirtualOS.bin > Disk.img
@echo
@echo ==================== Build Complete ====================
@echo
clean:
make -C 0.BootLoader clean
make -C 1.Kernel32 clean
rm -f Disk.img
(5) QEMU 실행
- 에러 발생
- 해결방안
- Floppy Disk가 1.44MB FD에서 2.88MB FD로 변경되면서, 트랙당 섹터 개수가 36개로 변경되었음. 따라서, 18개로 산정한 부트로더에서 에러가 발생하는 것임
- 0.BootLoader 디렉토리의 BootLoader.asm 코드 수정하여 해결할 수 있음 : 19(18+1) 비교하는 것을 37 (36+1)로 변경하면 에러 해결할 수 있음
- 참고: http://jsandroidapp.cafe24.com/xe/development/12035
- 정상적인 결과 모습
'MINT64 OS 개발' 카테고리의 다른 글
6. 32비트 보호 모드로 전환 (0) | 2023.05.06 |
---|---|
5. OS이미지 메모리에 복사 (0) | 2023.05.06 |
4. 32비트 보호 모드로 전환 (0) | 2023.04.13 |
2. 간단한 부트로더 제작 (0) | 2023.04.10 |
1. 개요 & 환경 구축 (0) | 2023.04.10 |