xv6 Syscall Tracing
In this lab, we will implement a simple strace-like syscall tracing on xv6 to understand what system calls are made by an user program.
This lab has two parts: We will start with a warm-up exercise: you will add the find.c user program into xv6.
After you are not cold, you can complete the lab by following three steps:
Step 1: Add a new trace system call to mark a process for syscall tracing.
Step 2: Extend the kernel system call dispatcher to log syscalls made by traced processes.
Step 3: Write a simple user-space strace program that attaches to a child and observes its syscalls, similar to strace on Linux.
By the end of this lab, you will know how to …
- Implement new system calls in xv6
- Understand process structure and the flow of dispatching a system call
- Create a user program in xv6
Getting Started
Please clone the starter code from Github:
Warm-up exercise: Add find Command
Before starting the main lab, you will add a simple find command to get familiar with xv6 file operations (we provide you the code, you just copy paste it 😘😘). find command recursively searches for files matching a pattern:
find [directory_name] [filename_pattern]The find command should:
Recursively traverse all subdirectories under the given directory
Print the full path of all files whose names match the given pattern
Skip the
.(current) and..(parent) directory entriesHandle nested directory structures correctly
Expected Output Format
When you run these instruction:
$ echo > b
$ mkdir a
$ echo > a/b
$ mkdir a/aa
$ echo > a/aa/band
$ find . bYour output will be:
./b
./a/b
./a/aa/bIf you have already run the above command, running it again will fail because the file has already been created.
$ echo > b
$ echo > b
exec echo failedCreate the file
- Copy and paste the following code into
user/find.c:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
int ismatch(char*s,char*p){
int advance = 1 ;//advance p
if(*p == 0)
return *s == 0;
if(*p && *(p+1) && *(p+1)=='*'){
if(ismatch(s,p+2))
return 1;
advance = 0;
}
if((*s&&*p=='.')||*s==*p)
return ismatch(s+1,p+advance);
return 0;
}
char buf[512];
char*
fmtname(char *path)
{
static char buf[DIRSIZ+1];
char *p;
// Find first character after last slash.
for(p=path+strlen(path); p >= path && *p != '/'; p--)
;
p++;
// Return blank-padded name.
if(strlen(p) >= DIRSIZ)
return p;
memmove(buf, p, strlen(p));
memset(buf+strlen(p), '\0', DIRSIZ-strlen(p));
return buf;
}
void
find(char *path,char*name)
{
char *p;
int fd;
struct dirent de;
struct stat st;
if((fd = open(path, 0)) < 0){
fprintf(2, "ls: cannot open %s\n", path);
return;
}
if(fstat(fd, &st) < 0){
fprintf(2, "ls: cannot stat %s\n", path);
close(fd);
return;
}
switch(st.type){
case T_FILE:
if(ismatch(fmtname(path),name)!=0){
printf("%s\n",path);
}
break;
case T_DIR:
if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
printf("ls: path too long\n");
break;
}
strcpy(buf, path);
p = buf+strlen(buf);
*p++ = '/';
while(read(fd, &de, sizeof(de)) == sizeof(de)){
if(de.inum == 0 || strcmp(de.name,".")==0 || strcmp(de.name,"..")==0)
continue;
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0;
find(buf,name);
}
break;
}
close(fd);
}
int
main(int argc, char *argv[])
{
find(argv[1],argv[2]);
exit(0);
}Modify the Makefile
Add find to the UPROGS list in the Makefile.
Testing
If you complete all the steps, run the grading script to see if the behavior of find meets the testing data.
python3 grade-find.pyMain lab: Syscall tracing
Once you complete the warm-up exercise and pass the tests, we can start the main lab: syscall tracing. Add tracing functionality to the process structure. How?: Try to add a tracing flag Implement this function will: Add a new system call that allows us to trace a specific process (Learn how to add a new system call here this function will: What would happen if the tracing flag is not set for the matching process? Modify the syscall dispatcher to log traced system calls. At the top of syscall.c, Create a static array mapping syscall numbers to name strings For each traced syscall, we print: What would happen if we don’t handle Create a user program that traces system calls of child processes. This user program will: This user program should: before executing When you first run: you might see something like: Notice that the Try to reason it out, and try to dig the kernel with Hints: Think about how you can modify the behavior so that the syscall tracing and the user’s output If you have completed the above steps, please test your implementation: Example: When everything seems correct, verify your implementation using: Then also test:Step 1: Add Process Tracing State
traced to the process structure in kernel/proc.h.Step 2: Implement
find_proc_by_pid()find_proc_by_pid() to find a process by PID.Modify the file
kernel/proc.c: Please implement find_proc_by_pid()kernel/defs.h: Add the prototypes for find_proc_by_pid()Pseudo Code
struct proc* now_proc;
// Search through the process table
// if the matching process is found
// return pointer to the matching process
// the matching process is not found
return NULL;Step 3: Implement
sys_trace syscallImplement
sys_trace()find_proc_by_pid()Pseudo Code
int pid;
// Accept argument (PID)
struct proc *p = find_proc_by_pid(pid); // implement this function
if (p != NULL){
// The process with the specified PID exists
}
else{
// No such process
}Modify the file
kernel/sysproc.c: Implement sys_trace()kernel/syscall.h: Add syscall number definitionkernel/syscall.c: Register the syscalluser/user.h: Add user-space declarationuser/usys.pl: Add system call stubStep 4: Modify
syscall.cModify the file
kernel/syscall.c:static char *syscall_names[] = {
[SYS_fork] "fork",
[SYS_exit] "exit",
[SYS_wait] "wait",
[SYS_pipe] "pipe",
// ... Complete me
};Expected Output Format
[pid X] syscall_name(first_argument) = return_valueArgument Printing Rules
open, chdir, mkdir, unlink, link): Print as “string”argv[0] as “program” (When we run ls, the output should be exec(“ls”))Pseudo Code
void syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
if (num > 0 && num < NELEM(syscalls) && syscalls[num])
{
uint64 arg0 = p->trapframe->a0; // Save first argument BEFORE syscall dispatch
p->trapframe->a0 = syscalls[num](); // Use num to lookup the system call function for num, call it, and store its return value in p->trapframe->a0
if (p->traced)
{
// Print syscall name, first argument
if (num == SYS_open || num == SYS_unlink ||
num == SYS_chdir || num == SYS_mkdir || num == SYS_link
/* ignore the second string argument of `link` */)
{
// If the syscall is one of these five...
}
else if ( num == SYS_exec ){
// If the syscall is exec...
}
else
{
// If the syscall is any of the others...
}
// Print the remaining part.
}
}
else
{
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}Example Output
[pid 6] exec("grep") = 3
[pid 6] open("README") = 3
[pid 6] read(3) = 1023
[pid 6] close(3) = 0SYS_exec separately from the other five syscalls?Step 5: Create a User Program
straceCreate the file
user/strace.cstrace [args...]fork() to create child processexec() the target program with its argumentstrace() on child PID, then wait() for completion> make clean
> make> make qemuThe Interleaved Output Problem
$ strace echo hello[pid 5] exec("echo") = 2
hello[pid 5] write(1) = 5
[pid 5] write(1) = 1"hello" output appears in the middle of the syscall trace? But this is not good. We don’t want the output of traced program and tracing messages to mix up.gdb.How to Fix It
write() calls eventually reach consolewrite() in kernel/console.c.hello) and your tracing output use the console.But we only want the kernel tracer to print after or outside of user writes. To simplify, we completely drop the user write.myproc().don’t interfere with each other is dropped. Once you fix it, your result should look like this:$ strace echo hello
[pid 5] exec("echo") = 2
[pid 5] write(1) = 5
[pid 5] write(1) = 1Testing
$ strace grep hello README
[pid 6] exec("grep") = 3
[pid 6] open("README") = 3
[pid 6] read(3) = 1023
[pid 6] read(3) = 971
[pid 6] read(3) = 298
[pid 6] read(3) = 0
[pid 6] close(3) = 0Understanding the Output
[pid 4] exec("grep") = 3 → Process 4 executed grep using the exec syscall.[pid 6] open("README") = 3 → Process 6 opened file README and got file descriptor 3.Run the Grading Script
python3 grade-syscall_trace.pystrace find a b
strace ls a
Final Reflection & Demo
Make sure you can reason about the following questions:
- Why was the output interleaved earlier, and how did your fix resolve it?
- Why must we treat
SYS_execdifferently from other syscalls? - What happens if the tracing flag is never cleared or reset?
- Why do we print only the first argument of each syscall?
TA will ask you these questions during the demo. Please bring your own laptop to the classroom. You will be asked to:During Demo Time
python3 grade-find.py
python3 grade-syscall_trace.py
