strace i ltrace


strace jest narzędziem pozwalającym badać interakcję dowolnego programu z jądrem systemu operacyjnego. W najprostszym przypadku uruchamia dany program i przechwytuje generowane przez niego wywołania funkcji systemowych oraz sygnały przez niego otrzymywane. Nazwa każdej funkcji, razem z jej argumentami oraz zwracaną wartością są kierowane na wyjście diagnostyczne lub do zadanego pliku (którego nazwę należy podać używając opcji -o), np:

$ strace mkdir tmp
execve("/bin/mkdir", ["mkdir", "tmp"], [/* 29 vars */]) = 0
uname({sys="Linux", node="xubuntu", ...}) = 0
brk(0)                                  = 0x804e000
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f92000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f91000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f90000
open("/etc/ld.so.cache", O_RDONLY)      = 3

(...)

W przypadku błędu otrzymuje się informację o jego nazwie symbolicznej wraz z odpowiednim opisem, np:

open("/usr/lib/locale/en_US.UTF-8/LC_NUMERIC", O_RDONLY) = -1 ENOENT (No such file or directory)

ltrace jest narzędziem podobnym, z tą różnicą, że śledzi wywołania funkcji bibliotecznych (l- jak library).

Przykładowe dane, generowane przez

$ ltrace date

wygldają następująco:

__libc_start_main(0x8049505, 1, 0xbfe92424, 0x804fa74, 0x804fac5 <unfinished ...>
setlocale(6, "")                                                        = "en_US.UTF-8"
bindtextdomain("coreutils", "/usr/share/locale")                        = "/usr/share/locale"
textdomain("coreutils")                                                 = "coreutils"
__cxa_atexit(0x804bb2d, 0, 0, 0xb7f68adc, 0xbfe92398)                   = 0
getopt_long(1, 0xbfe92424, "d:f:I::r:Rs:u", 0x804fd00, NULL)            = -1
nl_langinfo(131180, 0xbfe92424, 0x804fde0, 0x804fd00, 0)                = 0xb7d53915
clock_gettime(0, 0xbfe9237c, 0xbfe92424, 1, 0xbfe92398)                 = 0
localtime(0xbfe92278)                                                   = 0xb7f6c500
strftime(" Tue", 1024, " %a", 0xb7f6c500)                               = 4
fwrite("Tue", 3, 1, 0xb7f690e0)                                         = 1

(...)

Istotną cechą obu tych narzędzi jest fakt, że do pracy z danym programem nie jest potrzebne posiadanie jego źródeł, nie musi on być również kompilowany w specjalny sposób (np. z opcją -g, jak to ma miejsce w przypadku gdb)

Znaczącą niedogodnocią jest natomiast ilość generowanych danych, która powoduje, że trudno z nich wyłowić istotne informacje - szczególnie w przypadku większych programów. Na przykład

$ strace ls

może generować kilkaset linii komunikatów:

execve("/bin/ls", ["ls"], [/* 29 vars */]) = 0
uname({sys="Linux", node="xubuntu", ...}) = 0
brk(0)                                  = 0x805c000
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f1e000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f1d000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f1c000
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=30762, ...}) = 0
old_mmap(NULL, 30762, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7f14000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/cmov/librt.so.1", 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\0\20\36\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=28116, ...}) = 0
...

Dlatego należy pamiętać, że informacje generowane przz strace i ltrace służą przede wszystkim do przeszukiwania, zamiast czytania w całości.

Ze względu na powyższe oraz fakt, że w przypadku wystąpienia błędu informacja o nim jest zwykle mało precyzyjna (może się zdarzyć, że nie wiadomo lub trudno jest ustalić która dokładnie instrukcja wywołała błąd), użyteczność strace i ltrace w debugowaniu jest ograniczona. Narzędzia te przydają się zwykle tam, gdzie ważne jest poznanie interakcji różnych programów z jądrem systemu operacyjnego. Przykładem takiej sytuacji może być program, korzystający z pewnych zewnętrznych plików, ale w przypadku ich braku kończy się bez wyświetlenia odpowiedniej informacji o błędzie. Dzięki strace można wtedy rozpoznać błąd i uzyskac informację o nazwie brakującego pliku.



Bibliografia

www.gnome.org/~newren/tutorials/developing-with-gnome/html/ch03.html
strony manuala strace i ltrace