xv6, line by line
tour 6
Tours6 System-call arguments and user pointers

Tour 6 · Traps and system calls · about 35 minutes · 20 steps

System-call arguments and user pointers

You type cat README. Before cat can print a single byte, it must ask the kernel to open a file, and to do that it hands the kernel a pointer: the address of the string "README" in its own memory. That pointer is just a number chosen by a user program. It might be correct. It might point at the kernel’s own code, at a page the program does not own, at the very top of the address space, or at a string with no end.

This tour follows open("README", O_RDONLY) from the moment cat makes the call until the kernel holds a safe private copy of the name: argraw, argint, argstr, fetchstr and copyinstr, which walks cat’s page table by hand, one page at a time. Then it feeds the same code the nastiest pointers that usertests can think of, and watches each one fail cleanly instead of crashing the kernel.

Tour 5: Life of a system call showed how a system call gets into the kernel and back. This tour stays in the gap between “the kernel has the user’s registers” and “the kernel can use what they mean”. The rule you will see enforced at every step: the kernel never trusts user memory, and it copies each user argument exactly once.

Best after: 5. Life of a system call

Who is running where

The machine has three harts. The numbers below come from one run of this build (after boot, cat README typed at the prompt); a different run may use other harts.

Hart What it is doing
0 Running cat (pid 3) in user mode: the process this tour follows
1 Its scheduler is looking for work, or it runs whatever else is runnable
2 The same: idle in its scheduler, or running another process

The shell (pid 2) is asleep in kwait, waiting for cat. Later steps switch to usertests, run the same way.

Three harts are running. This tour follows one path through the code, but the machine has three CPUs executing at the same time. Watch the locks held display at the top of each step, and read the Meanwhile, on other harts boxes: they show what the other CPUs could be doing at that very moment.
The route
  1. 1cat passes a pointer to its own memory user/cat.c
  2. 2The stub adds the number 15 user/usys.S
  3. 3sys_open makes room for a private copy kernel/sysfile.c
  4. 4argraw reads the saved registers, not the live ones kernel/syscall.c
  5. 5argint and argaddr check nothing, on purpose kernel/syscall.c
  6. 6argstr hands the pointer to fetchstr kernel/syscall.c
  7. 7copyinstr starts at the page containing the string kernel/vm.c
  8. 8walkaddr refuses anything that is not user memory kernel/vm.c
  9. 9walk reads cat's page table through the direct map kernel/vm.c
  10. 10The copy, byte by byte, until the NUL kernel/vm.c
  11. 11From here on, only the kernel's copy is used kernel/sysfile.c
  12. 12exec copies a two-level structure, one level at a time kernel/sysfile.c
  13. 13usertests fires five ridiculous pointers user/usertests.c
  14. 14Bad pointers die in walkaddr and vmfault kernel/vm.c
  15. 15A string exactly as long as the buffer user/usertests.c
  16. 16A string that runs off the end of memory user/usertests.c
  17. 17A string on a page that does not exist yet user/usertests.c
  18. 18Allocating that page takes the one shared lock kernel/kalloc.c
  19. 19copyin with a spinlock held, for a pipe kernel/pipe.c
  20. 20open returns a descriptor kernel/sysfile.c

Keys: ← → step · Home start