Initial commit

This commit is contained in:
Alexander Myltsev 2023-01-13 12:56:54 +04:00
commit 6e5222e2e3
15 changed files with 682 additions and 0 deletions

80
Makefile Normal file
View File

@ -0,0 +1,80 @@
GDB=gdb
ifeq ($(shell uname -s),Darwin)
AS=x86_64-elf-as
LD=x86_64-elf-ld
CC=x86_64-elf-gcc
GDB=x86_64-elf-gdb
endif
CFLAGS = -fno-pic -ffreestanding -static -fno-builtin -fno-strict-aliasing \
-Wall -ggdb -m32 -Werror -fno-omit-frame-pointer
CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)
run: image.bin
qemu-system-i386 -drive format=raw,file=$< -serial mon:stdio
run-nox: image.bin
qemu-system-i386 -nographic -drive format=raw,file=$< -serial mon:stdio
debug-boot-nox: image.bin mbr.elf
qemu-system-i386 -nographic -drive format=raw,file=$< -s -S &
$(GDB) mbr.elf \
-ex "set architecture i8086" \
-ex "target remote localhost:1234" \
-ex "break *0x7c00" \
-ex "continue"
debug-boot: image.bin mbr.elf
qemu-system-i386 -drive format=raw,file=$< -s -S &
$(GDB) mbr.elf \
-ex "set architecture i8086" \
-ex "target remote localhost:1234" \
-ex "break *0x7c00" \
-ex "continue"
debug: image.bin
qemu-system-i386 -drive format=raw,file=$< -s -S &
$(GDB) kernel.bin \
-ex "target remote localhost:1234" \
-ex "break _start" \
-ex "continue"
debug-nox: image.bin
qemu-system-i386 -nographic -drive format=raw,file=$< -s -S &
$(GDB) kernel.bin \
-ex "target remote localhost:1234" \
-ex "break _start" \
-ex "continue"
fs.img: kernel.bin tools/mkfs
tools/mkfs $@ $<
LDFLAGS=-m elf_i386
user/%: user/%.o user/crt.o
$(LD) $(LDFLAGS) -o $@ -Ttext 0x1000 $^
image.bin: mbr.bin fs.img
cat $^ >$@
kernel.bin: kernel.o console.o drivers/vga.o drivers/uart.o
$(LD) $(LDFLAGS) -o $@ -Ttext 0x1000 $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.S
$(CC) -m32 -ffreestanding -c -g $^ -o $@
mbr.bin: mbr.o
$(LD) -m elf_i386 -Ttext=0x7c00 --oformat=binary $^ -o $@
mbr.elf: mbr.o
$(LD) -m elf_i386 -Ttext=0x7c00 $^ -o $@
clean:
rm -f *.elf *.img *.bin *.o */*.o tools/mkfs
tools/%: tools/%.c
gcc -Wall -Werror -g $^ -o $@

13
README.md Normal file
View File

@ -0,0 +1,13 @@
Yet Another BootLoader, OS Kernel and Other stuff
Quickstart:
```
$ ./setup.sh
$ make
```
Includes code from:
* https://github.com/mit-pdos/xv6-public
* https://github.com/FRosner/FrOS
* https://github.com/dhavalhirdhav/LearnOS
* https://wiki.osdev.org

19
console.c Normal file
View File

@ -0,0 +1,19 @@
#include "console.h"
#include "drivers/vga.h"
#include "drivers/uart.h"
void printk(const char* msg) {
vga_print_string_noscroll(msg);
for (; *msg; ++msg) {
uartputc(*msg);
}
}
void panic(const char* msg) {
printk("\nKernel panic: ");
printk(msg);
asm("cli");
while (1) {
asm("hlt");
}
}

4
console.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
void printk(const char* msg);
void panic(const char* msg);

77
cpu/gdt.h Normal file
View File

@ -0,0 +1,77 @@
#pragma once
#define STA_X 0x8 // Executable segment
#define STA_W 0x2 // Writeable (non-executable segments)
#define STA_R 0x2 // Readable (executable segments)
// System segment type bits
#define STS_T32A 0x9 // Available 32-bit TSS
#define STS_IG32 0xE // 32-bit Interrupt Gate
#define STS_TG32 0xF // 32-bit Trap Gate
#define DPL_USER 3
#define FL_IF 0x00000200
#define SEG_KCODE 1
#define SEG_KDATA 2
#define SEG_UCODE 3
#define SEG_UDATA 4
#define SEG_TSS 5
#define SEG_ASM(type,base,lim) \
.word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \
.byte (((base) >> 16) & 0xff), (0x90 | (type)), \
(0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)
#define USER_BASE 0x400000 // 4 MB
#define USER_STACK_BASE 0xf00000 // 15 MB
#define KERN_STACK_BASE 0x90000
#ifndef __ASSEMBLER__
typedef unsigned uint;
typedef unsigned short ushort;
struct taskstate {
uint link; // Old ts selector
uint esp0; // Stack pointers and segment selectors
ushort ss0; // after an increase in privilege level
ushort padding1;
uint *esp1;
ushort ss1;
ushort padding2;
uint *esp2;
ushort ss2;
ushort padding3;
void *cr3; // Page directory base
uint *eip; // Saved state from last task switch
uint eflags;
uint eax; // More saved state (registers)
uint ecx;
uint edx;
uint ebx;
uint *esp;
uint *ebp;
uint esi;
uint edi;
ushort es; // Even more saved state (segment selectors)
ushort padding4;
ushort cs;
ushort padding5;
ushort ss;
ushort padding6;
ushort ds;
ushort padding7;
ushort fs;
ushort padding8;
ushort gs;
ushort padding9;
ushort ldt;
ushort padding10;
ushort t; // Trap on task switch
ushort iomb; // I/O map base address
};
void load_gdt();
void switchuvm(struct taskstate *tss, void* esp);
#endif

25
drivers/port.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
static inline unsigned char port_byte_in(unsigned short port) {
unsigned char result;
__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
return result;
}
static inline unsigned short port_word_in(unsigned short port) {
unsigned short result;
__asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
return result;
}
static inline void port_byte_out(unsigned short port, unsigned char data) {
__asm__("outb %%al, %%dx" : : "a" (data), "d" (port));
}
static inline void port_word_out(unsigned short port, unsigned short data) {
__asm__("outw %%ax, %%dx" : : "a" (data), "d" (port));
}
static inline void port_long_out(unsigned short port, unsigned int data) {
__asm__("outl %%eax, %%dx" : : "a" (data), "d" (port));
}

44
drivers/uart.c Normal file
View File

@ -0,0 +1,44 @@
#include "uart.h"
#include "port.h"
static int uart;
enum {
COM1 = 0x3f8,
};
void uartinit() {
// Turn off the FIFO
port_byte_out(COM1+2, 0);
// 9600 baud, 8 data bits, 1 stop bit, parity off.
port_byte_out(COM1+3, 0x80); // Unlock divisor
port_byte_out(COM1+0, 115200/9600);
port_byte_out(COM1+1, 0);
port_byte_out(COM1+3, 0x03); // Lock divisor, 8 data bits.
port_byte_out(COM1+4, 0);
port_byte_out(COM1+1, 0x01); // Enable receive interrupts.
// If status is 0xFF, no serial port.
if(port_byte_in(COM1+5) == 0xFF)
return;
uart = 1;
// Acknowledge pre-existing interrupt conditions;
// enable interrupts.
port_byte_in(COM1+2);
port_byte_in(COM1+0);
}
void
uartputc(char c)
{
int i;
if (!uart)
return;
for (i = 0; i < 128 && !(port_byte_in(COM1+5) & 0x20); i++) {
asm("pause");
}
port_byte_out(COM1+0, c);
}

4
drivers/uart.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
void uartputc(char c);
void uartinit();

48
drivers/vga.c Normal file
View File

@ -0,0 +1,48 @@
#include "port.h"
#include "vga.h"
static unsigned char get_color(unsigned char fg, unsigned char bg) {
return (bg << 4) + fg;
}
static unsigned get_offset(unsigned col, unsigned row) {
return row * COLS + col;
}
static unsigned get_row_from_offset(unsigned offset) {
return offset / COLS;
}
unsigned vga_get_cursor() {
port_byte_out(VGA_CTRL_REGISTER, VGA_OFFSET_HIGH);
unsigned offset = port_byte_in(VGA_DATA_REGISTER) << 8;
port_byte_out(VGA_CTRL_REGISTER, VGA_OFFSET_LOW);
offset += port_byte_in(VGA_DATA_REGISTER);
return offset;
}
void vga_set_char(unsigned offset, char c) {
video_memory[2 * offset] = c;
video_memory[2 * offset + 1] = get_color(light_gray, black);
}
static unsigned offset;
void vga_clear_screen() {
for (unsigned i = 0; i < ROWS * COLS; ++i) {
vga_set_char(i, ' ');
}
}
void vga_print_string_noscroll(const char* s) {
while (*s != 0) {
if (*s == '\n') {
offset = get_offset(0, get_row_from_offset(offset) + 1);
} else {
vga_set_char(offset, *s);
offset++;
}
offset %= COLS * ROWS;
s++;
}
}

38
drivers/vga.h Normal file
View File

@ -0,0 +1,38 @@
#pragma once
static char* const video_memory = (char*) 0xb8000;
enum {
ROWS = 25,
COLS = 80,
VGA_CTRL_REGISTER = 0x3d4,
VGA_DATA_REGISTER = 0x3d5,
VGA_OFFSET_LOW = 0x0f,
VGA_OFFSET_HIGH = 0x0e,
};
enum colors16 {
black = 0,
blue,
green,
cyan,
red,
magenta,
brown,
light_gray,
dark_gray,
light_blue,
light_green,
light_cyan,
light_red,
light_magenta,
yellow,
white,
};
void vga_clear_screen();
void vga_set_char(unsigned offset, char c);
void vga_print_string(const char* s);
void vga_print_string_noscroll(const char* s);

51
fs/fs.h Normal file
View File

@ -0,0 +1,51 @@
#pragma once
#include <stdint.h>
/* Directory structure:
32-byte entries
Reserved
char[32]
OffsetSize ReservedName
uint32uint32uint32 char[20]
...
Offset is in sectors (zero-based),
size is in bytes, name is 0-terminated.
*/
enum {
sector_size = 512,
ents_in_dir = 15,
};
struct dirent {
uint32_t offset_sectors;
uint32_t size_bytes;
uint32_t reserved;
char name[20];
};
struct dir {
char reserved[32];
struct dirent entries[ents_in_dir];
};
struct stat {
uint32_t size;
uint32_t reserved[3];
};
/* Find file by name and fill information in buf.
* Returns zero on success, nonzero on failure. */
int stat(const char* name, struct stat *buf);
/* Find file by name and read it into buffer with size bufsize.
* At most (bufsize & ~511) bytes will be read.
* Return number of bytes read or -1 on failure. */
int read_file(const char* name, void* buf, uint32_t bufsize);

12
kernel.c Normal file
View File

@ -0,0 +1,12 @@
#include "console.h"
#include "drivers/vga.h"
#include "drivers/uart.h"
void _start() {
uartinit();
vga_clear_screen();
printk("\nYABLOKO\n");
asm("hlt");
}

178
mbr.S Normal file
View File

@ -0,0 +1,178 @@
#include "cpu/gdt.h"
.code16
.global _start
_start:
mov %dl, boot_drive
mov $banner, %si
call print_string
call get_drive_geometry
call load_kernel
call switch_to_32bit
hlt
jmp . // loop forever
get_drive_geometry:
mov $8, %ah
mov boot_drive, %dl
int $0x13
inc %dh // number of heads
mov %dh, disk_heads
and 0x3f, %cl
mov %cl, sectors_per_track
ret
.equ ELF32_ENTRY_OFFSET, 0x18
.equ ELF32_PHDR_OFFSET, 0x1c
.equ ELF32_PHENTSIZE_OFFSET, ELF32_PHDR_OFFSET + 14
.equ ELF32_PHNUM_OFFSET, ELF32_PHENTSIZE_OFFSET + 2
.equ ELF32_PHDR_P_OFFSET, 4
.equ ELF32_PHDR_PTYPE_OFFSET, 0
.equ ELF32_PHDR_FILESZ_OFFSET, 4*4
.equ KERNEL_OFFSET, 0x1000
.equ PT_LOAD, 1
.equ MBR_SECTORS, 2
.equ SECTOR_BASE, 1
.equ ELFHDR_SECTORS, 8
.equ SECTOR_SIZE, 512
.equ SECTOR_SHIFT, 9
load_kernel:
mov $1, %al // sectors to read
mov $SECTOR_BASE + MBR_SECTORS, %cl // start after MBR
call bios_disk_read
mov KERNEL_OFFSET + ELF32_ENTRY_OFFSET, %si
mov %si, entry // store entry point
mov KERNEL_OFFSET + ELF32_PHNUM_OFFSET, %si
read_segment:
dec %si // no offset to the first entry
mov %si, %ax
mulb KERNEL_OFFSET + ELF32_PHENTSIZE_OFFSET
mov %ax, %di
add KERNEL_OFFSET + ELF32_PHDR_OFFSET, %di
// now di holds offset to the phentry
mov KERNEL_OFFSET + ELF32_PHDR_PTYPE_OFFSET(%di), %ax
cmp $PT_LOAD, %ax
jnz read_segment // not a PT_LOAD segment
mov KERNEL_OFFSET + ELF32_PHDR_FILESZ_OFFSET(%di), %ax
test %ax, %ax
jz read_segment // empty segment
// now di holds offset to the last phentry loaded from file, ax its filesz
add KERNEL_OFFSET + ELF32_PHDR_P_OFFSET(%di), %ax
sub $0x1000, %ax // we won't load the header
add $SECTOR_SIZE - 1, %ax
shr $SECTOR_SHIFT, %ax // round up to sector count
mov $SECTOR_BASE + MBR_SECTORS + ELFHDR_SECTORS, %cl //start after ELF header
call bios_disk_read
ret
bios_disk_read:
// expects %al to specify number of sectors, %cl the initial sector
xor %ah, %ah
mov %ax, %si
mov $0, %ch // cylinder 0
mov $0, %dh // head 0
mov $KERNEL_OFFSET, %bx // bx -> destination
mov boot_drive, %dl // dl -> disk
mov $1, %al
1:
mov $2, %ah // read mode
int $0x13
jc fail
add $SECTOR_SIZE, %bx
inc %cl
dec %si
jnz 1b
ret
fail:
mov $read_error, %si
call print_string
hlt
jmp .
switch_to_32bit:
mov $2, %al
out %al, $0x92 // enable A20
cli // 1. disable interrupts
lgdt gdt_descriptor // 2. load GDT descriptor
mov %cr0, %eax
or $1, %eax // 3. enable protected mode
mov %eax, %cr0
ljmp $SEG_KCODE << 3, $init_32bit // 4. far jump
.code32
init_32bit:
mov $SEG_KDATA << 3, %ax // 5. update segment registers
mov %ax, %ds
mov %ax, %ss
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov $KERN_STACK_BASE, %ebp // 6. setup stack
mov %ebp, %esp
movzwl entry, %esi
call *%esi // 7. jump to the kernel
jmp . // 8. loop forever
.code16
print_string:
mov $0x0e, %ah // "teletype output"
repeat:
lodsb // equivalent to mov (%si), %al; inc %si
test %al, %al
je done
int $0x10 // bios interrupt
jmp repeat
done:
ret
. = _start + 256 # pad to 256 bytes
boot_drive:
.byte 0
banner:
.asciz "YABLOKO bootloader started\n\r"
read_error:
.asciz "Read error\n\r"
.balign 2
entry:
.word 0
disk_heads:
.byte 0
sectors_per_track:
.byte 0
.balign 4
gdt_start:
.quad 0x0 // null descriptor
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg
SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg
gdt_end:
// GDT descriptor
gdt_descriptor:
.word gdt_end - gdt_start - 1 // size (16 bit)
.int gdt_start // address (32 bit)
. = _start + 510 # pad to 510 bytes
.byte 0x55, 0xaa # boot sector magic value

10
setup.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/sh -x
if [ `uname` = Darwin ]; then
brew install \
x86_64-elf-binutils x86_64-elf-gcc \
x86_64-elf-gdb qemu
elif [ `uname` = Linux ]; then
sudo apt-get update
sudo apt-get install qemu-system-x86
fi

79
tools/mkfs.c Normal file
View File

@ -0,0 +1,79 @@
#include "../fs/fs.h"
#include <stdio.h>
#include <string.h>
char* basename(char* path) {
char* c = strrchr(path, '/');
if (c && *c) {
return c + 1;
}
return path;
}
int main(int argc, char* argv[]) {
char sector[sector_size];
struct dir dir = {{0}};
if (argc < 3) {
fprintf(stderr, "Usage: %s OUT.FS KERNEL.BIN [FILES...]\n", argv[0]);
return 1;
}
FILE* image = fopen(argv[1], "wb");
if (!image) {
perror(argv[1]);
return 1;
}
if (fwrite(&dir, sizeof(dir), 1, image) < 1) {
perror("fwrite");
return 1;
}
uint32_t sector_offset = 1;
for (int i = 2; i < argc; ++i) {
char* name = argv[i];
struct dirent *dirent = &dir.entries[i-2];
dirent->offset_sectors = sector_offset;
dirent->size_bytes = 0;
FILE* file = fopen(name, "rb");
if (!file) {
perror(name);
return 1;
}
size_t read_size;
while ((read_size = fread(sector, 1, sizeof(sector), file))) {
if (fwrite(sector, 1, sizeof(sector), image) != sizeof(sector)) {
perror(name);
return 1;
}
sector_offset++;
dirent->size_bytes += read_size;
}
if (fclose(file)) {
perror(name);
return 1;
}
dirent->reserved = 0;
dirent->name[sizeof(dirent->name) - 1] = '\0';
strncpy(dirent->name, basename(name), sizeof(dirent->name) - 1);
}
fseek(image, 0, SEEK_SET);
if (fwrite(&dir, sizeof(dir), 1, image) < 1) {
perror("fwrite");
return 1;
}
if (fclose(image)) {
perror(argv[0]);
return 1;
}
return 0;
}