Skip to content
This repository has been archived by the owner on Dec 9, 2018. It is now read-only.

Long mode #8

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
[package]
authors = ["Jorge Aparicio <[email protected]>"]
build = "build.rs"
name = "kernel"
version = "0.1.0"

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ objects, etc.). Check the [lld branch] for more details.

## Progress

This section is written in journal style and in chronological order.

### Chapter 3

I've just finished [Chapter 3], and my "kernel" prints "Hello, world!" to the screen and does
Expand All @@ -45,6 +47,23 @@ $ xargo build --target x86_64

[Xargo]: https://crates.io/crates/xargo

### Chapter 4

We are now in long mode :tada:. Notable changes:

- We were always emitting 64-bit instructions even though we are only supposed to use 32-bit
instructions because we start in restricted mode :scream_cat:. This has been fixed by changing the
target specification from a 64-bit target (`x86_64.json`) to a 32-bit one (`x86.json`).
- All the data layout related stuff that intermezzos does in assembly has been implemented in the
linker script to reduce the amount of assembly. Notably, the layout of the page tables and the GDT
are done in the linker script.
- Linking the page tables is done in pure Rust :+1;.
- Actually entering long mode requires manipulation of (control) registers and those can't be
accessed via pure Rust, AFAIK. For that reason, this part is written in inline assembly.
- Caveat: Because we are telling `rustc` to use a 32-bit target, `rustc` always emit 32-bit
instructions and we can't actually use 64-bit instructions/registers in the section of the program
where the CPU is already in long mode. Yikes, I'll have to think about how to solve this.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be solved by splitting this crate in two: one crates that does the booting process and is compiled for a 32-bit target and the other crate that exposes the long_mode_start symbol and is compiled for a 64-bit target. The issue is that I think the compiler won't link together crates compiled for different types (imagine mixing a crate compiled for ARM with a crate compiled for x86). If that's the case we could compiled the boot crate as a staticlib and link that staticlib into the long crate. That should bypass the "same target" check.

All this is speculation from my part. I don't know if it will actually work.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds about right to me.


## License

Licensed under either of
Expand Down
3 changes: 3 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
println!("cargo:rerun-if-changed=layout.ld");
}
64 changes: 64 additions & 0 deletions layout.ld
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@ SECTIONS {
/* Our program/kernel starts at address: `0x00100000` */
. = 1M;

/* Multiboot headers */
/* See http://intermezzos.github.io/book/multiboot-headers.html#The%20Section */
.boot :
{
header_start = .;

/* Magic number */
LONG(MAGIC_NUMBER)

/* Architecture 0: Protected mode i386 */
LONG(0)

/* Header length */
LONG(header_end - header_start)

/* Checksum */
LONG(0x100000000 - (MAGIC_NUMBER + (header_end - header_start)))

/* Required end tag */
/* Type */
SHORT(0)
Expand All @@ -29,12 +34,71 @@ SECTIONS {
header_end = .;
}

/* Global Descriptor Table */
/* See http://intermezzos.github.io/book/setting-up-a-gdt.html */
/* FIXME if we use .rodata here, the linker (?) will inject a .got section just after this one.
This causes the boot fail, probably, because the processor executes the .got section instead of
text.start */
/* .rodata : */
.gdt :
{
gdt64 = .;
/* Zero entry */
QUAD(0);

/* NOTE This won't do we want! Linker script variables bind to *addresses*, not to arbitrary
integers. So this will actually bind to the address of the code segment and not store the
offset of the code segment with respect to the start of the GDT */
/* gdt64.code = . - gdt64; */

/* Code segment */
QUAD(1 << 44 | 1 << 47 | 1 << 41 | 1 << 43 | 1 << 53);
/* Meaning of the bits set */
/* 44: descriptor type */
/* 47: present */
/* 41: read/write */
/* 43: executable */
/* 53: 64-bit */

/* Data segment */
gdt64.data = .;
QUAD(1 << 44 | 1 << 47 | 1 << 41);

gdt64.pointer = .;
SHORT(gdt64.pointer - gdt64 - 1)
QUAD(gdt64)

/* We don't have an equivalent to the EQU instruction, which "gives a symbolic name to a numeric
constant". Instead of that we'll store the GDT offsets in (read-only) memory and use that
from our boot code */
/* TODO Should we do this arithmetic in the boot routine instead? It would save us 32 bits of
memory */
gdt64.data.offset = .;
SHORT(gdt64.data - gdt64);
}

.text :
{
/* NOTE we use KEEP here to prevent the linker from dropping these symbols */
KEEP(*(.text.start))
KEEP(*(.text.long_mode_start))
*(.text.*)
}

/* Page tables */
.bss ALIGN(4096) :
{
/* Page-Map Level-4 Table (PML4) */
p4_table = .;
. += 4096;
/* Page-Directory Pointer Table (PDP) */
p3_table = .;
. += 4096;
/* Page-Directory Table (PD) */
p2_table = .;
. += 4096;
}

/DISCARD/ :
{
*(.note.gnu.*)
Expand Down
2 changes: 1 addition & 1 deletion mk-iso.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ main() {

mkdir -p $td/boot/grub
cp grub.cfg $td/boot/grub/
cp target/x86_64/release/kernel $td/boot/
cp target/x86/release/kernel $td/boot/
grub-mkrescue -o x86.iso $td

rm -rf $td
Expand Down
87 changes: 69 additions & 18 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,77 @@
use core::ptr;

#[no_mangle]
pub fn start() {
unsafe {
ptr::write_volatile(0xb8000 as *mut u16, 0x0248); // H
ptr::write_volatile(0xb8002 as *mut u16, 0x0265); // e
ptr::write_volatile(0xb8004 as *mut u16, 0x026c); // l
ptr::write_volatile(0xb8006 as *mut u16, 0x026c); // l
ptr::write_volatile(0xb8008 as *mut u16, 0x026f); // o
ptr::write_volatile(0xb800a as *mut u16, 0x022c); // ,
ptr::write_volatile(0xb800c as *mut u16, 0x0220); //
ptr::write_volatile(0xb800e as *mut u16, 0x0277); // w
ptr::write_volatile(0xb8010 as *mut u16, 0x026f); // o
ptr::write_volatile(0xb8012 as *mut u16, 0x0272); // r
ptr::write_volatile(0xb8014 as *mut u16, 0x026c); // l
ptr::write_volatile(0xb8016 as *mut u16, 0x0264); // d
ptr::write_volatile(0xb8018 as *mut u16, 0x0221); // !
pub unsafe fn start() {
const NENTRIES: usize = 512;
const HUGE_PAGE_SIZE: u64 = 2 * 1024 * 1024; // 2 MiB

extern "C" {
static mut p2_table: [u64; NENTRIES];
static mut p3_table: [u64; NENTRIES];
static mut p4_table: [u64; NENTRIES];
}

// Link up page tables
p4_table[0] = &p3_table[0] as *const _ as usize as u64 | 0b11;
p3_table[0] = &p2_table[0] as *const _ as usize as u64 | 0b11;

for (entry, i) in p2_table.iter_mut().zip(0..) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.enumerate() rather than zip(0 ?

*entry = i * HUGE_PAGE_SIZE | 0b10000011;
}

// move page table address to cr3
asm!("mov eax, p4_table
mov cr3, eax" :::: "intel");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://crates.io/crates/x86 would be neat to use here, but it also only supports 64-bit for now, IIRC.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They added support for 32 bit lately, but I'm not sure if it's already on crates.io…

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0.8 on crates.io has the bulk of this.


// enable PAE
asm!("mov eax, cr4
or eax, 1 << 5
mov cr4, eax" :::: "intel");

// set long mode bit
asm!("mov ecx, 0xC0000080
rdmsr
or eax, 1 << 8
wrmsr" :::: "intel");

// enable paging
asm!("mov eax, cr0
or eax, 1 << 31
or eax, 1 << 16
mov cr0, eax" :::: "intel");

// load the GDT (Global Descriptor Table)
asm!("lgdt [gdt64.pointer]");

// update selectors
asm!("mov ax, gdt64.data.offset" :::: "intel");

// stack selector
asm!("mov ss, ax" :::: "intel");
// data selector
asm!("mov ds, ax" :::: "intel");
// extra selector
asm!("mov es, ax" :::: "intel");

// FIXME Is this the right syntax for a far jump?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought on this part?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so? Not 100% sure though.

asm!("jmp [long_mode_start]");

// Because this is a syntax error
// asm!("jmp gdt64.code:long_mode_start" :::: "intel");
// ^
// error: <inline asm>:2:16: error: unexpected token in argument list
}

#[export_name = "long_mode_start"]
pub unsafe fn main() {
// FIXME we are in 64-bit mode now BUT this whole crate is compiled for a 32-bit target so the
// _compiler_ won't let use 64-bit instructions.
// asm!("mov rax, 0x2f592f412f4b2f4f" :::: "intel");
// asm!("mov qword [0xb8000], rax" :::: "intel");

// Note that writing that ^ as ptr::write_volatile will emit 32-bit instructions.
ptr::write_volatile(0xb8000 as *mut u64, 0x2f592f412f4b2f4f);

asm::hlt();
}

Expand All @@ -34,8 +88,5 @@ mod asm {
}
}

#[lang = "eh_personality"]
fn eh_personality() {}

#[lang = "panic_fmt"]
fn panic_fmt() {}
11 changes: 11 additions & 0 deletions x86.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"arch": "x86",
"data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128",
"executables": true,
"llvm-target": "i386",
"no-compiler-rt": true,
"os": "none",
"pre-link-args": ["-m32", "-Tlayout.ld", "-Wl,-n", "-nostartfiles"],
"target-endian": "little",
"target-pointer-width": "32"
}
4 changes: 2 additions & 2 deletions x86_64.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"arch": "x86_64",
"cpu": "x86-64",
"cpu": "i386",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"executables": true,
"llvm-target": "x86_64",
"no-compiler-rt": true,
"os": "none",
"pre-link-args": ["-Tlayout.ld", "-Wl,-n", "-nostartfiles"],
"pre-link-args": ["-m32", "-Tlayout.ld", "-Wl,-n", "-nostartfiles"],
"target-endian": "little",
"target-pointer-width": "64"
}