rename: study -> qemu

This commit is contained in:
2025-12-30 08:33:13 +00:00
parent 33c5e8bfb6
commit cf722d75ff
11 changed files with 0 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
*.bin
*.img
+3
View File
@@ -0,0 +1,3 @@
# qemu study
Studying the booting sequence, qemu, bios, kernel, ...etc
+62
View File
@@ -0,0 +1,62 @@
; boot.asm
[BITS 16]
[ORG 0x7C00]
start:
; 1. 세그먼트 레지스터 초기화 (DS, ES, SS)
xor ax, ax ; AX = 0
mov ds, ax ; DS = 0
mov es, ax ; ES = 0
mov ss, ax ; SS = 0
mov sp, 0x7C00 ; 스택 포인터를 0x7C00 밑으로 설정 (안전지대)
; 2. 커널 로딩 안내 메시지 출력
mov si, msg_boot
call print_string
; 3. 디스크에서 커널 읽기 (BIOS INT 0x13 사용)
; 우리는 커널을 디스크의 2번째 섹터(Sector 2)에 저장할 것입니다.
; 그것을 메모리 주소 0x8000 번지에 로드합시다.
mov ah, 0x02 ; BIOS 기능: 섹터 읽기
mov al, 1 ; 읽을 섹터 수: 1개
mov ch, 0 ; 실린더 번호: 0
mov cl, 2 ; 섹터 번호: 2 (1번은 부트로더니까 2번부터)
mov dh, 0 ; 헤드 번호: 0
; DL은 BIOS가 부팅된 드라이브 번호를 자동으로 넣어줍니다. (건드리지 말 것)
mov bx, 0x8000 ; 데이터를 저장할 메모리 주소 (ES:BX = 0x0000:0x8000)
int 0x13 ; 인터럽트 발생! (읽어와라!)
jc disk_error ; 만약 에러가 나면(Carry Flag=1) 에러 처리로 점프
; 4. 커널로 점프!
; 0x8000 번지에 커널 코드가 도착했으니 거기로 제어권을 넘깁니다.
mov si, msg_jump
call print_string
jmp 0x8000
disk_error:
mov si, msg_err
call print_string
jmp $ ; 무한 루프
; --- 문자열 출력 함수 ---
print_string:
mov ah, 0x0E ; BIOS 기능: 텔레타입 출력 (한 글자 찍기)
.loop:
lodsb ; [SI]의 데이터를 AL로 로드하고 SI 1 증가
cmp al, 0 ; 문자열 끝(NULL)인지 확인
je .done
int 0x10 ; 화면에 출력 (BIOS INT 0x10)
jmp .loop
.done:
ret
msg_boot db "Bootloader started...", 0x0D, 0x0A, 0
msg_jump db "Jumping to Kernel...", 0x0D, 0x0A, 0
msg_err db "Disk Read Error!", 0
; --- 부트 서명 (MBR 필수) ---
times 510-($-$$) db 0 ; 510바이트까지 0으로 채움
dw 0xAA55 ; 마지막 2바이트는 무조건 0x55, 0xAA (매직 넘버)
+27
View File
@@ -0,0 +1,27 @@
; kernel.asm
[BITS 16] ; 여전히 16비트입니다.
[ORG 0x8000] ; 부트로더가 우리를 0x8000에 내려줬으므로 기준 주소를 맞춥니다.
kernel_entry:
; 커널이 실행되었다는 메시지 출력
mov si, msg_kernel
call print_string
; 할 일 다 했으니 죽기 (무한 루프)
cli ; 인터럽트 끄기 (방해 금지)
hlt ; CPU 정지 (전력 소모 최소화)
jmp $ ; 혹시 깨어나면 다시 제자리 걸음
; --- 문자열 출력 함수 (부트로더와 동일) ---
print_string:
mov ah, 0x0E
.loop:
lodsb
cmp al, 0
je .done
int 0x10
jmp .loop
.done:
ret
msg_kernel db "Hello from BARE METAL KERNEL!", 0x0D, 0x0A, 0
+20
View File
@@ -0,0 +1,20 @@
# boot & kernel
## 1. 어셈블리 컴파일 (바이너리 파일 생성)
```bash
nasm -f bin boot.asm -o boot.bin
nasm -f bin kernel.asm -o kernel.bin
```
## 2. 디스크 이미지 만들기
### boot.bin(512바이트) 뒤에 kernel.bin을 붙입니다.
```bash
cat boot.bin kernel.bin > os.img
truncate -s 1440k os.img
qemu-system-x86_64 -drive format=raw,file=os.img -display curses
```
+123
View File
@@ -0,0 +1,123 @@
; mybios.asm
[BITS 16]
[ORG 0]
bios_start:
; 1. 세그먼트 초기화 (CS=0xF000 이므로 DS, ES도 맞춤)
mov ax, 0xF000
mov ds, ax
mov es, ax
; mov ss, ax
; mov sp, 0xFFF0
xor ax, ax ; AX = 0
mov ss, ax ; SS = 0x0000 (RAM)
mov sp, 0xFFFE ; 0x0000 ~ 0xFFFF 영역 중 가장 끝!
; mov sp, 0x7C00 ; SP = 0x7C00 (부트로더가 쓰는 안전한 RAM 공간)
; 2. 시리얼 포트(COM1) 초기화 (필수!)
mov dx, 0x3FB ; Line Control Register
mov al, 0x80 ; DLAB bit set
out dx, al
mov dx, 0x3F8 ; Divisor (Low) - 9600 baud
mov al, 0x0C
out dx, al
mov dx, 0x3F9 ; Divisor (High)
mov al, 0x00
out dx, al
mov dx, 0x3FB ; LCR: 8 bits, no parity, 1 stop bit
mov al, 0x03
out dx, al
; 3. 메시지 출력
mov si, msg_bios
call print_serial
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ===========================================================
; [ACPI Shutdown Sequence for QEMU PIIX4 Chipset]
; Target: Bus 0, Device 1, Function 3 (Power Management)
; Address Format: 1(En) | Bus(0) | Dev(1) | Func(3) | Reg
; Hex Prefix: 0x80 | 00 | 08 | 03 | ..
; Combined: 0x80000B.. (Function 3 is the key)
; ===========================================================
; Step 0: Enable I/O Access (Command Register - 0x04)
; We must tell the chipset to allow I/O port access first.
mov dx, 0xCF8
mov eax, 0x80000B04 ; Address: Func 3, Reg 0x04
out dx, eax
mov dx, 0xCFC
mov eax, 0x0001 ; Value: Bit 0 (I/O Space Enable)
out dx, eax
; Step 1: Set ACPI Base Address (PMBA - 0x40)
; We map the ACPI registers to I/O port 0xB000.
mov dx, 0xCF8
mov eax, 0x80000B40 ; Address: Func 3, Reg 0x40
out dx, eax
mov dx, 0xCFC
mov eax, 0x0000B001 ; Value: Base 0xB000 | Bit 0 (Hardwired to 1)
out dx, eax
; Step 2: Enable ACPI Function (PMREGMISC - 0x80)
mov dx, 0xCF8
mov eax, 0x80000B80 ; Address: Func 3, Reg 0x80
out dx, eax
mov dx, 0xCFC
mov eax, 0x00000001 ; Value: Bit 0 (ACPI Enable)
out dx, eax
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov si, msg_off
call print_serial
; ; Exit with return value 0
mov dx, 0xB004
mov ax, 0x2000 ; SLP_TYP(Soft Off) | SLP_EN
out dx, ax
; ; reboot
; mov dx, 0x64
; mov al, 0xFE
; out dx, al
; ; exit (isa-debug-exit; need '-device isa-debug-exit,iobase=0x501,iosize=0x04')
; mov dx, 0x501
; mov al, 0x02 ; exit code (2) -> qemu exits with exit code (al<<1)|1
; out dx, al
; 그래도 안 꺼지면 그냥 멈춰라
cli
hlt
jmp $
; --- 시리얼 출력 함수 ---
print_serial:
mov dx, 0x3F8 ; COM1 데이터 포트
.loop:
lodsb ; [DS:SI] -> AL
cmp al, 0
je .done
out dx, al ; 한 글자 전송
jmp .loop
.done:
ret
; msg_bios db "Hello! This is a CUSTOM BIOS running on QEMU!", 0x0D, 0x0A, 0
msg_bios db "Hello! This is a CUSTOM BIOS running on QEMU!", 0x0D, 0x0A, 0
msg_off db "ACPI Enabled. Powering Off NOW!", 0x0D, 0x0A, 0
; Padding
times 65520-($-$$) db 0
reset_vector:
jmp 0xF000:0x0000
times 16-($-reset_vector) db 0
+206
View File
@@ -0,0 +1,206 @@
# BIOS
```bash
nasm -f bin mybios.asm -o mybios.bin
qemu-system-i386 -bios ./mybios.bin -nographic
# qemu-system-i386 -bios ./mybios.bin -nographic -device isa-debug-exit,iobase=0x501,iosize=0x04
```
## explanations
```diagram
[ 우리가 만든 파일 ] [ 실제 메모리 주소 (Legacy Area) ]
(mybios.bin, 64KB) (ROM 영역, Write Protected)
파일 시작 (0x0000) +------------------+ +-----------------------+ 0xF0000 (시작점)
| bios_start: | | jmp, mov ax... |
| (초기화 코드들) | ==> | (우리의 코드 실행됨) |
+------------------+ +-----------------------+
| | | |
| .......... | | |
| (0으로 채운 공간) | | |
| .......... | | |
| | | |
파일 끝 (0xFFFF) +------------------+ +-----------------------+ 0xFFFF0 (리셋 벡터)
(정확히 65535) | reset_vector: | | jmp 0xF000:0000 | <--- CPU가 눈뜨자마자 여기 실행
+------------------+ +-----------------------+ 0xFFFFF (끝)
```
## Gemini
지금 우리가 다루고 있는 **x86 리얼 모드(16-bit)** 기준으로, CPU 안에는 **"초고속 작업 공간(레지스터)"**들이 서랍처럼 정리되어 있습니다.
가장 중요한 **세그먼트 레지스터**부터 시작해서, CPU의 모든 서랍장을 털어보겠습니다.
---
### 1. 세그먼트 레지스터 (Segment Registers) - "동네 지정"
**"여기는 내 구역이야!"** 라고 선언하는 레지스터들입니다.
16비트 메모리 주소 체계(`Segment:Offset`)의 핵심입니다.
* **역할:** 메모리의 **시작 위치(Base Address)**를 가리킵니다.
* **특징:** **값을 직접 넣을 수 없습니다.** (`mov ds, 0xF000` ❌) 반드시 범용 레지스터를 거쳐야 합니다. (`mov ax, 0xF000` -> `mov ds, ax` ⭕)
| 이름 | 풀네임 | 역할 & 설명 | 짝꿍 레지스터 |
| --- | --- | --- | --- |
| **CS** | **Code** Segment | **"실행할 코드가 있는 동네"**. CPU는 무조건 `CS:IP`가 가리키는 곳의 명령어를 실행합니다. `jmp` 명령어로만 바꿀 수 있습니다. | **IP** |
| **DS** | **Data** Segment | **"변수(데이터)가 있는 동네"**. `mov al, [0x100]`을 하면 `DS`를 기준으로 봅니다. | **SI, BX** |
| **SS** | **Stack** Segment | **"스택이 있는 동네"**. `push`, `pop` 할 때 쓰는 공간의 기준점입니다. | **SP, BP** |
| **ES** | **Extra** Segment | "추가 데이터 동네". `memcpy` 같은 걸 할 때 '복사할 목적지'로 자주 쓰입니다. | **DI** |
| **FS, GS** | (Additional) | 나중에 추가된 여분 세그먼트. (보통 OS 커널 구조체 등을 가리킬 때 씀) | - |
> **💡 공식:** 물리 주소 = `(Segment * 16) + Offset`
> 예: `DS=0xF000`, 오프셋 `0x0050` → 실제 주소 `0xF0050`
---
### 2. 범용 레지스터 (General Purpose Registers) - "일꾼"
가장 많이 쓰고, 자유롭게 지지고 볶는 레지스터입니다.
16비트(AX)는 8비트(AH, AL)로 쪼개서 쓸 수 있습니다.
| 이름 | 풀네임 | 주특기 (물론 다른 용도로도 씀) |
| --- | --- | --- |
| **AX** | **Accumulator** | **"계산기 & 입출력"**. 산술 연산, `IN/OUT` 명령어는 무조건 AX(AL)를 통합니다. |
| **BX** | **Base** | **"주소 참조"**. 메모리 주소를 저장하는 용도(`[BX]`)로 유일하게 쓰였던 범용 레지스터. |
| **CX** | **Counter** | **"반복 카운터"**. 반복문(`LOOP`)이나 문자열 복사(`REP`)할 때 횟수를 셉니다. |
| **DX** | **Data** | **"보조 데이터 & 포트"**. 큰 수 곱셈의 상위 비트 저장, 혹은 **I/O 포트 번호** 저장용. |
* **구조:** `EAX (32bit)` -> `AX (16bit)` -> `AH (High 8bit) + AL (Low 8bit)`
---
### 3. 인덱스 & 포인터 레지스터 (Index & Pointer) - "내비게이션"
메모리의 **특정 위치(Offset)**를 가리키는 용도입니다. 세그먼트 레지스터와 짝을 이룹니다.
| 이름 | 풀네임 | 역할 | 짝꿍 세그먼트 |
| --- | --- | --- | --- |
| **SP** | **Stack** Pointer | **"스택 꼭대기"**. `push`하면 줄어들고 `pop`하면 늘어납니다. 절대 함부로 건드리면 안 됩니다. | **SS** |
| **BP** | **Base** Pointer | **"스택 바닥 기준"**. 함수 호출 시 지역변수에 접근할 때 기준점으로 씁니다. | **SS** |
| **SI** | **Source** Index | **"복사할 원본 위치"**. 문자열 복사할 때 '어디서' 가져올지 가리킵니다. | **DS** |
| **DI** | **Dest** Index | **"복사할 목적지 위치"**. 문자열 복사할 때 '어디로' 보낼지 가리킵니다. | **ES** |
---
### 4. 명령어 포인터 (Instruction Pointer) - "현재 위치"
* **IP (Instruction Pointer):**
* **"다음에 실행할 명령어의 오프셋"**입니다.
* 개발자가 직접 `mov ip, 0x1000` 처럼 수정할 수 **없습니다.**
* 오직 `jmp`, `call`, `ret` 같은 명령어를 통해서만 바뀝니다.
* 항상 **CS**와 함께 (`CS:IP`) 움직입니다.
---
### 5. 플래그 레지스터 (FLAGS) - "상태 알림판"
연산 결과의 상태를 비트 단위로 저장합니다. `if`문(분기)의 핵심입니다.
| 플래그 | 이름 | 의미 (값이 1일 때) |
| --- | --- | --- |
| **ZF** | **Zero** Flag | 결과가 **0**이다. (`cmp a, b` 했을 때 같으면 켜짐) |
| **CF** | **Carry** Flag | 받아올림/빌림이 발생했다. (unsigned 연산 범위 초과) |
| **OF** | **Overflow** Flag | 부호 있는 연산에서 범위가 터졌다. |
| **SF** | **Sign** Flag | 결과가 음수(-)다. |
| **IF** | **Interrupt** Flag | **"인터럽트 받아요"**. (`cli`하면 0, `sti`하면 1) |
---
### 6. 컨트롤 레지스터 (Control Registers) - "관리자 설정"
이건 우리가 맨 처음에 얘기했던, **CPU의 모드(System)** 자체를 제어하는 특수 레지스터입니다. (일반 프로그램은 못 건드림)
| 이름 | 역할 |
| --- | --- |
| **CR0** | **"모드 스위치"**. 리얼 모드 vs 보호 모드 전환, 페이징 켜기/끄기 등. |
| **CR2** | **"사고 현장"**. Page Fault가 났을 때 어떤 주소 때문인지 기록됨. |
| **CR3** | **"지도책"**. 페이지 디렉터리(Page Directory)의 물리 주소를 저장. |
| **CR4** | **"확장 기능"**. 4MB 페이지 등 최신 기능 활성화. |
---
### ✨ 요약: 우리가 짠 코드 해석
이제 작성하신 BIOS 코드 한 조각이 완벽하게 해석되실 겁니다.
```nasm
mov ax, 0xF000 ; (1) AX(일꾼)에게 0xF000을 줌
mov ds, ax ; (2) DS(데이터 동네)를 0xF000으로 설정 (AX를 통해서만 가능)
mov es, ax ; (3) ES(추가 동네)도 0xF000으로 설정
mov ss, 0 ; (X) 에러! SS(세그먼트)에 바로 숫자 못 넣음
xor ax, ax ; (4) AX를 0으로 만듦 (일꾼 초기화)
mov ss, ax ; (5) SS(스택 동네)를 0으로 설정
mov sp, 0x7C00 ; (6) SP(스택 꼭대기 포인터)를 0x7C00으로 설정
```
이 "서랍장 정리"가 끝나야 비로소 CPU가 꼬이지 않고 일을 할 수 있는 것입니다.
## gemini2
네, **정확하게 보셨습니다!** 방금 설명해 드린 내용은 **"16비트 리얼 모드(Real Mode)"** 기준입니다.
컴퓨터가 **32비트(Pintos)**나 **64비트(현대 OS)**로 모드를 전환하면, 레지스터의 **이름**과 **크기**, 그리고 **세그먼트의 역할**이 완전히 달라집니다.
어떻게 변하는지 딱 정리해 드릴게요.
---
### 1. 레지스터 크기의 진화 (러시아 인형 구조)
모드가 바뀔 때마다 레지스터가 **확장(Extended)**됩니다. 하지만 하위 호환성을 위해 **옛날 이름도 여전히 쓸 수 있습니다.**
| 구분 | 16-bit (Real Mode) | 32-bit (Protected Mode) | 64-bit (Long Mode) |
| --- | --- | --- | --- |
| **이름 규칙** | **AX**, BX, CX... | **EAX**, EBX... (E=Extended) | **RAX**, RBX... (R=Register) |
| **크기** | 2 Byte (16-bit) | 4 Byte (32-bit) | 8 Byte (64-bit) |
| **특징** | 64KB까지만 계산 가능 | 4GB까지 계산 가능 | 1844경(Exabyte)까지 가능 |
| **추가된 친구** | 없음 | 없음 | **R8 ~ R15** (8개 추가됨) |
> **재밌는 점:**
> 64비트 모드에서도 `mov al, 0x1`이라고 하면 `RAX` 레지스터의 **맨 끝 1바이트**만 건드립니다. 즉, `RAX` 안에 `EAX`가 있고, 그 안에 `AX`가 있고, 그 안에 `AL`이 들어있는 구조입니다.
---
### 2. 세그먼트 레지스터의 대격변 (CS, DS...)
이게 OS 개발에서 가장 헷갈리는 부분입니다. 레지스터 이름(CS, DS)은 그대로인데 **하는 짓이 완전히 바뀝니다.**
#### A. 16비트 리얼 모드 (지금 우리가 하는 것)
* **역할:** **"곱하기 16 계산기"**
* **공식:** `(CS * 16) + IP` = 물리 주소
* **비유:** "CS값은 10층이야." \rightarrow 실제 높이는 100m.
#### B. 32비트 보호 모드 (Pintos, 윈도우 XP 등)
* **역할:** **"ID 카드 (Selector)"**
* **공식:** CS에 들어있는 숫자는 **GDT(Global Descriptor Table)**라는 표의 **인덱스(순서)**입니다.
* **동작:**
1. `CS``0x08`이 들어있다.
2. CPU는 메모리에 있는 GDT 테이블의 **1번째 항목(0x08)**을 찾아본다.
3. 그 항목에 적힌 **"Base Address(시작 주소)"**와 **"권한(Permission)"**을 읽어온다.
4. 그 주소에 `EIP`를 더해서 이동한다.
* **비유:** "CS값은 1번 회원님이야." \rightarrow 회원 명부를 뒤져서 그 사람 집 주소를 찾음.
#### C. 64비트 롱 모드 (최신 윈도우, 리눅스)
* **역할:** **"거의 무시됨 (Flat Model)"**
* **특징:** `FS`, `GS`를 제외한 `CS`, `DS`, `ES`, `SS`는 베이스 주소가 무조건 **0**으로 고정됩니다.
* **이유:** 64비트 주소 공간이 워낙 넓어서 세그먼트로 나누는 게 의미가 없어졌기 때문입니다.
---
### 3. 요약: 개발자의 자세
1. **지금(BIOS 개발):** `mov ds, 0xF000`처럼 세그먼트를 **"위치 지정용"**으로 씁니다.
2. **Pintos(32비트) 넘어가면:** `mov ds, 0x10` 같은 코드를 보게 될 텐데, 이건 주소 16번지가 아니라 **"커널 데이터용 ID 카드(Selector)"**를 끼우는 행위입니다.
지금은 **"16비트 세상의 규칙"**만 확실히 익히시면 됩니다! 잘 따라오고 계십니다. 👍
+152
View File
@@ -0,0 +1,152 @@
; mybios_ssh.asm
[BITS 16]
[ORG 0]
bios_start:
; 1. 세그먼트 초기화
mov ax, 0xF000
mov ds, ax
mov es, ax
xor ax, ax
mov ss, ax
mov sp, 0x7C00
; 2. 시리얼 포트 초기화 (가장 중요!)
call init_serial
; 3. 부팅 메시지 출력
mov si, msg_boot
call print_serial
; 4. 메인 루프 (입력 대기)
mov si, msg_menu
call print_serial
main_loop:
; --- Heartbeat (살아있음을 표시) ---
; 입력이 없어도 멈춘 게 아니라는 걸 보여주기 위해 점(.)을 찍을 수도 있지만,
; 너무 지저분해지니 생략하고 입력만 기다립니다.
; [입력 확인] 0x3FD 포트의 0번 비트가 1이면 데이터가 온 것임
mov dx, 0x3FD
in al, dx
test al, 1
jz main_loop ; 데이터 없으면 계속 뺑뺑이(Loop)
; [데이터 읽기] 0x3F8 포트에서 읽음
mov dx, 0x3F8
in al, dx
; [내가 누른 키 화면에 보여주기 (Echo)]
; 이게 없으면 내가 뭘 눌렀는지 터미널에 안 보임!
out dx, al
; [키 판별]
cmp al, '1'
je action_shutdown
cmp al, '2'
je action_reboot
jmp main_loop
; ===========================================================
; [Actions]
; ===========================================================
action_shutdown:
mov si, msg_bye
call print_serial
; PIIX4 ACPI Shutdown (Power Off)
; 1. I/O Enable
mov dx, 0xCF8
mov eax, 0x80000B04
out dx, eax
mov dx, 0xCFC
mov eax, 0x0001
out dx, eax
; 2. PMBA -> 0xB000
mov dx, 0xCF8
mov eax, 0x80000B40
out dx, eax
mov dx, 0xCFC
mov eax, 0x0000B001
out dx, eax
; 3. ACPI Enable
mov dx, 0xCF8
mov eax, 0x80000B80
out dx, eax
mov dx, 0xCFC
mov eax, 0x00000001
out dx, eax
; 4. Shutdown Command
mov dx, 0xB004
mov ax, 0x2000
out dx, ax
; 안 꺼지면 대기
hlt
jmp $
action_reboot:
mov si, msg_reboot
call print_serial
; Reboot Command
mov dx, 0x64
mov al, 0xFE
out dx, al
hlt
jmp $
; ===========================================================
; [Drivers]
; ===========================================================
init_serial:
mov dx, 0x3FB
mov al, 0x80 ; DLAB ON
out dx, al
mov dx, 0x3F8
mov al, 0x0C ; 9600 baud
out dx, al
mov dx, 0x3F9
mov al, 0x00
out dx, al
mov dx, 0x3FB
mov al, 0x03 ; 8N1, DLAB OFF
out dx, al
ret
print_serial:
mov dx, 0x3F8
.loop:
lodsb
cmp al, 0
je .done
out dx, al
jmp .loop
.done:
ret
; ===========================================================
; [Data]
; ===========================================================
; 줄바꿈(0x0D, 0x0A)을 명확히 넣었습니다.
msg_boot db 0x0D, 0x0A, "===============================", 0x0D, 0x0A
db " SSH Serial BIOS v1.0 ", 0x0D, 0x0A
db "===============================", 0x0D, 0x0A, 0
msg_menu db 0x0D, 0x0A, "COMMANDS: [1] Shutdown [2] Reboot", 0x0D, 0x0A
db "Input > ", 0
msg_bye db 0x0D, 0x0A, ">>> System is HALTING...", 0x0D, 0x0A, 0
msg_reboot db 0x0D, 0x0A, ">>> REBOOTING...", 0x0D, 0x0A, 0
times 65520-($-$$) db 0
reset_vector:
jmp 0xF000:0x0000
times 16-($-reset_vector) db 0
+156
View File
@@ -0,0 +1,156 @@
; mybios_real.asm
[BITS 16]
[ORG 0]
bios_start:
; 1. 세그먼트 초기화
mov ax, 0xF000
mov ds, ax
mov es, ax
xor ax, ax
mov ss, ax
mov sp, 0x7C00
; ==========================================================
; [VGA BIOS (Option ROM) 실행하기]
; QEMU는 0xC0000 번지에 표준 VGA BIOS를 올려둡니다.
; 이걸 실행해야 화면이 켜지고 curses가 작동합니다.
; ==========================================================
; 2-1. VGA BIOS가 있는지 확인 (Signature Check)
; 0xC000:0000 = 0x55, 0xC000:0001 = 0xAA 여야 함
mov ax, 0xC000
mov es, ax
cmp byte [es:0], 0x55
jne no_vga
cmp byte [es:1], 0xAA
jne no_vga
; 2-2. VGA BIOS 실행 (Far Call)
; 보통 3번째 바이트부터 코드가 시작됩니다.
; PUSH CS, PUSH IP 방식으로 돌아올 주소를 저장하고 점프합니다.
push cs ; 돌아올 세그먼트 (0xF000)
push word .vga_ret ; 돌아올 오프셋 (Label 주소)
; 0xC000:0003 으로 점프!
push 0xC000 ; VGA Segment
push 0x0003 ; VGA Entry Point
retf ; (Far Return을 이용한 점프 기법)
.vga_ret:
; 여기까지 오면 VGA BIOS가 화면을 초기화하고 돌아온 상태입니다!
; 이제 다시 우리의 세그먼트 복구
mov ax, 0xF000
mov ds, ax
mov es, ax
; 3. 화면 파란색으로 칠하기 (이제 먹힘!)
mov ax, 0xB800
mov es, ax
xor di, di
mov cx, 2000
mov ax, 0x1F20 ; 파란바탕 + 공백
rep stosw
; 4. 메시지 출력
mov si, msg_title
mov di, 0
call print_vga
mov si, msg_menu
mov di, 160
call print_vga
; 5. 메인 루프 (키보드 0x60)
main_loop:
in al, 0x64
test al, 1
jz main_loop
in al, 0x60
cmp al, 0x02 ; '1'
je action_shutdown
cmp al, 0x03 ; '2'
je action_reboot
jmp main_loop
; --- 에러 처리: VGA BIOS가 없을 때 ---
no_vga:
; 시리얼로 에러 메시지라도 보내야 함 (여기선 생략하고 무한루프)
cli
hlt
jmp $
; ===========================================================
; [Actions] (이전과 동일)
; ===========================================================
action_shutdown:
mov si, msg_bye
mov di, 320
call print_vga
; ACPI PIIX4 Shutdown
mov dx, 0xCF8
mov eax, 0x80000B04
out dx, eax
mov dx, 0xCFC
mov eax, 0x0001
out dx, eax
mov dx, 0xCF8
mov eax, 0x80000B40
out dx, eax
mov dx, 0xCFC
mov eax, 0x0000B001
out dx, eax
mov dx, 0xCF8
mov eax, 0x80000B80
out dx, eax
mov dx, 0xCFC
mov eax, 0x00000001
out dx, eax
mov dx, 0xB004
mov ax, 0x2000
out dx, ax
hlt
jmp $
action_reboot:
mov si, msg_reboot
mov di, 320
call print_vga
mov dx, 0x64
mov al, 0xFE
out dx, al
hlt
jmp $
; ===========================================================
; [Drivers]
; ===========================================================
print_vga:
mov ah, 0x1F
.loop:
lodsb
cmp al, 0
je .done
stosw
jmp .loop
.done:
ret
; ===========================================================
; [Data]
; ===========================================================
msg_title db "=== REAL BIOS (VGA Initialized) ===", 0
msg_menu db "Press [1] to Shutdown, [2] to Reboot", 0
msg_bye db "System Halted.", 0
msg_reboot db "Rebooting...", 0
; --- BIOS 크기 맞추기 (64KB) ---
times 65520-($-$$) db 0
reset_vector:
jmp 0xF000:0x0000
times 16-($-reset_vector) db 0
+123
View File
@@ -0,0 +1,123 @@
; mybios_boot.asm
[BITS 16]
[ORG 0x7C00] ; BIOS가 우리를 0x7C00에 로딩해줍니다.
start:
; 1. 세그먼트 초기화
xor ax, ax ; AX = 0
mov ds, ax ; DS = 0
mov es, ax ; ES = 0
mov ss, ax ; SS = 0
mov sp, 0x7C00 ; 스택 설정
; 2. 화면 초기화 (블루 스크린)
; BIOS가 이미 VGA를 텍스트 모드로 켜둔 상태입니다!
; 우리는 메모리에 색칠만 하면 됩니다.
mov ax, 0xB800
mov es, ax ; ES = 비디오 메모리 세그먼트
xor di, di ; DI = 0
mov cx, 2000 ; 80x25 = 2000 글자
mov ax, 0x1F20 ; 파란바탕(1F) + 공백(20)
rep stosw ; 화면 지우기
; 3. 메시지 출력
mov si, msg_title
mov di, 0
call print_vga
mov si, msg_menu
mov di, 160
call print_vga
; 4. 키보드 입력 대기
main_loop:
in al, 0x64
test al, 1
jz main_loop
in al, 0x60 ; 키보드 스캔코드 읽기
cmp al, 0x02 ; 키 '1'
je action_shutdown
cmp al, 0x03 ; 키 '2'
je action_reboot
jmp main_loop
; ===========================================================
; [Actions]
; ===========================================================
action_shutdown:
mov si, msg_bye
mov di, 320
call print_vga
; ACPI Shutdown (PIIX4) - 부트로더 상태에서도 똑같이 동작합니다.
mov dx, 0xCF8
mov eax, 0x80000B04
out dx, eax
mov dx, 0xCFC
mov eax, 0x0001
out dx, eax
mov dx, 0xCF8
mov eax, 0x80000B40
out dx, eax
mov dx, 0xCFC
mov eax, 0x0000B001
out dx, eax
mov dx, 0xCF8
mov eax, 0x80000B80
out dx, eax
mov dx, 0xCFC
mov eax, 0x00000001
out dx, eax
mov dx, 0xB004
mov ax, 0x2000
out dx, ax
hlt
jmp $
action_reboot:
mov si, msg_reboot
mov di, 320
call print_vga
mov dx, 0x64
mov al, 0xFE
out dx, al
hlt
jmp $
; ===========================================================
; [Drivers]
; ===========================================================
print_vga:
mov ah, 0x1F ; 파란색 속성 고정
.loop:
lodsb
cmp al, 0
je .done
stosw
jmp .loop
.done:
ret
; ===========================================================
; [Data]
; ===========================================================
msg_title db "=== SSH Curses Bootloader (Blue Screen) ===", 0
msg_menu db "Press [1] to Shutdown, [2] to Reboot", 0
msg_bye db "System Halted.", 0
msg_reboot db "Rebooting...", 0
; --- 부트 섹터 마법 (512바이트 맞추기) ---
times 510-($-$$) db 0 ; 510바이트까지 0으로 채움
dw 0xAA55 ; 부트 서명 (Boot Signature)
+11
View File
@@ -0,0 +1,11 @@
# read&write
```bash
nasm -f bin mybios.asm -o mybios.bin
qemu-system-i386 -bios mybios.bin -nographic
```
```bash
nasm -f bin myboot.asm -o myboot.bin
qemu-system-i386 -M pc -drive file=myboot.bin,format=raw,index=0,media=disk -display curses
```