Strace Linux-en: historia, diseinua eta erabilera

Strace Linux-en: historia, diseinua eta erabilera

Unix antzeko sistema eragileetan, programa batek kanpoko munduarekin eta sistema eragilearekin komunikazioa funtzio multzo txiki baten bidez gertatzen da - sistema-deiak. Horrek esan nahi du arazketa helburuetarako baliagarria izan daitekeela prozesuek exekutatzen ari diren sistema-deiak espiatzea.

Utilitate batek Linux-en programen "bizitza intimoa" kontrolatzen laguntzen dizu strace, artikulu honen gaia dena. Espioitza ekipamenduen erabileraren adibideek historia labur batekin batera doaz strace eta programa horien diseinuaren deskribapena.

Edukia

Espezieen jatorria

Unix-en programen eta OS kernelaren arteko interfaze nagusia sistema deiak dira. sistema deiak, syscall-ak), programek kanpoko munduarekin duten elkarrekintza haien bitartez soilik gertatzen da.

Baina Unix-en lehen bertsio publikoan (6. bertsioa Unix, 1975) ez zegoen erabiltzaile-prozesuen portaeraren jarraipena egiteko modu erosoak. Arazo hau konpontzeko, Bell Labs hurrengo bertsiora eguneratuko da (7. bertsioa Unix, 1979) sistema deialdi berri bat proposatu zuen - ptrace.

ptrace arazgailu interaktiboetarako garatu zen batez ere, baina 80ko hamarkadaren amaieran (komertzialaren garaian). System V bertsioa 4) oinarri horretan, fokatutako araztaileak β€”sistema deien trazatzaileakβ€” agertu ziren eta oso erabiliak izan ziren.

Lehen strace-ren bertsio bera Paul Cronenburg-ek argitaratu zuen comp.sources.sun posta-zerrendan 1992an, utilitate itxi baten alternatiba gisa. trace Eguzkitik. Klona eta jatorrizkoa SunOSerako pentsatuta zeuden, baina 1994rako strace System V, Solaris eta gero eta ezagunagoa den Linuxera eraman zen.

Gaur egun, strace-k Linux bakarrik onartzen du eta horretan oinarritzen da ptrace, luzapen askorekin hazita.

Mantentzaile modernoa (eta oso aktiboa). strace - Dmitri Levin. Berari esker, utilitateak ezaugarri aurreratuak eskuratu zituen, hala nola sistema deietan akatsen injekzioa, arkitektura sorta zabalerako laguntza eta, batez ere, maskota. Iturri ez ofizialek diotenez, hautua ostrukaren gainean erori zen, errusiar "ostruka" hitzaren eta ingelesezko "strace" hitzaren arteko kontsonantziagatik.

Garrantzitsua da, halaber, ptrace sistema-deia eta trazatzaileak POSIX-en inoiz sartu ez izana, Linux, FreeBSD, OpenBSD eta Unix tradizionaletan historia eta inplementazio luzea izan arren.

Strace gailua laburbilduz: Piglet Trace

"Ez duzu hau ulertzea espero" (Dennis Ritchie, iruzkina 6. bertsioan Unix iturburu kodean)

Txikitatik, ezin ditut kutxa beltzak jasan: ez nintzen jostailuekin jolasten, haien egitura ulertzen saiatu nintzen (helduek "hautsi" hitza erabiltzen zuten, baina ez sinetsi hizkuntza gaiztoak). Beharbada horregatik dago niregandik hain hurbil lehen Unix-en eta kode irekiko mugimendu modernoaren kultura informala.

Artikulu honen ondorioetarako, zentzugabea da strace-ren iturburu kodea desmuntatzea, hamarkadetan zehar hazi dena. Baina ez luke sekreturik geratu behar irakurleentzat. Hori dela eta, horrelako programen funtzionamendu-printzipioa erakusteko, miniaturazko trazatzaile baten kodea emango dut - Txerrikumeen arrastoa (ptr). Ez daki ezer berezirik nola egin, baina gauza nagusia programaren sistema-deiak dira - ateratzen ditu:

$ 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-k Linux sistemaren ehunka dei ezagutzen ditu (ikus. taula) eta x86-64 arkitekturan bakarrik funtzionatzen du. Hau nahikoa da hezkuntza helburuetarako.

Ikus dezagun gure klonaren lana. Linux-en kasuan, arazteek eta trazatzaileek goian esan bezala, ptrace sistema deia erabiltzen dute. Lehen argumentuan komandoen identifikatzaileak pasatuz funtzionatzen du, horietatik soilik behar ditugunak PTRACE_TRACEME, PTRACE_SYSCALL ΠΈ PTRACE_GETREGS.

Trazatzailea ohiko Unix estiloan hasten da: fork(2) ume-prozesu bat abiarazten du, eta horrek, aldi berean, erabiltzen du exec(3) aztergai den programa martxan jartzen du. Hemen sotiltasun bakarra erronka da ptrace(PTRACE_TRACEME) aurretik exec: Haurraren prozesuak gurasoen prozesuak kontrolatzea espero du:

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

Guraso-prozesuak deitu beharko luke orain wait(2) haurraren prozesuan, hau da, ziurtatu traza modura aldatu dela:

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

Une honetan, prestaketak amaitu dira eta zuzenean jarraitu dezakezu sistema-deien jarraipena amaigabeko begizta batean.

deiaren ptrace(PTRACE_SYSCALL) ondorengoa bermatzen du wait gurasoak sistemaren deia exekutatu aurretik edo amaitu eta berehala osatuko du. Bi deien artean edozein ekintza egin dezakezu: ordezkatu deia ordezko batekin, aldatu argumentuak edo itzultzeko balioa.

Komandoari bi aldiz deitzea besterik ez dugu behar ptrace(PTRACE_GETREGS)erregistro-egoera lortzeko rax deiaren aurretik (sistemaren dei-zenbakia) eta berehala (itzultzeko balioa).

Egia esan, zikloa:

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

Hori da arrasto osoa. Orain badakizu non hasi hurrengo porturatzea DTrace Linux-en.

Oinarrizkoak: Strace exekutatzen duen programa bat martxan jartzea

Lehen erabilera kasu gisa strace, beharbada merezi du metodorik errazena aipatzea: exekutatzen ari den aplikazio bat abiaraztea strace.

Programa tipiko baten deien zerrenda amaigabean ez sakontzeko, idazten dugu gutxieneko programa inguruan 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;
}

Eraiki dezagun programa eta ziurtatu funtzionatzen duela:

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

Eta azkenik, exekutatu dezagun strace kontrolpean:

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

Oso β€œhitzak” eta ez oso hezitzailea. Hemen bi arazo daude: programaren irteera irteerarekin nahasten da strace eta interesatzen ez zaizkigun sistema-dei ugari.

Programaren irteera-korronte estandarra eta estrace errorearen irteera bereiz ditzakezu -o etengailua erabiliz, sistema-deien zerrenda argumentu-fitxategi batera birbideratzen duena.

Dei β€œgehigarrien” arazoari aurre egiteko geratzen da. Demagun deiak bakarrik interesatzen zaizkigula write. Gakoa -e sistema-deiak iragaziko diren esamoldeak zehazteko aukera ematen du. Baldintza aukerarik ezagunena da, jakina, trace=*, zeinekin interesatzen zaizkigun deiak bakarrik utzi ditzakezu.

Aldi berean erabiltzen denean -o ΠΈ -e lortuko dugu:

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

Beraz, ikusten duzu, askoz errazagoa da irakurtzen.

Sistema-deiak ere ken ditzakezu, adibidez, memoria esleitzeari eta askatzeari dagozkionak:

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

Kontuan izan baztertutako deien zerrendan ihes egindako harridura-ikurra: hori komando shell-ak eskatzen du. shell).

Glibc-en nire bertsioan, sistema dei batek prozesua amaitzen du exit_group, ez tradizionala _exit. Hau da sistema-deiekin lan egiteko zailtasuna: programatzaileak lan egiten duen interfazeak ez du zerikusi zuzena sistema-deiekin. Gainera, aldizka aldatzen da ezarpenaren eta plataformaren arabera.

Oinarriak: prozesuarekin bat egitea

Hasieran, eraiki zen ptrace sistema deia strace, programa modu berezi batean exekutatzen denean bakarrik erabil daiteke. Baliteke muga honek zentzuzkoa iruditu 6 bertsioaren Unix-en garaian. Gaur egun, hori ez da nahikoa: batzuetan lan-programa baten arazoak ikertu behar dira. Adibide tipiko bat helduleku batean edo lotan blokeatutako prozesu bat da. Beraz, modernoa strace prozesuetan bat egin dezake hegalean.

Adibide izozgarria programak:

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

Eraiki dezagun programa eta ziurtatu izoztuta dagoela:

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

Orain saia gaitezen sartzen:

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

Deiak blokeatu du programa pause. Ikus dezagun nola erreakzionatzen duen seinaleen aurrean:

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

Frozen programa abiarazi genuen eta erabiliz sartu ginen strace. Bi gauza argi geratu ziren: pausa sistemako deiak kudeatzailerik gabeko seinaleei jaramonik egiten ez die eta, interesgarriagoa dena, strace-k sistema deiak ez ezik, sarrerako seinaleak ere kontrolatzen ditu.

Adibidea: Haurren Prozesuen jarraipena

Dei baten bidez prozesuekin lan egitea fork - Unix guztien oinarria. Ikus dezagun nola funtzionatzen duen strace-k prozesu-zuhaitz batekin "hazkuntza" sinple baten adibidea erabiliz programak:

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

Hemen jatorrizko prozesuak haur prozesu bat sortzen du, biak irteera estandarrera idatziz:

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

Lehenespenez, prozesu nagusiko sistema-deiak bakarrik ikusiko ditugu:

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

Banderak prozesu-zuhaitzaren jarraipena egiten laguntzen dizu -f, zein strace sistema-deiak kontrolatzen ditu haur-prozesuetan. Honek irteera-lerro bakoitzari gehitzen dio pid Sistemaren irteera egiten duen prozesua:

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

Testuinguru honetan, sistema-deien taldeen arabera iragaztea erabilgarria izan daiteke:

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

Bide batez, zein sistema-dei erabiltzen da prozesu berri bat sortzeko?

Adibidea: fitxategien bideak heldulekuen ordez

Fitxategien deskribatzaileak ezagutzea erabilgarria da, zalantzarik gabe, baina programa batek sartzen dituen fitxategi zehatzen izenak ere erabilgarriak izan daitezke.

Hurrengoa programa lerroa aldi baterako fitxategi batean idazten du:

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

Dei arrunt batean strace sistema-deiari emandako deskribatzaile-zenbakiaren balioa erakutsiko du:

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

Bandera batekin -y Utilitateak deskribatzailea dagokion fitxategirako bidea erakusten du:

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

Adibidez: Fitxategien Sarbideen Jarraipena

Beste ezaugarri erabilgarria: fitxategi zehatz bati lotutako sistema-deiak soilik bistaratu. Hurrengoa programa argumentu gisa pasatako fitxategi arbitrario bati lerro bat eransten dio:

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

Lehenespenez strace beharrezkoa ez den informazio asko erakusten du. Bandera -P argumentu batekin strace-k zehaztutako fitxategirako deiak soilik inprimatzea eragiten du:

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

Adibidea: Hari anitzeko programak

Erabilgarritasuna strace hari anitzeko lanarekin ere lagun dezake programa. Programa honek irteera estandarrean idazten du bi korronteetatik:

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

Jakina, lokailuari agur berezi batekin bildu behar da -pthread bandera:

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

bandera -f, prozesu arrunten kasuan bezala, prozesuaren pid-a gehituko du lerro bakoitzaren hasieran.

Berez, ez gara hari-identifikatzaile bati buruz ari POSIX Threads estandarraren ezarpenaren zentzuan, baizik eta Linux-en ataza-planifikatzaileak erabiltzen duen zenbakiaz. Azken honen ikuspuntutik, ez dago prozesu edo haririk - makinaren nukleoen artean banatu behar diren zereginak daude.

Hainbat haritan lan egiten duzunean, sistema-deiak gehiegi bihurtzen dira:

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

Zentzuzkoa da prozesuen kudeaketara eta sistema-deiak soilik mugatzea 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 +++

Bide batez, galderak. Zein sistema-dei erabiltzen da hari berri bat sortzeko? Zertan desberdintzen da hari-dei hau prozesu-deiarekin?

Klase magistrala: prozesu pila sistema-dei baten unean

Duela gutxi agertu den bat strace gaitasunak - sistema-deiaren unean funtzio-deien pila bistaratzea. Sinplea Adibidez:

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

Jakina, programaren irteera oso bolumentsua bihurtzen da, eta, banderaz gain -k (deien pila bistaratzea), zentzuzkoa da sistemaren deiak izenaren arabera iragaztea:

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

Klase magistrala: akatsen injekzioa

Eta beste ezaugarri berri eta oso erabilgarria: akatsen injekzioa. Hemen programa, irteerako korrontean bi lerro idatziz:

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

Ikus ditzagun bi idazketa-deiak:

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

Orain esamoldea erabiltzen dugu injecterrore bat sartzeko EBADF idazketa-dei guztietan:

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

Interesgarria da zein errore itzultzen diren guztiak erronkak write, perroren atzean ezkutatzen den deia barne. Zentzuzkoa da lehen deietarako errore bat itzultzea:

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

Edo bigarrena:

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

Ez da beharrezkoa errore mota zehaztea:

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

Beste bandera batzuekin batera, fitxategi zehatz baterako sarbidea "apurtu" dezakezu. Adibidea:

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

Akatsen injekzioaz gain, ko ahal deiak egitean edo seinaleak jasotzean atzerapenak sartu.

afterword

Erabilgarritasuna strace - Tresna sinple eta fidagarria. Baina sistema-deiez gain, programen eta sistema eragilearen funtzionamenduaren beste alderdi batzuk arazketa daitezke. Adibidez, dinamikoki lotuta dauden liburutegietarako deien jarraipena egin dezake. ltraza, sistema eragilearen funtzionamendua azter dezakete SistemaTap ΠΈ aztarna, eta programaren errendimendua sakon ikertzeko aukera ematen du perfektua. Hala ere, hala da strace - Lehen defentsa-lerroa nire eta besteen programekin arazoen kasuan, eta astean gutxienez pare bat aldiz erabiltzen dut.

Laburbilduz, Unix maite baduzu, irakurri man 1 strace eta anima zaitez zure programetara begiratzera!

Iturria: www.habr.com

Gehitu iruzkin berria