Skip to content

Commit 15ce095

Browse files
committed
Create a 16-bit 'Hello World'
1 parent 90f5b89 commit 15ce095

File tree

10 files changed

+168
-0
lines changed

10 files changed

+168
-0
lines changed

real_mode/.cargo/config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
target = "x86_64-bootloader-real-mode.json"

real_mode/.gitignore

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

real_mode/Cargo.lock

Lines changed: 5 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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "real_mode"
3+
version = "0.1.0"
4+
authors = ["Philipp Oppermann <[email protected]>"]
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+
[profile.release]
12+
opt-level = "z"

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_64-bootloader-real-mode/release/real_mode 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/linker.ld

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
ENTRY(_start)
2+
3+
SECTIONS {
4+
. = 0x500;
5+
_stack_start = .;
6+
. = 0x7c00;
7+
_stack_end = .;
8+
9+
.bootloader :
10+
{
11+
*(.boot-first-stage)
12+
*(.text .text.*)
13+
*(.rodata .rodata.*)
14+
*(.data .data.*)
15+
*(.got)
16+
. = 0x7c00 + 510;
17+
SHORT(0xaa55) /* magic number for bootable disk */
18+
}
19+
_bootloader_end = .;
20+
}

real_mode/src/boot.s

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.section .boot-first-stage, "awx"
2+
.global _start
3+
.intel_syntax noprefix
4+
.code16
5+
6+
# This stage initializes the stack, enables the A20 line, loads the rest of
7+
# the bootloader from disk, and jumps to stage_2.
8+
9+
_start:
10+
# zero segment registers
11+
xor ax, ax
12+
mov ds, ax
13+
mov es, ax
14+
mov ss, ax
15+
mov fs, ax
16+
mov gs, ax
17+
18+
# clear the direction flag (e.g. go forward in memory when using
19+
# instructions like lodsb)
20+
cld
21+
22+
# initialize stack
23+
mov sp, 0x7c00
24+
25+
call rust_main
26+
27+
spin:
28+
hlt
29+
jmp spin

real_mode/src/main.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#![feature(asm, global_asm)]
2+
#![no_std]
3+
#![no_main]
4+
5+
use core::panic::PanicInfo;
6+
7+
global_asm!(include_str!("boot.s"));
8+
9+
#[no_mangle]
10+
pub extern "C" fn rust_main() {
11+
println(b"Hello from Rust!");
12+
panic!()
13+
}
14+
15+
fn println(s: &[u8]) {
16+
print(s);
17+
print_char(b'\n');
18+
}
19+
20+
fn print(s: &[u8]) {
21+
for &c in s {
22+
print_char(c);
23+
}
24+
}
25+
26+
fn print_char(c: u8) {
27+
let ax = u16::from(c) | 0x0e00;
28+
unsafe {
29+
asm!("int 0x10" :: "{ax}"(ax) :: "intel" );
30+
}
31+
}
32+
33+
#[panic_handler]
34+
pub fn panic(_info: &PanicInfo) -> ! {
35+
println(b"PANIC!");
36+
loop {}
37+
}
38+

real_mode/test.bin

524 Bytes
Binary file not shown.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"arch": "x86",
3+
"cpu": "i386",
4+
"data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128",
5+
"dynamic-linking": false,
6+
"executables": true,
7+
"linker-flavor": "ld.lld",
8+
"linker": "rust-lld",
9+
"llvm-target": "i386-unknown-none-code16",
10+
"pre-link-args": {
11+
"ld.lld": [
12+
"-Tlinker.ld"
13+
]
14+
},
15+
"max-atomic-width": 64,
16+
"position-independent-executables": false,
17+
"disable-redzone": true,
18+
"relro-level": "full",
19+
"target-c-int-width": "32",
20+
"target-pointer-width": "32",
21+
"target-endian": "little",
22+
"panic-strategy": "abort",
23+
"os": "none",
24+
"vendor": "unknown"
25+
}

0 commit comments

Comments
 (0)