Protected Mode로 변환하기

2장에서 사용한 boot.txt 파일을 그대로 사용하며 kernel.txt 파일만 아래와 같이 작성한다.

[org 0]

[bits 16]


start:

mov ax, cs

mov ds, ax

xor ax, ax

mov ss, ax

cli

lgdt[gdtr]    ->  lgdt[gdtr] 명령어를 통해 GDT를 등록시킴.


mov eax, cr0

or eax, 0x00000001

mov cr0, eax

-> CPU에는 CR0, CR1, CR2, CR3 레지스터가 있으며, 프로그래머가 디버깅 할 때 사용
    위 3줄은 CR0에 0x00000001을 OR 연산을 통해 추가하는 루틴
    CR0의 최하위 비트는 PE비트라 하여 이제부터 Protected Mode이다 라는 것을 표시
    이 비트가 세트된 후 Protected Mode 안에서 동작하게 된다.
    OR 연산을 이용한 이유는 CR0 레지스터에 다른 비트 값들이 손상되지 않도록 하기 위해서이다.
   
위 루틴을 통해 Protected Mode로 바뀌나, CPU 내부 사정 상 한가지 추가작업을 해주어야 한다.
   
CPU에는 명령을 읽고, 해석하고 실행하는 각각의 유닛이 있다.
    이 동작은 동시에 진행되며 CR0 최하위 비트를 세트하는 명령 실행 동안 그 다음 명령이 해석 중에
   있고 그 다음 다음 명령이 읽혀지고 있다.


jmp $+2

nop

nop


db 0x66    -> Operand Prefix라고 하여 CPU에게 16비트 명령이 32비트 명령으로 바뀌었다 라던가 32비트
                    명령이 16비트로 바뀌었다는 것을 알려주기 위한 표시, 0x66은 32비트 및 16비트 명령어 사용
                    16비트에서 0x66 사용 시 32비트로 전환, 32비트에서 0x66 사용 시 16비트로 전환

db 0x67    -> 16비트 코드가 진행중이지만 32비트 주소값을 사용, 66 사용 및 67 사용 시 그 뒤로부터 자동으
                    로 자동으로 32비트로 사용됨

db 0xEA    -> JMP 명령을 그대로 기계어로 바꿔놓은 것

dd PM_Start

dw SysCodeSelector


;------------------------------------------------------------------;

;************** 여기부터 Protected Mode 입니다. *******************;

;------------------------------------------------------------------;


[bits 32]


PM_Start:    -> 셀렉터 레지스터에 16비트가 남아있기 때문에 초기화를 위한 작업 해주는 곳

mov bx, SysDataSelector

mov ds, bx

mov es, bx

mov fs, bx

mov gs, bx

mov ss, bx


xor eax, eax

mov ax, VideoSelector

mov es, ax

mov edi, 80*2*10+2*10    -> 문자열의 위치 (한 열의 칸 수 * 2 * 써야할 열 번호 + 2 * 서야할 칸 번호)

lea esi, [ds:msgPMode]

call printf


jmp $


;------------------------------------------------------------------;

;************** Sub Routines        *******************;

;------------------------------------------------------------------;

printf:

push eax


printf_loop:

or al, al

jz printf_end

mov al, byte [esi]

mov byte [es:edi], al

inc edi

mov byte [es:edi], 0x06

inc esi

inc edi

jmp printf_loop


printf_end:

pop eax

ret


msgPMode db "We are in Protected Mode", 0


;------------------------------------------------------------------;

;************** GDT Table 입니다. *******************;

;------------------------------------------------------------------;

gdtr:

dw gdt_end - gdt - 1 ; GDT의 limit

: 워드 값으로 GDT의 크기를 나타내며, 이 프로그램에서 사용하는 GDT에 포함되어 있는 모든 디스크립터가 차지하는 총 바이트 량

디스크립터는 NULL, SysCode, SysCodeData, Video 4개이며 한개가 8바이트 이므로, 총 바이트량은 8 * 4 = 32 바이트이다. 그러므로 GDTR 레지스터의 사용할 GDT의 크기 부분에 들어갈 숫자는 32이다.

gdt_end - gdt - 1의 표현을 보면 GDT의 마지막 번지에서 첫번째 번지를 빼고, 거기에서 1을 또 빼게 되어 있음. gdt_end는 GDT의 맨 끝 주소의 다음 주소를 가리키고 있기 때문에 gdt_end - gdt에서 1을 또 빼주어야 한다.

dd gdt+0x10000 ; GDT의 Base Address

GDT의 시작 번지를 물리 주소 값으로 가지고 있음. 그러나 주소값에 0x10000을 더하고 있다. 이 프로그램의 첫 부분에서 [org 0]으로 시작하였기 때문에 이 소스 안에 있는 모든 주소는 0을 기준으로 하고 있으며 프로그램의 물리 주소의 시작은 0x10000이고, gdt: 번지도 0x10000 이후에 있을 것이다.
GDTR에 넣어야 하는 값은 세그먼트:오프셋 이 아닌 물리주소 이므로 gdt: 번지가 실제 존재하는 물리 주소의 값을 만들어 GDTR에 넣어야 한다.


;NULL 디스크립터 ( GDT 첫번째는 형식 상 NULL 디스크립터를 기재해야함, 그렇지 않으면 실행 도중 에러 발생)

gdt:   

dw 0 ; limit 0~15비트

dw 0 ; Base Address의 하위 두 바이트

db 0 ; Base Address 16~23 비트

db 0 ; 타입

db 0 ; limit 16~19비트, 플래그

db 0 ; Base Address 31~24비트


; 코드 세그먼트 디스크립터

SysCodeSelector equ 0x08

dw 0xFFFF ; limit:0xFFFF

dw 0x0000 ; base 0~15 bit

db 0x01 ; base 16~23 bit

db 0x9A ; P:1, DPL:0, Code, non-conforming, readable

   -> 0x9(2진수 1001) 4번째 비트가 1, 이것은 코드 세그먼트를 의미

  DPL 은 00이므로 커널 영역으로 사용됨을 확인 가능

  첫번째 비트 1, 따라서 0xFFF를 곱해줘야함

  Limit 0xFFFFF(바로 아래 줄 Limit 16~19가 0xF이므로) * FFF = 0xFFFFFFFF


db 0xCF ; G:1, D:1, limit 16~19(bit:0xF)

D 비트가 1이므로 이 세그먼트는 32비트 코드를 담을 수 있는 세그먼트

db 0x00 ; base 24~32 bit


; 데이터 세그먼트 디스크립터

SysDataSelector equ 0x10

dw 0xFFFF ; limit:0xFFFF

dw 0x0000 ; base 0~15 bit

db 0x01 ; base 16~23 bit

db 0x92 ; P:1, DPL:0, Data, expand-up, writeable

db 0xCF ; G:1, D:1, limit 16~19 bit:0xF

db 0x00 ; base 24~32 bit


; 비디오 세그먼트 디스크립터

VideoSelector equ 0x18

dw 0xFFFF ; limit:0xFFFF

dw 0x8000 ; base 0~15 bit

db 0x0B ; base 16~23 bit    -> Base Address는 0xB8000이 된다.

db 0x92 ; P:1, DPL:0, Data, expand-up, writable

db 0x40 ; G:0, D:1, limit 16~19 bit:0x0

db 0x00 ; base 24~32 bit

gdt_end:


--------------------------------------------------------------------------


작성 완료 후 아래와 같이 컴파일 한다.


컴파일 후 img를 플로피에 넣어 결과를 확인한다.



Protected Mode에서 출력 성공!



오늘 중요사항 정리

1. Real Mode에는 16비트 레지스터 사용 및 세그먼트:오프셋 형식으로 지정한다.

  논리 주소        CPU의 주소 변환 방법        물리 주소

0x3030:4040    -> 0x30300 + 0x4040    ->     0x34340

0x????:4040    -> 0x30305 + 0x4040    ->     0x34345    -> 표현 불가, CPU가 0x10을 곱하기 때문 세

먼트 지정은 항상 16단위로만 이루어짐

0x3031:4040    -> 0x30310 + 0x4040   ->      0x34350

2. A20 Gate를 켜지 않은 상태에서 주소 지정이 가능하다고 해도 RAM 자체 물리주소 0x100000(1MB)로 사용 제한

3. A20 게이트를 켜면 0x100000 이상의 주소 접근 가능

2Protected Mode로 전환 전에 GDT(Global Descriptor Table)가 필요함

4. GDT는 어떤 형식이 있는 데이터의 나열이며, RAM 영역 중 어디든지 기입 가능(전원 OFF까지 그자리에 존재하여야 함)

5. GDT는 각 세그먼트 영역에 대해 어떻게 사용할지 설명해 놓음, 디스크립터는 아래와 같다.

Base Address : 세그먼트의 시작 주소, 물리주소로 하위 16비트와 상위 16비트로 두군데로 나누어 저장

Limit : 세그먼트의 한계점(크기)을 나타내며 오프셋은 이 숫자를 넘어갈 수 없다. 넘어간다면 GP fault(Protected Mode 규약 위반)가 발생한다. Limit는 총 20비트로 구성되며 2군데로 나누어 기재

  Limit는 또한 디스크립터 내 G 비트와 관련, G비트가 0이면 이 세그먼트 크기를 바이트 단위로 하며, 1이면       4KB 단위로 한다. 따라서 G 비트가 0일때, Limit 값이 그대로 한계점(크기)을 나타내고, 1일 때 Limit 값에     0xFFF 의 곱하여 그 수를 한계점으로 나타낸다. Limit 값이 0이면 크기가 0인 세그먼트라는 이야기이며 이렇     게 되어선 안됨)

Limit 값이 1일 때

G 비트가 0이면 세그먼트의 크기 : 1바이트

G 비트가 1이면 세그먼트의 크기 : 1 * 0xFFF = 0xFFF바이트가 된다.

Limit 값이 1234 일때

G 비트가 0이면 세그먼트의 크기 : 1234바이트

G 비트가 1이면 세그먼트의 크기 : 1234 * 0xFFF = 0x1234FFF바이트가 된다.

Limit 값이 20 비트를 모두 채워 0xFFFFF를 기재해 두었을 때

G 비트가 0이면 세그먼트의 크기 : 1메가바이트

G 비트가 1이면 세그먼트의 크기 : 0xFFFFF * 0xFFF = 0xFFFFFFFF바이트(4GB)가 된다.

P 비트 : 세그먼트가 메모리 상에 존재하는지를 나타내는 값, 커널 프로그램의 메모리 관리 루틴이 사용, 페이징 기능
          과 관련 있음

DPL(2Bits) : 이 세그먼트가 커널 레벨인지, 유저레벨인지 나타낸다. 인텔 x86 계열 CPU에서는 0~3의 값으로 4가                  지 레벨이 있으나, 보통의 경우 커널 제작 시 0과 3레벨만 사용. DPL 값이 0이면 이 세그먼트가 커널                  레벨이라는 것을 나타내고, 3(2진수 11)이면 이 세그먼트가 유저 레벨이라는 뜻

S : 이 세그먼트가 시스템 세그먼트인지(0일 때), 코드 혹은 데이터 세그먼트인지(1일 때)지정, 항상 1로 해둠 

Type(4Bits) : 디스크립터의 Type 적용시킬 4개 비트 값은 아래와 같다.

Type의 최상위 비트는 이 세그먼트가 코드 세그먼트인지 데이터 세그먼트인지 구분하여 지정

마지막 비트는 Access 비트이며 어떤 프로그램이 이 세그먼트에 접근했을 때 CPU가 이곳을 찾아 A비트를 1로 바꿔준다. 그러나 CPU는 이 비트를 0으로 클리어 해주지는 않음

커널은 메모리 관리를 할 때 이 비트가 1이 되었는지를 조사하거나, 액세스 된 세그먼트의 디스크립터를 찾아 이 A비트를 어느시간이 지나면 0으로 바꿔주는 일을 함

이 비트는 커널의 메모리 관리를 도와주는 의미, CPU의 동작에는 아무런 영향을 주지 않음, GDT 초기화 시 0 기재

코드 세그먼트일때와 데이터 세그먼트일 때의 두번째 비트와 세번째 비트의 역할이 달라짐

첫번째 비트가 0 일때

이 세그먼트는 데이터 세그먼트로 사용

두번째 비트가 1이면 EXPAND DOWN 형태

두번째 비트가 0이면 EXPAND UP 형태

이 세그먼트를 스택으로 사용할 경우 세그먼트의 크기가 동적으로 변하도록 만들기 위한 장치

비트를 1로 세트하고, 프로그램에서 도중에 이 세그먼트의 Limit 값을 바꾸면 스택의 제일 밑에 스택 공간이 추가된다.

스택을 위한 세그먼트는 항상 데이터 세그먼트여야 하며, 읽기 쓰기 모두 가능해야 한다. 만약 쓰기가 불가능한 세그먼트를 스택으로 사용 시, 다시말해 SS 세그먼트 레지스터에 쓰기가 불가능한 데이터 세그먼트를 로드하면 GP 폴트 발생

세번째 비트를 0으로 하면 이 세그먼트 영역은 읽기만 가능하고, 1로 세트 시 읽기 쓰기 모두 가능


첫번째 비트가 1 일때

이 세그먼트는 코드 세그먼트로 사용

두번째 비트가 1이면 Conforming을 지원(CPU의 Protected Mode의 보호 정책과 관련있음), 소스에선 0으로 해줌

세번째 비트가 1이면 읽기 가능, 0이면 실행만 가능하고 읽기 불가

코드 세그먼트의 경우 쓰기는 지원 안됨, 읽기가 되느냐 안되느냐 사항만 세팅 가능


디스크립터 내 D 비트는 이 세그먼트가 16비트인지 32비트인지 나타냄, 0이면 16비트, 1이면 32비트(커널 프로그램은 32비트이므로 1로 설정)


6. 위에서 만든 GDT는 어디에 만들어졌는지, 몇개나 있는지 확인하기 위해 CPU가 알도록 등록을 하여야함.

  -> CPU에는 GDTR이라는 레지스터가 존재, 48비트의 크기를 가지고 있음

 처음 16비트에는 이 프로그램에서 사용할 GDT의 크기가 들어감

 하위 32비트에는 GDT의 시작 주소가 물리주소로 들어감

 이렇게 2가지 값을 통해 CPU는 GDT가 어디에 있는지 알 수 있음

7. Real Mode 에는 CS, DS등 세그먼트 레지스터가 16비트의 한 워드로 구성되어 있음.

8. Protected Mode에서는 각 세그먼트 레지스터가 16비트의 셀렉터 레지스터64비트의 디스크립터 레지스터로 다시 나뉘어짐

9. 세그먼트 셀렉터(16비트) 중 상위 13비트(3~15비트)에는 디스크립터를 찾기 위한 인덱스가 들어있음.

10. 2번째 비트에는 TI의 값이 들어가고, 0비트와 1비트 두비트에는 RPL의 값이 들어감

11. 디스크립터의 앞쪽에 SysCodeSelector equ 0x08, SysDataSelector equ 0x10, VideoSelector equ 0x18 존재

12. 위 값은 세그먼트 셀렉터에 넣을 인덱스 값, TI, RPL을 포함하고 있음

13. 소스 위쪽의 mov bx, SysDataSelector

   mov ds, bx

또는

   mov ax, VideoSelector

   mov es, ax

   는 세그먼트 셀렉터에 값을 넣는 일을 한다.

세그먼트 셀렉터에 값이 들어가는 순간 CPU는 GDTR 레지스터에 등록되어 있는 GDT의 제일 앞 번지를 가지고 GDT를 찾아냄
그리고 세그먼트 셀렉터의 인덱스 값에 8(GDT에서 몇 번째의 디스크립터를 요구한다는 순서 값, 실제로 찾는 데에 물리주소가 사용되었기 때문에 8을 곱하여 물리주소의 자리를 찾아간다. 하나의 디스크립터가 8 바이트이기 때문, 그래서 세그먼트 셀렉터 값에 넣을때 0x8(8), 0x10(16), 0x18(24) .. 식으로 8씩 건넌 수를 넣게 된다.) 을 곱하여 인덱스로 요구한 디스크립터를 찾아냄
8은 2진수로 1000이며 셀렉터에 8을 넣으면 인덱스가 1이 될것이다. ( 3,4번째 비트는 RPL, 2번쨰는 TI의 값이 들어감)
16은 2진수로 10000이며 셀렉터에 16을 넣으면 인덱스는 10, 즉 2가 됨
24는 2진수로 11000이며 셀렉터에 24를 넣으면 인덱스는 11, 즉 3이 됨
디스크립터를 찾을 때 셀렉터에서 상위 13비트만 사용하고 3비트는 정보 전달에 쓰임
인덱스가 13비트이므로 GDT에 최대로 들어갈 수 있는 갯수는 8192이다.

14. DS 세그먼트 셀렉터 동작 원리

(1) 세그먼트 셀렉터에 10 입력 시 DS 세그먼트 레지스터에는 10진수 2가 들어가 2 * 8 = 16이 된다.

(2) GDTR에서 가져온 GDT의 제일 앞 번지에 이것을 더한다.

(3) 제일 앞 번지는 소스에서 lgdt로 GDT를 등록할 때 사용했던 변수를 보면 gdt+0x10000이라는 것을 알 수 있다.

(4) gdt + 0x10000 + 16 이라는 계산으로 DS가 가져야 할 디스크립터 값을 얻을 수 있으며 이 값은 DS 세그먼트 셀렉터로 요구한 GDT의 디스크립터의 번지가 된다.

(5) 이 디스크립터에서 DPL 값(커널 레벨, 유저 레벨 결정하는 값)을 세그먼트 셀렉터의 RPL 값과 대조한다.

(6) 위 값이 같다면 디스크립터의 내용을 DS 세그먼트 디스크립터 레지스터에 복사한다. 디스크립터와 디스크립터 레지스터의 생김새가 같으므로 그대로 들어가게 된다.

15. CPU의 동작 원리는 아래와 같다.

Protected Mode로 넘어간 후 CPU의 모드는 Protected Mode로 바뀌어 있지만 현재 해석 유닛읽기 유닛에는 Real Mode용 명령어들이 존재하고 있다.
Real Mode 명령어가 남아 있는 상태에서 진행된다면 다음 명령어부터는 Protected Mode에서 Real Mode 명령어를 실행하고 있는 꼴이 되어 프로그램 수행에 차질이 생길 가능성이 많다.
그래서 해석하는 유닛과 읽어 들이는 유닛에 존재하는 프로그램을 지워줄 필요가 있다. 이것을 하는 루틴이 소스 상 
jmp $+2
nop
nop
이다. 이 점프 명령어는 2개의 명령(2개의 NOP을 점프하여 그 다음 명령으로 가는데, JMP 명령은 CPU의 유닛에 남아 있는 명령들을 지워준다. 그 뒤 CPU는 Protected Mode로 되었지만 세그먼트 레지스터들이 16비트 값을 계속 포함하고 있다. 이것을 바꿔주어야 완전하게 Protected Mode가 된다.



부트스트랩을 이용하여 커널 로드 하기


1장에서 사용한 boot.txt를 이용하여 커널을 로드하여 본다.

여기서는 2개의 파일이 필요하다. 첫번째는 boot.txt파일, 두번째는 kernel.txt 파일이 필요하다.

첫번째 파일 boot.txt의 소스는 아래와 같다

boot.txt

[org 0]

[bits 16]

jmp 0x07C0:start

start:

mov ax, cs

mov ds, ax

mov ax, 0xB800

mov es, ax

mov di, 0

mov ax, word [msgBack]

mov cx, 0x7FF


paint:

mov word [es:di], ax

add di, 2

dec cx

jnz paint


read:

mov ax, 0x1000

mov es, ax

mov bx, 0

mov ah, 2    : 디스크에 있는 데이터를 es:bx의 주소로

mov al, 1    : 1섹터를 읽는다.

mov ch, 0    : 0번째 Cylinder

mov cl, 2    : 2번째 섹터부터 읽기 시작

mov dh, 0    : Head = 0

mov dl, 0    : Drive = 0, A: 드라이브

int 0x13    : Read 시작


jc read    : 에러가 날 경우 다시 함    : 이 루틴에서 에러 발생 시 CPU의 CF비트가 1로 세트됨,

1로 세트되어 있다면 erad: 번지로 점프하여 디스크 읽기를 다시 실행한다.

jmp 0x1000:0000    : kernel.bin이 위치한 곳으로 점프한다.



msgBack db '.', 0x67


times 510-($-$$) db 0

dw 0xAA55

------------------------------------------------------------------

kernel.txt

.[org 0]

[bits 16]


start:

mov ax, cs    : cs에는 0x1000이 들어있다. boot.txt 파일 마지막 jmp 0x1000:0000 에서 0x1000이 cs로 들어가기 때문

mov ds, ax

xor ax, ax

mov ss, ax


lea esi, [msgKernel]    : 문자열이 있는 곳의 주소를 구한다.

mov ax, 0xB800

mov es, ax    : es에 0xB800을 넣는다.

mov edi, 0    : 화면의 제일 처음 부분부터 시작한다.

call printf

jmp $


printf:

push eax    : 먼저 있던 eax 값을 스택에 보존해 놓는다.


printf_loop:

mov al, byte [esi]    : esi가 가리키는 주소에서 문자를 하나 가져온다.

mov byte [es:edi], al    : 문자를 화면에 나타낸다.

or al, al    : al이 0인지 알아본다.

jz printf_end    : 0이라면 print_end로 점프한다.

inc edi    : 0이 아니라면 edi를 1 증가시켜

mov byte [es:edi], 0x06    : 문자색과 배경색의 값을 넣는다.

inc esi    : 다음 문자를 꺼내기 위해 esi를 하나 증가

inc edi    : 화면에 다음 문자를 나타내기 위해 edi를 증가

jmp printf_loop    : 루프를 돈다.


printf_end:

pop eax    : 스택에 보존했던 eax를 다시 꺼낸다.

ret    : 호출한 부분으로 돌아간다.


msgKernel db "We are in kernel program", 0


아래와 같이 컴파일 및 copy 명령어를 통해 합친다.


floopy disk에 넣고 vm 실행 시 아래 그림과 같이 문자열이 출력된다.


커널 로드 성공!


부트스트랩 만들기


어셈 파일을 만들어 주기 위한 파일



nasm-2.11.09rc1-win32.zip



플로피 디스켓, 또는 드라이버에 쓰기 위한 파일

rawwritewin-0.7.zip

boot.txt 파일을 만든다.

[org 0]

[bits 16]

jmp 0x07C0:start

start:

mov ax, cs    -> cs에는 0x07C0이 들어있음

mov ds, ax    -> ds를 cs와 같게 해준다.

mov ax, 0xB800    -> 비디오 메모리의 세그먼트를 es 레지스터에 넣는다.

mov es, ax

mov di, 0        -> 제일 윗줄의 처음에 쓸 것이다.

mov ax, word [msgBack]    -> 써야할 데이터의 주소값을 지정한다.

mov cx, 0x7FF        -> 화면 전체에 쓰기 위해서는 0x7FF(10진수 2047)개의 WORD가 필요하다.


paint:

mov word [es:di], ax    -> 비디오 메모리를 쓴다.

add di, 2    -> 한 WORD를 썼으니 2를 더한다.

dec cx    -> 한 WORD를 썼으니 cx의 값을 하나 줄인다.

jnz paint    -> CX가 0이 아니면 paint로 점프하여 나머지를 더 쓴다.


mov edi, 0    -> 제일 윗줄의 처음에 쓸것이다.

mov byte [es:edi], 'A'    -> 비디오 메모리에 쓴다.    비디오 메모리는 2바이트이며 아스키코드 1바이트, 배

경 및 글자색 지정하는 1바이트

inc edi    -> 한개의 BYTE를 썼으므로 1을 더한다.

mov byte [es:edi], 0x06    -> 배경색을 쓴다.

inc edi    -> 한개의 BYTE를 썼으므로 1을 더한다.

mov byte [es:edi], 'B'

inc edi

mov byte [es:edi], 0x06

inc edi

mov byte [es:edi], 'C'

inc edi

mov byte [es:edi], 0x06

inc edi

mov byte [es:edi], '1'

inc edi

mov byte [es:edi], 0x06

inc edi

mov byte [es:edi], '2'

inc edi

mov byte [es:edi], 0x06

inc edi

mov byte [es:edi], '3'

inc edi

mov byte [es:edi], 0x06

jmp $    -> 이 번지에서 무한루프를 돈다.


msgBack db '.', 0x67    -> 배경색으로 사용할 데이터


times 510-($-$$) db 0    -> 여기서부터 509번지까지 0으로 채운다.

dw 0xAA55    -> 510번지에 0x55를, 511번지에 0xAA를 채워준다.    -> Magic Number라고 부름


위와 같이 소스를 짠다.


그 후 아래와 같이 컴파일 한다.

nasm -f bin -o test.img boot.txt

-f bin 옵션은 제작할 실행 파일이 exe나 com등의 일반 DOS 실행파일이 아닌 바이너리 실행 파일이라는 것을 어셈블러에게 알려줌

vmware 내 플로피디스크 드라이버를 추가한다. 추가 후 img 파일 적용 후 부팅 시 아래 그림과 같이 적용된다.



부팅 시 아래 그림과 같이 적용되는것을 확인할 수 있다.



부트스트랩 만들기 및 적용 성공










+ Recent posts