Skip to content

Commit 9b887fc

Browse files
committed
Merge branch '16-bit-wip' into next
2 parents 1563a44 + f6c3d68 commit 9b887fc

27 files changed

+1037
-0
lines changed

real_mode/.cargo/config

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[build]
2+
target = "x86-32bit.json"
3+
4+
[alias]
5+
xbuild = "build -Zbuild-std=core"

real_mode/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/target/
2+
**/*.rs.bk
3+
/test.bin
4+

real_mode/Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

real_mode/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "bootloader"
3+
version = "0.1.0"
4+
authors = ["Philipp Oppermann <dev@phil-opp.com>"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
11+
[build-dependencies]
12+
llvm-tools = "0.1.1"
13+
14+
[profile.release]
15+
opt-level = "s"
16+
lto = true
17+
debug = true
18+
codegen-units = 1

real_mode/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# 16-bit Rust (Experiment)
2+
3+
This is an experiment to translate the 16-bit code of the bootloader from assembly to Rust.
4+
5+
## Building
6+
7+
To build the project, use cargo-xbuild:
8+
9+
```
10+
cargo xbuild --release
11+
```
12+
13+
The BIOS only loads the first 512 bytes of our executable into memory, so the amount of code that this binary can contain is very limited. This is also the reason why this can only be built in release mode.
14+
15+
If the code does not fit into 512 bytes, the linker will throw the following error:
16+
17+
> rust-lld: error: linker.ld:16: unable to move location counter backward for: .bootloader
18+
19+
## Creating a Disk Image
20+
21+
The output of `cargo xbuild` is an ELF binary, which can't be loaded directly by the BIOS. To boot our project, we must therefore convert it into a flat binary first. This works with the following `objcopy` command:
22+
23+
```
24+
objcopy -I elf32-i386 -O binary target/x86-32bit/release/bootloader image.bin
25+
```
26+
27+
This creates a file named `image.bin` in the root folder of the project, which is a bootable disk image.
28+
29+
## Running it in QEMU
30+
31+
To run the disk image in QEMU, execute the following command:
32+
33+
```
34+
qemu-system-x86_64 -drive format=raw,file=image.bin
35+
```

real_mode/build.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use std::process::Command;
2+
use std::env;
3+
use std::path::Path;
4+
use llvm_tools::{LlvmTools, exe};
5+
6+
fn main() {
7+
let out_dir = env::var("OUT_DIR").unwrap();
8+
let llvm_tools = LlvmTools::new().expect("LLVM tools not found");
9+
let objcopy = llvm_tools.tool(&exe("llvm-objcopy")).expect("llvm-objcopy not found");
10+
11+
build_subproject(Path::new("first_stage"), &["_start", "print_char"], "x86-16bit.json", &out_dir, &objcopy);
12+
build_subproject(Path::new("real_mode"), &["second_stage"], "x86-16bit.json", &out_dir, &objcopy);
13+
}
14+
15+
fn build_subproject(dir: &Path, global_symbols: &[&str], target: &str, out_dir: &str, objcopy: &Path) {
16+
let dir_name = dir.file_name().unwrap().to_str().unwrap();
17+
let manifest_path = dir.join("Cargo.toml");
18+
let out_path = Path::new(&out_dir);
19+
assert!(global_symbols.len() > 0, "must have at least one global symbol");
20+
21+
// build
22+
let mut cmd = Command::new("cargo");
23+
cmd.arg("xbuild").arg("--release");
24+
cmd.arg("--verbose");
25+
cmd.arg(format!("--manifest-path={}", manifest_path.display()));
26+
cmd.arg(format!("--target={}", dir.join(target).display()));
27+
cmd.arg("-Z").arg("unstable-options");
28+
cmd.arg("--out-dir").arg(&out_dir);
29+
cmd.arg("--target-dir").arg(out_path.join("target").join(dir_name));
30+
cmd.env_remove("RUSTFLAGS");
31+
cmd.env("XBUILD_SYSROOT_PATH", out_path.join("target").join(dir_name).join("sysroot"));
32+
let status = cmd.status().unwrap();
33+
assert!(status.success());
34+
35+
// localize symbols
36+
let mut cmd = Command::new(objcopy);
37+
for symbol in global_symbols {
38+
cmd.arg("-G").arg(symbol);
39+
}
40+
cmd.arg(out_path.join(format!("lib{}.a", dir_name)));
41+
let status = cmd.status().unwrap();
42+
assert!(status.success());
43+
44+
/*
45+
// FIXME: it seems like this messes up relocations
46+
// convert to ELF64
47+
let mut cmd = Command::new(objcopy);
48+
cmd.arg("-I").arg("elf32-i386").arg("-O").arg("elf64-x86-64");
49+
cmd.arg(out_path.join(format!("lib{}.a", dir_name)));
50+
let status = cmd.status().unwrap();
51+
assert!(status.success());
52+
*/
53+
54+
// emit linker flags
55+
println!("cargo:rustc-link-search=native={}", out_dir);
56+
println!("cargo:rustc-link-lib=static={}", dir_name);
57+
}

real_mode/first_stage/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
ENTRY(_start)
2+
3+
SECTIONS {
4+
. = 0x500;
5+
_stack_start = .;
6+
. = 0x7c00;
7+
_stack_end = .;
8+
9+
_mbr_start = .;
10+
.boot :
11+
{
12+
*(.boot .boot.*)
13+
}
14+
.text :
15+
{
16+
*(.text .text.*)
17+
}
18+
.rodata :
19+
{
20+
*(.rodata .rodata.*)
21+
}
22+
.data :
23+
{
24+
*(.rodata .rodata.*)
25+
*(.data .data.*)
26+
*(.got .got.*)
27+
}
28+
_mbr_end = .;
29+
30+
. = 0x7c00 + 446;
31+
.partition_table :
32+
{
33+
SHORT(0x0000) /* partition table entry 0 */
34+
SHORT(0x0000) /* partition table entry 1 */
35+
SHORT(0x0000) /* partition table entry 2 */
36+
SHORT(0x0000) /* partition table entry 3 */
37+
}
38+
39+
. = 0x7c00 + 510;
40+
41+
.magic_number :
42+
{
43+
SHORT(0xaa55) /* magic number for bootable disk */
44+
}
45+
}

real_mode/first_stage/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

real_mode/first_stage/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "first_stage"
3+
version = "0.1.0"
4+
authors = ["Philipp Oppermann <dev@phil-opp.com>"]
5+
edition = "2021"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
11+
[profile.release]
12+
opt-level = "s"
13+
lto = true
14+
codegen-units = 1
15+
16+
# debug = true

real_mode/first_stage/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# First Stage: Bootsector
2+
3+
This executable needs to fit into the 512-byte boot sector, so we need to use all kinds of tricks to keep the size down.
4+
5+
## Build Commands
6+
7+
1. `cargo build --release -Zbuild-std=core --target x86-16bit.json -Zbuild-std-features=compiler-builtins-mem`
8+
2. `objcopy -I elf32-i386 -O binary target/x86-16bit/release/first_stage target/disk_image.bin
9+
10+
To run in QEMU:
11+
12+
- `qemu-system-x86_64 -drive format=raw,file=target/disk_image.bin`
13+
14+
To print the contents of the ELF file, e.g. for trying to bring the size down:
15+
16+
- `objdump -xsdS -M i8086,intel target/x86-16bit/release/first_stage`

real_mode/first_stage/src/boot.s

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
.section .boot, "awx"
2+
.global _start
3+
.code16
4+
5+
# This stage initializes the stack, enables the A20 line
6+
7+
_start:
8+
# zero segment registers
9+
xor ax, ax
10+
mov ds, ax
11+
mov es, ax
12+
mov ss, ax
13+
mov fs, ax
14+
mov gs, ax
15+
16+
# clear the direction flag (e.g. go forward in memory when using
17+
# instructions like lodsb)
18+
cld
19+
20+
# initialize stack
21+
mov sp, 0x7c00
22+
23+
enable_a20:
24+
# enable A20-Line via IO-Port 92, might not work on all motherboards
25+
in al, 0x92
26+
test al, 2
27+
jnz enable_a20_after
28+
or al, 2
29+
and al, 0xFE
30+
out 0x92, al
31+
enable_a20_after:
32+
33+
check_int13h_extensions:
34+
push 'y' # error code
35+
mov ah, 0x41
36+
mov bx, 0x55aa
37+
# dl contains drive number
38+
int 0x13
39+
jc fail
40+
pop ax # pop error code again
41+
42+
rust:
43+
# push arguments
44+
push dx # disk number
45+
call first_stage
46+
47+
spin:
48+
hlt
49+
jmp spin
50+

real_mode/first_stage/src/dap.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use core::arch::{asm, global_asm};
2+
3+
#[repr(packed)]
4+
pub struct DiskAddressPacket {
5+
/// Size of the DAP structure
6+
packet_size: u8,
7+
/// always zero
8+
zero: u8,
9+
/// Number of sectors to transfer
10+
number_of_sectors: u16,
11+
/// Offset to memory buffer
12+
offset: u16,
13+
/// Segment of memory buffer
14+
segment: u16,
15+
/// Start logical block address
16+
start_lba: u64,
17+
}
18+
19+
impl DiskAddressPacket {
20+
pub fn new(memory_buffer_start: u16, file_offset: u64, bytes: u32) -> Self {
21+
Self {
22+
packet_size: 0x10,
23+
zero: 0,
24+
number_of_sectors: (bytes / 512) as u16,
25+
offset: memory_buffer_start,
26+
segment: 0,
27+
start_lba: file_offset / 512,
28+
}
29+
}
30+
31+
pub fn from_lba(memory_buffer_start: u16, start_lba: u64, number_of_sectors: u16) -> Self {
32+
Self {
33+
packet_size: 0x10,
34+
zero: 0,
35+
number_of_sectors,
36+
offset: memory_buffer_start,
37+
segment: 0,
38+
start_lba,
39+
}
40+
}
41+
42+
pub unsafe fn perform_load(&self, disk_number: u16) {
43+
let self_addr = self as *const Self as u16;
44+
asm!(
45+
"push 0x7a", // error code `z`, passed to `fail` on error
46+
"mov {1:x}, si",
47+
"mov si, {0:x}",
48+
"int 0x13",
49+
"jc fail",
50+
"pop si", // remove error code again
51+
"mov si, {1:x}",
52+
in(reg) self_addr,
53+
out(reg) _,
54+
in("ax") 0x4200u16,
55+
in("dx") disk_number,
56+
);
57+
}
58+
}

real_mode/first_stage/src/fail.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use core::arch::asm;
2+
3+
pub trait UnwrapOrFail {
4+
type Out;
5+
6+
fn unwrap_or_fail(self, code: u8) -> Self::Out;
7+
}
8+
9+
impl<T> UnwrapOrFail for Option<T> {
10+
type Out = T;
11+
12+
fn unwrap_or_fail(self, code: u8) -> Self::Out {
13+
match self {
14+
Some(v) => v,
15+
None => fail(code),
16+
}
17+
}
18+
}
19+
20+
impl<T, E> UnwrapOrFail for Result<T, E> {
21+
type Out = T;
22+
23+
fn unwrap_or_fail(self, code: u8) -> Self::Out {
24+
match self {
25+
Ok(v) => v,
26+
Err(_) => fail(code),
27+
}
28+
}
29+
}
30+
31+
#[no_mangle]
32+
pub extern "C" fn print_char(c: u8) {
33+
let ax = u16::from(c) | 0x0e00;
34+
unsafe {
35+
asm!("int 0x10", in("ax") ax, in("bx") 0);
36+
}
37+
}
38+
39+
#[cold]
40+
#[inline(never)]
41+
#[no_mangle]
42+
pub extern "C" fn fail(code: u8) -> ! {
43+
print_char(b'!');
44+
print_char(code);
45+
loop {
46+
hlt()
47+
}
48+
}
49+
50+
fn hlt() {
51+
unsafe {
52+
asm!("hlt");
53+
}
54+
}
55+
56+
#[panic_handler]
57+
pub fn panic(_info: &core::panic::PanicInfo) -> ! {
58+
fail(b'P');
59+
}

0 commit comments

Comments
 (0)