Skip to content

Lab1 - Boot Loader, System Call

Print

การทำงานของ Boot Loader

Section titled “การทำงานของ Boot Loader”

Boot loader

รูป 1: แสดงการโหลดโปรแกรม boot loader จากอุปกรณ์ไปยัง memory

510 Bytes

จาก foreward ในหนังสือข้างต้น ได้เขียนสิ่งที่น่าสนใจดังนี้


Boot Sector: พื้นที่จำกัดกับการใช้งานที่เหนือความคาดหมาย

Section titled “Boot Sector: พื้นที่จำกัดกับการใช้งานที่เหนือความคาดหมาย”

Boot sector ในระบบที่เข้ากันได้กับ IBM ถูกออกแบบมาเพื่อเก็บโค้ดและข้อมูลขนาด 510 ไบต์ (2 ไบต์สุดท้ายเป็นลายเซ็นสำหรับตรวจสอบความถูกต้อง) ซึ่งเพียงพอสำหรับการระบุตำแหน่งและโหลดขั้นตอนถัดไปของการบูตเครื่อง สำหรับดิสก์ DOS มาตรฐาน โค้ดจะทำหน้าที่แยกวิเคราะห์ระบบไฟล์ FAT12 เพื่อตรวจหาไฟล์ชื่อ IBMBIO.COM หรือ IO.SYS และ IBMDOS.COM หรือ MSDOS.SYS จากนั้นจะโหลดไฟล์ IBMBIO.COM หรือ IO.SYS (ซึ่งจะโหลดไฟล์ IBMDOS.COM หรือ MSDOS.SYS ต่อไป) มีพื้นที่เหลือใน boot sector น้อยมากสำหรับการแสดงข้อความผิดพลาดโดยละเอียด หากไม่พบไฟล์ดังกล่าว ระบบที่ไม่ใช่ DOS สามารถทำการเข้าถึงดิสก์โดยตรงหรือแยกวิเคราะห์ระบบไฟล์ของตนเองได้ แต่ข้อจำกัดด้านขนาดก็ยังคงอยู่

ด้วยข้อจำกัดของสภาพแวดล้อมดังกล่าว บ่อยครั้งที่ boot sector ถูกมองข้ามว่ามีหน้าที่อื่นนอกเหนือจากการบูตระบบ อย่างไรก็ตาม ตลอดหลายปีที่ผ่านมา มีคนสองกลุ่มที่ค้นพบการใช้งานทางเลือกสำหรับมัน กลุ่มหนึ่งคือ นักเขียนไวรัส และอีกกลุ่มคือ ผู้สร้างเดโม (demo maker)


Boot Sector กับไวรัสคอมพิวเตอร์

Section titled “Boot Sector กับไวรัสคอมพิวเตอร์”

นักเขียนไวรัสสร้าง ไวรัสบูตเซกเตอร์ ที่จะทำงานเมื่อดิสก์ที่ติดเชื้อถูกบูตขึ้นมา โดยจะอาศัยอยู่ในหน่วยความจำและแพร่เชื้อไปยังฟล็อปปี้ดิสก์อื่น ๆ ที่ใส่เข้าไปในไดรฟ์ ไวรัสบูตเซกเตอร์มักจะคัดลอก boot sector ดั้งเดิมไปยังตำแหน่งอื่นบนดิสก์ จากนั้นจึงแทนที่โค้ดดั้งเดิมด้วยโค้ดเฉพาะของไวรัส

ไวรัสบูตเซกเตอร์ยุคแรก ๆ จะคัดลอก boot sector ดั้งเดิมไปไว้ที่ท้ายดิสก์ โดยหวังว่าจะไม่มีการใช้งานพื้นที่นั้นอยู่แล้ว เทคนิคนี้ไม่ได้ถูกใช้นานนัก เพราะการบูตฟล็อปปี้ดิสก์ที่ติดเชื้อดังกล่าวจะมีความล่าช้าที่สังเกตได้และมีเสียงผิดปกติขณะที่หัวอ่านไดรฟ์เคลื่อนไปยังท้ายดิสก์แล้วกลับมาเพื่อดำเนินการโหลดต่อ

เทคนิคการแทนที่นี้อาศัยข้อเท็จจริงที่ว่าดิสก์ DOS ทั่วไปมี สำเนาของ File Allocation Table (FAT) สองชุด โดยชุดหนึ่งอยู่ถัดจากอีกชุดหนึ่งทันที และทั้งสองชุดอยู่ในแทร็กแรกของดิสก์ สำเนา FAT ชุดแรกจะถูกใช้เป็นเวอร์ชันหลัก ส่วนชุดที่สองเป็นสำรองที่ในบางกรณีสามารถใช้กู้คืนไฟล์ที่ถูกลบและการซ่อมแซมข้อผิดพลาดของดิสก์บางอย่างได้

นักเขียนไวรัสตัดสินใจว่าสำเนาสำรองของ FAT จะไม่ถูกใช้งานบ่อยนัก และถือว่าสำเนานั้นเป็นพื้นที่ว่าง นั่นกลายเป็นที่สำหรับเก็บ boot sector ดั้งเดิม พร้อมกับโค้ดเพิ่มเติมที่ไวรัสต้องการเป็นขั้นตอนที่สอง การใช้ตำแหน่งนี้ช่วยหลีกเลี่ยงการเคลื่อนที่ของหัวอ่านไดรฟ์ระหว่างการบูต ทำให้การบูตดูคล้ายกันมากระหว่างดิสก์ที่ไม่ได้ติดเชื้อกับดิสก์ที่ติดเชื้อ

เมื่อฮาร์ดดิสก์แพร่หลายมากขึ้นและมีความจุมากขึ้น มักจะถูก “พาร์ติชัน” ออกเป็นส่วน ๆ ตามวัตถุประสงค์เฉพาะ หรือเพื่อหลีกเลี่ยงข้อจำกัดของคำอธิบายความจุของดิสก์ใน BIOS พาร์ติชันสามารถช่วยให้มีระบบมัลติบูตได้ — หนึ่งสภาพแวดล้อมต่อหนึ่งพาร์ติชัน แต่ละพาร์ติชันมี boot sector ของตัวเองที่รู้วิธีการแยกวิเคราะห์รูปแบบของข้อมูลในพาร์ติชันนั้น

นักเขียนไวรัสมักจะเล็งเป้าไปที่ Master Boot Record (MBR) ของฮาร์ดดิสก์ แทนที่จะเป็น boot sector ของแต่ละพาร์ติชัน MBR ใช้เพื่อเลือกพาร์ติชันที่สนใจและส่งการควบคุมไปยัง boot sector ของพาร์ติชันนั้นเพื่อบูตเนื้อหาของพาร์ติชันนั้น ดังนั้นจึงเป็นโค้ดแรกที่ทำงานและถูกเรียกใช้งานเสมอ ในทางตรงกันข้าม boot sector ของพาร์ติชันอาจถูกเรียกใช้งานไม่บ่อยนัก หรืออาจไม่ถูกเรียกเลยหากพาร์ติชันนั้นใช้รูปแบบดิสก์เดียวกันกับพาร์ติชันอื่นที่ถูกบูตแทน ไวรัสใน MBR ยังคงสามารถแพร่เชื้อไปยังฟล็อปปี้ดิสก์ได้เมื่อใส่เข้าไปในไดรฟ์ฟล็อปปี้

ไวรัสบูตเซกเตอร์แพร่กระจายอย่างอิสระและกว้างขวางในหมู่ดิสก์ละเมิดลิขสิทธิ์ เนื่องจากดิสก์ที่คัดลอกมามักจะไม่ได้ถูกป้องกันการเขียน พวกมันคงอยู่จนกระทั่งฟล็อปปี้ไดรฟ์เลิกผลิตพร้อมกับพีซีเครื่องใหม่ ถึงกระนั้น MBR ก็ยังคงมีอยู่ ในปี 2019 ที่ผู้เขียนเขียนบทความนี้ BIOS ในพีซีที่เข้ากันได้กับ IBM ยังคงมีโค้ดที่อนุญาตให้ระบบบูตจาก MBR ได้


Peter Ferrie

Distinguished Engineer.

Symantec Corp.

July 24, 2019

  • Bochs is a highly portable open source IA-32 (x86) PC emulator written in C++, that runs on most popular platforms.

  1. โหลดโปรแกรม NASM โดยโหลดที่ https://www.nasm.us/pub/nasm/releasebuilds/2.16.03/win64/nasm-2.16.03-installer-x64.exe

  2. ติดตั้ง NASM

  3. เพิ่ม path ของโปรแกรมไปใน environment variables Environment Variables

[org 7c00h] ; BIOS will load us to this address
mov ax, 0b800h ; Console memory is at 0xb8000
; set up a segment
mov es, ax ; for the start of the console text.
;
; Let's clear the screen....
;
xor di, di ; Start at beginning of screen
mov cx, 80*25 ; Number of chars in the screen
mov al, ' ' ; Space character
mov ah, 0fh ; Color (white on black)
repne stosw ; Copy!
mov byte [es:0], 'H' ; Write an 'H'
mov byte [es:1], 08ch
sleep:
hlt ; Halts CPU until the next external
;interrupt is fired
jmp sleep ; Loop forever
times 510-($-$$) db 0 ; Pad to 510 bytes
dw 0aa55h ; Add boot magic word to mark us
; as bootable

  1. นำโค๊ดด้านบนไปใส่ในไฟล์ boot.asm

  2. compile ไปเป็น .bin ไฟล์

    Terminal window
    nasm -f bin boot.asm -o boot.bin
  3. สร้างไฟล์ .bochsrc

    ata0-master: type=disk, path="boot.bin", mode=flat, cylinders=1, heads=1, spt=1
    boot: disk
    megs: 128
  4. กดรัน bochs + Enter

    Terminal window
    bochs

    Bochs

    Bochs Bootloader

การทำ Boot image และรันโปรแกรม Boot loader โดยไม่ใช้ Bochs

Section titled “การทำ Boot image และรันโปรแกรม Boot loader โดยไม่ใช้ Bochs”

  1. นำโค๊ดด้านบนไปใส่ในไฟล์ boot.asm

  2. compile ไปเป็น .img ไฟล์

    Terminal window
    nasm -f bin boot.asm -o boot.img
  3. สร้าง Virtual Machine ด้วย VirtualBox ตามรูป โดยเลือก Type เป็น Other และ Version เป็น DOS New VM

  4. เพิ่ม boot.img ลงใน floppy Floppy

  5. เลือกไฟล์ boot.img Boot Image

  6. กด Run

เพิ่ม DOS6 ลงใน Virtual Box

Section titled “เพิ่ม DOS6 ลงใน Virtual Box”

  1. เปิด Virtual Box แล้วกด Add VirtualBox Dos6 DOS6 VDI
  2. กด Open
  3. กด Start จะรัน DOS6 ขึ้นมา DOS6

  1. ไปที่ Settings ของ dos6hvd ที่เราเพิ่มเข้ามา
  2. ไปที่ Shared Folders
  3. เพิ่ม Shared folder ดังรูป Create Shared Folder
  4. ตั้งค่า Shared Folder

  1. เมื่อโหลด dos6 ขึ้นมา
  2. พิมพ์ p แล้วกด Enter
  3. เพื่อโหลดโปรแกรม Borland Compiler กับ Shared Folder ขึ้นมา Enable BC and Shared Folder

การเรียกใช้ Shared Folder

Section titled “การเรียกใช้ Shared Folder”

  1. เมื่อโหลด dos6 ขึ้นมา
  2. พิมพ์ p แล้วกด Enter
  3. เพื่อโหลดโปรแกรม Borland Compiler กับ Shared Folder ขึ้นมา
  4. พิมพ์ v แล้วกด Enter
  5. Dos จะเปลี่ยนไปที่ Shared Folder ที่เรา map ไว้กับ Host เครื่อง V drive

อ้างอิงจาก Assembly เบื้องต้น มหาวิทยาลัยราชภัฏสุราษฎร์ธานี หน้า 24

บริการของระบบปฏิบัติการ (System Calls)

Section titled “บริการของระบบปฏิบัติการ (System Calls)”

การเขียนโปรแกรม จำเป็นต้องเรียกใช้บริการที่ระบบปฏิบัติการเตรียมไว้ให้โปรแกรมประยุกต์เรียกใช้ บริการเช่นนี้เรียกว่า Application Programming Interface (API) หรือนิยมเรียกกันในกลุ่มผู้เขียนโปรแกรม ระบบ (System Programmer) ว่า System Call

บริการของระบบปฏิบัติการ MS-DOS ให้บริการผ่าน Software Interrupt ผู้เขียนโปรแกรมสามารถ เรียกใช้ได้ผ่านคำสั่ง INT ของหน่วยประมวลผลกลาง แต่รายละเอียดของบริการแต่ละอย่างต้องศึกษาจากคู่มือ ของระบบปฏิบัติการ MS-DOS

คำสั่ง INT – Generate Software Interrupt

รูปแบบการใช้งาน : INT หมายเลขของ Software Interrupt

คำอธิบาย คำสั่ง INT จะใช้หมายเลขของ Software Interrupt ที่ผู้เขียนโปรแกรมกำหนดให้เป็นดัชนีของตาราง ที่จัดเก็บตำแหน่งเริ่มต้น ( address) ของโปรแกรมย่อยที่ทำหน้าที่ตอบสนองต่อการ Interrupt ซึ่งเก็บไว้ใน หน่วยความจำที่เรียกว่า Interrupt Vector Table หรือ Interrupt Descriptor Table เมื่อได้ตำแหน่ง เริ่มต้นของโปรแกรมย่อยนั้นแล้วจะทำการส่งผ่านการทำงานโดยไม่มีเงื่อนไขไปยังตำแหน่งนั้น โปรแกรมย่อย ดังกล่าวนี้เรียกว่า Interrupt Service Routine หรือ Interrupt Handler ซึ่งจะให้บริการตามหมายเลข Interrupt ที่ร้องขอมาและเมื่อทำงานเสร็จแล้ว จะส่งผ่านการทำงานกลับคืนมายังจุดเดิม ทำให้สามารถทำงาน เดิมที่ค้างอยู่ต่อไปได้ เรื่องของ Interrupt

ระบบปฏิบัติการ MS-DOS ทำการสงวนหมายเลข Interrupt ระหว่าง 20H – 3FH ไว้สำหรับใช้งาน โดยกำหนดให้ Interrupt หมายเลข 20 H (INT 20) ให้บริการในการจบการทำงานของโพรเซส ซึ่งต้อง ดำเนินการหลายอย่างทั้งการกำหนดค่าที่จำเป็น การจัดการกับหน่วยความจำที่ใช้พักข้อมูลของแฟ้ม ( buffer) การปิดแฟ้มที่ยังเปิดค้างอยู่ สำหรับ Interrupt หมายเลข 21H (INT 21) แยกออกเป็นบริการย่อยอีกประมาณ 100 บริการ แต่ละบริการเรียกว่า MS-DOS function call โดยทำการกำหนดหมายเลขฟังก์ชันที่ต้องการใน รีจิสเตอร์ AH ก่อนเรียกใช้คำสั่ง INT 21H ในที่นี้จะกล่าวถึงเฉพาะบริการรับแสดงผลตัวอักขระหนึ่งตัวทาง อุปกรณ์นำข้อมูลเข้าและแสดงผลมาตรฐานก่อน

ตัวอย่างของ System Call

Section titled “ตัวอย่างของ System Call”

ตัวอย่าง System Call ที่เรียกผ่าน Library ของภาษา C

#include <stdio.h>
int main(void) {
printf("hello, world!\n"); //จะไปเรียก systemcall
return 0;
}

ตัวอย่าง 1 - ใช้ได้เฉพาะ Linux อันนี้จะขึ้นอยู่กับระบบปฏิบัติการ unix เพียงอย่างเดียว

#include <unistd.h>
int main(void) {
write(1, "hello, world!\n", 14);
return 0;
}

ตัวอย่าง 2 - ใช้ได้เฉพาะ Linux อันนี้เป็นการเรียกใช้ system call โดยตรง แต่ข้อเสียคือ number ของ system call ที่เรียกใช้จะขึ้นอยู่กับ OS ที่เป็น unix

#include <unistd.h>
#include <sys/syscall.h>
int main(void) {
syscall(SYS_write, 1, "hello, world!\n", 14);
return 0;
}

  1. พิมพ์คำสั่ง bc ตามด้วยชื่อไฟล์ lab1.c

    Terminal window
    C:\PRJ\bc lab1.c
  2. พิมพ์โค๊ดภาษา C ลงไป

    #include <stdio.h>
    int main(){
    printf("Hello World!");
    return 0;
    }
  3. กด ALT-F9 เพื่อ COMPILE

    COMPILE

  4. กด F9 เพื่อ Make

    Make

  5. กด ALT-F แล้วไปที่ Dos Shell

    Execute lab1

  6. กด Exit เพื่อกลับไป Borland Compiler

ตัวอย่างของ System Call ใน DOS6

Section titled “ตัวอย่างของ System Call ใน DOS6”

ฟังก์ชั่น 2

i21_2.c
#include <stdio.h>
int main(){
asm {
mov ah, 0x2 // ใช้ function 2 ส่งอักขระไปยังจอภาพ
mov dl, 'a' // ส่งตัวอักษร ‘a’ ไปจอภาพ
int 21h
}
return 0;
}

ฟังก์ชั่น 9

i21_9.c
#include <stdio.h>
int main(){
char far* greetings = “Hello World!$”;
asm {
lds dx, greetings // ส่งข้อความไปยังจอภาพ
mov ah, 0x9 // ใช้ฟังก์ชั่น 9
int 21h
}
return 0;
}

i10.c
#include <stdio.h>
int main(){
asm {
mov ah,0x9 //ฟังก์ชั่น 9 พิมพ์ตัวอักษร
mov al,'b' // ตัวอักษร b
mov bh,0x0 //
mov bl,0xcc // สีของตัวอักษร
mov cx,10 //จำนวนตัวอักษร
int 10h
}
return 0;
}

i8.c
#include <stdio.h>
#include <dos.h>
#include <bios.h>
#include <conio.h>
void far interrupt (*oldint_8h)(void);
void far interrupt timer_hook(void);
int m=0;
int t=0;
int active=0;
char far* vga = (char far*)0xB8000000; // แสดงผลออกการ์ดจอ
void far interrupt timer_hook(void){
int i;
if(active==1){
(*oldint_8h)();
return;
}
t++;
m++;
if(t>10){
active = 1;
t = 0;
for(i=0;i<200;i+=2){
*(vga + i) = m%2==0? 'A' : 'B';
*(vga + i + 1) = 0x7;
}
active=0;
}
(*oldint_8h)();
}
int main(void){
int ch;
asm {
mov ax, 0x3;
int 10h
}
oldint_8h = getvect(0x8);
setvect(0x8, timer_hook);
while(ch!='z'){
ch = getch();
putch(ch);
}
setvect(0x8, oldint_8h);
asm{
mov ah,0
mov al,3
int 10h
}
return 0;
}

ตัวอย่างของการเขียน assembly ใน Linux

Section titled “ตัวอย่างของการเขียน assembly ใน Linux”

ตามปกติ assembly จะต้องขึ้นต้นด้วย

  • section .text The .text section is where the executable code resides, such as instructions for the processor to execute. Assemblers and linkers treat this section as the code segment.
section .text
global _start
  • ถ้าประกาศตัวแปรพร้อมกับค่าเริ่มต้นจะใส่ใน section .data
section .data
  • ถ้าประกาศตัวแปรเฉยๆ จะใส่ใน section .bss
section .bss
buffer resb 256 ; Reserve 256 bytes for a buffer
counter resd 1 ; Reserve 1 double word (4 bytes) for a counter
  • แต่ nasm ไม่ได้สนใจ section .text ดังนั้น nasm จะไปสนใจ global _start เลย
global _start
  1. global Directive:
    • This tells the assembler that the following label (in this case, _start) is something that can be accessed by external files, like the linker. It essentially makes the _start label a global symbol.
  2. _label Label:
    • _start is commonly used as the entry point of an assembly program. It’s where the execution begins. By convention, when you create an assembly program, you define this label, and the linker expects it.
global _start
_start:
mov eax, 1
mov ebx, 42
sub ebx, 29
int 0x80
  1. เป็นการเรียกใช้ system call ที่ชื่อว่า exit โดยการ assign 1 ให้กับ register eax
  2. เป็นการบอกว่า system call ที่ชื่อว่า exit นี้จะ return ค่าอะไรออกมา ซึ่ง return 42 ออกมา
  3. sub คือการลบค่า ดังนั้น sub ebx, 29 จะได้ 13 เป็นค่าที่ return ออกมา
  4. int 0x80 เป็นการเรียกใช้ software interrupt ดังนั้น kernel จึงจะอ่านค่าจาก eax และ ebx

ใช้ nasm ในการคอมไพล์

Terminal window
nasm -f elf32 ex1.asm -o ex1.o
  1. nasm เป็นตัวแปลงภาษา assembly (human-readable low-level instructions) into machine code
  2. -f elf32 This specifies the output format of the object file as 32-bit ELF (Executable and Linkable Format). ELF is commonly used on Linux systems.
  3. ex1.asm This is the input file, which contains your assembly code.
  4. -o ex1.o This specifies the output file name as ex1.o, the object file that will be generated
Terminal window
ld -m elf_i386 ex1.o -o ex1
  • ld: Invokes the linker to combine the object file and any necessary libraries into a single executable file.

  • -m elf_i386: Specifies the target as 32-bit ELF format (important for 32-bit programs). If you’re on a 64-bit system, this ensures compatibility for running 32-bit code.

  • ex1.o: The input object file created by NASM during the assembly step.

  • -o ex1: Specifies the name of the final output executable (ex1).

Terminal window
./ex1
  1. เวลารันโปรแกรมออกมา มันจะไม่ขึ้นอะไรบนจอ
Terminal window
echo $?
  1. คำสั่งนี้จะทำให้รู้ว่า exit status ของคำสั่งล่าสุดคืออะไร จะใช้ใน unix กับ linux

Assembly สำหรับ Linux ยังไม่เป็น real mode

Section titled “Assembly สำหรับ Linux ยังไม่เป็น real mode”
  • Real Mode:

    • Used by the BIOS and initial stages of bootloaders.

    • Limited to 1 MB of memory and doesn’t support advanced features like virtual memory.

    • If you’re writing assembly for bare-metal (like bootloaders or OS kernels before transitioning to protected mode), you’ll be working in real mode.

  • Protected Mode:

    • Linux and most modern operating systems use protected mode for their normal operation.

    • Linux assembly programs use protected mode features, meaning you have access to 32-bit or 64-bit registers and advanced memory management.

    • To run assembly on Linux, you typically interact with the operating system through system calls, which are executed in protected mode.

อันนี้คือตาราง system call ใน Linux

Section titled “อันนี้คือตาราง system call ใน Linux”
  • for 32bit
System CallPurpose
readReads data from a file descriptor.
writeWrites data to a file descriptor.
openOpens a file and returns a file descriptor.
closeCloses an open file descriptor.
forkCreates a new process.
execveExecutes a program.
exitTerminates a process.
waitWaits for child process termination.
mmapMaps files or devices into memory.
brkChanges the memory allocation of a process.
ioctlManipulates I/O device parameters.
socketCreates a socket.
bindAssigns an address to a socket.
listenListens for socket connections.
acceptAccepts a socket connection.