01 At reset, six instructions in ROM hand the hart to 0x8000_0000.
at reset, six instructions in ROM hand the hart to 0x8000_0000 · point at [t0 + 24] = 0x80000000 loaded into t0, then jr t0
Every program in examples/src/bin/ beside the output it printed, and every
file in examples/broken/ beside what rustc said about it. Each colored,
numbered section of code sits in the same row as the output it produced. Click an output line to light
up the println! that printed it, or click a println! to find its output.
Click a section's number to copy a link straight to that section, to paste where students will
follow it. These programs are built for riscv64gc-unknown-none-elf and run under qemu-system-riscv64, not in a browser, so this page shows the output they printed there and cannot run them itself. To run one yourself, cd week07/examples and cargo run --bin it. If one ever hangs, Ctrl-A then x quits QEMU. On a phone the two columns do not fit, so a
Code/Output switch shows one at a time.
| Program | The one idea | The line to point at | Sections | |
|---|---|---|---|---|
| 01 | the_boot_rom | at reset, six instructions in ROM hand the hart to 0x8000_0000 | [t0 + 24] = 0x80000000 loaded into t0, then jr t0 | 4 |
| 02 | where_the_linker_put_it | the linker script gives every byte an address, and end names the first one past the image | &end as *const u8 as usize = 0x80003… | 3 |
| 03 | the_boot_stack | the stack is 16 KiB of plain RAM: sp starts at the top, and nothing guards the bottom | FLOOR[63], just below end, now holds … = dig + 0x12 | 4 |
| 04 | reset_to_rust | each step of the boot needs the one before it, and each left evidence | all three match: true. Nothing between the ROM and main wrote them | 3 |
| 05 | page_number_and_offset | an address is a page number and an offset | >> 12 beside & 0xFFF for five real addresses | 3 |
| 06 | why_one_page_size | one block size: every request costs whole pages | 4097 bytes -> 2 pages … 4095 unused | 3 |
| 07 | a_page_holds_an_address | a free page has room for the address of another page | P holds Q's address, and Q itself was never touched | 4 |
| 08 | where_free_memory_starts | free memory starts at the first whole page above the stack | add 0xFFF, then clear the low 12 bits | 4 |
| 09 | bits_by_hand | shift, mask, extract, pack; nothing checks that a field fits | pack_date(46, 10, 32) = 0x5D60: month 11, day 0 | 4 |
| 10 | split_the_address | an Sv39 address is three 9-bit indices over a 12-bit offset | va 0x20_00A0_35F0 into 128, 5, 3, 0x5F0 | 4 |
| 11 | pte_by_hand | a PTE is a page number at bit 10, above ten bits of flags | | V R W 0x2048_D007 | 4 |
| 12 | satp_reads_zero | satp names the root table, and here it reads 0: paging is off | csrr satp = 0x0 | 3 |
| 13 | w_xor_x | a page may be writable or executable, never both | answer() = 42, after a store rewrote its code | 4 |
| Error | What rustc says | The fix |
|---|---|---|
| E0080 | attempt to shift left by 60_i32, which would overflow | widen before the shift: (SV39 as u64) << 60 |
| E0133 | use of extern static is unsafe and requires unsafe function or block | unsafe { &end as *const u8 as usize }, or the safe addr_of!(end) |
| E0308 | mismatched types expected bool, found u64 | if pte & V != 0: Rust's & binds tighter than != |
| E0308 | mismatched types expected usize, found u64 | convert where the type changes, as usize |
| E0369 | cannot add usize to *mut u8 | page.wrapping_add(PGSIZE), or do the arithmetic on usize |
| E0606 | casting &u8 as usize is invalid | &end as *const u8 as usize, or addr_of!(end) as usize |
| E0614 | type usize cannot be dereferenced | cast first: *(p as *mut usize) = q |
| end_is_not_a_const | error: pointers cannot be cast to integers during const eval | work it out at run time: only the linker knows it |
| tilde_is_not_an_operator | error: ~ cannot be used as a unary operator | !0xFFF: Rust's bitwise NOT is ! |
| Space j ↓ → | next section (dims the rest) |
| Shift+Space k ↑ ← | previous section |
| 0–9 | jump to that section |
| click | pin an output line and the code that printed it (click again to unpin) |
| click a number | copy a link to that section, or to the whole example from the number beside its title |
| l | copy a link to the pinned line, else the section in focus, else this example |
| Esc | unpin, then leave section focus, and drop it from the link so a reload does not bring it back |
| n p | next or previous example |
| r | next run, for programs run more than once |
| e | open or close the explanation (broken files) or the build warnings |
| + - | larger or smaller text (remembered) |
| o | on a narrow screen, switch between the code and the output column |
at reset, six instructions in ROM hand the hart to 0x8000_0000 · point at [t0 + 24] = 0x80000000 loaded into t0, then jr t0
end names the first one past the image.the linker script gives every byte an address, and end names the first one past the image · point at &end as *const u8 as usize = 0x80003…
sp starts at the top, and nothing guards the bottom.the stack is 16 KiB of plain RAM: sp starts at the top, and nothing guards the bottom · point at FLOOR[63], just below end, now holds … = dig + 0x12
each step of the boot needs the one before it, and each left evidence · point at all three match: true. Nothing between the ROM and main wrote them
an address is a page number and an offset · point at >> 12 beside & 0xFFF for five real addresses
one block size: every request costs whole pages · point at 4097 bytes -> 2 pages … 4095 unused
a free page has room for the address of another page · point at P holds Q's address, and Q itself was never touched
free memory starts at the first whole page above the stack · point at add 0xFFF, then clear the low 12 bits
shift, mask, extract, pack; nothing checks that a field fits · point at pack_date(46, 10, 32) = 0x5D60: month 11, day 0
an Sv39 address is three 9-bit indices over a 12-bit offset · point at va 0x20_00A0_35F0 into 128, 5, 3, 0x5F0
a PTE is a page number at bit 10, above ten bits of flags · point at | V R W 0x2048_D007
satp names the root table, and here it reads 0: Bare, no tree.satp names the root table, and here it reads 0: paging is off · point at csrr satp = 0x0
a page may be writable or executable, never both · point at answer() = 42, after a store rewrote its code
60_i32, which would overflowSV39 << 60 with SV39: u32 · fix: widen before the shift: (SV39 as u64) << 60
MODE belongs in bits 63..60, but SV39 is a u32, so SV39 << 60 shifts a 32-bit value by 60. A const is computed while compiling, and there a shift by the type's width or more is an error. The as u64 comes too late: it widens the result, after the u32 shift has already failed.
(SV39 as u64) << 60: widen first, then shift.const SV39: u64 = 8;: give the field the register's type.reading the linker's end outside unsafe · fix: unsafe { &end as *const u8 as usize }, or the safe addr_of!(end)
end is declared in Rust but defined by the linker script, so rustc cannot check that a byte really lives there, or that nothing else changes it. Reading it, or making a reference to it, is unsafe, even &end, which only takes its address.
unsafe { &end as *const u8 as usize }, as the lecture writes it.core::ptr::addr_of!(end) as usize, which is safe: it makes the pointer without making a reference, and never reads the byte.bool, found u64if pte & V · fix: if pte & V != 0: Rust's & binds tighter than !=
C counts any nonzero integer as true, so if (pte & PTE_V) tests a flag. Rust's if takes only a bool, and pte & V is still a u64: the flag bit, or 0. Say the question you mean, which is "is it nonzero?".
if pte & V != 0: Rust's & binds tighter than != (program 09).if (pte & V) != 0: the same test, and it reads the same in C.usize, found u64a u64 from satp into a usize · fix: convert where the type changes, as usize
On RV64 a usize is 64 bits, as wide as a u64, and Rust still keeps the two apart: an integer changes type only where you convert it, with as or the try_into rustc suggests. csrr fills in whatever type the variable was declared with, and here that was u64.
let root = ((satp & PPN_BITS) << 12) as usize;: say the conversion.let satp: usize; with PPN_BITS: usize: the type rv6 gives addresses, from the start.usize to *mut u8page + PGSIZE on a *mut u8 · fix: page.wrapping_add(PGSIZE), or do the arithmetic on usize
A raw pointer has no +. The methods rustc suggests, wrapping_add and add, count elements, not bytes: 4096 steps along a *mut u8 is one page, but along a *mut u64 it is eight. Rust makes you name the step.
page.wrapping_add(PGSIZE): safe to call, and a u8 is one byte.(page as usize + PGSIZE) as *mut u8.&u8 as usize is invalid&end as usize · fix: &end as *const u8 as usize, or addr_of!(end) as usize
A reference is not a number, so as will not turn one into a usize; it must become a raw pointer first. rustc's help, "remove the unneeded borrow", is wrong here. end as usize compiles, but it reads the byte stored at end and gives you its value. That byte is meaningless: the address is the answer.
&end as *const u8 as usize: reference, raw pointer, then integer (the line program 02 prints).addr_of!(end) as usize makes the raw pointer directly, with no unsafe (program 08).usize cannot be dereferenced*p = q with p: usize · fix: cast first: *(p as *mut usize) = q
A usize that holds an address is still just a number, and * works only on a pointer or a reference. The cast makes the pointer, and its type, *mut usize, says the store is 8 bytes wide. Storing through a raw pointer is unsafe: only you know the page is free.
unsafe { *(p as *mut usize) = q }: the store program 07 makes.unsafe { (p as *mut usize).write(q) }: the same store, as a method.end's address in a const · fix: work it out at run time: only the linker knows it
rustc works out a const while it compiles this file. end gets its address later, when the linker lays out the whole image, so at compile time there is no number to give. That is why end is a symbol and not a constant. The same is true of every static's address: the linker picks them all. This error has no code; the whole message is its one line.
let first_free = unsafe { &end as *const u8 as usize };~ cannot be used as a unary operatorC's ~0xFFF · fix: !0xFFF: Rust's bitwise NOT is !
C clears a page offset with va & ~0xFFF. Rust has no ~: its bitwise NOT is !, the same operator that negates a bool. This error has no code, and rustc's help line under it names the fix.
va & !0xFFF: all ones except the low 12 bits.va & !(4096 - 1): the same mask, built from the page size.