Strace дар Linux: таърих, тарҳрезӣ ва истифода

Strace дар Linux: таърих, тарҳрезӣ ва истифода

Дар системаҳои оператсионии ба Unix монанд алоқаи барнома бо олами беруна ва системаи оператсионӣ тавассути маҷмӯи хурди функсияҳо - зангҳои системавӣ сурат мегирад. Ин маънои онро дорад, ки бо мақсади ислоҳи хатогӣ метавонад ҷосусии зангҳои системаро, ки аз ҷониби равандҳо иҷро мешаванд, муфид бошад.

Утилита ба шумо кӯмак мекунад, ки "ҳаёти интимӣ" -и барномаҳоро дар Linux назорат кунед strace, ки мавзуи ин макола мебошад. Намунаҳои истифодаи таҷҳизоти ҷосусӣ бо таърихи мухтасар оварда шудаанд strace ва тавсифи тарҳрезии ин гуна барномаҳо.

Мундариҷа

Пайдоиши намудҳо

Интерфейси асосии байни барномаҳо ва ядрои ОС дар Unix зангҳои системавӣ мебошад. зангҳои системавӣ, зарбаҳо), таъсири мутақобилаи барномаҳо бо ҷаҳони беруна танҳо тавассути онҳо сурат мегирад.

Аммо дар версияи якуми оммавии Unix (Версияи 6 Unix, 1975) роҳҳои қулай барои пайгирии рафтори равандҳои корбар вуҷуд надоштанд. Барои ҳалли ин масъала, Bell Labs ба версияи оянда навсозӣ мекунад (Версияи 7 Unix, 1979) даъвати нави системаро пешниҳод кард - ptrace.

ptrace асосан барои дебаггерҳои интерактивӣ таҳия шудааст, аммо дар охири солҳои 80-ум (дар давраи тиҷоратӣ) Системаи V Нашри 4) дар ин асос, дебаггерҳои ба таври маҳдуд нигаронидашуда — тресерҳои зангҳои системавӣ пайдо шуданд ва васеъ истифода шуданд.

Аввал ҳамон версияи strace аз ҷониби Пол Кроненбург дар рӯйхати почтаҳои comp.sources.sun дар соли 1992 ҳамчун алтернатива ба як барномаи пӯшида нашр шудааст. trace аз офтоб. Ҳам клон ва ҳам аслӣ барои SunOS пешбинӣ шуда буданд, аммо то соли 1994 strace ба System V, Solaris ва Linux бештар маъмул интиқол дода шуд.

Имрӯз strace танҳо Linux-ро дастгирӣ мекунад ва ба ҳамон такя мекунад ptrace, нашъунамо ёфта, бо васеъшавии зиёд.

Нигоҳдорандаи муосир (ва хеле фаъол). strace - Дмитрий Левин. Бо шарофати ӯ, утилита дорои хусусиятҳои пешрафта ба монанди воридкунии хатоҳо ба зангҳои система, дастгирии доираи васеи меъморӣ ва муҳимтар аз ҳама, маскот. Манобеъи ғайрирасмӣ иддао доранд, ки ин интихоб ба шутурмурғ афтодааст, зеро дар байни вожаи русии “ostrich” ва калимаи инглисии “strace” мувофиқат мекунад.

Инчунин муҳим аст, ки занги системаи ptrace ва паймоишҳо, сарфи назар аз таърихи тӯлонӣ ва татбиқ дар Linux, FreeBSD, OpenBSD ва Unix анъанавӣ ҳеҷ гоҳ ба POSIX дохил карда нашудаанд.

Дастгоҳи Strace ба таври мухтасар: Piglet Trace

"Интизор нест, ки шумо инро дарк кунед" (Деннис Ричи, шарҳ дар коди сарчашмаи версияи 6 Unix)

Ман аз хурдсолӣ ба қуттиҳои сиёҳ тоб оварда наметавонам: ман бо бозичаҳо бозӣ намекардам, аммо кӯшиш мекардам, ки сохтори онҳоро бифаҳмам (калонсолон калимаи "шикаста" -ро истифода мекарданд, аммо ба забонҳои бад бовар намекунанд). Шояд аз ин рӯст, ки фарҳанги ғайрирасмии аввалин Unix ва ҳаракати муосири кушодаасос ба ман ин қадар наздик аст.

Барои мақсадҳои ин мақола, ҷудо кардани коди ибтидоии strace, ки дар тӯли даҳсолаҳо афзоиш ёфтааст, беасос аст. Аммо барои хонандагон ягон асрор набояд монд. Аз ин рӯ, барои нишон додани принсипи кори чунин барномаҳои strace, ман коди трекери миниётураиро пешниҳод мекунам - Trace Piglet (ptr). Он намедонад, ки чӣ гуна ягон чизи махсусро иҷро кунад, аммо чизи асосӣ зангҳои системавии барнома аст - он натиҷа медиҳад:

$ 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 тақрибан садҳо зангҳои системаи Linuxро эътироф мекунад (ниг. мизи) ва танҳо дар меъмории x86-64 кор мекунад. Ин барои мақсадҳои таълимӣ кофӣ аст.

Биёед ба кори клонамон назар андозем. Дар мавриди Linux, debuggers ва tracers, тавре ки дар боло зикр гардид, занги системаи ptrace -ро истифода мебаранд. Он тавассути гузаштани идентификаторҳои фармон, ки ба мо танҳо лозим аст, дар аргументи аввал кор мекунад PTRACE_TRACEME, PTRACE_SYSCALL и PTRACE_GETREGS.

Tracer бо услуби муқаррарии Unix оғоз меёбад: fork(2) раванди бачагиро оғоз мекунад, ки дар навбати худ онро истифода мебарад exec(3) барномаи мавриди омӯзишро оғоз мекунад. Ягона нозуки дар ин ҷо мушкилот аст ptrace(PTRACE_TRACEME) пеш аз он exec: Раванди кӯдак интизор аст, ки раванди волидайн онро назорат кунад:

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

Раванди волидайн бояд ҳоло занг занад wait(2) дар раванди кӯдак, яъне боварӣ ҳосил кунед, ки гузариш ба режими пайгирӣ рух додааст:

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

Дар ин лаҳза, омодагӣ ба анҷом расид ва шумо метавонед мустақиман ба пайгирии зангҳои система дар як ҳалқаи беохир идома диҳед.

Мушкилот ptrace(PTRACE_SYSCALL) кафолат медиҳад, ки минбаъд wait волидайн ё пеш аз иҷро шудани занги система ё дарҳол пас аз анҷом ёфтани он ба анҷом мерасад. Байни ду занг шумо метавонед ҳама гуна амалҳоро иҷро кунед: зангро бо занги алтернативӣ иваз кунед, аргументҳоро тағир диҳед ё арзиши баргардонед.

Мо бояд танҳо ду маротиба фармонро даъват кунем ptrace(PTRACE_GETREGS)барои гирифтани ҳолати бақайдгирӣ rax пеш аз занг (рақами занги система) ва фавран пас аз (қимати бозгашт).

Дар асл, давра:

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

Ин тамоми трекер аст. Акнун шумо медонед, ки интиқоли навбатиро аз куҷо оғоз кунед DTrace дар Linux.

Асосҳо: иҷро кардани барномаи иҷрокунандаи strace

Ҳамчун ҳолати аввалини истифода strace, шояд ба таври оддӣ истинод кардан лозим аст - оғоз кардани барномае, ки кор мекунад strace.

Барои он ки ба рӯйхати беохири зангҳои як барномаи маъмулӣ наафтем, мо менависем барномаи минималӣ атрофи он 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;
}

Биёед барномаро созем ва боварӣ ҳосил кунем, ки он кор мекунад:

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

Ва дар ниҳоят, биёед онро таҳти назорати 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)                           = ?

Хеле «калима» ва на чандон тарбиявӣ. Дар ин ҷо ду мушкилӣ вуҷуд дорад: баромади барнома бо баромад омехта мешавад strace ва шумораи зиёди зангҳои системавӣ, ки ба мо таваҷҷӯҳ надоранд.

Шумо метавонед ҷараёни стандартии баромади барнома ва баромади хатогиро бо истифода аз гузариши -o, ки рӯйхати зангҳои системаро ба файли аргумент равона мекунад, ҷудо кунед.

Мушкилоти зангҳои "иловагӣ" ҳал мешавад. Фарз мекунем, ки мо танҳо ба зангҳо манфиатдорем write. Калид -e ба шумо имкон медиҳад, ки ифодаҳоеро муайян кунед, ки тавассути онҳо зангҳои система филтр карда мешаванд. Варианти маъмултарини шарт, табиист, ки trace=*, ки бо он шумо метавонед танҳо зангҳоеро тарк кунед, ки ба мо таваҷҷӯҳ доранд.

Вақте ки дар як вақт истифода бурда мешавад -o и -e мо мегирем:

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

Ҳамин тавр, шумо мебинед, хондан хеле осонтар аст.

Шумо инчунин метавонед зангҳои системавиро хориҷ кунед, масалан онҳое, ки ба тақсимоти хотира ва озодкунӣ алоқаманданд:

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

Дар рӯйхати зангҳои хориҷшуда аломати нидои фироршударо қайд кунед: ин аз ҷониби қабати фармон талаб карда мешавад. пӯст).

Дар версияи ман glibc, занги система равандро қатъ мекунад exit_group, на анъанавӣ _exit. Ин мушкилии кор бо зангҳои системавӣ аст: интерфейсе, ки барномасоз бо он кор мекунад, бевосита ба зангҳои системавӣ алоқаманд нест. Илова бар ин, он вобаста ба татбиқ ва платформа мунтазам тағйир меёбад.

Асосҳо: ҳамроҳ шудан ба раванд дар парвоз

Дар аввал, системаи ptrace, ки дар он сохта шудааст strace, метавонад танҳо ҳангоми иҷро кардани барнома дар режими махсус истифода шавад. Ин маҳдудият шояд дар рӯзҳои Версияи 6 Unix оқилона садо медод. Дар айни замон ин дигар кифоя нест: баъзан ба шумо лозим меояд, ки мушкилоти барномаи кориро тафтиш кунед. Намунаи маъмулӣ ин равандест, ки дар даста баста ё хоб аст. Бинобар ин замонавй strace метавонад ба равандҳо дар парвоз ҳамроҳ шавад.

Намунаи яхкунӣ барномаҳои:

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

Биёед барномаро созем ва боварӣ ҳосил кунем, ки он яхбандӣ шудааст:

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

Акнун биёед кӯшиш кунем, ки ба он ҳамроҳ шавем:

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

Барнома бо занг баста шудааст pause. Биёед бубинем, ки вай ба сигналҳо чӣ гуна муносибат мекунад:

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

Мо барномаи яхкардашударо оғоз кардем ва бо истифода аз он ҳамроҳ шудем strace. Ду чиз равшан шуд: занги системаи таваққуф сигналҳоро бе коркардкунандагон сарфи назар мекунад ва ҷолибтараш он аст, ки strace на танҳо зангҳои система, балки сигналҳои даромадро низ назорат мекунад.

Мисол: Пайгирии равандҳои кӯдакон

Кор бо равандҳо тавассути занг fork - асоси ҳамаи Unixes. Биёед бубинем, ки чӣ тавр strace бо дарахти равандҳо бо истифода аз мисоли оддии "селексия" кор мекунад барномаҳои:

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

Дар ин ҷо раванди аслӣ як раванди кӯдакро эҷод мекунад, ки ҳарду ба баромади стандартӣ менависанд:

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

Бо нобаёнӣ, мо танҳо зангҳои системаро аз раванди волидайн мебинем:

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

Парчам ба шумо кӯмак мекунад, ки тамоми дарахти равандро пайгирӣ кунед -fбо кадом strace зангҳои системаро дар равандҳои кӯдак назорат мекунад. Ин ба ҳар як сатри баромад илова мекунад pid раванде, ки баромади системаро медиҳад:

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

Дар ин замина, филтркунӣ аз рӯи гурӯҳи зангҳои система метавонад муфид бошад:

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

Воқеан, барои эҷоди раванди нав кадом занги система истифода мешавад?

Мисол: роҳҳои файл ба ҷои дастаки

Донистани дескрипторҳои файл бешубҳа муфид аст, аммо номи файлҳои мушаххасе, ки барнома дастрас мекунад, низ метавонад муфид бошад.

Баъдан барнома сатрро ба файли муваққатӣ менависад:

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

Ҳангоми занги муқаррарӣ strace арзиши рақами тавсифкунандаи ба занги системавӣ додашударо нишон медиҳад:

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

Бо парчам -y Утилита роҳро ба файле нишон медиҳад, ки ба он дескриптор мувофиқ аст:

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

Мисол: Пайгирии дастрасии файл

Дигар хусусияти муфид: намоиш танҳо зангҳои системавие, ки бо файли мушаххас алоқаманданд. Баъдӣ барнома сатрро ба файли худсарона, ки ҳамчун аргумент дода шудааст, замима мекунад:

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

бо пешфарз strace бисьёр маълумоти нолозимро нишон медихад. Парчам -P бо аргумент боиси чопи strace танҳо зангҳо ба файли мушаххас мегардад:

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

Мисол: Барномаҳои бисёрҷабҳа

Коммуналӣ strace инчунин метавонад ҳангоми кор бо чанд ришта кӯмак расонад барнома. Барномаи зерин ба баромади стандартӣ аз ду ҷараён менависад:

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

Табиист, ки он бояд бо саломи махсус ба пайвандкунанда - парчами -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
$

Флаг -f, чунон ки дар мавриди равандҳои муқаррарӣ, pid-и равандро ба аввали ҳар як сатр илова мекунад.

Табиист, ки мо на дар бораи идентификатори ришта ба маънои татбиқи стандарти POSIX Threads, балки дар бораи рақаме, ки нақшаи вазифаҳоро дар Linux истифода мебарад. Аз нуқтаи назари охирин, равандҳо ё риштаҳо вуҷуд надоранд - вазифаҳое ҳастанд, ки бояд дар байни ядроҳои мавҷудаи мошин тақсим карда шаванд.

Ҳангоми кор дар риштаҳои сершумор, зангҳои система хеле зиёд мешаванд:

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

Худро танҳо бо коркарди идоракунӣ ва зангҳои система маҳдуд кардан маъно дорад 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 +++

Воқеан, саволҳо. Барои сохтани риштаи нав кадом занги системавӣ истифода мешавад? Чӣ тавр ин даъват барои риштаҳо аз даъват барои равандҳо фарқ мекунад?

Мастер-класс: стеки раванд дар вақти занги система

Яке аз он ба наздикӣ пайдо шуд strace қобилиятҳо - намоиш додани стеки зангҳои функсионалӣ дар вақти занги система. Содда намуна:

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

Табиист, ки баромади барнома хеле ҳаҷмнок мешавад ва ба ғайр аз парчам -k (намоиши стеки зангҳо), филтр кардани зангҳои система аз рӯи ном маъно дорад:

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

Синфи мастер: тазриқи хато

Ва боз як хусусияти нав ва хеле муфид: тазриқи хато. Ин ҷо барнома, навиштани ду сатр ба ҷараёни баромад:

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

Биёед ҳарду зангҳои навиштанро пайгирӣ кунем:

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

Акнун мо ифодаро истифода мебарем injectбарои ворид кардани хато EBADF дар ҳама зангҳои нависед:

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

Ҷолиб он аст, ки кадом хатогиҳо баргардонида мешаванд ҳама мушкилот write, аз ҷумла занги дар паси хато пинҳоншуда. Баргардонидани хато барои аввалин зангҳо танҳо маъно дорад:

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

Ё дуюм:

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

Муайян кардани навъи хато шарт нест:

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

Дар якҷоягӣ бо дигар парчамҳо, шумо метавонед дастрасӣ ба файли мушаххасро "шиканед". Мисол:

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

Ба ғайр аз тазриқи хато, метавонад хангоми занг задан ё кабули сигналхо таъхирхоро чорй кунанд.

Пас аз он

Коммуналӣ strace - воситаи оддӣ ва боэътимод. Аммо ба ғайр аз зангҳои системавӣ, ҷанбаҳои дигари кори барномаҳо ва системаи амалиётиро метавон ислоҳ кард. Масалан, он метавонад зангҳоро ба китобхонаҳои динамикӣ пайваст пайгирӣ кунад. ltrace, онҳо метавонанд ба кори системаи оператсионӣ назар кунанд SystemTap и ftrace, ва ба шумо имкон медиҳад, ки иҷрои барномаро амиқ тафтиш кунед комил. Бо вуҷуди ин, он аст strace - хатти аввалини дифоъ дар сурати мушкилот бо барномаҳои худам ва дигарон ва ман ҳадди аққал дар як ҳафта ду маротиба истифода мекунам.

Хулоса, агар шумо Unix-ро дӯст доред, хонед man 1 strace ва озодона ба барномаҳои худ назар кунед!

Манбаъ: will.com

Илова Эзоҳ