WEBVTT

00:00.000 --> 00:03.883
Practice Exam 2, Problem 3: From Reset to kmain.

00:04.483 --> 00:09.731
We'll work it out on the exam's own pages, as the answer key's handwriting appears.

00:10.331 --> 00:14.043
A script reveals it, and this explainer was built with Claude.

00:14.560 --> 00:20.363
Problem 3 draws on 31k_boot, from week 7, on Thursday, October 8.

00:20.963 --> 00:23.864
You wrote the four instructions of _entry,

00:24.464 --> 00:27.301
and the line in kmain that prints OSLINGS:PASS.

00:27.901 --> 00:33.021
kernel.ld, uart.rs and testdev.rs were given.

00:33.520 --> 00:40.603
Part (c) also leans on 21r_unsafe_bridge, from week 6, on Friday, October 2:

00:41.203 --> 00:43.955
you wrote a volatile store to a device register.

00:44.555 --> 00:51.189
And it leans on 30k_kernel_basics, from the same day: your panic handler, which never returns.

00:52.320 --> 00:54.837
Problem 3 opens with given material.

00:55.837 --> 01:05.672
QEMU's virt machine, started with -bios none, copies the kernel into RAM, and jumps to address 0x8000_0000.

01:06.200 --> 01:08.525
Three entries of the machine's memory map.

01:09.125 --> 01:14.715
The test finisher: storing 0x5555 to it powers QEMU off.

01:15.315 --> 01:18.323
The UART: a byte stored here is sent to the terminal.

01:18.923 --> 01:20.885
And RAM, where the kernel was copied.

01:21.400 --> 01:28.824
kernel.ld, the linker script, lays the kernel out, lowest address first. The addresses are from one build.

01:29.424 --> 01:32.837
.text holds the code, with _entry first.

01:33.437 --> 01:37.341
.rodata holds the constants, string literals first.

01:37.941 --> 01:41.397
Then .data, only 8 bytes, and .bss.

01:41.920 --> 01:50.475
STACK0 is all of .bss: 0x4000 bytes, starting at 0x8000_1290.

01:51.075 --> 01:55.043
The last line, end, is the first address past the kernel image.

01:55.643 --> 01:57.541
Part (b) comes back to this layout.

01:59.040 --> 02:00.747
This is entry.rs.

02:01.347 --> 02:05.165
The four instructions are the ones you wrote, each with its address.

02:05.765 --> 02:10.523
Beside kmain are the first two instructions the compiler generated for it:

02:11.123 --> 02:15.027
a 16-byte frame, and ra, all 8 bytes, saved in it.

02:16.160 --> 02:23.349
Part (a). _entry is the boot trampoline: the few instructions that must run before any Rust can.

02:23.949 --> 02:28.003
At reset, sp, t0 and ra hold nothing useful.

02:28.603 --> 02:32.485
We fill in the table: a question mark wherever that's still so.

02:33.000 --> 02:34.792
First, la: load address.

02:35.392 --> 02:41.259
sp gets the address of STACK0, which the layout put at the start of .bss:

02:42.259 --> 02:46.760
0x8000_1290, the array's first byte.

02:47.760 --> 02:50.533
t0 and ra are still question marks.

02:51.040 --> 02:52.960
Next, li: load immediate.

02:53.560 --> 02:56.739
t0 gets a constant, the size of the array:

02:57.739 --> 02:58.997
0x4000.

02:59.997 --> 03:03.069
sp has not moved, and ra is still unknown.

03:03.600 --> 03:05.733
Now the third instruction: the add.

03:06.333 --> 03:08.552
The array's first byte, plus its size:

03:10.052 --> 03:17.007
sp is 0x8000_5290: the first address past the array, its top.

03:18.007 --> 03:20.204
t0 and ra are unchanged.

03:20.720 --> 03:21.680
Then the call.

03:22.280 --> 03:25.288
It sets ra to the address of the next instruction:

03:26.788 --> 03:31.311
0x8000_0014. Then it goes to kmain.

03:32.311 --> 03:34.423
sp and t0 are as they were.

03:34.920 --> 03:36.179
Now we're in kmain.

03:36.779 --> 03:40.576
Its first instruction, the addi, makes a 16-byte frame.

03:42.076 --> 03:52.743
sp drops by 16 bytes. 16 is one in the next hex digit, so the nine becomes an eight: 0x8000_5280.

03:53.743 --> 03:55.833
t0 and ra are untouched.

03:57.360 --> 04:00.731
(i): at what address does kmain's sd store ra?

04:01.731 --> 04:07.469
At sp plus 8: that is 0x8000_5280 plus 8.

04:08.969 --> 04:13.001
The answer: 0x8000_5288.

04:14.000 --> 04:15.920
Is that address inside STACK0?

04:17.420 --> 04:24.055
The array starts at the address ending 1290, and is 0x4000 bytes long.

04:24.655 --> 04:30.031
It runs up to, but not including, the address ending 5290.

04:31.531 --> 04:33.984
So yes: the top 8 bytes of the array.

04:35.000 --> 04:38.648
(ii): the value saved there is never loaded again. Why not?

04:40.148 --> 04:41.705
kmain never returns.

04:43.205 --> 04:52.293
Its return type is the never type, the exclamation mark after the arrow, and its last act is exit_success, which powers the machine off.

04:52.800 --> 04:55.744
The saved ra is where kmain's ret would go,

04:57.244 --> 04:58.993
and kmain never runs one.

04:59.593 --> 05:06.228
Nothing meaningful is at that return address anyway, which is why _entry says noreturn.

05:07.160 --> 05:12.344
(iii): why must sp start at the high end, when STACK0 names its low end?

05:13.844 --> 05:15.124
The stack grows down:

05:16.624 --> 05:24.240
a function that makes a frame subtracts from sp, as kmain's addi did, and stores above the new sp.

05:24.760 --> 05:27.875
Starting at the top keeps all of that inside the array.

05:29.375 --> 05:33.215
But a symbol names where an array begins: its lowest address.

05:33.815 --> 05:36.289
So _entry has to add the size.

05:38.000 --> 05:42.971
Part (b). A student's _entry leaves out its third instruction, the add.

05:43.571 --> 05:48.243
Nothing else changes, and kmain begins with the same two instructions.

05:48.843 --> 05:52.235
Above the page, we keep the layout from two pages back.

05:52.760 --> 05:55.235
(i): what is sp when kmain starts?

05:56.735 --> 05:58.164
sp is never raised.

05:59.164 --> 06:04.540
It is still 0x8000_1290, the bottom of STACK0.

06:05.040 --> 06:07.920
And where does kmain's sd store ra now?

06:08.920 --> 06:17.197
kmain's addi subtracts the same amount as before. sp becomes 0x8000_1280.

06:18.697 --> 06:24.863
So ra is stored 8 above that: at 0x8000_1288.

06:25.880 --> 06:29.507
(ii): is that address inside STACK0? And what lives there?

06:31.007 --> 06:33.524
No. It is 8 bytes below STACK0.

06:34.524 --> 06:39.111
In the layout, that address is .data: the kernel's initialized statics.

06:39.640 --> 06:47.341
Then kmain calls puts, which calls putc, and each makes its own stack frame. What do those frames overwrite?

06:48.841 --> 06:51.487
Every deeper call subtracts more from sp,

06:52.987 --> 06:58.683
so those frames land lower still: in .rodata, the kernel's own constants.

06:59.600 --> 07:00.688
The key draws it.

07:02.188 --> 07:05.452
sp starts at STACK0, and only goes down.

07:06.452 --> 07:10.292
Directly below, in .data: kmain's saved ra.

07:11.292 --> 07:15.943
And below that, in .rodata: the frames of puts and putc.

07:16.960 --> 07:22.123
(iii): built and run, this kernel still prints OSLINGS:PASS, and exits normally.

07:22.723 --> 07:24.941
Why did none of those stores cause a fault?

07:25.541 --> 07:30.576
And why is the passing test not evidence that this _entry is correct?

07:32.580 --> 07:34.223
Nothing protects that memory.

07:34.823 --> 07:38.620
The MMU is off, and the kernel runs in machine mode,

07:40.120 --> 07:48.440
so .data and .rodata are ordinary writable RAM. A store there just works: no fault, no message.

07:48.960 --> 07:50.283
Now the passing test.

07:51.783 --> 07:52.935
It passed by luck.

07:53.535 --> 08:00.084
In this build, the frames reached down 240 bytes, to 0x8000_11A0.

08:02.100 --> 08:08.991
The strings it prints sit lower: they end at 0x8000_1034, as the layout says.

08:09.591 --> 08:12.492
The frames stopped above them, so the strings survived.

08:14.500 --> 08:18.020
A deeper call chain, a bigger frame, or a different link order,

08:18.620 --> 08:21.628
and the kernel overwrites data it is about to use.

08:22.228 --> 08:27.327
In the week 7 explainer, Reset to Rust, this same mistake did exactly that.

08:27.840 --> 08:31.339
The explainer's build put the strings within reach of the frames.

08:31.939 --> 08:34.115
So the passing test is not evidence:

08:34.715 --> 08:38.000
a test that checks the output cannot see where the stack was.

08:39.920 --> 08:40.816
Part (c).

08:41.816 --> 08:46.872
Once kmain runs, printing and powering off are stores to device addresses.

08:47.472 --> 08:50.608
putc is one volatile store, to UART0.

08:51.208 --> 08:56.307
exit_success stores 0x5555 to the test finisher, then loops.

08:56.840 --> 09:02.451
(i): kmain runs puts on the string O, K, newline; the ASCII codes are given.

09:03.051 --> 09:06.592
List the stores the CPU makes to the device, in order:

09:07.192 --> 09:09.112
the address and the value of each.

09:11.140 --> 09:17.412
Three 1-byte stores, one for each call to putc, all to 0x1000_0000.

09:18.912 --> 09:22.837
The values: 0x4F, then 0x4B, then 0x0A.

09:23.840 --> 09:28.256
They all go to one address. Why does each not replace the one before?

09:29.756 --> 09:32.700
That address is a device register, not memory.

09:34.200 --> 09:39.149
Each store hands the UART a byte to send; nothing is kept there to be replaced.

09:40.680 --> 09:42.131
That's why they are volatile.

09:42.731 --> 09:50.261
As 21r explained, plain stores to one address would look dead to the compiler: they would be merged into one.

09:52.160 --> 09:56.128
(ii): using exit_success as the model, write exit_failure.

09:56.728 --> 09:59.480
It reports a failure code to the same register.

10:00.080 --> 10:06.907
The value has 0x3333 in its low 16 bits, and code in the 16 bits above them.

10:08.440 --> 10:09.912
The same unsafe block,

10:11.412 --> 10:13.972
and the same volatile store to TEST_FINISHER.

10:14.572 --> 10:22.295
The value: code, widened to 32 bits and shifted left by 16, then ORed with 0x3333.

10:23.800 --> 10:25.741
A closing brace ends the block.

10:26.741 --> 10:29.365
Then loop forever, as exit_success does.

10:29.965 --> 10:37.283
In 31k_boot, exit_failure came with testdev.rs, and the given panic handler calls it.

10:39.280 --> 10:43.717
(iii): exit_failure(2). What 32-bit value does it store?

10:44.717 --> 10:48.323
2 shifted left by 16 is 0x0002_0000.

10:48.923 --> 10:50.821
ORed with 0x3333,

10:52.321 --> 10:54.753
that gives 0x0002_3333.

10:55.760 --> 10:56.848
And without the loop?

10:58.348 --> 11:07.009
The compiler reports E0308, mismatched types: expected the never type, found unit, the empty parentheses.

11:08.509 --> 11:13.373
The function promises never to return, and falling off the end would return.

11:14.680 --> 11:16.685
You met the never type in 30k.

11:17.285 --> 11:20.315
The panic handler you wrote there made the same promise,

11:20.915 --> 11:23.048
and kept it the same way: with a loop.

11:24.760 --> 11:26.061
That's Problem 3.

11:26.661 --> 11:30.629
(a): _entry gives Rust a stack, starting at its top.

11:31.229 --> 11:36.093
(b): MMU off, machine mode: nothing protects the kernel's own memory.

11:36.693 --> 11:40.469
(c): printing and powering off are stores to device registers.
