Lab 4: Tracing System Calls
The strace
utility allows us to trace system calls on running programs. We can learn quite a bit about a program just by inspecting its system calls.
Here are a few example usages of strace
:
# Trace a run of 'ls':
$ strace ls
# Trace only file-related system calls
$ strace -e trace=file ls
# Get a nice summary of unique system calls used
$ strace -c ls
# Search for a specific system call (stat in this case):
$ strace ls 2>&1 | grep '^stat'
# Note that we search the start of the string (^) because the system call's
# name comes first, followed by its parameters and return value.
When you run a command, such as strace cat
, each system call will be printed interactively to your terminal. So the general workflow is: run strace on a command, which will then print a list of system calls. You can run strace
on any binary file; if you compile your own C code, strace a.out
will display the system calls being used by your code (most likely the calls are invoked by the C library, not your code directly).
Part I: Tracing System Calls (on Linux)
For the first part of this lab, you will trace several programs and record the results on a Linux machine. You will need to write about what happens below, so
you should create a docs
directory inside your OS repo and create a new file there called syscalls.txt
.
- First, run a trace on
ls
. Record all of the unique system calls (just their names) used byls
. To avoid doing a lot of tedious work, automate most of this with a shell pipeline or command line flag (see theman
page for strace).
(list the syscalls here as a bulleted list, use ‘*’ before each system call)
-
How many unique system calls are in your list?
-
Next, trace several commands you already know and look for new system calls that you haven’t seen before (or look up some new commands if you’d like). List any new system calls you find with each command.
Command: cat /etc/passwd
New system calls:
* mprotect64
(next command goes here)
- Take a look at the system calls that your OS supports and compare them with the Linux system calls. Which calls overlap, and which do not?
Part II: Adding strace to your OS
Given how nice strace functionality is to have, let’s add it to our own OS.
- Add a flag (maybe 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. - 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 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. - 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 to take what
generate-traces.sh
produces and write it to a file. - You will include this header in
syscall.c
.
- Hint: You can use output redirection to take what
- 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!
Part III: Rick Ropen() (bonus)
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 50% credit:
- Do the tracing exercise with
strace
on Linux and check your notes (syscalls.txt
) into your OS/docs
directory.
To receive 75% credit:
- Complete all previous requirements
- 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 (Part III)
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.