In i sistemi operativi Unix-like, a cumunicazione di un prugramma cù u mondu esternu è u sistema operatore si faci per mezu di una piccula serie di funzioni - chiamate di sistema. Questu significa chì per i scopi di debugging pò esse utile per spiegà e chjama di u sistema chì sò eseguiti da prucessi.
Una utilità vi aiuta à monitorà a "vita intima" di i prugrammi in Linux strace, chì hè u sughjettu di stu articulu. Esempii di l'usu di l'equipaggiu spia sò accumpagnati da una breve storia strace è una descrizzione di u disignu di tali prugrammi.
L'interfaccia principale trà i prugrammi è u kernel OS in Unix hè e chjama di sistema. chiama sistema, syscalls), l'interazzione di i prugrammi cù u mondu esternu si faci solu per elli.
Ma in a prima versione publica di Unix (Versione 6 Unix, 1975) ùn ci era micca modi convenienti per seguità u cumpurtamentu di i prucessi di l'utilizatori. Per risolve stu prublema, Bell Labs aghjurnerà à a prossima versione (Versione 7 Unix, 1979) hà prupostu un novu sistema chjamatu - ptrace.
ptrace hè statu sviluppatu principarmenti per i debuggers interattivi, ma à a fine di l'anni 80 (in l'era di u cummerciale System V Release 4) nantu à sta basa, i debuggers cuncentrati ristretti-tracciatori di chjama di u sistema-apparsu è sò diventati largamente utilizati.
Prima a listessa versione di strace hè stata publicata da Paul Cronenburg nantu à a lista di mailing comp.sources.sun in 1992 com'è una alternativa à una utilità chjusa trace da Sun. Sia u clone è l'uriginale eranu destinati à SunOS, ma da u 1994 strace hè statu purtatu à System V, Solaris è u Linux sempre più populari.
Oghje strace supporta solu Linux è si basa in u stessu ptrace, overgrown with many extensions.
Manutentore mudernu (è assai attivu). strace - Dmitri Levin. Grazie à ellu, l'utilità hà acquistatu funzioni avanzate cum'è l'iniezione d'errore in e chjama di u sistema, supportu per una larga gamma di architetture è, più impurtante, mascotte. Fonti non ufficiali dichjaranu chì l'scelta hè cascata nantu à l'ostru, per via di a cunsonanza trà a parolla russa "ostruch" è a parolla inglesa "strace".
Hè ancu impurtante chì a chjama di u sistema ptrace è i tracciatori ùn sò mai stati inclusi in POSIX, malgradu una longa storia è implementazione in Linux, FreeBSD, OpenBSD è Unix tradiziunale.
Dispositivu di Strace in una cunchiglia: Piglet Trace
"Ùn vi aspetta micca di capisce questu" (Dennis Ritchie, cumentu in u codice fonte Unix Version 6)
Dapoi a prima zitiddina, ùn possu micca chjappà e scatuli neri: ùn aghju micca ghjucatu cù i ghjoculi, ma pruvatu à capiscenu a so struttura (l'adulti anu utilizatu a parolla "ruttu", ma ùn crede micca e lingue maligni). Forse hè per quessa chì a cultura informale di u primu Unix è u muvimentu mudernu open-source hè cusì vicinu à mè.
Per i scopi di stu articulu, ùn hè micca ragiunate di disassemble u codice fonte di strace, chì hè cresciutu annantu à decennii. Ma ùn deve esse micca sicreti per i lettori. Dunque, per dimustrà u principiu di funziunamentu di tali prugrammi strace, furniraghju u codice per un tracciatore in miniatura - Piglet Trace (ptr). Ùn sà micca fà qualcosa di spiciale, ma a cosa principal hè u sistema di chjama di u prugramma - produce:
Piglet Trace ricunnosce circa centinaie di chjamate di u sistema Linux (vede. tavulinu) è travaglia solu nantu à l'architettura x86-64. Questu hè abbastanza per scopi educativi.
Fighjemu u travagliu di u nostru clone. In u casu di Linux, i debuggers è i tracciatori utilizanu, cum'è chjamatu sopra, a chjama di u sistema ptrace. Funziona passendu in u primu argumentu l'identificatori di cumandamenti, di quale avemu solu bisognu PTRACE_TRACEME, PTRACE_SYSCALL и PTRACE_GETREGS.
U tracciatore principia in u solitu stile Unix: fork(2) lancia un prucessu zitellu, chì in turnu usa exec(3) lancia u prugramma in studiu. L'unica sutilezza quì hè a sfida ptrace(PTRACE_TRACEME) prima exec: U prucessu di u zitellu aspetta chì u prucessu parent per monitorà:
pid_t child_pid = fork();
switch (child_pid) {
case -1:
err(EXIT_FAILURE, "fork");
case 0:
/* Child here */
/* A traced mode has to be enabled. A parent will have to wait(2) for it
* to happen. */
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
/* Replace itself with a program to be run. */
execvp(argv[1], argv + 1);
err(EXIT_FAILURE, "exec");
}
U prucessu parent deve avà chjamà wait(2) in u prucessu di u zitellu, vale à dì, assicuratevi chì u cambiamentu à u modu di traccia hè accadutu:
/* Parent */
/* First we wait for the child to set the traced mode (see
* ptrace(PTRACE_TRACEME) above) */
if (waitpid(child_pid, NULL, 0) == -1)
err(EXIT_FAILURE, "traceme -> waitpid");
À questu puntu, i preparativi sò cumpleti è pudete andà direttamente à seguità e chjama di u sistema in un ciclu infinitu.
Challenge ptrace(PTRACE_SYSCALL) guarantisci chì dopu wait parent compie o prima chì a chjama di u sistema hè eseguita o subitu dopu à a fine. Trà duie chjamate pudete fà qualsiasi azzione: rimpiazzà a chjama cù una alternativa, cambia l'argumenti o u valore di ritornu.
Avemu bisognu di chjamà u cumandamentu duie volte ptrace(PTRACE_GETREGS)pè ottene u statu di registru rax prima di a chjama (numeru di chjama di u sistema) è immediatamente dopu (valore di ritornu).
In fatti, u ciclu:
/* A system call tracing loop, one interation per call. */
for (;;) {
/* A non-portable structure defined for ptrace/GDB/strace usage mostly.
* It allows to conveniently dump and access register state using
* ptrace. */
struct user_regs_struct registers;
/* Enter syscall: continue execution until the next system call
* beginning. Stop right before syscall.
*
* It's possible to change the system call number, system call
* arguments, return value or even avoid executing the system call
* completely. */
if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1)
err(EXIT_FAILURE, "enter_syscall");
if (waitpid(child_pid, NULL, 0) == -1)
err(EXIT_FAILURE, "enter_syscall -> waitpid");
/* According to the x86-64 system call convention on Linux (see man 2
* syscall) the number identifying a syscall should be put into the rax
* general purpose register, with the rest of the arguments residing in
* other general purpose registers (rdi,rsi, rdx, r10, r8, r9). */
if (ptrace(PTRACE_GETREGS, child_pid, NULL, ®isters) == -1)
err(EXIT_FAILURE, "enter_syscall -> getregs");
/* Note how orig_rax is used here. That's because on x86-64 rax is used
* both for executing a syscall, and returning a value from it. To
* differentiate between the cases both rax and orig_rax are updated on
* syscall entry/exit, and only rax is updated on exit. */
print_syscall_enter(registers.orig_rax);
/* Exit syscall: execute of the syscall, and stop on system
* call exit.
*
* More system call tinkering possible: change the return value, record
* time it took to finish the system call, etc. */
if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL) == -1)
err(EXIT_FAILURE, "exit_syscall");
if (waitpid(child_pid, NULL, 0) == -1)
err(EXIT_FAILURE, "exit_syscall -> waitpid");
/* Retrieve register state again as we want to inspect system call
* return value. */
if (ptrace(PTRACE_GETREGS, child_pid, NULL, ®isters) == -1) {
/* ESRCH is returned when a child terminates using a syscall and no
* return value is possible, e.g. as a result of exit(2). */
if (errno == ESRCH) {
fprintf(stderr, "nTracee terminatedn");
break;
}
err(EXIT_FAILURE, "exit_syscall -> getregs");
}
/* Done with this system call, let the next iteration handle the next
* one */
print_syscall_exit(registers.rax);
}
Hè tuttu u tracciatore. Avà sapete induve principià u prossimu portu DTrace nantu à Linux.
Basics: esecuzione di un prugramma in esecuzione strace
Cum'è un primu casu d'usu strace, forse vale a pena citare u modu più simplice - lanciare una applicazione in esecuzione strace.
Per ùn sfondate in a lista infinita di chjama di un prugramma tipicu, scrivimu prugramma minimu вокруг write:
int main(int argc, char *argv[])
{
char str[] = "write me to stdoutn";
/* write(2) is a simple wrapper around a syscall so it should be easy to
* find in the syscall trace. */
if (sizeof(str) != write(STDOUT_FILENO, str, sizeof(str))){
perror("write");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Custruemu u prugramma è assicuratevi chì funziona:
$ gcc examples/write-simple.c -o write-simple
$ ./write-simple
write me to stdout
Moltu "parola" è micca assai educativu. Ci sò dui prublemi quì: l'output di u prugramma hè mischju cù u output strace è una bundanza di chjama di sistema chì ùn ci interessanu micca.
Pudete separà u flussu di output standard di u prugramma è l'output di l'errore di strace usendu u -o switch, chì redirige a lista di e chjama di u sistema à un schedariu d'argumentu.
Resta da trattà cù u prublema di chjamate "extra". Assumimu chì ci interessanu solu e chjama write. Chjave -e permette di specificà l'espressioni da quale e chjama di u sistema seranu filtrate. L'opzione di cundizione più populari hè, naturalmente, trace=*, cù quale pudete lascià solu e chjama chì ci interessanu.
Quandu s'utilice simultaneamente -o и -e averemu:
$ strace -e trace=write -owrite-simple.log ./write-simple
write me to stdout
$ cat write-simple.log
write(1, "write me to stdoutn", 20
) = 20
+++ exited with 0 +++
Allora, vede, hè assai più faciule di leghje.
Pudete ancu sguassà e chjama di u sistema, per esempiu, quelli chì sò ligati à l'allocazione di memoria è a liberazione:
$ strace -e trace=!brk,mmap,mprotect,munmap -owrite-simple.log ./write-simple
write me to stdout
$ cat write-simple.log
execve("./write-simple", ["./write-simple"], 0x7ffe9972a498 /* 69 vars */) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=124066, ...}) = 0
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF21133>1260342"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7f00f0be74c0) = 0
write(1, "write me to stdoutn", 20) = 20
exit_group(0) = ?
+++ exited with 0 +++
Nota u segnu d'esclamazione scappatu in a lista di e chjama escluse: questu hè necessariu da a shell di cumanda. conchiglia).
In a mo versione di glibc, una chjama di u sistema termina u prucessu exit_group, micca tradiziunale _exit. Questa hè a difficultà di travaglià cù e chjama di u sistema: l'interfaccia cù quale u prugrammatore travaglia ùn hè micca direttamente ligata à e chjama di u sistema. Inoltre, cambia regularmente secondu l'implementazione è a piattaforma.
Basics: unisce à u prucessu nantu à a mosca
In principiu, u sistema ptrace chjama nantu à quale hè statu custruitu strace, puderia esse usatu solu quandu eseguisce u prugramma in un modu speciale. Questa limitazione pò avè sonatu raghjone in i ghjorni di a Versione 6 Unix. Oghje, questu ùn hè più abbastanza: qualchì volta avete bisognu à investigà i prublemi di un prugramma di travagliu. Un esempiu tipicu hè un prucessu bluccatu nantu à un manicu o dorme. Dunque mudernu strace pò unisce à i prucessi nantu à a mosca.
$ ./write-sleep &
[1] 15329
write me
$ strace -p 15329
strace: Process 15329 attached
pause(
^Cstrace: Process 15329 detached
<detached ...>
Prugramma bluccatu da chjama pause. Videmu cumu reagisce à i signali:
$ strace -o write-sleep.log -p 15329 &
strace: Process 15329 attached
$
$ kill -CONT 15329
$ cat write-sleep.log
pause() = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=14989, si_uid=1001} ---
pause(
$
$ kill -TERM 15329
$ cat write-sleep.log
pause() = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=14989, si_uid=1001} ---
pause() = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=14989, si_uid=1001} ---
+++ killed by SIGTERM +++
Avemu lanciatu u prugramma congelatu è unitu cù l'usu strace. Dui cose sò diventati chjaru: a chjama di u sistema di pausa ignora i signali senza handlers è, più interessante, strace monitors micca solu e chjama di u sistema, ma ancu i signali entranti.
Esempiu: Tracking Child Processes
U travagliu cù prucessi attraversu una chjama fork - a basa di tutti l'Unixes. Videmu cumu u strace travaglia cù un arbulu di prucessu usendu l'esempiu di un "rilevamentu" simplice. programmi:
int main(int argc, char *argv[])
{
pid_t parent_pid = getpid();
pid_t child_pid = fork();
if (child_pid == 0) {
/* A child is born! */
child_pid = getpid();
/* In the end of the day printf is just a call to write(2). */
printf("child (self=%d)n", child_pid);
exit(EXIT_SUCCESS);
}
printf("parent (self=%d, child=%d)n", parent_pid, child_pid);
wait(NULL);
exit(EXIT_SUCCESS);
}
Quì u prucessu uriginale crea un prucessu di u zitellu, tramindui scrivite in output standard:
A bandiera vi aiuta à seguità tuttu l'arburu di u prucessu -f, chì strace monitors chjama sistema in i prucessi di u zitellu. Questu aghjunghje à ogni linea di output pid prucessu chì face un output di u sistema:
Per via, chì chjama di u sistema hè utilizatu per creà un novu prucessu?
Esempiu: percorsi di file invece di manichi
A cunniscenza di descriptori di schedari hè certamente utile, ma i nomi di i schedarii specifichi chì accede à un prugramma pò ancu esse utile.
U prossimu u prugramma scrive a linea à u schedariu tempurale:
void do_write(int out_fd)
{
char str[] = "write me to a filen";
if (sizeof(str) != write(out_fd, str, sizeof(str))){
perror("write");
exit(EXIT_FAILURE);
}
}
int main(int argc, char *argv[])
{
char tmp_filename_template[] = "/tmp/output_fileXXXXXX";
int out_fd = mkstemp(tmp_filename_template);
if (out_fd == -1) {
perror("mkstemp");
exit(EXIT_FAILURE);
}
do_write(out_fd);
return EXIT_SUCCESS;
}
Durante una chjama normale strace mostrarà u valore di u numeru di descrittore passatu à a chjama di u sistema:
$ strace -e trace=write -o write-tmp-file.log ./write-tmp-file
$ cat write-tmp-file.log
write(3, "write me to a filen", 20) = 20
+++ exited with 0 +++
Cù una bandiera -y L'utilità mostra u percorsu à u schedariu à quale currisponde u descrittore:
$ strace -y -e trace=write -o write-tmp-file.log ./write-tmp-file
$ cat write-tmp-file.log
write(3</tmp/output_fileCf5MyW>, "write me to a filen", 20) = 20
+++ exited with 0 +++
Esempiu: Tracking Access File
Un'altra funzione utile: mostra solu e chjama di u sistema assuciatu cù un schedariu specificu. Dopu u prugramma aghjunghje una linea à un schedariu arbitrariu passatu cum'è argumentu:
void do_write(int out_fd)
{
char str[] = "write me to a filen";
if (sizeof(str) != write(out_fd, str, sizeof(str))){
perror("write");
exit(EXIT_FAILURE);
}
}
int main(int argc, char *argv[])
{
/*
* Path will be provided by the first program argument.
* */
const char *path = argv[1];
/*
* Open an existing file for writing in append mode.
* */
int out_fd = open(path, O_APPEND | O_WRONLY);
if (out_fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
do_write(out_fd);
return EXIT_SUCCESS;
}
automaticamente strace mostra assai infurmazione inutile. Bandiera -P cù un argumentu face chì strace imprima solu chjamate à u schedariu specificatu:
$ strace -y -P/tmp/test_file.log -o write-file.log ./write-file /tmp/test_file.log
$ cat write-file.log
openat(AT_FDCWD, "/tmp/test_file.log", O_WRONLY|O_APPEND) = 3</tmp/test_file.log>
write(3</tmp/test_file.log>, "write me to a filen", 20) = 20
+++ exited with 0 +++
Esempiu: Programmi Multithreaded
Utilità strace pò ancu aiutà quandu u travagliu cù multi-threaded u prugramma. U prugramma seguente scrive à a pruduzzioni standard da dui flussi:
void *thread(void *arg)
{
(void) arg;
printf("Secondary thread: workingn");
sleep(1);
printf("Secondary thread: donen");
return NULL;
}
int main(int argc, char *argv[])
{
printf("Initial thread: launching a threadn");
pthread_t thr;
if (0 != pthread_create(&thr, NULL, thread, NULL)) {
fprintf(stderr, "Initial thread: failed to create a thread");
exit(EXIT_FAILURE);
}
printf("Initial thread: joining a threadn");
if (0 != pthread_join(thr, NULL)) {
fprintf(stderr, "Initial thread: failed to join a thread");
exit(EXIT_FAILURE);
};
printf("Initial thread: done");
exit(EXIT_SUCCESS);
}
Naturalmente, deve esse cumpilatu cù un salutu speciale à u linker - a bandiera -pthread:
$ gcc examples/thread-write.c -pthread -o thread-write
$ ./thread-write
/thread-write
Initial thread: launching a thread
Initial thread: joining a thread
Secondary thread: working
Secondary thread: done
Initial thread: done
$
Bandiera -f, cum'è in u casu di prucessi regulare, aghjunghje u pid di u prucessu à u principiu di ogni linea.
Naturalmente, ùn parlemu micca di un identificatore di filu in u sensu di l'implementazione di u standard POSIX Threads, ma di u numeru utilizatu da u pianificatore di task in Linux. Da u puntu di vista di l'ultime, ùn ci sò micca prucessi o filamenti - ci sò compiti chì deve esse distribuitu trà i nuclei dispunibuli di a macchina.
Quandu travaglia in parechje fili, e chjama di u sistema diventanu troppu:
Per via, dumande. Chì chjama di sistema hè utilizata per creà un novu filu? Cumu hè questa chjamata per i fili difiere da a chjama per i prucessi?
Master class: stack di prucessu à u mumentu di una chjama di u sistema
Unu di i recentemente apparsu strace capacità - affissà a pila di chjama di funzione à u mumentu di a chjama di u sistema. Semplice esempiu:
void do_write(void)
{
char str[] = "write me to stdoutn";
if (sizeof(str) != write(STDOUT_FILENO, str, sizeof(str))){
perror("write");
exit(EXIT_FAILURE);
}
}
int main(int argc, char *argv[])
{
do_write();
return EXIT_SUCCESS;
}
Naturalmente, a pruduzzione di u prugramma diventa assai voluminosa, è, in più di a bandiera -k (display di stack di chjama), hè sensu di filtrà e chjama di u sistema per nome:
$ gcc examples/write-simple.c -o write-simple
$ strace -k -e trace=write -o write-simple.log ./write-simple
write me to stdout
$ cat write-simple.log
write(1, "write me to stdoutn", 20) = 20
> /lib/x86_64-linux-gnu/libc-2.27.so(__write+0x14) [0x110154]
> /home/vkazanov/projects-my/strace-post/write-simple(do_write+0x50) [0x78a]
> /home/vkazanov/projects-my/strace-post/write-simple(main+0x14) [0x7d1]
> /lib/x86_64-linux-gnu/libc-2.27.so(__libc_start_main+0xe7) [0x21b97]
> /home/vkazanov/projects-my/strace-post/write-simple(_start+0x2a) [0x65a]
+++ exited with 0 +++
Master class: iniezione di errore
È una altra funzione nova è assai utile: iniezione d'errore. Quì u prugramma, scrivendu duie linee à u flussu di output:
Hè interessante ciò chì l'errori sò tornati tutte e sfidi write, cumpresa a chjama ammucciata daretu à u peru. Hè solu sensu di vultà un errore per a prima di e chjama:
$ strace -e trace=write -e inject=write:error=EBADF:when=1 -owrite-twice.log ./write-twice
write: Bad file descriptor
$ cat write-twice.log
write(1, "write me 1n", 12) = -1 EBADF (Bad file descriptor) (INJECTED)
write(3, "write: Bad file descriptorn", 27) = 27
+++ exited with 1 +++
O u sicondu:
$ strace -e trace=write -e inject=write:error=EBADF:when=2 -owrite-twice.log ./write-twice
write me 1
write: Bad file descriptor
$ cat write-twice.log
write(1, "write me 1n", 12) = 12
write(1, "write me 2n", 12) = -1 EBADF (Bad file descriptor) (INJECTED)
write(3, "write: Bad file descriptorn", 27) = 27
+++ exited with 1 +++
Ùn hè micca necessariu di specificà u tipu d'errore:
$ strace -e trace=write -e fault=write:when=1 -owrite-twice.log ./write-twice
$ cat write-twice.log
write(1, "write me 1n", 12) = -1 ENOSYS (Function not implemented) (INJECTED)
write(3, "write: Function not implementedn", 32) = 32
+++ exited with 1 +++
In cumbinazione cù altre bandiere, pudete "rumpià" l'accessu à un schedariu specificu. Esempiu:
$ strace -y -P/tmp/test_file.log -e inject=file:error=ENOENT -o write-file.log ./write-file /tmp/test_file.log
open: No such file or directory
$ cat write-file.log
openat(AT_FDCWD, "/tmp/test_file.log", O_WRONLY|O_APPEND) = -1 ENOENT (No such file or directory) (INJECTED)
+++ exited with 1 +++
Oltre à l'iniezione di errore, pò intruduce ritardi quandu facenu chjamate o riceve signali.
Afterword
Utilità strace - un strumentu simplice è affidabile. Ma in più di e chjama di u sistema, altri aspetti di u funziunamentu di i prugrammi è u sistema upirativu ponu esse debugged. Per esempiu, pò seguità e chjama à biblioteche ligati dinamicamente. ltrace, ponu guardà in u funziunamentu di u sistema operatore SystemTap и ftrace, è vi permette di investigà in fondu u rendiment di u prugramma perfetta. Tuttavia, hè strace - a prima linea di difesa in casu di prublemi cù u mo propiu è i prugrammi di l'altri, è l'aghju utilizatu almenu un paru di volte à settimana.
In corta, se ti piace Unix, leghjite man 1 strace è sentite liberu di vede i vostri prugrammi!