Many times we come across hopeless situations where a program when compiled and installed in GNU/Linux just fails to run. Then we have to trace the output of the misbehaving program. But tracing the output of a program throws up a lot of data and it is a daunting task to go through volumes of data. Still there are cases where we are not fruitful in pin pointing the cause of error.
In this situation
strace also known as system-call tracer comes for rescue. It is a debugging tool that monitors the system calls used by a program and all the signals it receives.
System administrators, diagnosticians and trouble-shooters will find it invaluable for solving problems with programs for which the source is not readily available since they do not need to be recompiled in order to trace them. Students, hackers and the overly-curious will find that a great deal can be learned about a system and its system calls by tracing even ordinary programs. And programmers will find that since system calls and signals are events that happen at the user/kernel interface, a close examination of this boundary is very useful for bug isolation, sanity checking and attempting to capture race conditions.
A system call is the most common way programs communicate with the kernel. System calls include reading and writing data, opening and closing files and all kinds of network communication. Under Linux, a system call is done by calling a special interrupt with the number of the system call and its parameters stored in the CPU's registers.
Using
strace is quite simple. There are two ways to let
strace monitor a program.
FIRST METHOD
Start a program using
strace command as shown below which prints the list of system calls made by the program.
strace program-name
For example let us trace
uname command.
$strace uname
execve("/bin/uname", ["uname"], [/* 23 vars */]) = 0
brk(0) = 0x871d000
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f30000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=59428, ...}) = 0
mmap2(NULL, 59428, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7f21000
close(3) = 0
open("/lib/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\220\272"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1577052, ...}) = 0
mmap2(0x45b93000, 1295780, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x45b93000
mmap2(0x45cca000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x137) = 0x45cca000
mmap2(0x45ccd000, 9636, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x45ccd000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f20000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7f206c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0x45cca000, 8192, PROT_READ) = 0
mprotect(0x451dd000, 4096, PROT_READ) = 0
munmap(0xb7f21000, 59428) = 0
brk(0) = 0x871d000
brk(0x873e000) = 0x873e000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=55511552, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7d20000
close(3) = 0
uname({sys="Linux", node="localhost.localdomain", ...}) = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f2f000
write(1, "Linux\n", 6Linux
) = 6
close(1) = 0
munmap(0xb7f2f000, 4096) = 0
exit_group(0) = ?
Process 7477 detached
Even though output from
strace looks very complicated, but this is only due to many system calls made when loading shared libraries. However, once we have found which system calls are the important ones (mainly open, read, write and the like), the results will look fairly intuitive to us.
SECOND METHOD
We can use the
-p flag to attach to a running process and debug a running process. Let us try out on the sample code shown below.
#include <stdio.h>
#include <unistd.h>
int main()
{
sleep(20);
return 0;
}
We will compile the above code and run it as a background process. Then using its process id and -p flag we will run
strace on it.
$ gcc main.c
$ ./a.out &
[1] 7609
$ strace -p 7609
Process 7609 attached - interrupt to quit
restart_syscall(<... resuming interrupted call ...>) = 0
exit_group(0) = ?
Process 7609 detached
[1]+ Done ./a.out
Using
-p flag we even debug daemon processes. In contrast to a debugger,
strace does not need a program's source code to produce human-readable output.
But sometimes
strace produces great amount of data which creates problems in scrolling through huge output. In this case we can use
-o option as shown below.
$ strace -o uname_output uname
If you use the
-t option, then
strace will prefix each line of the trace with the time of day. We can even specify the system call functions to trace using the
-e option. For example, to trace only open() and close() function system calls use the following command:
$ strace -o uname_output -e trace=open,close uname
References:
1.
OS Review