Utils and System Calls

First, pull the latest code from the xv6 repo and switch to the hw1 branch.

git pull origin
git checkout hw1

Note: If you haven't cloned the repository yet, do so with:

git clone https://github.com/sysec-uic/xv6-public.git
git checkout hw1

Step 1: Write a time Program for xv6 (2 pt)

Create a new program called time that measures cycles used by a command. Example:

$ time echo Hello
Hello
echo took 76374348 cycles to run.
  • Use fork(), exec(), and wait().
  • Use the rdtscp assembly instruction.
  • Do not trigger any kernel exceptions.
  • Read the Makefile for how to add a new user program (see UPROGS).
  • No standard C library — study ULIB in Makefile.

Turn-in: Upload a screenshot of executing time echo "Hello 461" to Gradescope.

Step 2: Write a pingpong Program for xv6 (2 pt)

Write a user-level program that uses xv6 system calls to ''ping-pong'' a byte between two processes over a pair of pipes [1], one for each direction.

The parent should send a byte to the child; the child should print <pid>: received ping, where <pid> is its process ID, write the byte on the pipe to the parent, and exit; the parent should read the byte from the child, print <pid>: received pong, and exit.

Run the program from the xv6 shell, and it should produce the following output:

$ make qemu-nox
...
init: starting sh
$ pingpong
4: received ping
3: received pong
$

If you encounter a zombie!, please refer to [2]. You should avoid zombies whenever possible!

Turn-in: Upload a screenshot of executing pingpong to Gradescope.

Step 3: Using gdb (4pt)

In many cases, print statements will be sufficient to debug your kernel, but sometimes it is useful to single-step through code or get a stack back-trace. The GDB debugger can help.

To help you become familiar with gdb, run make qemu-nox-gdb and then fire up gdb in another window (see the gdb material on the guidance page). Once you have two windows open, type in the gdb window:

(gdb) b syscall
Breakpoint 2 at 0xffff800000107ee8: file syscall.c, line 154.
(gdb) c
Continuing.
(gdb) layout src
(gdb) backtrace

The layout command splits the window in two, showing where gdb is in the source code. backtrace prints a stack backtrace.

Answer the following questions on Gradescope

Q3.1 Look at the backtrace output, which function called syscall?

Q3.2 Value of proc->tf->rax

Type n a few times to step past uint64 num = proc->tf->rax;

Once past this statement, type p num, which prints the value of tf->rax.

What is the value of proc->tf->rax and what does that value represent? (Hint: look at initcode.S, the first user program xv6 starts.)

Q3.3 Handle kernel crash

The xv6 kernel code contains consistency checks whose failure causes the kernel to panic; you may find that your kernel modifications cause panics. For example, replace the statement uint64 num = proc->tf->rax; with uint64 num = * (int *)0; at the beginning of syscall, run make qemu-nox, and you will see something similar to:

cpu1: starting
cpu0: starting
[1~unexpected trap 14 from cpu 1 rip ffff800000107f00 (cr2=0x0000000000000000)
proc id: 1
cpu0: panic: trap
 ffff800000100c94
 ffff800000109912
 ffff8000001093fc
 ...

Quit out of qemu.

To track down the source of a kernel page-fault panic, search for the rip value printed for the panic you just saw in the file kernel.asm, which contains the assembly for the compiled kernel.

Write down the assembly instruction the kernel is panicing at. Which register corresponds to the variable num?

Q3.4 Why kernel crash?

To inspect the state of the processor and the kernel at the faulting instruction, fire up gdb, and set a breakpoint at the faulting epc, like this:

(gdb) b *0xffff800000107f00
Breakpoint 2 at 0xffff800000107f00: file syscall.c, line 156.
(gdb) layout asm
(gdb) c

Confirm that the faulting assembly instruction is the same as the one you found above.

Why does the kernel crash?

Step 4: System call tracing (4 pt)

In this assignment, you will add a system call tracing feature that may help you when debugging later labs. You'll create a new trace system call that will control tracing. It should take one argument, an integer "mask", whose bits specify which system calls to trace. For example, to trace the fork system call, a program calls trace(1 << SYS_fork), where SYS_fork is a syscall number from syscall.h.

You have to modify the xv6 kernel to print a line when each system call is about to return, if the system call's number is set in the mask. The line should contain the process ID, the name of the system call, and the return value; you don't need to print the system call arguments. The trace system call should enable tracing for the process that calls it and any children that it subsequently forks, but should not affect other processes.

We provide a trace user-level program that runs another program with tracing enabled (see trace.c). When you're done, you should see output like this:

$ trace 32 grep hello README
3: syscall read -> 1023
3: syscall read -> 966
3: syscall read -> 70
3: syscall read -> 0
$
$ trace 2147483647 grep hello README
4: syscall trace -> 0
4: syscall exec -> 3
4: syscall open -> 3
4: syscall read -> 1023
4: syscall read -> 966
4: syscall read -> 70
4: syscall read -> 0
4: syscall close -> 0
$
$ grep hello README
$
$ trace 2 usertests forkforkfork
usertests starting
test forkforkfork: 407: syscall fork -> 408
408: syscall fork -> 409
409: syscall fork -> 410
410: syscall fork -> 411
409: syscall fork -> 412
410: syscall fork -> 413
409: syscall fork -> 414
411: syscall fork -> 415
...

In the first example above, trace invokes grep tracing just the read system call. The 32 is 1 << SYS_read. In the second example, trace runs grep while tracing all system calls; the 2147483647 has all 31 low bits set. In the third example, the program isn't traced, so no trace output is printed.

In the fourth example, the fork system calls of all the descendants of the forkforkfork test in usertests are being traced. Your solution is correct if your program behaves as shown above (though the process IDs may be different).

Some hints:

  • Add _trace to UPROGS in Makefile
  • Run make qemu-nox and you will see that the compiler cannot compile trace.c, because the user-space stubs for the trace system call don't exist yet: add a prototype for trace to user.h, a stub to usys.S, and a syscall number to syscall.h. Once you fix the compilation issues, run trace 32 grep hello README; it will fail because you haven't implemented the system call in the kernel yet.
  • Add a sys_trace() function in sysproc.c that implements the new system call by remembering its argument in a new variable in the proc structure (see proc.h). The functions to retrieve system call arguments from user space are in syscall.c, and you can see examples of their use in sysproc.c. Add your new sys_trace to the syscalls array in syscall.c.
  • Modify fork() (see proc.c) to copy the trace mask from the parent to the child process.
  • Modify the syscall() function in syscall.c to print the trace output. You will need to add an array of syscall names to index into.

Turn-in: Upload a screenshot of executing trace 2147483647 grep hello README to Gradescope.


Submission

Please follow instructions on Gradescope to submit your solution.