Back Original

strace tips for better debugging

Recently, I have been building software without libc to better understand Linux syscalls and internals better. So far, I have built a minimal shell, terminal Snake game, pure ARM64 assembly HTTP server and threads implementation. I have been using strace extensively while debugging.

Useful options and flags

I use a version of the following command:

strace -fintrCDTYyy -o strace.log -v -s128 ./binary

This looks like an alphabet soup of options! Here’s what they do and how they are useful:

The -k or --stack-trace prints the stacktrace along with the syscall. This is useful if your program is compiled with with -g. This post is a good read on using strace to show backtraces for a Golang program compiled with GODEBUG.

Selectively tracing syscalls

By default, strace traces all syscalls. We can be very selective in tracing calls by using the -e option. It allows us to trace families of syscalls e.g -e t=%net will trace all network related syscalls while -e t=%mem will trace memory related syscalls.

We can also trace only syscalls which succeed using -z option or fail using -Z option. Another useful option is -P which can be used to only trace syscalls which access a particular path e.g strace -f -P /usr/bin/ls sh -c ls.

Tampering with syscalls!

strace is very powerful and can do stuff like inject faults for particular syscalls e.g strace -e inject=%file:error=ENOENT:when=3+ ls which fails the all file related syscalls after 2 successful invocations. We can also make syscalls return a particular value using retval or send signal using signal or delay at beginning or end of syscall using delay_enter and delay_exit with -e inject.

I found this useful when debugging failure cases in code for the syscalls. The strace output will have lines like openat(AT_FDCWD, "/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) (INJECTED) which have INJECTED marked in them. This is useful to differentiate genuine syscall errors from the injected ones.