17. strace

Although debugging with GDB is useful, it has the big disadvantage of focusing on the program’s internal state at a single moment in time. Often, we instead need to see how a program is interacting with its environment. In this post, we look at strace, a utility which traces system calls.

Unlike gdb, strace works on unmodified programs. We can either start a program in it (e.g. strace touch foo.txt), or attach it to an already running process (e.g. strace -p 3062). Regardless of the way it’s started, strace prints out the syscalls the target program does, the values of their parameters, and their return values:

% strace touch foo 2> t1.txt

% wc -l t1.txt
65 t1.txt

% head t1.txt
execve("/usr/bin/touch", ["touch", "foo"], [/* 84 vars */]) = 0
brk(0)                                  = 0xa96000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd7fef86000
access("/etc/", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=176078, ...}) = 0
mmap(NULL, 176078, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fd7fef5b000
close(3)                                = 0
open("/lib64/", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`'\0\0\0\0\0\0"..., 832) = 832

% tail t1.txt
close(3)                                = 0
open("foo", O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, 0666) = 3
dup2(3, 0)                              = 0
close(3)                                = 0
utimensat(0, NULL, NULL, 0)             = 0
close(0)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

Most programs do a lot of syscalls, and strace’s output is similarly long and verbose. As we can see above, the output for touch foo is \(65\) lines long, and almost all of it is just the setup and teardown done by all Linux programs; only \(5\) lines in the above output are specific to touch foo. This “noise” usually only happens at the beginning and end of a program. So, it only dominates short-lived programs like touch.

Like gdb, strace has many uses, but I find that the following are the most common.

Where’s the config? Programs have config files, and games have save files, but the documentation usually isn’t very clear about their location. By straceing a program, we see exactly what files it open(2)’d.

Did it freeze doing something external? When debugging a frozen program, it’s useful to know if it’s waiting for external input. By straceing it, we can see if it’s waiting for a syscall to return.

How do you do that? For instance, how do you list a directory in C? We could search the web, or we could strace ls and see how the ubiquitous program does it.