Strace di Linux de: dîrok, sêwirandin û bikar anîn

Strace di Linux de: dîrok, sêwirandin û bikar anîn

Di pergalên xebitandinê yên mîna Unix-ê de, pêwendiya bernameyek bi cîhana derve û pergala xebitandinê re bi komek piçûk a fonksiyonan pêk tê - bangên pergalê. Ev tê vê wateyê ku ji bo mebestên xeletkirinê ew dikare bikêrhatî be ku li ser bangên pergalê yên ku ji hêla pêvajoyan ve têne darve kirin bişopînin.

Karûbarek ji we re dibe alîkar ku hûn "jiyana samîmî" ya bernameyên li Linux-ê bişopînin strace, ku mijara vê gotarê ye. Mînakên bikaranîna alavên sîxuriyê bi kurte dîrokek ve girêdayî ne strace û şirovekirina sêwirana bernameyên weha.

Contains

Koka cureyan

Navbera sereke ya di navbera bername û kernel OS-ê de li Unix-ê bangên pergalê ye. bangên pergalê, syscalls), danûstendina bernameyan bi cîhana derve re tenê bi rêya wan pêk tê.

Lê di yekem guhertoya giştî ya Unix de (Guhertoya 6 Unix, 1975) tu awayên hêsan tune ku tevgeriya pêvajoyên bikarhêner bişopîne. Ji bo çareserkirina vê pirsgirêkê, Bell Labs dê guhertoya paşîn nûve bike (Guhertoya 7 Unix, 1979) bangek pergala nû pêşniyar kir - ptrace.

ptrace di serî de ji bo debuggerên înteraktîf hate pêşve xistin, lê di dawiya salên 80-an de (di serdema bazirganî de Pergala V berdan 4) li ser vê bingehê, debuggerên hûr-kûr - şopgerên gazîkirina pergalê - xuya bûn û bi berfirehî hatin bikar anîn.

Yekem heman guhertoya strace ji hêla Paul Cronenburg ve di navnîşa posta comp.sources.sun de di sala 1992-an de wekî alternatîfek karûbarek girtî hate weşandin. trace ji Sun. Hem klon û hem jî orîjînal ji bo SunOS-ê hatine armanc kirin, lê heya 1994-an strace li System V, Solaris û Linux-a ku her ku diçe populer bû hate şandin.

Îro strace tenê Linux piştgirî dike û xwe dispêre heman ptrace, bi gelek pêvekirinan mezin bûye.

Parêzgerê nûjen (û pir çalak). strace - Dmitry Levin. Bi saya wî, karûbar taybetmendiyên pêşkeftî yên wekî derziya xeletiyê di nav bangên pergalê de, piştgirî ji bo cûrbecûr mîmarî û, ya herî girîng, peyda kir, mascot. Çavkaniyên nefermî îdia dikin ku bijarte ji ber lihevhatina peyva rûsî "ostrich" û peyva îngilîzî "strace" ketiye ser stûyê.

Di heman demê de girîng e ku banga pergala ptrace û şopgeran qet di POSIX-ê de nehatin nav kirin, tevî ku dîrokek dirêj û pêkanîn li Linux, FreeBSD, OpenBSD û Unix kevneşopî.

Amûra Strace bi kurtî: Piglet Trace

"Tê li bendê ne ku hûn vê yekê fêm bikin" (Dennis Ritchie, di Versiyon 6 koda çavkaniya Unix de şîrove bike)

Ji zaroktiya xwe ve, ez nikarim qutiyên reş ragirim: Min bi pêlîstokan nelîst, lê hewl da ku avahiya wan fam bikim (mezinan peyva "şikestin" bikar anîn, lê ji zimanên xerab bawer nakin). Dibe ku ji ber vê yekê çanda nefermî ya yekem Unix û tevgera nûjen-çavkaniya vekirî ew qas nêzî min e.

Ji bo mebestên vê gotarê, ne maqûl e ku meriv koda çavkaniyê ya strace, ku bi dehsalan mezin bûye, veqetîne. Lê divê tu sir ji xwendevanan re nemîne. Ji ber vê yekê, ji bo ku prensîba xebitandina van bernameyên strace nîşan bide, ez ê kodê ji bo şopek piçûk peyda bikim - Piglet Trace (ptr). Ew nizane meriv çawa tiştek taybetî bike, lê ya sereke bangên pergalê yên bernameyê ye - ew derdixe:

$ 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 bi sedan bangên pergala Linux nas dike (binêre. maseyê) û tenê li ser mîmariya x86-64 dixebite. Ev ji bo armancên perwerdehiyê bes e.

Ka em li karê kloneya xwe binêrin. Di mijara Linux de, debugger û şopger, wekî ku li jor hatî destnîşan kirin, banga pergala ptrace bikar tînin. Ew bi derbaskirina di argumana yekem de nasnavên fermanê, yên ku em tenê hewce ne, dixebite PTRACE_TRACEME, PTRACE_SYSCALL и PTRACE_GETREGS.

Tracer bi şêwaza Unix-ê ya gelemperî dest pê dike: fork(2) pêvajoyek zarokê dest pê dike, ku di encamê de bikar tîne exec(3) bernameya di bin lêkolînê de dest pê dike. Li vir tenê hûrgulî dijwarî ye ptrace(PTRACE_TRACEME) en.wiktionary.org перед (Noun) exec: Pêvajoya zarokê hêvî dike ku pêvajoya dêûbav çavdêriya wê bike:

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

Pêvajoya dêûbav divê nuha bang bike wait(2) di pêvajoya zarokê de, ango, pê ewle bine ku guheztina moda şopandinê çêbûye:

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

Di vê nuqteyê de, amadekarî qediyane û hûn dikarin rasterast bi şopandina bangên pergalê di kelekek bêdawî de bimeşin.

Rallenge ptrace(PTRACE_SYSCALL) garantî dike ku paşê wait dêûbav dê berî ku banga pergalê were darve kirin an tavilê piştî ku ew biqede temam bike. Di navbera du bangan de hûn dikarin her çalakiyan bikin: bangê bi yekî alternatîf biguhezînin, argumanan an nirxa vegerê biguherînin.

Em tenê hewce ne ku em du caran bang bikin ptrace(PTRACE_GETREGS)ji bo bidestxistina dewleta qeydê rax berî bangê (hejmara banga pergalê) û yekser piştî (nirxa vegerê).

Bi rastî, çerxa:

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

Ew hemî şopger e. Naha hûn dizanin ku hûn li kuderê dest bi veguheztina paşîn bikin DTrace li ser Linux.

Bingeh: rêvekirina bernameyeke ku dimeşîne

Wekî yekem doza karanîna strace, dibe ku ew hêja ye ku rêbaza herî hêsan binav bike - destpêkirina serîlêdanek xebitandinê strace.

Ji bo ku em nekevin navnîşa bêdawî ya bangên bernameyek tîpîk, em dinivîsin bernameya herî kêm derdora 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;
}

Ka em bernameyê ava bikin û pê ewle bin ku ew dixebite:

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

Û di dawiyê de, em wê di bin kontrola strace de bimeşînin:

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

Pir "biwêj" û ne pir perwerdeyî. Li vir du pirsgirêk hene: derana bernameyê bi hilanînê re tevlihev e strace û gelek bangên pergalê yên ku me eleqedar nakin.

Hûn dikarin bi guheztina -o, ku navnîşa bangên pergalê beralî pelek argûman dike, pêlava hilana standard û derketina xeletiya rêzê ya bernameyê ji hev veqetînin.

Ew dimîne ku bi pirsgirêka bangên "zêde" re mijûl bibe. Ka em bifikirin ku em tenê bi bangan re eleqedar in write. Qûfle -e destûrê dide te ku bêjeyan bi kîjan bangên pergalê dê werin fîlter kirin diyar bike. Vebijarka şertê ya herî populer, xwezayî ye, trace=*, ku hûn dikarin tenê bangên ku me eleqedar dikin bihêlin.

Dema ku bi hevdemî tê bikar anîn -o и -e em ê bistînin:

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

Ji ber vê yekê, hûn dibînin, xwendin pir hêsantir e.

Her weha hûn dikarin bangên pergalê jêbirin, mînakî yên ku bi veqetandin û azadkirina bîranînê ve girêdayî ne:

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

Di navnîşa bangên jênebûyî de nîşana raveya revyayî binihêrin: ev ji hêla şêlê fermanê ve tê xwestin. legan).

Di guhertoya min a glibc de, bangek pergalê pêvajoyê bi dawî dike exit_group, ne kevneşopî _exit. Zehmetiya xebata bi bangên pergalê re ev e: pêwendiya ku bernamesaz pê re dixebite ne rasterast bi bangên pergalê ve girêdayî ye. Wekî din, ew bi rêkûpêk li gorî pêkanîn û platformê ve girêdayî ye.

Bingeh: tevlêbûna pêvajoyê li ser firînê

Di destpêkê de, banga pergala ptrace ya ku li ser hatî çêkirin strace, tenê dema ku bernameyê di moda taybetî de dimeşîne dikare were bikar anîn. Dibe ku ev sînorkirin di rojên Versiyon 6 Unix de maqûl xuya bû. Naha, ev êdî ne bes e: carinan hûn hewce ne ku pirsgirêkên bernameyek xebatê lêkolîn bikin. Mînakek tîpîk pêvajoyek e ku li ser destek an razanê hatî asteng kirin. Ji ber vê yekê modern strace dikarin li ser firînê beşdarî pêvajoyê bibin.

Mînaka cemidandinê bernameyên:

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

Ka em bernameyê ava bikin û pê ewle bibin ku ew qeşa ye:

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

Naha em hewl bidin ku beşdarî wê bibin:

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

Bername bi bangê hate asteng kirin pause. Ka em bibînin ka ew çawa li ser nîşanan reaksiyon dike:

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

Me bernameya cemidandî da destpêkirin û bi karanîna wê tevlî bûn strace. Du tişt eşkere bûn: banga pergala sekinandinê îşaretên bêyî hilberan paşguh dike û, ya balkêştir, şopandina strace ne tenê bangên pergalê, lê di heman demê de îşaretên hatin jî dişopîne.

Mînak: Şopandina Pêvajoyên Zarokan

Bi pêvajoyên bi bangekê re dixebitin fork - bingeha hemî Unixes. Ka em binihêrin ka strace çawa bi dara pêvajoyê re bi mînaka "hilberînek" a hêsan re dixebite bernameyên:

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

Li vir pêvajoya orîjînal pêvajoyek zarokê diafirîne, hem jî li hilberîna standard dinivîse:

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

Bi xwerû, em ê tenê bangên pergalê ji pêvajoya dêûbav bibînin:

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

Ala ji we re dibe alîkar ku hûn tevahiya dara pêvajoyê bişopînin -f, ku strace di pêvajoyên zarokan de bangên pergalê dişopîne. Ev li her rêza hilberanê zêde dike pid Pêvajoya ku encamek pergalê çêdike:

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

Di vê çarçoveyê de, fîlterkirin ji hêla koma bangên pergalê ve dikare kêrhatî be:

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

Bi awayê, kîjan gazîkirina pergalê ji bo afirandina pêvajoyek nû tê bikar anîn?

Mînak: rêyên pelan li şûna destan

Naskirina ravekerên pelan bê guman bikêr e, lê navên pelên taybetî yên ku bername digihîje wan jî dikarin bikêrhatî bin.

Piştre bernameyê rêzê li pelek demkî dinivîse:

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

Di dema bangek normal de strace dê nirxa jimareya danasînê ya ku ji banga pergalê re derbas bûye nîşan bide:

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

Bi ala -y Vebijêrk riya pelê ya ku raveker pê re têkildar nîşan dide:

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

Mînak: Şopandina Gihîştina Pelê

Taybetmendiyek din a kêrhatî: tenê bangên pergalê yên ku bi pelek taybetî ve girêdayî ne nîşan bidin. Piştî bernameyê xêzekê li dosyayek keyfî ku wekî arguman hatiye derbas kirin pêve dike:

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 default strace gelek agahiyên nepêwîst nîşan dide. Al -P bi arguman re dibe sedem ku strace tenê bangên pelê diyarkirî çap bike:

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

Nimûne: Bernameyên Multithreaded

Utility strace di heman demê de dema ku bi pir-mijalan re dixebitin jî dikare bibe alîkar bername. Bernameya jêrîn ji du stûnan ji hilberîna standard dinivîse:

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

Bi xwezayî, pêdivî ye ku ew bi silavek taybetî ji lînkerê re were berhev kirin - ala -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
$

Flag -f, wekî di pêvajoya pêvajoyên birêkûpêk de, dê pidê pêvajoyê li destpêka her rêzê zêde bike.

Bi xwezayî, em di wateya pêkanîna standarda POSIX Threads de ne li ser nasnavek mijarê dipeyivin, lê li ser hejmarê ku ji hêla plansazkerê peywirê ve li Linux-ê tê bikar anîn. Ji nihêrîna paşîn, pêvajo û pêvajo tune - peywir hene ku hewce ne ku di nav beşên berdest ên makîneyê de bêne belav kirin.

Dema ku di gelek mijaran de dixebitin, bangên pergalê pir zêde dibin:

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

Aqil e ku meriv xwe bi rêveberiya pêvajoyê û bangên pergalê tenê sînordar bike 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 +++

Bi awayê, pirsan. Kîjan bangek pergalê ji bo afirandina mijarek nû tê bikar anîn? Çawa ev banga ji bo têlan ji banga ji bo pêvajoyan cuda ye?

Dersa masterê: di dema banga pergalê de stacka pêvajoyê

Yek ji van demên dawî xuya bû strace kapasîteyên - di dema banga pergalê de stûna bangên fonksiyonê nîşan dide. Asan nimûne:

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

Bi xwezayî, derketina bernameyê pir mezin dibe, û ji bilî ala -k (nîşandana stûna bangê), maqûl e ku meriv bangên pergalê bi navê fîlter bike:

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

Dersa masterê: derziya xeletiyê

Û taybetmendiyek din a nû û pir bikêr: derziya xeletiyê. Vir bernameyê, du rêzan ji herika derketinê re dinivîsin:

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

Ka em herdu bangên nivîsandinê bişopînin:

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

Niha em îfadeyê bi kar tînin injectji bo têxistina xeletiyek EBADF di hemî bangan de binivîse:

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

Balkêş e ku kîjan xelet têne vegerandin hemî dijwarî write, di nav de banga veşartî li pişt xeletiyê. Tenê wateya ku ji bo banga yekem xeletiyek vegere:

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

An jî ya duyemîn:

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

Ne hewce ye ku celebê xeletiyê diyar bike:

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

Bi tevlêbûna alayên din re, hûn dikarin gihîştina pelek taybetî "bişkînin". Mînak:

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

Ji bilî derzîlêdana xeletiyê, dikare di dema çêkirina bangan an wergirtina sînyalan de derengiyan destnîşan bikin.

Paşê

Utility strace - amûrek hêsan û pêbawer. Lê ji bilî bangên pergalê, aliyên din ên xebitandina bernameyan û pergala xebitandinê dikarin werin debugkirin. Mînakî, ew dikare bangên pirtûkxaneyên bi dînamîkî ve girêdayî bişopîne. ltrace, ew dikarin li operasyona pergala xebitandinê binêrin SystemTap и ftrace, û dihêle hûn performansa bernameyê bi kûr vekolîn bikin lhevderketî. Lêbelê, ew e strace - Rêza yekem a parastinê di dema pirsgirêkên bernameyên xwe û yên din de, û ez wê bi kêmî ve hefteyek du caran bikar tînim.

Bi kurtasî, heke hûn ji Unix hez dikin, bixwînin man 1 strace û bi serbestî li bernameyên xwe binêre!

Source: www.habr.com

Add a comment