Lab 3: Tracing System Calls
As we saw in the previous lab, strace
can be quite useful to diagnose what programs are doing on Linux (at least as far as what system calls they are using). In this lab, we’ll add similar functionality to your OS.
Adding strace to your OS
Given how nice strace functionality is to have, let’s build it!
- Add a flag (perhaps an int to indicate on/off status) to the
process
struct so that we can turn tracing on for particular processes. To do this, editkernel/proc.h
.- You’ll also want to update
freeproc()
inkernel/proc.c
so that the flag gets cleared when the process is freed.
- You’ll also want to update
- Add a system call that turns tracing on. HINT: You can access the flag you added in the previous step from a system call with the
myproc()
function. - Every time a system call is used, print tracing information. To start with, this can just be the system call number and the value of the first argument expressed as an integer.
- Every time a system call returns, print its return value.
- Since tracing everything will be information overload, add a user space program called
tracer
that forks a child process, turns tracing on for it with your system call, and then executes whatever command line options were passed in. For example, if I run/tracer /cat README.md
then it will run/cat README.md
with tracing enabled. This might sound a bit complicated, but it’s not a whole lot of code.- The
fork.c
example should be useful here.
- The
- Implement full tracing information: system call names, argument names and values, and return values. There are some tips on how to do this below.
Here’s what a representative run of what tracer
would produce:
$ /tracer /cat README.md
[4|tracer] strace_on() = 0
[4|cat] exec(pathname = 0x0000000000003fe0, argv = 0x0000000000003fc0) = 2
[4|cat] open(pathname = README.md, mode = 0) = 3
[4|cat] read(fd = 3, buf = 0x0000000000001010, count = 512) = 75
# FogOS
Welcome to FogOS! Voted #1 Fog-themed OS in all of San Francisco!
[4|cat] write(fd = 1, buf = 0x0000000000001010, count = 75) = 75
[4|cat] read(fd = 3, buf = 0x0000000000001010, count = 512) = 0
[4|cat] close(fd = 3) = 0
$ /tracer /echo bye
[6|tracer] strace_on() = 0
[6|echo] exec(pathname = 0x0000000000003fe0, argv = 0x0000000000003fc0) = 2
bye[6|echo] write(fd = 1, buf = 0x0000000000003fe0, count = 3) = 3
[6|echo] write(fd = 1, buf = 0x0000000000000858, count = 1) = 1
$ /tracer ls /doesnotexist
[8|tracer] strace_on() = 0
[8|ls] exec(pathname = 0x0000000000003fe0, argv = 0x0000000000003fc0) = 2
[8|ls] open(pathname = /doesnotexist, mode = 0) = -1
( ... output snipped ... )
You can observe that the tracing output happens after the system call, which makes it easy for us to include the return values.
While we could modify each system call to support tracing, that would be quite tedious. Instead, we need to understand how system calls actually work, and with a bit of shell scripting and code generation, we can automate much of the tracing process. First, figure out how the transition happens from user space to kernel space when a system call is made, and add the steps and a small explanation to your syscalls.txt
log. Ultimately you’ll end up in syscall.c
, and this is the file we will need to modify — specifically, the syscall
function.
In syscall
, the system call number is used to look up the appropriate function pointer for handling the call. We can determine the name of the system call from its number, and given its name, we can get the user space function’s name, arguments, and return types.
Check out generate-traces.sh to see how this information can be extracted, and modify the script so that it will produce a new header file called kernel/strace.h
that contains helper functions to print the traces. This script will run during the build process and auto-generate kernel/strace.h
any time the list of user space system calls changes. This may seem a bit daunting at first, but the process can be summarized as:
- Modify
user/user.h
to include more detail about the system call arguments. You can look at the Linuxman
pages for inspiration on what to name the arguments, but make sure they make sense in the context of FogOS. There are a few examples near the top ofgenerate-traces.sh
. - Run
generate-traces.sh
to get an idea of what it does. You will need to modify the script and the code it generates to implement a function:strace(struct proc *p, int syscall_num, int return_value)
- This function is responsible for taking a system call number and producing the user-friendly output shown in the example above.
- Add a new Makefile recipe that builds
kernel/strace.h
usinggenerate-traces.sh
. It should depend on theuser/user.h
file so that any changes to the user-facing system calls will trigger a rebuild ofstrace.h
.- Hint: You can use output redirection (
generate-traces.sh > some_file
) to take whatgenerate-traces.sh
produces and write it to a file. - You will include this header in
syscall.c
.
- Hint: You can use output redirection (
- Modify the
syscall
function inkernel/syscall.c
to call yourstrace
function if tracing is enabled for the given process. Be careful here: the return values of the system calls are stored inp->trapframe->a0
, which is also where the first argument is stored. Make sure you don’t overwrite the argument before printing out its value! - Test
tracer
and confirm the system call information is correct.
Phew! Hopefully that was fun. You got to modify the kernel, generate C code instead of writing it yourself, experiment with bash scripting, and update a Makefile. All in a day’s work!
Bonus: Rick Ropen()
In CS 326, we do not acknowledge the existence of programming languages other than C. Modify the open
system call so that whenever a user tries to open a file that ends in .java
(or some other programming language of your choice), the open call is redirected to opening /roll.txt
instead. The contents of the file should be:
We're no strangers to love
You know the rules and so do I
A full commitment's what I'm thinking of
You wouldn't get this from any other guy
I just wanna tell you how I'm feeling
Gotta make you understand
Never gonna give you up
Never gonna let you down
Never gonna run around and desert you
Never gonna make you cry
Never gonna say goodbye
Never gonna tell a lie and hurt you
Context: Exhibit A, Exhibit B.
Now whenever someone tries to open a .java
file, they’ll see the contents of roll.txt
instead:
cat AbstractFactoryPatternBeanAdapterAdapterInterface.java
We're no strangers to love
...
(You get the idea)
Grading and Submission
To receive 75% credit:
- Add a system call to turn tracing on for a given process.
- Implement basic tracing (system call numbers, first argument, and return values)
To receive full credit for this lab:
- Complete all previous requirements
- Add your explanation of how system calls transition from user space to kernel space to
syscalls.txt
- Update the Makefile to auto-generate
kernel/strace.h
with tracing information for each system call and helper functions to display the information. - Implement the
tracer
command line utility
To receive 105% credit for this lab:
- Complete all previous requirements
- Complete the Rick Ropen() bonus task
Once you are finished, check your changes into your OS repo. Then have a member of the course staff take a look at your lab to check it.
Check off procedure:
- Show the contents of
kernel/strace.h
,kernel/syscall.c
, and show your Makefile modifications - Demonstrate running your
tracer
program with thecat
,echo
, andls
commands above- Run
echo
andls
without tracing so we can compare their output
- Run