enter protected mode and jump to rust binary

main
pantonshire 1 year ago
parent e20030fbc3
commit a86d73e6ee

@ -2,11 +2,330 @@
[bits 16]
extern boot1_bin_len
extern boot1_bin_sectors
extern boot1_magic
extern _start
section .prelude
extern boot1_bin_len
%macro copy_stack_var_to_globals 2
mov %1, [bp - %2]
mov [GLOBALS + %2], %1
%endmacro
; boot0 loads only our first sector into memory. We must load the rest.
self_load:
; Now that we're not doing instruction byte golf like we were in boot0, we can afford to move
; the various boot0 stack variables to the globals section.
copy_stack_var_to_globals ax, BOOT_DRIVE
copy_stack_var_to_globals ax, SECTORS_PER_TRACK
copy_stack_var_to_globals ax, N_HEADS
copy_stack_var_to_globals ax, GPT_ENTRIES_START_LBA
copy_stack_var_to_globals ax, GPT_N_ENTRIES_16
copy_stack_var_to_globals ax, GPT_SECTOR_STRIDE
copy_stack_var_to_globals ax, GPT_BYTE_STRIDE
copy_stack_var_to_globals ax, GPT_ENTRIES_PER_SECTOR
copy_stack_var_to_globals ax, GPT_CURRENT_ENTRY_IDX
copy_stack_var_to_globals ax, GPT_SECTOR_ENTRY_IDX
copy_stack_var_to_globals ax, GPT_SECTORS_LOADED
copy_stack_var_to_globals ax, GPT_CURRENT_LBA
copy_stack_var_to_globals ax, BOOT1_GPT_ENTRY_ADDR
; Reset the stack, now we've got everything we need from it.
mov sp, bp
mov si, [GLOBALS + BOOT1_GPT_ENTRY_ADDR]
mov eax, [si + 0x20] ; Partition / boot1 start LBA lower
mov ebx, [si + 0x24] ; Partition / boot1 start LBA upper
mov ecx, [si + 0x28] ; Partition end LBA lower
mov edx, [si + 0x32] ; Partition LBA upper
; Panic if the partition / boot1 starting LBA overflows 16 bits.
or ebx, ebx
jnz panic_simple
ror eax, 16
or ax, ax
jnz panic_simple
ror eax, 16
; Calculate the boot1 end LBA and panic if it overflows 16 bits.
; n.b. ebx is zero before this so both bx and ebx can be used as the boot1 end LBA.
mov bx, ax
add bx, boot1_bin_sectors
jc panic_simple
; Panic if the boot1 end LBA is after the partition end LBA.
; If the upper 32 bits of the partition end LBA are nonzero, then it must be greater than our
; 16-bit boot1 end LBA.
or edx, edx
jnz .end_lba_ok
; Compare the boot1 end LBA to the lower 32 bits of the partition end LBA.
cmp ebx, ecx
ja panic_simple
.end_lba_ok:
; The first sector has already been loaded (we're running it right now!) so increment the
; current LBA.
inc ax
push ax ; Current LBA
push bx ; boot1 end LBA
mov ebx, BOOT1_LOADPOINT + 512 ; Current sector load address
.self_load_loop:
mov ax, [bp - 0x02] ; Load current LBA
cmp word [bp - 0x04], ax ; Compare to boot1 end LBA
jb .self_load_done
mov ecx, ebx
call read_sector
jc panic_simple
add ebx, 512
inc word [bp - 0x02]
jmp .self_load_loop
.self_load_done:
; Check the magic bytes at the end of boot1.
push es
mov ebx, boot1_magic
call addr32_to_addr16
cmp dword es:[bx], BOOT1_MAGIC
pop es
jne panic_simple
jmp prelude_main
; Converts a 32-bit address to a 16-bit sector and offset.
; Arguments:
; - ebx: 32-bit address
; Return:
; - es: 16-bit address segment (unchanged on failure)
; - ebx: 16-bit address offset
; - cf: unset on success, set on failure
; Clobber: none
addr32_to_addr16:
fnstart
push es
push eax
mov eax, ebx
; Divide addr by 16 and saturate to 16 bits to get the segment.
shr eax, 4
ror eax, 16
or ax, ax
jz .segment_ok
mov eax, 0xffff0000
.segment_ok:
ror eax, 16
mov es, ax
; Calculate offset = addr - (16 * segment), failing if the offset doesn't fit in 16 bits.
shl eax, 4
sub ebx, eax
ror ebx, 16
or bx, bx
jnz .fail
ror ebx, 16
pop eax
add sp, 2 ; Discard the original es from the stack
pop bp
clc
ret
.fail:
pop eax
pop es
stc
fnret
; Reads a single sector at the given LBA into memory.
; Arguments:
; - ax: start LBA
; - ecx: address to read sector to
; Return:
; - cf: unset on success, set on failure
; Clobber: eax, ecx, edx
read_sector:
; sector - 1 = LBA % sectors_per_track
; temp = LBA / sectors_per_track
; head = temp % n_heads
; cylinder = temp / n_heads
fnstart
push es
push ebx
mov ebx, ecx
call addr32_to_addr16
jc .return
; Calculate sector and temp
xor dx, dx
; Divide by sectors per track. dx = mod (sector - 1), ax = div (temp)
div word [GLOBALS + SECTORS_PER_TRACK]
; Put the sector into cx (the bios call will use cl)
mov cx, dx
inc cx
; Calculate head and cylinder
xor dx, dx
; Divide by number of heads. dx = mod (head), ax = div (cylinder)
div word [GLOBALS + N_HEADS]
mov dh, dl
mov ch, al
mov dl, byte [GLOBALS + BOOT_DRIVE]
mov ah, 0x02
mov al, 1
; Read sector
int 0x13
global prelude
prelude:
.return:
pop ebx
pop es
fnret
panic_simple:
mov ax, 0x0003
int 0x10
mov word fs:[0x0000], 0x4f21
.halt:
hlt
dd boot1_bin_len
jmp .halt
%if ($ - $$) > 512
%error "boot1 self-loader exceeded sector size"
%endif
; TODO:
; - Make sure A20 is enabled before the switch to protected mode
prelude_main:
mov ax, 0x0003
int 0x10
; Ensure interrupts are definitely disabled.
cli
; Load our flat-address-space GDT.
lgdt [flat_gdt_slice]
; Set the protected-mode bit in cr0.
mov eax, cr0
or al, 0x01
mov cr0, eax
; Long jump to set the code segment to flat_gdt.segment_code, and to clear the instruction
; pipeline.
jmp (flat_gdt.segment_code_32 - flat_gdt):.protected_mode
[bits 32]
.protected_mode:
; Set the data segments to flat_gdt.segment_data.
mov eax, (flat_gdt.segment_data - flat_gdt)
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
; Reset the stack.
mov ebp, STACK_BASE
mov esp, ebp
jmp _start
jmp panic_simple
section .data
flat_gdt_slice:
dw FLAT_GDT_LEN
dd flat_gdt
; Segment descriptor layout
; | Range (bits) | Field |
; |--------------|---------------|
; | 0-16 | limit |
; | 16-32 | base |
; | 32-40 | base cont. |
; | 40-48 | access |
; | 48-52 | limit cont. |
; | 52-56 | flags |
; | 56-64 | base cont. |
;
; Flags
; - 0: reserved
; - 1: long-mode code segment
; - 2: size
; - unset: 16-bit
; - set: 32-bit
; - 3: granularity
; - unset: limit is measured in bytes
; - set: limit is measured in 4KiB pages
;
; Access
; - 0: accessed
; - unset: CPU will set it when the segment is accessed
; - 1: readable / writable
; - data segments: is segment writable (data segments are always readable)
; - code segments: is segment readable (code segments are never writable)
; - 2: direction / conforming
; - data segments: whether segment grows down
; - code segments: whether this can be executed from a lower-privilege ring
; - 3: executable
; - unset: this is a data segment
; - set: this is a code segment
; - 4: descriptor type
; - unset: this is a task state segment
; - set: this is a data or code segment
; - 5-6: privilege level (ring number)
; - 7: present (must be set)
;
align 8
flat_gdt:
; First GDT entry must be 0.
.segment_null:
dq 0
; 32-bit code segment.
; Bytes 0x0000 - 0xffff.
.segment_code_32:
db 0xff, 0xff, \
0x00, 0x00, \
0x00, \
10011011b, \
01000000b, \
0x00
; 16-bit code segment, to use if we want to switch back to real mode.
; Bytes 0x0000 - 0xffff.
.segment_code_16:
db 0xff, 0xff, \
0x00, 0x00, \
0x00, \
10011011b, \
00000000b, \
0x00
; Data segment.
; Pages 0x000000 - 0x0fffff, which covers the entire 32-bit address space (start of 0xfffff-th page
; is 0xfffff * 4096 = 0xfffff000, end of page exclusive is 0xfffff000 + 4096 = 0x100000000).
.segment_data:
db 0xff, 0xff, \
0x00, 0x00, \
0x00, \
10010011b, \
11001111b, \
0x00
FLAT_GDT_LEN equ ($ - flat_gdt)

@ -4,6 +4,7 @@ OUTPUT_FORMAT("binary")
SECTIONS {
.prelude : {
KEEP(*(.prelude))
*(.prelude)
}
@ -27,8 +28,16 @@ SECTIONS {
*(.rodata.*)
}
.magic : {
/* Magic bytes the prelude uses to make sure it's loaded the subsequent sectors correctly. */
LONG(0x544e4150)
}
boot1_magic = ADDR(.magic);
/* Define a symbol for the total length of the binary, so the prelude knows how many blocks to
* load from disk.
*/
boot1_bin_len = . - 0x8200;
boot1_bin_sectors = (boot1_bin_len + 511) / 512;
}

@ -10,9 +10,9 @@ const VGA_ADDR: usize = 0xb8000;
const STR: &[u8] = b"the quick brown fox jumps over the lazy dog";
extern "C" {
fn prelude() -> !;
}
// extern "C" {
// fn prelude() -> !;
// }
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
@ -45,10 +45,7 @@ pub extern "C" fn _start() -> ! {
vga_buf[i] = 0x1f00 | u16::from(b);
}
vga_buf[0] = 0x1f02; //smiley
// hlt()
unsafe { prelude() };
hlt()
}
#[inline]

@ -1,13 +1,17 @@
run:
qemu-system-x86_64 \
-monitor stdio \
-d int \
-no-reboot \
-bios seabios/out/bios.bin \
-m 512M \
-drive format=raw,file=disk.bin
build:
nasm -f bin -Iinclude -o boot0.bin boot0.s
nasm -f bin -Iinclude -o boot1.bin boot1.s
cd boot1; cargo build --release
# nasm -f bin -Iinclude -o boot1.bin boot1.s
zero_disk:
dd if=/dev/zero of=disk.bin bs=512 count=1000
@ -33,4 +37,5 @@ write_stage1:
dd if=boot0.bin of=disk.bin conv=notrunc
write_stage2:
dd if=boot1.bin of=disk.bin bs=512 seek=70 conv=notrunc
# dd if=boot1.bin of=disk.bin bs=512 seek=70 conv=notrunc
dd if=boot1/target/target_protected/release/boot1 of=disk.bin bs=512 seek=70 conv=notrunc

Loading…
Cancel
Save