Last active
May 23, 2020 08:56
-
-
Save uucidl/8ac2a1adb1cf0959f7568427458bf8a3 to your computer and use it in GitHub Desktop.
legacy bios bootsector
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// -*- mode: c ; c-file-style: "k&r" -*- | |
// x86 boot sector. | |
// | |
// Once you execute this program you will get a series of disk images, which you can try in qemu: | |
// qemu -drive file=boot.img,format=raw | |
// | |
// and burn to a usb stick using something like rufus or dd | |
#include <assert.h> | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <stdarg.h> | |
#include <stdbool.h> | |
char* asnprintf(char const* format, ...) { | |
va_list args; | |
va_start(args, format); | |
int needed_size_or_error = vsnprintf(NULL, 0, format, args); | |
va_end(args); | |
if (needed_size_or_error < 0) { | |
return NULL; | |
} | |
int needed_size = needed_size_or_error; | |
char *res = calloc(needed_size + 1, 1); | |
va_start(args, format); | |
vsnprintf(res, needed_size + 1, format, args); | |
va_end(args); | |
return res; | |
} | |
struct CodePointer { | |
char* ptr; | |
}; | |
enum Mod { | |
Mod_RegisterDirect = 3, | |
}; | |
enum RM { | |
RM_BX_SI, | |
RM_BX_DI, | |
RM_BP_SI, | |
RM_BP_DI, | |
RM_SI, | |
RM_DI, | |
RM_BP, | |
RM_BX, | |
}; | |
struct ModRM8 { | |
enum Mod mod; | |
enum GPR8 reg; | |
enum RM r_m; | |
}; | |
// 0x07 == AL, byte ptr [BX] | |
int bits(unsigned int x, int offset, int length) { | |
return (x >> offset) & ((1<<length) - 1); | |
} | |
char pack_modrm8(struct ModRM8 x) { | |
return (bits(x.mod, 0, 2) << 6) | | |
(bits(x.reg, 0, 3) << 3) | | |
bits(x.r_m, 0, 3); | |
} | |
struct Reference16 { | |
char *ptr; | |
}; | |
struct Reference8 { | |
char *ptr; | |
}; | |
enum { | |
ENC_AL = 0, | |
ENC_CL = 1, | |
ENC_DL = 2, | |
ENC_BL = 3, | |
ENC_AH = 4, | |
ENC_CH = 5, | |
ENC_DH = 6, | |
ENC_BH = 7, | |
}; | |
struct Register8 { | |
int enc; | |
}; | |
enum { | |
ENC_AX = 0, | |
ENC_CX = 1, | |
ENC_DX = 2, | |
ENC_BX = 3, | |
ENC_SP = 4, | |
ENC_BP = 5, | |
ENC_SI = 6, | |
ENC_DI = 7, | |
}; | |
struct Register16 { | |
int enc; | |
}; | |
enum { | |
ENC_ES = 0, | |
ENC_CS = 1, | |
ENC_SS = 2, | |
ENC_DS = 3, | |
}; | |
struct SegmentRegister { | |
int enc; | |
}; | |
struct Register8 GPR_AL = {ENC_AL}; | |
struct Register8 GPR_CL = {ENC_CL}; | |
struct Register8 GPR_DL = {ENC_DL}; | |
struct Register8 GPR_BL = {ENC_BL}; | |
struct Register8 GPR_AH = {ENC_AH}; | |
struct Register8 GPR_CH = {ENC_CH}; | |
struct Register8 GPR_DH = {ENC_DH}; | |
struct Register8 GPR_BH = {ENC_BH}; | |
struct Register16 GPR_AX = {ENC_AX}; | |
struct Register16 GPR_CX = {ENC_CX}; | |
struct Register16 GPR_DX = {ENC_DX}; | |
struct Register16 GPR_BX = {ENC_BX}; | |
struct Register16 GPR_SP = {ENC_SP}; | |
struct Register16 GPR_BP = {ENC_BP}; | |
struct Register16 GPR_SI = {ENC_SI}; | |
struct Register16 GPR_DI = {ENC_DI}; | |
struct SegmentRegister SEG_DS = {ENC_DS}; | |
struct SegmentRegister SEG_ES = {ENC_ES}; | |
struct SegmentRegister SEG_SS = {ENC_SS}; | |
void emit_byte(struct CodePointer *cp, char byte) { | |
*(cp->ptr++) = byte; | |
} | |
void emit_byte_n(struct CodePointer *cp, char *bytes, size_t n) { | |
while(n--) { | |
emit_byte(cp, *bytes++); | |
} | |
} | |
void emit_cli(struct CodePointer *cp) { | |
emit_byte(cp, 0xFA); | |
} | |
void emit_sti(struct CodePointer *cp) { | |
emit_byte(cp, 0xFB); | |
} | |
void emit_nop(struct CodePointer *cp) { | |
emit_byte(cp, 0x90); | |
} | |
void emit_word16(struct CodePointer *cp, short word16) { | |
emit_byte(cp, bits(word16, 0, 8)); | |
emit_byte(cp, bits(word16, 8, 8)); | |
} | |
void emit_word32(struct CodePointer *cp, unsigned int w32) { | |
assert(0 <= w32 && w32 <= ((1<<31) + ((1<<31)-1))); | |
emit_byte(cp, bits(w32, 0, 8)); | |
emit_byte(cp, bits(w32, 8, 8)); | |
emit_byte(cp, bits(w32, 16, 8)); | |
emit_byte(cp, bits(w32, 24, 8)); | |
} | |
void emit_je_rel8_ref(struct CodePointer *cp, struct Reference8* ref) { | |
emit_byte(cp, 0x74); | |
ref->ptr = cp->ptr++; | |
} | |
void emit_jne_rel8_ref(struct CodePointer *cp, struct Reference8* ref) { | |
emit_byte(cp, 0x75); | |
ref->ptr = cp->ptr++; | |
} | |
void emit_jmp_rel8(struct CodePointer *cp, char byte) { | |
emit_byte(cp, 0xeb); | |
emit_byte(cp, byte - 2 /* length of this instruction */); | |
} | |
void emit_jmp_rel8_ref(struct CodePointer *cp, struct Reference8* ref) { | |
emit_byte(cp, 0xeb); | |
ref->ptr = cp->ptr++; | |
} | |
void emit_lodsb(struct CodePointer* cp) { | |
emit_byte(cp, 0xac); | |
} | |
void emit_cld(struct CodePointer* cp) { | |
emit_byte(cp, 0xfc); | |
} | |
void emit_cmp_al(struct CodePointer* cp, char byte) { | |
emit_byte(cp, 0x3c); | |
emit_byte(cp, byte); | |
} | |
void emit_movb_imm(struct CodePointer *cp, struct Register8 reg, char byte) { | |
emit_byte(cp, 0xB0 + reg.enc); | |
emit_byte(cp, byte); | |
} | |
void emit_movw_ref(struct CodePointer* cp, struct Register16 reg, struct Reference16* ref_ptr) { | |
emit_byte(cp, 0xB8 + reg.enc); | |
ref_ptr->ptr = cp->ptr; cp->ptr += 2; | |
} | |
void emit_movw_imm(struct CodePointer* cp, struct Register16 reg, short value) { | |
emit_byte(cp, 0xB8 + reg.enc); | |
emit_word16(cp, value); | |
} | |
void emit_movw(struct CodePointer* cp, struct Register16 dst, struct Register16 src) { | |
emit_byte(cp, 0x89); | |
emit_byte(cp, pack_modrm8((struct ModRM8){.mod=Mod_RegisterDirect, .r_m=dst.enc, .reg=src.enc})); | |
} | |
void emit_movw_segreg(struct CodePointer *cp, struct SegmentRegister dst, struct Register16 src) { | |
emit_byte(cp, 0x8e); | |
emit_byte(cp, pack_modrm8((struct ModRM8) { .mod=Mod_RegisterDirect, .r_m = src.enc, .reg = dst.enc })); | |
} | |
#if 0 | |
void emit_movb_from_ptr(struct CodePointer* cp, struct Register8 dst, struct Register8 src_ptr) { | |
emit_byte(cp, 0x8a); | |
emit_byte(cp, pack_modrm8((struct ModRM8) { .r_m = dst })); | |
} | |
#endif | |
void emit_xor16(struct CodePointer *cp, struct Register16 dst, struct Register16 src) { | |
emit_byte(cp, 0x31); | |
emit_byte(cp, pack_modrm8((struct ModRM8){.mod=Mod_RegisterDirect,.reg = src.enc, .r_m=dst.enc})); | |
} | |
void emit_int(struct CodePointer* cp, char interrupt) { | |
emit_byte(cp, 0xCD); | |
emit_byte(cp, interrupt); | |
} | |
void emit_hlt(struct CodePointer* cp) { | |
emit_byte(cp, 0xf4); | |
} | |
void wait_forever(struct CodePointer *cp) { | |
struct CodePointer label = *cp; | |
emit_hlt(cp); | |
emit_jmp_rel8(cp, label.ptr - cp->ptr); | |
} | |
void output_ascii_char_with_int10(struct CodePointer *cp, char c) { | |
assert(0 <= c && c < 128); | |
emit_movb_imm(cp, GPR_AH, 0x0e); | |
emit_movb_imm(cp, GPR_AL, c); | |
emit_movw_imm(cp, GPR_BX, 0x0F); | |
emit_int(cp, 0x10); | |
} | |
enum BPB_DiskType { | |
BPB_DiskType_DoubleSided_18SectorsPerTrack_3inch5 = 0xf0, | |
BPB_DiskType_HardDisk = 0xf8, | |
}; | |
void push_bios_param_block(struct CodePointer* cp) { | |
// Bios Param Block: | |
struct CodePointer start = *cp; | |
emit_word16(cp, 512); // 0x0B: Bytes Per Sector | |
emit_byte(cp, 1); // 0x0D: Num Sectors Per Cluster | |
emit_word16(cp, 1); // 0x0E: Num Reserved Sectors | |
emit_byte(cp, 0); // 0x10: Num File Allocation Tables | |
emit_word16(cp, 0); // 0x11: Num Entry in Directory | |
emit_word16(cp, 0); // 0x13: Num Available Sectors | |
emit_byte(cp, BPB_DiskType_HardDisk); // 0x15: Disk Type | |
emit_word16(cp, 0); // 0x16: Num Sectors per File Allocation Table | |
emit_word16(cp, 0); // 0x18: Num Sectors per Track | |
emit_word16(cp, 0); // 0x1a: Num Heads | |
emit_word16(cp, 0); // 0x1c: Num Hidden Sectors | |
while (cp->ptr - start.ptr < 0x3d - 0x0b) { | |
emit_byte(cp, 0); | |
} | |
} | |
void emit_hello(struct CodePointer* cp, struct CodePointer* origin, char const* recipient) { | |
struct Reference16 msg_ref = {0}; | |
int entry_point = 0x7c00; | |
{ | |
// Reset data segment DS: | |
//emit_xor16(cp, GPR_AX, GPR_AX); | |
emit_movw_imm(cp, GPR_AX, entry_point>>4); | |
emit_movw_segreg(cp, SEG_DS, GPR_AX); | |
emit_movw_segreg(cp, SEG_ES, GPR_AX); | |
// Setup the stack somewhere high | |
emit_movw_imm(cp, GPR_AX, 0x8000); | |
emit_movw_segreg(cp, SEG_SS, GPR_AX); | |
emit_xor16(cp, GPR_AX, GPR_AX); | |
emit_movw(cp, GPR_SP, GPR_AX); | |
emit_movw(cp, GPR_BP, GPR_AX); | |
} | |
emit_sti(cp); | |
emit_movw_imm(cp, GPR_AX, 0x0003); | |
emit_int(cp, 0x10); | |
// INT 10,5 - Select Active Display Page | |
emit_movw_imm(cp, GPR_AX, 0x0500); | |
emit_int(cp, 0x10); | |
// Int 10, 2: Set Cursor Position | |
emit_movw_imm(cp, GPR_AX, 0x0200); | |
emit_movw_imm(cp, GPR_BX, 0x0000); | |
emit_movw_imm(cp, GPR_DX, 0x0101); | |
emit_int(cp, 0x10); | |
// Print msg: | |
{ | |
emit_movw_ref(cp, GPR_SI, &msg_ref); | |
emit_cld(cp); | |
// While(c = str++) | |
{ | |
struct CodePointer while_char = *cp; | |
emit_lodsb(cp); | |
emit_cmp_al(cp, 0); | |
struct Reference8 loop_done_ref = {0}; | |
emit_je_rel8_ref(cp, &loop_done_ref); | |
struct CodePointer loop_done_ref_base = *cp; | |
// Int10(c, 0x0e, 0x07) | |
{ | |
emit_movb_imm(cp, GPR_AH, 0x0e); | |
emit_movw_imm(cp, GPR_BX, 0x07); | |
emit_int(cp, 0x10); | |
} | |
emit_jmp_rel8(cp, while_char.ptr - cp->ptr); | |
*loop_done_ref.ptr = cp->ptr - loop_done_ref_base.ptr; // back patch | |
} | |
} | |
emit_movw_imm(cp, GPR_AX, 0x0200); | |
emit_movw_imm(cp, GPR_BX, 0x0000); | |
emit_movw_imm(cp, GPR_DX, 0x0301); | |
emit_int(cp, 0x10); | |
wait_forever(cp); | |
// Static data: | |
emit_word16(&(struct CodePointer){msg_ref.ptr}, cp->ptr - origin->ptr); // back patch | |
char *msg = asnprintf("Hello %s!", recipient); | |
size_t msg_size = strlen(msg) + 1; | |
emit_byte_n(cp, msg, msg_size); | |
free(msg); | |
} | |
void create_floppy(char const *filename) { | |
char boot_sector[512] = {0}; | |
struct CodePointer cp = {&boot_sector[0]}; | |
struct CodePointer origin = cp; | |
struct Reference8 start_ref = {0}; | |
emit_jmp_rel8_ref(&cp, &start_ref); | |
struct CodePointer start_ref_jmp_base = cp; | |
while (cp.ptr - &boot_sector[0] < 3) { | |
emit_nop(&cp); | |
} | |
// MS-DOS type bootsector | |
// Null-terminated OEMname (must be less than 8 bytes) | |
emit_byte_n(&cp, "KNOS", strlen("KNOS")); | |
while (cp.ptr - &boot_sector[0] < 11) { | |
emit_byte(&cp, 0); | |
} | |
push_bios_param_block(&cp); | |
*start_ref.ptr = cp.ptr - start_ref_jmp_base.ptr; // back patch | |
emit_hello(&cp, &origin, "Floppy"); | |
// Mark boot sector as bootable: | |
boot_sector[510] = 0x55; | |
boot_sector[511] = 0xaa; | |
for (FILE *fd = fopen(filename, "wb"); fd; ) { | |
if(1 != fwrite(boot_sector, 512, 1, fd)) exit(-1); | |
printf("Wrote %s\n", filename); | |
fclose(fd); | |
break; | |
} | |
} | |
struct MBR_CHS { | |
int cylinder, head, sector; | |
}; | |
enum MBR_PartitionType { | |
// source, Wikipedia @url: https://en.wikipedia.org/wiki/Partition_type | |
// also see @url: https://www.win.tue.nl/~aeb/partitions/partition_types-1.html | |
MBR_PartitionType_None = 0x00, | |
MBR_PartitionType_IBM_DOS2_0 = 0x01, // FAT12 as primary partition in first physical 32 MB of disk or as logical drive anywhere on disk (else use 06h instead) | |
MBR_PartitionType_ReservedForLocalUse = 0x7f, // Reserved for individual or local use and temporary or experimental projects (alt.os.development) | |
}; | |
struct MBR_Entry { | |
enum MBR_PartitionType partition_type; | |
bool is_boot_active; | |
struct MBR_CHS start_chs; | |
struct MBR_CHS end_chs; | |
int starting_sector; | |
int num_sectors_in_partition; | |
}; | |
void emit_mbr_chs_value(struct CodePointer *cp, struct MBR_CHS chs) { | |
assert(0 <= chs.cylinder && chs.cylinder <= 1023); | |
assert(0 <= chs.head && chs.head <= 254); | |
assert(0 <= chs.sector && chs.sector <= 63); | |
emit_byte(cp, chs.head); | |
emit_byte(cp, chs.sector | (bits(chs.cylinder, 8, 2)<<6)); | |
emit_byte(cp, bits(chs.cylinder, 0, 8)); | |
} | |
void emit_mbr_entry(struct CodePointer *cp, struct MBR_Entry entry) { | |
emit_byte(cp, entry.is_boot_active? 0x80:0x00); | |
emit_mbr_chs_value(cp, entry.start_chs); | |
assert(0 <= entry.partition_type && entry.partition_type <= 255); | |
emit_byte(cp, entry.partition_type); | |
emit_mbr_chs_value(cp, entry.end_chs); | |
emit_word32(cp, entry.starting_sector); | |
emit_word32(cp, entry.num_sectors_in_partition); | |
} | |
void create_harddisk_empty(char const* filename) { | |
// Hardisks have a Master Boot Record with a partition table. | |
// @url: https://thestarman.pcministry.com/asm/mbr/PartTables.htm | |
char boot_sector[512] = {0}; | |
int entry_point = 0x7c00; | |
struct CodePointer cp = {&boot_sector[0]}; | |
struct CodePointer origin = cp; | |
emit_hello(&cp, &origin, "Empty Hardy"); | |
while (cp.ptr - origin.ptr < 0x1be) { | |
emit_byte(&cp, 0x00); | |
} | |
// Partition table | |
// [0x1BE, 0x1FE) | |
emit_mbr_entry(&cp, (struct MBR_Entry){.partition_type=0x00}); | |
emit_mbr_entry(&cp, (struct MBR_Entry){.partition_type=0x00}); | |
emit_mbr_entry(&cp, (struct MBR_Entry){.partition_type=0x00}); | |
emit_mbr_entry(&cp, (struct MBR_Entry){.partition_type=0x00}); | |
// Mark boot sector as bootable: | |
boot_sector[510] = 0x55; | |
boot_sector[511] = 0xaa; | |
for (FILE *fd = fopen(filename, "wb"); fd; ) { | |
if(1 != fwrite(boot_sector, 512, 1, fd)) exit(-1); | |
printf("Wrote %s\n", filename); | |
fclose(fd); | |
break; | |
} | |
} | |
void create_harddisk(char const* filename) { | |
// Hardisks have a Master Boot Record with a partition table. | |
// @url: https://thestarman.pcministry.com/asm/mbr/PartTables.htm | |
char master_boot_sector[512] = {0}; | |
int entry_point = 0x7c00; | |
struct CodePointer cp = {&master_boot_sector[0]}; | |
struct CodePointer origin = cp; | |
emit_hello(&cp, &origin, "Hardy"); | |
while (cp.ptr - origin.ptr < 0x1be) { | |
emit_byte(&cp, 0x00); | |
} | |
// Partition table | |
// [0x1BE, 0x1FE) | |
emit_mbr_entry(&cp, (struct MBR_Entry){ | |
.partition_type = MBR_PartitionType_ReservedForLocalUse, | |
.is_boot_active = true, | |
.start_chs = { .cylinder = 0, .head = 0, .sector = 1 }, | |
.end_chs = { .cylinder = 0, .head = 0, .sector = 1 }, | |
.starting_sector = 1, | |
.num_sectors_in_partition = 1, | |
}); | |
emit_mbr_entry(&cp, (struct MBR_Entry){.partition_type=0x00}); | |
emit_mbr_entry(&cp, (struct MBR_Entry){.partition_type=0x00}); | |
emit_mbr_entry(&cp, (struct MBR_Entry){.partition_type=0x00}); | |
// Mark boot sector as bootable: | |
master_boot_sector[510] = 0x55; | |
master_boot_sector[511] = 0xaa; | |
// First partition boot sector | |
char first_partition_boot_sector[512] = {0}; | |
{ | |
cp = (struct CodePointer){&first_partition_boot_sector[0]}; | |
origin = cp; | |
emit_hello(&cp, &origin, "First Partition"); | |
first_partition_boot_sector[510] = 0x55; | |
first_partition_boot_sector[511] = 0xaa; | |
} | |
for (FILE *fd = fopen(filename, "wb"); fd; ) { | |
if(1 != fwrite(master_boot_sector, 512, 1, fd)) exit(-1); | |
if(1 != fwrite(first_partition_boot_sector, 512, 1, fd)) exit(-1); | |
printf("Wrote %s\n", filename); | |
fclose(fd); | |
break; | |
} | |
} | |
void create_hybrid(char const* filename) { | |
} | |
int main() { | |
create_floppy("floppy.img"); | |
create_harddisk_empty("hdd_empty.img"); | |
create_harddisk("hdd.img"); | |
create_hybrid("hybrid.img"); | |
return 0; | |
} | |
// Some documentation | |
// ================== | |
// | |
// @url: https://wiki.osdev.org/Problems_Booting_From_USB_Flash | |
// | |
// @url: https://stackoverflow.com/questions/32701854/boot-loader-doesnt-jump-to-kernel-code/32705076#32705076 | |
// @url: https://stackoverflow.com/questions/47482308/usb-hard-disk-emulation-cause-a-disk-read-to-fail-bios-int-13 | |
// | |
// See also: | |
// @url: https://forum.osdev.org/viewtopic.php?f=1&t=32495&sid=85e98915edf28f0849afc19b85a63e9c | |
// MBR: | |
// @url: https://thestarman.pcministry.com/asm/mbr/95BMEMBR.htm | |
// Local Variables: | |
// compile-command: "cl real_mode_boot.c && real_mode_boot.exe && qemu -drive file=$img,format=raw" | |
// End: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment