Skip to content

Instantly share code, notes, and snippets.

@uucidl
Last active May 23, 2020 08:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save uucidl/8ac2a1adb1cf0959f7568427458bf8a3 to your computer and use it in GitHub Desktop.
Save uucidl/8ac2a1adb1cf0959f7568427458bf8a3 to your computer and use it in GitHub Desktop.
legacy bios bootsector
// -*- 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