Strace katika Linux: historia, muundo na matumizi

Strace katika Linux: historia, muundo na matumizi

Katika mifumo ya uendeshaji ya Unix, mawasiliano ya programu na ulimwengu wa nje na mfumo wa uendeshaji hutokea kupitia seti ndogo ya kazi - simu za mfumo. Hii inamaanisha kuwa kwa madhumuni ya kurekebisha inaweza kuwa muhimu kupeleleza simu za mfumo zinazotekelezwa na michakato.

Huduma hukusaidia kufuatilia "maisha ya karibu" ya programu kwenye Linux strace, ambayo ndiyo mada ya makala hii. Mifano ya matumizi ya vifaa vya kijasusi inaambatana na historia fupi strace na maelezo ya muundo wa programu hizo.

yaliyomo

Asili ya spishi

Muunganisho kuu kati ya programu na kernel ya OS katika Unix ni simu za mfumo. simu za mfumo, syscalls), mwingiliano wa programu na ulimwengu wa nje hutokea pekee kupitia kwao.

Lakini katika toleo la kwanza la umma la Unix (Toleo la 6 Unix, 1975) hakukuwa na njia rahisi za kufuatilia tabia ya michakato ya mtumiaji. Ili kutatua suala hili, Bell Labs itasasisha hadi toleo linalofuata (Toleo la 7 Unix, 1979) alipendekeza wito mpya wa mfumo - ptrace.

ptrace ilitengenezwa kimsingi kwa vitatuzi vinavyoingiliana, lakini hadi mwisho wa miaka ya 80 (katika enzi ya kibiashara. Toleo la Mfumo V 4) kwa msingi huu, vitatuzi vilivyolenga kwa ufinyuβ€”vifuatiliaji vya simu vya mfumoβ€”vilionekana na kutumika sana.

Kwanza toleo lile lile la strace lilichapishwa na Paul Cronenburg kwenye orodha ya utumaji barua ya comp.sources.sun mnamo 1992 kama njia mbadala ya shirika lililofungwa. trace kutoka kwa Jua. Clone na asili zilikusudiwa kwa SunOS, lakini kufikia 1994 strace ilitumwa kwa System V, Solaris na Linux inayozidi kuwa maarufu.

Leo strace inasaidia tu Linux na inategemea sawa ptrace, iliyokua na viendelezi vingi.

Mtunzaji wa kisasa (na anayefanya kazi sana). strace - Dmitry Levin. Shukrani kwake, shirika lilipata huduma za hali ya juu kama vile sindano ya makosa kwenye simu za mfumo, msaada kwa anuwai ya usanifu na, muhimu zaidi, mascot. Vyanzo visivyo rasmi vinadai kwamba chaguo lilianguka kwa mbuni kwa sababu ya upatanisho kati ya neno la Kirusi "mbuni" na neno la Kiingereza "strace".

Pia ni muhimu kwamba simu na vifuatiliaji vya mfumo wa ptrace havikuwahi kujumuishwa kwenye POSIX, licha ya historia ndefu na utekelezaji katika Linux, FreeBSD, OpenBSD na Unix ya jadi.

Strace kifaa kwa kifupi: Piglet Trace

"Hutarajiwi kuelewa hili" (Dennis Ritchie, maoni katika Toleo la 6 msimbo wa chanzo wa Unix)

Tangu utoto wa mapema, siwezi kusimama masanduku nyeusi: Sikucheza na vinyago, lakini nilijaribu kuelewa muundo wao (watu wazima walitumia neno "kuvunja," lakini usiamini lugha mbaya). Labda hii ndiyo sababu utamaduni usio rasmi wa Unix ya kwanza na harakati ya kisasa ya chanzo-wazi iko karibu sana nami.

Kwa madhumuni ya kifungu hiki, sio busara kutenganisha msimbo wa chanzo wa kamba, ambao umekua kwa miongo kadhaa. Lakini haipaswi kuwa na siri kwa wasomaji. Kwa hivyo, ili kuonyesha kanuni ya uendeshaji wa programu kama hizi, nitatoa nambari ya tracer miniature - Ufuatiliaji wa Nguruwe (ptr). Haijui jinsi ya kufanya kitu chochote maalum, lakini jambo kuu ni simu za mfumo wa programu - hutoa matokeo:

$ 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 inatambua kuhusu mamia ya simu za mfumo wa Linux (ona. meza) na inafanya kazi tu kwenye usanifu wa x86-64. Hii inatosha kwa madhumuni ya kielimu.

Wacha tuangalie kazi ya msaidizi wetu. Kwa upande wa Linux, vitatuzi na vifuatiliaji hutumia, kama ilivyotajwa hapo juu, simu ya mfumo wa ptrace. Inafanya kazi kwa kupitisha katika hoja ya kwanza vitambulisho vya amri, ambavyo tunahitaji tu PTRACE_TRACEME, PTRACE_SYSCALL ΠΈ PTRACE_GETREGS.

Mfuatiliaji huanza kwa mtindo wa kawaida wa Unix: fork(2) huzindua mchakato wa mtoto, ambao kwa upande wake hutumia exec(3) inazindua programu inayosomewa. Ujanja pekee hapa ni changamoto ptrace(PTRACE_TRACEME) kabla ya exec: Mchakato wa mtoto unatarajia mchakato wa mzazi kuufuatilia:

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

Mchakato wa mzazi unapaswa kupiga simu sasa wait(2) katika mchakato wa mtoto, yaani, hakikisha kuwa kubadili kwa modi ya kufuatilia kumetokea:

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

Kwa hatua hii, maandalizi yamekamilika na unaweza kuendelea moja kwa moja kufuatilia simu za mfumo kwa kitanzi kisicho na mwisho.

Changamoto ptrace(PTRACE_SYSCALL) inahakikisha kwamba baadae wait mzazi atakamilisha kabla ya simu ya mfumo kutekelezwa au mara tu baada ya kukamilika. Kati ya simu mbili unaweza kufanya vitendo vyovyote: badilisha simu na mbadala, badilisha hoja au thamani ya kurejesha.

Tunahitaji tu kuita amri mara mbili ptrace(PTRACE_GETREGS)kupata hali ya usajili rax kabla ya simu (nambari ya simu ya mfumo) na mara baada ya (thamani ya kurejesha).

Kwa kweli, mzunguko:

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

Huyo ndiye mfuatiliaji mzima. Sasa unajua wapi pa kuanzia uhamishaji unaofuata DTrace kwenye Linux.

Misingi: kuendesha programu inayoendesha safu

Kama kesi ya matumizi ya kwanza strace, labda inafaa kutaja njia rahisi - kuzindua programu inayoendesha strace.

Ili usiingie kwenye orodha isiyo na mwisho ya simu za programu ya kawaida, tunaandika programu ya chini Π²ΠΎΠΊΡ€ΡƒΠ³ 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;
}

Wacha tujenge programu na tuhakikishe kuwa inafanya kazi:

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

Na mwishowe, wacha tuiendeshe chini ya udhibiti wa kamba:

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

"Maneno" sana na sio ya kuelimisha sana. Kuna matatizo mawili hapa: matokeo ya programu yanachanganywa na matokeo strace na simu nyingi za mfumo ambazo hazituvutii.

Unaweza kutenganisha mtiririko wa kawaida wa pato la programu na matokeo ya hitilafu ya strace kwa kutumia -o swichi, ambayo inaelekeza upya orodha ya simu za mfumo hadi faili ya hoja.

Inabakia kukabiliana na tatizo la wito "ziada". Wacha tuchukue kuwa tunavutiwa na simu tu write. Ufunguo -e hukuruhusu kubainisha misemo ambayo simu za mfumo zitachujwa. Chaguo maarufu zaidi la hali ni, kwa asili, trace=*, ambayo unaweza kuacha tu simu zinazotuvutia.

Inapotumiwa wakati huo huo -o ΠΈ -e tutapata:

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

Kwa hiyo, unaona, ni rahisi zaidi kusoma.

Unaweza pia kuondoa simu za mfumo, kwa mfano zile zinazohusiana na mgao wa kumbukumbu na kufungia:

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

Kumbuka alama ya mshangao iliyotoroka katika orodha ya simu zisizojumuishwa: hii inahitajika na ganda la amri. shell).

Katika toleo langu la glibc, simu ya mfumo inasitisha mchakato exit_group, sio jadi _exit. Huu ni ugumu wa kufanya kazi na simu za mfumo: interface ambayo programu hufanya kazi haihusiani moja kwa moja na simu za mfumo. Aidha, inabadilika mara kwa mara kulingana na utekelezaji na jukwaa.

Msingi: kujiunga na mchakato juu ya kuruka

Hapo awali, simu ya mfumo wa ptrace ambayo ilijengwa strace, inaweza kutumika tu wakati wa kuendesha programu katika hali maalum. Kizuizi hiki kinaweza kuonekana kuwa sawa katika siku za Toleo la 6 Unix. Siku hizi, hii haitoshi tena: wakati mwingine unahitaji kuchunguza matatizo ya programu ya kufanya kazi. Mfano wa kawaida ni mchakato uliozuiwa kwenye kushughulikia au kulala. Kwa hivyo kisasa strace inaweza kujiunga na michakato kwa kuruka.

Mfano wa kufungia mipango:

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

Wacha tuunde programu na tuhakikishe kuwa imeganda:

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

Sasa hebu tujaribu kujiunga nayo:

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

Mpango umezuiwa kwa simu pause. Wacha tuone jinsi anavyoitikia ishara:

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

Tulizindua programu iliyogandishwa na tukajiunga nayo kwa kutumia strace. Mambo mawili yakawa wazi: simu ya mfumo wa pause inapuuza ishara bila washughulikiaji na, zaidi ya kuvutia, wachunguzi wa strace sio tu simu za mfumo, lakini pia ishara zinazoingia.

Mfano: Kufuatilia Michakato ya Mtoto

Kufanya kazi na michakato kupitia simu fork - msingi wa Unixes zote. Wacha tuone jinsi strace inavyofanya kazi na mti wa mchakato kwa kutumia mfano wa "ufugaji" rahisi. mipango:

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

Hapa mchakato wa asili huunda mchakato wa mtoto, wote wakiandika kwa pato la kawaida:

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

Kwa chaguo-msingi, tutaona tu simu za mfumo kutoka kwa mchakato wa mzazi:

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

Bendera hukusaidia kufuatilia mti mzima wa mchakato -f, ambayo strace hufuatilia simu za mfumo katika michakato ya mtoto. Hii inaongeza kwa kila mstari wa pato pid mchakato ambao hutoa pato la mfumo:

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

Katika muktadha huu, kuchuja kwa kikundi cha simu za mfumo kunaweza kuwa muhimu:

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

Kwa njia, ni simu gani ya mfumo inayotumiwa kuunda mchakato mpya?

Mfano: njia za faili badala ya vipini

Kujua vielezi vya faili hakika ni muhimu, lakini majina ya faili maalum ambazo programu hufikia pia zinaweza kuwa muhimu.

ijayo mpango huandika mstari kwa faili ya muda:

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

Wakati wa simu ya kawaida strace itaonyesha thamani ya nambari ya maelezo iliyopitishwa kwa simu ya mfumo:

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

Na bendera -y Huduma inaonyesha njia ya faili ambayo maelezo yanalingana:

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

Mfano: Ufuatiliaji wa Ufikiaji wa Faili

Kipengele kingine muhimu: onyesha simu za mfumo pekee zinazohusiana na faili maalum. Inayofuata mpango inaongeza mstari kwa faili ya kiholela iliyopitishwa kama hoja:

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 inaonyesha habari nyingi zisizo za lazima. Bendera -P na hoja husababisha strace kuchapisha simu tu kwa faili maalum:

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

Mfano: Programu zenye nyuzi nyingi

Huduma strace inaweza pia kusaidia wakati wa kufanya kazi na nyuzi nyingi mpango. Programu ifuatayo inaandika kwa pato la kawaida kutoka kwa mitiririko miwili:

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

Kwa kawaida, lazima ijumuishwe na salamu maalum kwa kiunganishi - bendera ya -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
$

Bendera -f, kama ilivyo kwa michakato ya kawaida, itaongeza pid ya mchakato hadi mwanzo wa kila mstari.

Kwa kawaida, hatuzungumzii juu ya kitambulisho cha thread kwa maana ya utekelezaji wa kiwango cha POSIX Threads, lakini kuhusu nambari inayotumiwa na mpangaji wa kazi katika Linux. Kwa mtazamo wa mwisho, hakuna michakato au nyuzi - kuna kazi zinazohitaji kusambazwa kati ya cores zilizopo za mashine.

Wakati wa kufanya kazi katika nyuzi nyingi, simu za mfumo huwa nyingi sana:

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

Inaleta akili kujiwekea kikomo kwa kushughulikia usimamizi na simu za mfumo pekee 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 +++

Kwa njia, maswali. Je, ni simu gani ya mfumo inatumiwa kuunda mazungumzo mapya? Je, simu hii ya nyuzi inatofautiana vipi na mwito wa michakato?

Darasa la bwana: mchakato wa mrundikano wakati wa simu ya mfumo

Mmoja wa hivi karibuni alionekana strace uwezo - kuonyesha safu ya simu za kazi wakati wa simu ya mfumo. Rahisi mfano:

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

Kwa kawaida, pato la programu inakuwa kubwa sana, na, pamoja na bendera -k (onyesho la rundo la simu), inaeleweka kuchuja simu za mfumo kwa jina:

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

Darasa la bwana: sindano ya makosa

Na kipengele kimoja kipya na muhimu sana: sindano ya makosa. Hapa mpango, kuandika mistari miwili kwenye mkondo wa pato:

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

Wacha tufuate simu zote mbili za kuandika:

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

Sasa tunatumia usemi injectkuingiza hitilafu EBADF katika simu zote andika:

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

Inafurahisha ni makosa gani yanarudishwa wote changamoto write, ikijumuisha simu iliyofichwa nyuma ya ugaidi. Inaeleweka tu kurudisha kosa kwa simu ya kwanza:

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

Au ya pili:

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

Sio lazima kutaja aina ya makosa:

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

Kwa kuchanganya na bendera nyingine, unaweza "kuvunja" ufikiaji wa faili maalum. Mfano:

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

Mbali na sindano ya makosa, mtu anaweza anzisha ucheleweshaji wakati wa kupiga simu au kupokea ishara.

Baada ya

Huduma strace - chombo rahisi na cha kuaminika. Lakini pamoja na simu za mfumo, vipengele vingine vya uendeshaji wa programu na mfumo wa uendeshaji vinaweza kufutwa. Kwa mfano, inaweza kufuatilia simu kwa maktaba zilizounganishwa kwa nguvu. ltrace, wanaweza kuangalia uendeshaji wa mfumo wa uendeshaji SystemTap ΠΈ mguu, na hukuruhusu kuchunguza kwa kina utendaji wa programu perf. Hata hivyo, ni strace - safu ya kwanza ya utetezi katika kesi ya shida na programu zangu mwenyewe na za watu wengine, na mimi hutumia angalau mara kadhaa kwa wiki.

Kwa kifupi, ikiwa unapenda Unix, soma man 1 strace na jisikie huru kutazama programu zako!

Chanzo: mapenzi.com

Kuongeza maoni