Strace am Linux: Geschicht, Design a Gebrauch

Strace am Linux: Geschicht, Design a Gebrauch

An Unix-ähnleche Betribssystemer geschitt d'Kommunikatioun vun engem Programm mat der Äussewelt an dem Betribssystem duerch e klenge Set vu Funktiounen - Systemruffen. Dëst bedeit datt et fir Debuggingszwecker nëtzlech ka sinn fir Systemappellen ze spionéieren déi duerch Prozesser ausgefouert ginn.

En Utility hëlleft Iech dat "intimt Liewen" vu Programmer op Linux ze iwwerwaachen strace, wat d'Thema vun dësem Artikel ass. Beispiller vun der Notzung vun Spioun Equipement gi vun enger kuerzer Geschicht begleet strace an eng Beschreiwung vum Design vun esou Programmer.

Inhalt

Urspronk vun Aarten

D'Haaptinterface tëscht Programmer an dem OS Kernel an Unix ass System Uriff. System rifft, syscalls), d'Interaktioun vu Programmer mat der Äussewelt geschitt exklusiv duerch si.

Awer an der éischter ëffentlecher Versioun vun Unix (Versioun 6 Unix, 1975) gouf et keng praktesch Weeër fir d'Behuele vu Benotzerprozesser ze verfolgen. Fir dëst Thema ze léisen, wäert Bell Labs op déi nächst Versioun aktualiséieren (Versioun 7 Unix, 1979) en neie Systemruff proposéiert - ptrace.

ptrace gouf haaptsächlech fir interaktiv Debugger entwéckelt, awer um Enn vun den 80er Joren (an der Ära vum kommerziellen) System V Release 4) op dëser Basis, schmuel fokusséiert Debugger - System Call Tracers - opgetaucht a vill benotzt ginn.

Déi éischt déi selwecht Versioun vu Strace gouf vum Paul Cronenburg op der comp.sources.sun Mailing Lëscht am Joer 1992 als Alternativ zu engem zouenen Utility publizéiert. trace vun Sonn. Souwuel de Klon wéi d'Original ware fir SunOS geduecht, awer bis 1994 strace gouf op System V, Solaris an déi ëmmer méi populär Linux portéiert.

Haut Strace ënnerstëtzt nëmmen Linux a setzt op datselwecht ptrace, iwwerwältegt mat ville Verlängerungen.

Modern (a ganz aktiven) Ënnerhalter strace - Dmitry Levin. Dank him huet d'Utility fortgeschratt Features kritt wéi Fehlerinjektioun a Systemruffen, Ënnerstëtzung fir eng breet Palette vun Architekturen an, am wichtegsten, Maskottchen. Inoffiziell Quelle behaapten datt d'Wiel op de Struif gefall ass wéinst der Konsonanz tëscht dem russesche Wuert "Strauss" an dem englesche Wuert "strace".

Et ass och wichteg datt de ptrace System Uruff an Tracer ni am POSIX abegraff goufen, trotz enger laanger Geschicht an Implementatioun an Linux, FreeBSD, OpenBSD an traditionell Unix.

Strace Apparat an enger Nossschuel: Piglet Trace

"Dir gëtt net erwaart dëst ze verstoen" (Dennis Ritchie, Kommentar an der Versioun 6 Unix Quellcode)

Zënter fréi Kandheet kann ech net schwaarz Këschte stoen: Ech hunn net mat Spillsaachen gespillt, awer probéiert hir Struktur ze verstoen (Erwuessener hunn d'Wuert "gebrach", awer gleewen net déi béis Sproochen). Vläicht ass dat firwat déi informell Kultur vun der éischter Unix an der moderner Open-Source Bewegung sou no bei mir ass.

Fir den Zweck vun dësem Artikel ass et onverständlech de Quellcode vu Strace ze disassemble, deen iwwer Joerzéngte gewuess ass. Awer et sollt keng Geheimnisser fir d'Lieser bleiwen. Dofir, fir de Prinzip vun der Operatioun vun esou Strace Programmer ze weisen, ginn ech de Code fir e Miniatur Tracer - Piglet Trace (ptr). Et weess net wéi eppes Besonnesches ze maachen, awer den Haapt Saach ass d'Systemappellen vum Programm - et gëtt eraus:

$ gcc examples/piglet-trace.c -o ptr
$ ptr echo test > /dev/null
BRK(12) -> 94744690540544
ACCESS(21) -> 18446744073709551614
ACCESS(21) -> 18446744073709551614
unknown(257) -> 3
FSTAT(5) -> 0
MMAP(9) -> 140694657216512
CLOSE(3) -> 0
ACCESS(21) -> 18446744073709551614
unknown(257) -> 3
READ(0) -> 832
FSTAT(5) -> 0
MMAP(9) -> 140694657208320
MMAP(9) -> 140694650953728
MPROTECT(10) -> 0
MMAP(9) -> 140694655045632
MMAP(9) -> 140694655070208
CLOSE(3) -> 0
unknown(158) -> 0
MPROTECT(10) -> 0
MPROTECT(10) -> 0
MPROTECT(10) -> 0
MUNMAP(11) -> 0
BRK(12) -> 94744690540544
BRK(12) -> 94744690675712
unknown(257) -> 3
FSTAT(5) -> 0
MMAP(9) -> 140694646390784
CLOSE(3) -> 0
FSTAT(5) -> 0
IOCTL(16) -> 18446744073709551591
WRITE(1) -> 5
CLOSE(3) -> 0
CLOSE(3) -> 0
unknown(231)
Tracee terminated

Piglet Trace erkennt ongeféier Honnerte vu Linux System Appellen (kuckt. Dësch) a funktionnéiert nëmmen op x86-64 Architektur. Dëst ass genuch fir pädagogesch Zwecker.

Loosst eis d'Aarbecht vun eisem Klon kucken. Am Fall vu Linux, Debugger an Tracer benotzen, wéi uewen ernimmt, de ptrace System Call. Et funktionnéiert andeems Dir am éischten Argument d'Kommandoidentifizéierer passéiert, vun deenen mir nëmmen brauchen PTRACE_TRACEME, PTRACE_SYSCALL и PTRACE_GETREGS.

De Tracer fänkt am üblechen Unix-Stil un: fork(2) lancéiert e Kand Prozess, déi am Tour benotzt exec(3) lancéiert de Programm ënner Studie. Déi eenzeg Subtilitéit hei ass d'Erausfuerderung ptrace(PTRACE_TRACEME) virdrun exec: De Kannerprozess erwaart datt den Elterende Prozess et iwwerwaacht:

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");
}

D'Elteren Prozess soll elo ruffen wait(2) am Kand Prozess, dat ass, sécherstellen, datt de Wiessel op Spuer Modus geschitt ass:

/* 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");

Zu dësem Zäitpunkt sinn d'Preparatiounen fäerdeg an Dir kënnt direkt weidergoen fir System Uriff an enger endloser Loop ze verfolgen.

Rufft ptrace(PTRACE_SYSCALL) garantéiert datt duerno wait Elterendeel wäert entweder fäerdeg sinn ier de System Uruff ausgefouert gëtt oder direkt nodeems se ofgeschloss ass. Tëscht zwee Uruff kënnt Dir all Aktiounen ausféieren: den Uruff duerch en alternativen ersetzen, d'Argumenter änneren oder de Retourwäert.

Mir brauchen just de Kommando zweemol ze ruffen ptrace(PTRACE_GETREGS)de Registerstaat ze kréien rax virum Opruff (System Call Zuel) an direkt no (Retour Wäert).

Eigentlech, den Zyklus:

/* 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, &registers) == -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, &registers) == -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);
}

Dat ass de ganzen Tracer. Elo wësst Dir wou Dir déi nächst Porting ufänken DTrace op Linux.

Basics: Lafen e Programm Lafen strace

Als éischt Benotzungsfall strace, vläicht ass et derwäert déi einfachst Method ze zitéieren - eng Applikatioun ze starten strace.

Fir net an déi endlos Lëscht vun Uriff vun engem typesche Programm ze verdéiwen, schreiwen mir Minimum Programm ronderëm 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;
}

Loosst eis de Programm bauen a sécherstellen datt et funktionnéiert:

$ gcc examples/write-simple.c -o write-simple
$ ./write-simple
write me to stdout

A schliisslech lafe mer et ënner Strace Kontroll:

$ strace ./write-simple
pexecve("./write", ["./write"], 0x7ffebd6145b0 /* 71 vars */) = 0
brk(NULL)                               = 0x55ff5489e000
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=197410, ...}) = 0
mmap(NULL, 197410, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f7a2a633000
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
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7a2a631000
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f7a2a04c000
mprotect(0x7f7a2a233000, 2097152, PROT_NONE) = 0
mmap(0x7f7a2a433000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f7a2a433000
mmap(0x7f7a2a439000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f7a2a439000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f7a2a6324c0) = 0
mprotect(0x7f7a2a433000, 16384, PROT_READ) = 0
mprotect(0x55ff52b52000, 4096, PROT_READ) = 0
mprotect(0x7f7a2a664000, 4096, PROT_READ) = 0
munmap(0x7f7a2a633000, 197410)          = 0
write(1, "write me to stdoutn", 20write me to stdout
)  = 20
exit_group(0)                           = ?

Ganz "worteg" an net ganz pädagogesch. Et ginn zwee Probleemer hei: de Programmausgang gëtt mat der Ausgang gemëscht strace an en Iwwerfloss vu Systemappellen déi eis net interesséieren.

Dir kënnt de Standardausgangsstroum vum Programm trennen an d'Strace-Fehlerausgang mat dem -o-Schalter trennen, deen d'Lëscht vu Systemruffen op eng Argumentdatei ëmgeleet.

Et bleift mam Problem vun "extra" Uriff ze këmmeren. Loosst eis dovun ausgoen, datt mir nëmme fir Uruff interesséiert sinn write. Schlëssel -e erlaabt Iech Ausdréck ze spezifizéieren duerch déi System Uriff gefiltert ginn. Déi populärste Konditiounsoptioun ass natierlech, trace=*, mat deenen Dir nëmmen déi Uriff loosse kënnt, déi eis interesséieren.

Wann gläichzäiteg benotzt -o и -e mir kréien:

$ 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 +++

Also, Dir gesitt, et ass vill méi einfach ze liesen.

Dir kënnt och Systemappellen ewechhuelen, zum Beispill déi am Zesummenhang mat der Erënnerungsallokatioun a Befreiung:

$ 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 +++

Notéiert dat entkomment Ausrufezeeche an der Lëscht vun ausgeschlossenen Uruff: dëst ass vun der Kommandoshell erfuerderlech. Réibau).

A menger Versioun vu glibc schléisst e Systemruff de Prozess of exit_group, net traditionell _exit. Dëst ass d'Schwieregkeet fir mat Systemappellen ze schaffen: d'Interface mat deem de Programméierer funktionnéiert ass net direkt mat Systemriffe verbonnen. Ausserdeem ännert se regelméisseg ofhängeg vun der Implementatioun an der Plattform.

Basics: de Prozess op der Flucht matzemaachen

Ufank, de ptrace System Opruff op déi et gebaut gouf strace, konnt nëmme benotzt ginn wann de Programm an engem speziellen Modus leeft. Dës Begrenzung kann an den Deeg vun der Versioun 6 Unix raisonnabel geklongen hunn. Haut geet dat net méi duer: heiansdo musst Dir d'Problemer vun engem funktionnéierende Programm ënnersichen. En typescht Beispill ass e Prozess blockéiert op engem Grëff oder schlofen. Dofir modern strace kënne Prozesser op der Flucht matmaachen.

Afréiere Beispill Programmer:

int main(int argc, char *argv[])
{
    (void) argc; (void) argv;

    char str[] = "write men";

    write(STDOUT_FILENO, str, sizeof(str));

    /* Sleep indefinitely or until a signal arrives */
    pause();

    write(STDOUT_FILENO, str, sizeof(str));

    return EXIT_SUCCESS;
}

Loosst eis de Programm bauen a sécherstellen datt et gefruer ass:

$ gcc examples/write-sleep.c -o write-sleep
$ ./write-sleep
./write-sleep
write me
^C
$

Elo probéiere mer dobäi ze sinn:

$ ./write-sleep &
[1] 15329
write me
$ strace -p 15329
strace: Process 15329 attached
pause(
^Cstrace: Process 15329 detached
 <detached ...>

Programm blockéiert duerch Uruff pause. Loosst eis kucken wéi hatt op d'Signaler reagéiert:

$ 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 +++

Mir hunn de gefruerene Programm lancéiert a si matgemaach strace. Zwou Saache goufe kloer: de Paussystemruff ignoréiert Signaler ouni Handler a, méi interessant, Strace iwwerwaacht net nëmmen System Uriff, awer och Entréeën Signaler.

Beispill: Tracking Kand Prozesser

Schafft mat Prozesser duerch en Opruff fork - d'Basis vun all Unixen. Loosst eis kucken wéi d'Strace mat engem Prozessbaum funktionnéiert mam Beispill vun enger einfacher "Zucht" Programmer:

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);
}

Hei erstellt den urspréngleche Prozess e Kandprozess, souwuel op Standardoutput schreiwen:

$ gcc examples/fork-write.c -o fork-write
$ ./fork-write
parent (self=11274, child=11275)
child (self=11275)

Par défaut, wäerte mir nëmmen System Appellen aus dem Elterendeel Prozess gesinn:

$ strace -e trace=write -ofork-write.log ./fork-write
child (self=22049)
parent (self=22048, child=22049)
$ cat fork-write.log
write(1, "parent (self=22048, child=22049)"..., 33) = 33
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=22049, si_uid=1001, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

De Fändel hëlleft Iech de ganze Prozessbaum ze verfolgen -f, déi strace iwwerwaacht System Appellen am Kand Prozesser. Dëst füügt all Ausgangslinn un pid Prozess deen e Systemoutput mécht:

$ strace -f -e trace=write -ofork-write.log ./fork-write
parent (self=22710, child=22711)
child (self=22711)
$ cat fork-write.log
22710 write(1, "parent (self=22710, child=22711)"..., 33) = 33
22711 write(1, "child (self=22711)n", 19) = 19
22711 +++ exited with 0 +++
22710 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=22711, si_uid=1001, si_status=0, si_utime=0, si_stime=0} ---
22710 +++ exited with 0 +++

An dësem Kontext kann Filteren no Grupp vu Systemruffen nëtzlech sinn:

$ strace -f -e trace=%process -ofork-write.log ./fork-write
parent (self=23610, child=23611)
child (self=23611)
$ cat fork-write.log
23610 execve("./fork-write", ["./fork-write"], 0x7fff696ff720 /* 63 vars */) = 0
23610 arch_prctl(ARCH_SET_FS, 0x7f3d03ba44c0) = 0
23610 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f3d03ba4790) = 23611
23610 wait4(-1,  <unfinished ...>
23611 exit_group(0)                     = ?
23611 +++ exited with 0 +++
23610 <... wait4 resumed> NULL, 0, NULL) = 23611
23610 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=23611, si_uid=1001, si_status=0, si_utime=0, si_stime=0} ---
23610 exit_group(0)                     = ?
23610 +++ exited with 0 +++

Iwwregens, wat System Uruff gëtt benotzt fir en neie Prozess ze kreéieren?

Beispill: Dateiweeër anstatt Handle

Wësse vun Dateidescriptoren ass sécherlech nëtzlech, awer d'Nimm vun de spezifesche Dateien op déi e Programm zougräifen kënnen och praktesch kommen.

Déi nächst Programm schreift d'Linn an déi temporär Datei:

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;
}

Während engem normalen Uruff strace wäert de Wäert vun der Deskriptornummer weisen, déi un de Systemruff passéiert ass:

$ 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 +++

Mat engem Fändel -y D'Utility weist de Wee op d'Datei op déi den Deskriptor entsprécht:

$ 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 +++

Beispill: File Access Tracking

Aner nëtzlech Fonktioun: nëmmen System Uriff mat enger spezifescher Datei assoziéiert weisen. Nächst Programm fügt eng Zeil un eng arbiträr Datei, déi als Argument passéiert ass:

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;
}

Par défaut strace weist vill onnéideg Informatioun. Fändel -P mat engem Argument verursaacht Strace fir nëmmen Uruff un déi spezifizéiert Datei ze drécken:

$ 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 +++

Beispill: Multithreaded Programmer

Utility strace kann och hëllefen wann Dir mat Multi-threaded schafft Programm. De folgende Programm schreift op Standardoutput vun zwee Streamen:

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);
}

Natierlech muss et mat enger spezieller Begréissung op de Linker zesummegesat ginn - de -pthread Fändel:

$ 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
$

Fändel -f, wéi am Fall vu regelméissege Prozesser, wäert de Pid vum Prozess un den Ufank vun all Zeil addéieren.

Natierlech schwätze mir net iwwer e Fuedemidentifizéierer am Sënn vun der Ëmsetzung vum POSIX Threads Standard, mee iwwer d'Zuel déi vum Task Scheduler an Linux benotzt gëtt. Aus der Siicht vun der leschter, ginn et keng Prozesser oder thread - et sinn Aufgaben, déi ënnert de verfügbare Käre vun der Maschinn verdeelt musse ginn.

Wann Dir a multiple Threads schafft, ginn Systemruffen ze vill:

$ strace -f -othread-write.log ./thread-write
$ wc -l thread-write.log
60 thread-write.log

Et mécht Sënn fir Iech nëmmen op Prozessmanagement a Systemriff ze limitéieren write:

$ strace -f -e trace="%process,write" -othread-write.log ./thread-write
$ cat thread-write.log
18211 execve("./thread-write", ["./thread-write"], 0x7ffc6b8d58f0 /* 64 vars */) = 0
18211 arch_prctl(ARCH_SET_FS, 0x7f38ea3b7740) = 0
18211 write(1, "Initial thread: launching a thre"..., 35) = 35
18211 clone(child_stack=0x7f38e9ba2fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f38e9ba39d0, tls=0x7f38e9ba3700, child_tidptr=0x7f38e9ba39d0) = 18212
18211 write(1, "Initial thread: joining a thread"..., 33) = 33
18212 write(1, "Secondary thread: workingn", 26) = 26
18212 write(1, "Secondary thread: donen", 23) = 23
18212 exit(0)                           = ?
18212 +++ exited with 0 +++
18211 write(1, "Initial thread: done", 20) = 20
18211 exit_group(0)                     = ?
18211 +++ exited with 0 +++

Iwwregens, Froen. Wéi ee Systemruff gëtt benotzt fir en neie Fuedem ze kreéieren? Wéi ënnerscheet sech dësen Uruff fir Threads vum Uruff fir Prozesser?

Master Klass: Prozess Stack an der Zäit vun engem System Opruff

Ee vun de kuerzem erschéngt strace Fäegkeeten - de Stack vu Funktiounsruffen zur Zäit vum Systemruff affichéieren. Einfach Beispill:

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;
}

Natierlech gëtt de Programmausgang ganz voluminös, an zousätzlech zum Fändel -k (Call Stack Display), mécht et Sënn fir Systemriff mam Numm ze filteren:

$ 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 Klass: Feeler Sprëtz

An nach eng nei a ganz nëtzlech Feature: Fehlerinjektioun. Hei Programm, Schreift zwou Zeilen an den Ausgangsstroum:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

void do_write(const char *str, ssize_t len)
{
    if (len != write(STDOUT_FILENO, str, (size_t)len)){
        perror("write");
        exit(EXIT_FAILURE);
    }
}

int main(int argc, char *argv[])
{
    (void) argc; (void) argv;

    char str1[] = "write me 1n";
    do_write(str1, sizeof(str1));

    char str2[] = "write me 2n";
    do_write(str2, sizeof(str2));

    return EXIT_SUCCESS;
}

Loosst eis béid Schreifruffen verfollegen:

$ gcc examples/write-twice.c -o write-twice
$ ./write-twice
write me 1
write me 2
$ strace -e trace=write -owrite-twice.log ./write-twice
write me 1
write me 2
$ cat write-twice.log
write(1, "write me 1n", 12)          = 12
write(1, "write me 2n", 12)          = 12
+++ exited with 0 +++

Elo benotze mir den Ausdrock injecte Feeler anzeginn EBADF an all Schreiwen Appellen:

$ strace -e trace=write -e inject=write:error=EBADF -owrite-twice.log ./write-twice
$ cat write-twice.log
write(1, "write me 1n", 12)          = -1 EBADF (Bad file descriptor) (INJECTED)
write(3, "write: Bad file descriptorn", 27) = -1 EBADF (Bad file descriptor) (INJECTED)
+++ exited with 1 +++

Et ass interessant wat Feeler zréckginn all dat Erausfuerderungen write, dorënner den Uruff hannert Perror verstoppt. Et mécht nëmme Sënn fir e Feeler fir den éischte vun den Uruff zréckzekommen:

$ 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 +++

Oder déi zweet:

$ 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 +++

Et ass net néideg de Feeler Typ ze spezifizéieren:

$ 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 +++

A Kombinatioun mat anere Fändelen kënnt Dir den Zougang zu enger spezifescher Datei "briechen". Beispill:

$ 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 +++

Nieft Fehlerinjektioun, kënnen Verspéidungen aféieren wann Dir Uriff mécht oder Signaler kritt.

Afterword

Utility strace - eng einfach an zouverlässeg Outil. Awer zousätzlech zu Systemruffen kënnen aner Aspekter vun der Operatioun vu Programmer an dem Betribssystem debugged ginn. Zum Beispill kann et Uriff op dynamesch verlinkte Bibliothéiken verfollegen. ltraz, si kënnen an d'Operatioun vum Betribssystem kucken SystemTap и ftrace, an erlaabt Iech déif Programm Leeschtung ze ermëttelen perfekt. Trotzdem ass et strace - déi éischt Verteidegungslinn am Fall vu Probleemer mat menger eegener an anere Leit Programmer, an ech benotzen se op d'mannst e puer Mol pro Woch.

Kuerz gesot, wann Dir Unix gär hutt, liest man 1 strace a fillt Iech gratis op Är Programmer ze kucken!

Source: will.com

Setzt e Commentaire