Strace in Linux: geskiedenis, toestel en gebruik

Strace in Linux: geskiedenis, toestel en gebruik

In Unix-agtige bedryfstelsels kommunikeer 'n program met die buitewêreld en die bedryfstelsel deur 'n klein stel funksies - stelseloproepe. Dus, vir ontfoutingsdoeleindes, is dit nuttig om te spioeneer op lopende prosesse met stelseloproepe.

Die hulpprogram help om die "intieme lewe" van programme op Linux te monitor stracewaaraan hierdie artikel gewy is. 'n Kort geskiedenis word aangeheg aan voorbeelde van die gebruik van "spioenasie" toerusting. strace en beskrywing van die toestel van sulke programme.

inhoud

Oorsprong van spesies

Die hoofkoppelvlak tussen programme en die OC-kern in Unix is ​​stelseloproepe. stelseloproepe, syskale), vind die interaksie van programme met die buitewêreld uitsluitlik daardeur plaas.

Maar in die eerste publieke weergawe van Unix (Weergawe 6 Unix, 1975) was daar geen gerieflike maniere om die gedrag van gebruikersprosesse na te spoor nie. Om hierdie probleem op te los, Bell Labs na die volgende weergawe (Weergawe 7 Unix, 1979) het 'n nuwe stelseloproep voorgestel - ptrace.

ptrace is hoofsaaklik ontwikkel vir interaktiewe ontfouters, maar teen die einde van die 80's (reeds in die era van kommersiële Stelsel V-vrystelling 4) op hierdie basis het eng gefokusde ontfouters - stelseloproepspoorders - verskyn en wyd gebruik geword.

Eerste dieselfde weergawe van strace is in 1992 deur Paul Cronenburg in die comp.sources.sun-poslys gepubliseer as 'n alternatief vir die eie nut trace van son. Beide die kloon en die oorspronklike was bedoel vir SunOS, maar teen 1994 strace is oorgedra na System V, Solaris en die toenemend gewilde Linux.

Vandag ondersteun strace net Linux en maak dit staat op dieselfde ptrace, oorgroei met baie uitbreidings.

Moderne (en baie aktiewe) onderhouer strace - Dmitri Levin. Danksy hom het die nutsprogram gevorderde kenmerke verkry, soos foutinspuiting in stelseloproepe, ondersteuning vir 'n wye verskeidenheid argitekture, en, bowenal, gelukbringer. Nie-amptelike bronne beweer dat die keuse op die volstruis geval het weens die konsonansie van die Russiese woord "volstruis" en die Engelse woord "strace".

Dit is betekenisvol dat die ptrace-stelseloproep en -spoorsnyers nooit by POSIX ingesluit is nie, ten spyte van hul lang geskiedenis en implementering in Linux, FreeBSD, OpenBSD en tradisionele Unixes.

Die strace-toestel in 'n neutedop: Piglet Trace

"Daar word nie van jou verwag om dit te verstaan ​​nie" (Dennis Ritchie, kommentaar in weergawe 6 Unix-bronkode)

Van kleintyd af haat ek swart bokse: ek het nie met speelgoed gespeel nie, maar probeer om hul toestel uit te vind (volwassenes het die woord "gebreek", maar glo nie bose tonge nie). Miskien is dit hoekom die informele kultuur van die eerste Unixes en die moderne oopbronbeweging so na aan my is.

In die raamwerk van hierdie artikel is dit onredelik om die bronkode van strace, wat oor dekades vrugbaar geword het, uitmekaar te haal. Maar geheime vir lesers moet nie bly nie. Daarom, om die beginsel van werking van sulke strace-programme te wys, sal ek die kode van 'n miniatuurspoorder gee - Varkie spoor (ptr). Dit weet nie hoe om iets spesiaals te doen nie, maar die belangrikste ding - die stelseloproepe van die program - vertoon:

$ 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 herken ongeveer honderd Linux-stelseloproepe (sien hieronder). tafel) en werk net op x86-64-argitektuur. Dit is voldoende vir opvoedkundige doeleindes.

Kom ons kyk hoe ons kloon werk. In die geval van Linux gebruik ontfouters en spoorsnyers, soos hierbo genoem, die ptrace-stelseloproep. Dit werk deur die opdrag-ID's in die eerste argument deur te gee, waarvan ons net nodig het PTRACE_TRACEME, PTRACE_SYSCALL и PTRACE_GETREGS.

Die tracer begin in normale Unix-styl: fork(2) begin 'n kinderproses, wat weer met behulp van exec(3) loods die program wat ondersoek word. Die enigste subtiliteit hier is die uitdaging ptrace(PTRACE_TRACEME) voor exec: die kinderproses wag vir die ouerproses om daarna te kyk:

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

Die ouerproses behoort nou te bel wait(2) in die kinderproses, dit wil sê, maak seker dat die oorskakeling na spoormodus plaasgevind het:

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

Dit voltooi die voorbereidings en jy kan direk voortgaan om stelseloproepe in 'n oneindige lus op te spoor.

oproep ptrace(PTRACE_SYSCALL) verseker dat die volgende wait ouer sal óf voltooi voordat die stelseloproep uitgevoer word, óf onmiddellik nadat dit voltooi is. Tussen twee oproepe kan jy enige aksies uitvoer: vervang die oproep met 'n alternatiewe een, verander die argumente of gee waarde terug.

Dit is genoeg vir ons om die bevel twee keer te roep ptrace(PTRACE_GETREGS)om die stand van die register te kry rax voor die oproep (stelseloproepnommer) en onmiddellik daarna (terugstuurwaarde).

Eintlik, die lus:

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

Dit is die hele spoorsnyer. Nou weet jy waar om die volgende oordrag te begin DTrace op Linux.

Basiese beginsels: hardloop 'n program onder spanning

As 'n eerste gebruiksgeval strace, miskien is dit die moeite werd om die maklikste manier te gee - om 'n toepassing te laat loop strace.

Om nie in die eindelose lys van oproepe na 'n tipiese program te delf nie, skryf ons minimum program rondom 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;
}

Kom ons bou die program en maak seker dit werk:

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

En uiteindelik hardloop dit onder strace:

$ 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)                           = ?

Baie breedvoerig en nie baie insiggewend nie. Daar is twee probleme hier: die uitset van die program word gemeng met die uitset strace en 'n oorvloed van stelseloproepe wat ons nie interesseer nie.

U kan die program se standaard-uitsetstroom skei en die foutuitset afsny deur die -o-skakelaar te gebruik, wat die lys stelseloproepe na die argumentlêer herlei.

Dit bly om die probleem van "ekstra" oproepe te hanteer. Gestel ons stel net in oproepe belang write. Sleutel -e laat jou toe om uitdrukkings te spesifiseer waarmee stelseloproepe gefiltreer sal word. Die gewildste variant van die toestand is natuurlik, trace=*, waarmee u slegs die oproepe van belang vir ons kan laat.

Wanneer dit gelyktydig gebruik word -o и -e ons sal kry:

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

So, jy sien, dit is baie makliker om te lees.

En jy kan ook stelseloproepe verwyder - byvoorbeeld dié wat verband hou met die toekenning en vrystelling van geheue:

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

Let op die ontsnapte uitroepteken in die lys van uitgeslote oproepe: dit word deur die dop vereis. dop).

In my weergawe van glibc beëindig die stelseloproep die uitvoering van 'n proses exit_groupeerder as tradisioneel _exit. Dit is die moeilikheid om met stelseloproepe te werk: die koppelvlak waarmee die programmeerder werk, hou nie direk verband met stelseloproepe nie. Boonop verander dit gereeld na gelang van implementering en platform.

Die basiese beginsels: heg aan 'n proses op die vlieg

Aanvanklik het die ptrace stelsel oproep waarop die strace, kon slegs gebruik word wanneer die program in spesiale modus uitgevoer word. So 'n beperking het dalk redelik geklink in die dae van Weergawe 6 Unix. Deesdae is dit nie meer genoeg nie: soms moet jy die probleme van 'n werkende program ondersoek. 'n Tipiese voorbeeld is 'n proses wat op 'n handvatsel gesluit is of aan die slaap is. Daarom, modern strace prosesse kan aansluit op die vlieg.

Hangende voorbeeld programme:

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

Kom ons stel die program saam en maak seker dat dit hang:

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

Kom ons probeer nou om daarby aan te sluit:

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

Program geblokkeer deur 'n oproep pause. Kom ons kyk hoe sy op die seine reageer:

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

Ons het die hang-program van stapel gestuur en daarby aangesluit strace. Twee dinge het aan die lig gekom: die pouse-stelseloproep ignoreer seine sonder hanteerders, en, meer interessant, strace monitor nie net stelseloproepe nie, maar ook inkomende seine.

Voorbeeld: dop van kinderprosesse

Werk met prosesse deur 'n oproep fork is die grondslag van alle Unixes. Kom ons kyk hoe strace met die prosesboom werk deur die voorbeeld van 'n eenvoudige "teling" te gebruik programme:

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

Hier skep die ouerproses 'n kinderproses, wat albei na standaarduitvoer skryf:

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

By verstek sal ons slegs die ouerproses se stelseloproepe sien:

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

Die vlag help om tred te hou met die hele prosesboom. -f, watter strace hou rekord van stelseloproepe in kinderprosesse. By elke reël van die afvoer word bygevoeg pid proses wat stelseluitset maak:

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

In hierdie konteks kan filtering volgens stelseloproepgroepe nuttig wees:

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

Terloops, watter stelseloproep word gebruik om 'n nuwe proses te skep?

Voorbeeld: lêerpaaie in plaas van handvatsels

Om die lêerbeskrywings te ken is beslis nuttig, maar die name van die spesifieke lêers waartoe die program toegang kry, kan ook handig te pas kom.

die volgende program skryf 'n reël na 'n tydelike lêer:

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

Op 'n gewone oproep strace sal die waarde van die beskrywernommer wat na die stelseloproep oorgedra is, wys:

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

Met 'n vlag -y die hulpprogram wys die pad na die lêer waarmee die beskrywing ooreenstem:

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

Voorbeeld: Volg lêertoegange

Nog 'n nuttige kenmerk is om slegs stelseloproepe wat met 'n spesifieke lêer geassosieer word, te vertoon. Volgende program voeg 'n string by 'n arbitrêre lêer wat as 'n argument deurgegee word:

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

By verstek strace lewer baie oortollige inligting uit. Vlag -P met 'n argument veroorsaak dat strace slegs toegang tot die gespesifiseerde lêer uitvoer:

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

Voorbeeld: multithreaded programme

Nuts strace kan help wanneer jy met multi-threaded werk die program. Die volgende program skryf na standaarduitvoer vanaf twee strome:

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

Dit is natuurlik nodig om dit saam te stel met 'n spesiale hallo vir die skakelaar - die -pthread vlag:

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

vlag -f, soos in die geval van normale prosesse, sal elke reël met die pid van die proses voorafgaan.

Natuurlik praat ons nie van 'n draad-identifiseerder in die sin van die implementering van die POSIX Threads-standaard nie, maar van 'n nommer wat deur die Linux-taakskeduleerder gebruik word. Uit die oogpunt van laasgenoemde is daar geen prosesse en drade nie - daar is take wat onder die beskikbare kern van die masjien verdeel moet word.

As u in verskeie drade werk, word stelseloproepe te veel:

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

Dit maak sin om onsself te beperk tot prosesbestuur en stelseloproepe 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 +++

Terloops, vrae. Watter stelseloproep word gebruik om 'n nuwe draad te skep? Hoe verskil hierdie oproep vir drade van die oproep vir prosesse?

Meesterklas: Prosesstapel ten tye van 'n stelseloproep

Een van die onlangse strace vermoëns - vertoon die stapel funksie-oproepe ten tyde van die stelseloproep. Eenvoudig Byvoorbeeld:

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

Natuurlik word die uitset van die program baie lywig, en benewens die vlag -k (wat die oproepstapel vertoon), maak dit sin om stelseloproepe op naam te filter:

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

Meesterklas: foutinspuiting

En nog 'n nuwe en baie nuttige kenmerk: foutinspuiting. Hier program, wat twee reëls na die uitsetstroom skryf:

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

Volg beide skryfoproepe:

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

En nou gebruik ons ​​die uitdrukking injectfout in te voeg EBADF aan almal skryf oproepe:

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

Ek wonder watter foute terugkeer alle uitdagings write, insluitend die oproep wat agter perror versteek is. Dit maak sin om 'n fout slegs vir die eerste van die oproepe terug te gee:

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

Of die tweede een:

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

Die fouttipe is opsioneel:

$ 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 kombinasie met ander vlae kan jy toegang tot 'n spesifieke lêer "breek". Voorbeeld:

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

Benewens foutinspuitings, kan 'n mens stel vertragings in wanneer oproepe gemaak word of seine ontvang word.

nawoord

Nuts strace is 'n eenvoudige en betroubare hulpmiddel. Maar benewens stelseloproepe, word ander aspekte van die werking van programme en die bedryfstelsel ook ontfout. Dit kan byvoorbeeld oproepe na dinamies gekoppelde biblioteke opspoor spoor, kyk na die werking van die bedryfstelsel kan Stelsel Tik и spoor, en die werkverrigting van programme diep ondersoek perf. Dit is egter strace - die eerste linie van verdediging in geval van probleme met my eie en ander mense se programme, en ek gebruik dit ten minste 'n paar keer per week.

Kortom, lief vir Unix, lees man 1 strace en spioeneer gerus op jou programme!

Bron: will.com

Voeg 'n opmerking