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.

  1. First, run a trace on ls. Record all of the unique system calls (just their names) used by ls. To avoid doing a lot of tedious work, automate most of this with a shell pipeline or command line flag (see the man page for strace).

(list the syscalls here as a bulleted list, use ‘*’ before each system call)

  1. How many unique system calls are in your list?

  2. 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)
  1. 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.

  1. 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, edit kernel/proc.h.
    • You’ll also want to update freeproc() in kernel/proc.c so that the flag gets cleared when the process is freed.
  2. 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.
  3. 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.
  4. Every time a system call returns, print its return value.
  5. 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.
  6. 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:

  1. Modify user/user.h to include more detail about the system call arguments. You can look at the Linux man pages for inspiration on what to name the arguments, but make sure they make sense in the context of FogOS.
  2. 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.
  3. Add a new Makefile recipe that builds kernel/strace.h using generate-traces.sh. It should depend on the user/user.h file so that any changes to the user-facing system calls will trigger a rebuild of strace.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.
  4. Modify the syscall function in kernel/syscall.c to call your strace function if tracing is enabled for the given process. Be careful here: the return values of the system calls are stored in p->trapframe->a0, which is also where the first argument is stored. Make sure you don’t overwrite the argument before printing out its value!
  5. 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:

To receive 75% credit:

To receive full credit for this lab:

To receive 105% credit for this lab:

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.