From 8f1a81b699151fbcdd1919892f5835dd59b7e074 Mon Sep 17 00:00:00 2001 From: Minseong Gwak Date: Tue, 30 Dec 2025 08:19:49 +0000 Subject: [PATCH] update qemu study --- study/.gitignore | 2 + study/README.md | 3 + study/first/boot.asm | 62 ++++++++++++ study/first/kernel.asm | 27 ++++++ study/first/readme.md | 20 ++++ study/second/mybios.asm | 123 ++++++++++++++++++++++++ study/second/readme.md | 206 ++++++++++++++++++++++++++++++++++++++++ study/third/mybios.asm | 152 +++++++++++++++++++++++++++++ study/third/mybios2.asm | 156 ++++++++++++++++++++++++++++++ study/third/myboot.asm | 123 ++++++++++++++++++++++++ study/third/readme.md | 11 +++ 11 files changed, 885 insertions(+) create mode 100644 study/.gitignore create mode 100644 study/README.md create mode 100644 study/first/boot.asm create mode 100644 study/first/kernel.asm create mode 100644 study/first/readme.md create mode 100644 study/second/mybios.asm create mode 100644 study/second/readme.md create mode 100644 study/third/mybios.asm create mode 100644 study/third/mybios2.asm create mode 100644 study/third/myboot.asm create mode 100644 study/third/readme.md diff --git a/study/.gitignore b/study/.gitignore new file mode 100644 index 0000000..1aa8e33 --- /dev/null +++ b/study/.gitignore @@ -0,0 +1,2 @@ +*.bin +*.img \ No newline at end of file diff --git a/study/README.md b/study/README.md new file mode 100644 index 0000000..89fc2d5 --- /dev/null +++ b/study/README.md @@ -0,0 +1,3 @@ +# qemu study + +Studying the booting sequence, qemu, bios, kernel, ...etc \ No newline at end of file diff --git a/study/first/boot.asm b/study/first/boot.asm new file mode 100644 index 0000000..d62a3d1 --- /dev/null +++ b/study/first/boot.asm @@ -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 (매직 넘버) \ No newline at end of file diff --git a/study/first/kernel.asm b/study/first/kernel.asm new file mode 100644 index 0000000..c869166 --- /dev/null +++ b/study/first/kernel.asm @@ -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 \ No newline at end of file diff --git a/study/first/readme.md b/study/first/readme.md new file mode 100644 index 0000000..4fa7052 --- /dev/null +++ b/study/first/readme.md @@ -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 +``` diff --git a/study/second/mybios.asm b/study/second/mybios.asm new file mode 100644 index 0000000..cc52f80 --- /dev/null +++ b/study/second/mybios.asm @@ -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 \ No newline at end of file diff --git a/study/second/readme.md b/study/second/readme.md new file mode 100644 index 0000000..8259b60 --- /dev/null +++ b/study/second/readme.md @@ -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비트 세상의 규칙"**만 확실히 익히시면 됩니다! 잘 따라오고 계십니다. 👍 \ No newline at end of file diff --git a/study/third/mybios.asm b/study/third/mybios.asm new file mode 100644 index 0000000..8f1c322 --- /dev/null +++ b/study/third/mybios.asm @@ -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 \ No newline at end of file diff --git a/study/third/mybios2.asm b/study/third/mybios2.asm new file mode 100644 index 0000000..51efffe --- /dev/null +++ b/study/third/mybios2.asm @@ -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 \ No newline at end of file diff --git a/study/third/myboot.asm b/study/third/myboot.asm new file mode 100644 index 0000000..48525ce --- /dev/null +++ b/study/third/myboot.asm @@ -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) \ No newline at end of file diff --git a/study/third/readme.md b/study/third/readme.md new file mode 100644 index 0000000..89324c6 --- /dev/null +++ b/study/third/readme.md @@ -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 +```