Limiting which Syscalls to Trace with Strace

Tracing a program with strace generates a lot of output because of the sheer number of syscalls every program calls during its runtime. This can become overwhelming very quickly, making it hard to analyze the trace and find what you are looking for.

Fortunately, strace has several features that allow you to limit which syscalls it actually traces.

Limiting by Name

The simplest mechanism to limit the number of traced syscalls is by specifying the name of one syscall you want to trace. To do this, you can use the -e flag followed by trace= and the name of the syscall. In this example we trace the /bin/ls program and we only want strace to report the openat syscalls executed by the program:

$ strace -e trace=openat /bin/ls

This should give you an output similar to this:

openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpcre2-8.so.0", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/proc/filesystems", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
+++ exited with 0 +++

Strace traced only the openat syscall and ignored all the others. If you want to trace several syscalls by name you can specify them separated by a comma:

$ strace -e trace=openat,read,close /bin/ls

This would then give you an output similar to this:

openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libselinux.so.1", 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\0\0"..., 832) = 832
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220\243\2\0\0\0\0\0"..., 832) = 832
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpcre2-8.so.0", 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\0\0"..., 832) = 832
close(3)                                = 0
openat(AT_FDCWD, "/proc/filesystems", O_RDONLY|O_CLOEXEC) = 3
read(3, "nodev\tsysfs\nnodev\ttmpfs\nnodev\tbd"..., 1024) = 393
read(3, "", 1024)                       = 0
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
close(3)                                = 0
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
close(3)                                = 0
close(1)                                = 0
close(2)                                = 0
+++ exited with 0 +++

Limiting by Regex

Sometimes you want to trace all syscalls that match a certain pattern without specifying them explicitly. This can be either because you don’t want to type more than you have to or because there are some variations of the syscall name that you might not even be aware of. E.g. there is a chown and a fchown syscall and there is open and openat.

Strace supports us in such cases by allowing us to specify a POSIX regex that specifies the syscalls to trace:

$ strace -e trace=/read /bin/ls

This command will trace all syscalls that contain the string “read” anywhere in their name. In this concrete example, strace will print output for the syscalls read and pread. But when you check the list of all available syscalls and search for “read” you will see that a lot more syscalls would be matched by this regex. To limit the regex to syscalls with a name that starts with “read” you could use this command:

$ strace -e trace=/^read /bin/ls

It is also possible to use multiple regexes in one command or combine them with name of specific syscalls:

strace -e trace=munmap,/^read,/^open /bin/ls

This command would trace the munmap syscall and all syscalls that start with “read” or “open”.

Limiting by Syscall Category

Specifying all syscalls you want to trace by name and regex is possible but can be cumbersome and it is easy to miss some syscalls. They would then not show up in the trace and you might miss something important.

The good news is that strace has built-in categories of syscalls for things like file access, networking, memory, and much more.

So if you want to only trace the syscalls related to memory mapping called by /bin/ls you could do it with this command:

$ strace -e trace=%memory /bin/ls

The following syscall categories are supported by strace:

CategoryDescription
%fileAll syscalls that have a filename as an argument
%processAll syscalls related to process management
%net %networkAll syscalls related to networking
%signalAll syscalls related to signals
%ipcAll syscalls related to interprocess communication
%descAll syscalls that have a file descriptor as an argument
%memoryAll syscall related to memory mapping
%credsAll syscalls that are related to user, group and capabilities
%statAll variants of stat
%lstatAll variants of lstat
%fstatAll variants of fstat
%%statAll variants of stat, lstat, fstat, fstatat, and statx
%statfsAll variants of statfs
%fstatfsAll variants of fstatfs
%%statfsAll variants of statfs, fstatfs, and ustat
%clockAll syscalls related to reading and modification of the system clock
%pureAll syscalls that have no arguments
The different classes of syscalls as supported by strace

Limiting by File Descriptor

Are you interested in a certain file descriptor and want to see every syscall that operates on it? With the --trace-fds option you can specify one or more file descriptor numbers and strace will show you all the syscalls that operate on them:

$ strace --trace-fds=0,1 /bin/ls

This would give you an output similar to that:

ioctl(1, TCGETS, {c_iflag=ICRNL|IXON|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
ioctl(1, TIOCGWINSZ, {ws_row=40, ws_col=128, ws_xpixel=0, ws_ypixel=0}) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
write(1, "test.txt\n", 9test.txt
)               = 9
close(1)                                = 0
+++ exited with 0 +++

Limiting by Path

Did you ever trace a program with strace and tried to figure out what it does with a certain file path during its operation?

Of course, you can grep the output or save it and search it in your text editor. You can also look for the line in the output where the file is opened, write it down, and search the output for all the syscalls where this file descriptor is accessed.

But strace can do all of this for you with its very convenient --trace-path option (short-form is -P).

To demonstrate this first create a text file:

$ echo "This is some text..." > strace-test.txt

Now we can run cat to output this newly created text file and trace it with strace which we parameterize to trace the name of the file:

$ strace --trace-path=strace-test.txt cat strace-test.txt

This will get us an output similar to this:

strace: Requested path "strace-test.txt" resolved into "/home/user/strace-test.txt"
openat(AT_FDCWD, "strace-test.txt", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0664, st_size=21, ...}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
read(3, "This is some text...\n", 131072) = 21
This is some text...
read(3, "", 131072)                     = 0
close(3)                                = 0
+++ exited with 0 +++

As we see here, strace reported the syscalls which got the filename as a parameter. It also remembered the file descriptor number that was assigned when the file was opened with the openat syscall so it could report all the syscalls that operate on this file descriptor.

Tracing a file can’t be much easier, can it?

In case you want to trace more than one file path you can just add additional --trace-path parameters to your strace command-line.

Limiting by Success/Failure

Do you want to see only the syscalls that succeeded and didn’t return with an error? Then the option --successful-only is your friend:

$ strace --successful-only /bin/ls

This would give us nearly all of the syscalls called by the program. Therefore, for the sake of brevity we omit the output here.

If you only want to see the failing syscalls the option --failed-only will give you what you want:

$ strace --failed-only /bin/ls

The output would look similar to this:

access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
statfs("/sys/fs/selinux", 0x7ffe74655250) = -1 ENOENT (No such file or directory)
statfs("/selinux", 0x7ffe74655250)      = -1 ENOENT (No such file or directory)
access("/etc/selinux/config", F_OK)     = -1 ENOENT (No such file or directory)
+++ exited with 0 +++

Conclusion

Strace has some very powerful features to limit the syscalls it actually traces. In many cases this makes it unnecessary to filter the output with tools like grep or search it in a text editor. These features can be of great help to quickly get exactly the information you want out of strace.

Leave a comment