Linuxda Strace: tarix, dizayn va foydalanish

Linuxda Strace: tarix, dizayn va foydalanish

Unix-ga o'xshash operatsion tizimlarda dasturning tashqi dunyo va operatsion tizim bilan aloqasi kichik funktsiyalar to'plami - tizim chaqiruvlari orqali amalga oshiriladi. Bu shuni anglatadiki, disk raskadrovka maqsadida jarayonlar tomonidan bajarilayotgan tizim chaqiruvlariga josuslik qilish foydali bo'lishi mumkin.

Yordamchi dastur Linuxda dasturlarning "intim hayotini" kuzatishga yordam beradi strace, bu maqolaning mavzusi. Ayg'oqchi uskunalardan foydalanish misollari qisqacha tarix bilan birga keladi strace va bunday dasturlarni loyihalash tavsifi.

Mundarija

Turlarning kelib chiqishi

Unix-dagi dasturlar va OT yadrosi o'rtasidagi asosiy interfeys tizim qo'ng'iroqlaridir. tizim qo'ng'iroqlari, syscall), dasturlarning tashqi dunyo bilan o'zaro ta'siri faqat ular orqali sodir bo'ladi.

Lekin Unix ning birinchi ommaviy versiyasida (Unix versiyasi 6, 1975) foydalanuvchi jarayonlarining xatti-harakatlarini kuzatishning qulay usullari mavjud emas edi. Ushbu muammoni hal qilish uchun Bell Labs keyingi versiyaga yangilanadi (Unix versiyasi 7, 1979) yangi tizim chaqiruvini taklif qildi - ptrace.

ptrace asosan interaktiv tuzatuvchilar uchun ishlab chiqilgan, ammo 80-yillarning oxiriga kelib (tijorat davrida) Tizim V versiyasi 4) shu asosda tor yoʻnaltirilgan tuzatuvchi – tizim chaqiruv izlagichlari paydo boʻldi va keng qoʻllanila boshlandi.

birinchi strace-ning xuddi shu versiyasi 1992 yilda yopiq yordamchi dasturga muqobil sifatida Paul Cronenburg tomonidan comp.sources.sun pochta ro'yxatida nashr etilgan. trace quyoshdan. Klon ham, asl nusxa ham SunOS uchun mo'ljallangan edi, ammo 1994 yilga kelib strace System V, Solaris va tobora ommalashib borayotgan Linux-ga o'tkazildi.

Bugungi kunda strace faqat Linuxni qo'llab-quvvatlaydi va shunga tayanadi ptrace, ko'plab kengaytmalar bilan o'sib chiqqan.

Zamonaviy (va juda faol) ta'minotchi strace - Dmitriy Levin. Uning yordami bilan yordamchi dastur tizim qo'ng'iroqlariga xatolik kiritish, keng arxitekturani qo'llab-quvvatlash va eng muhimi, ilg'or xususiyatlarni qo'lga kiritdi. maskot. Norasmiy manbalarning ta'kidlashicha, ruscha "tuyaqush" so'zi va inglizcha "strace" so'zlari o'rtasidagi uyg'unlik tufayli tanlov tuyaqushga tushgan.

Bundan tashqari, Linux, FreeBSD, OpenBSD va an'anaviy Unix-da uzoq tarix va amaliyotga ega bo'lishiga qaramay, ptrace tizimi chaqiruvi va izlovchilar hech qachon POSIX-ga kiritilmaganligi muhimdir.

Qisqacha aytganda, Strace qurilmasi: Piglet Trace

"Siz buni tushunishingiz kutilmaydi" (Dennis Ritchie, Unix manba kodining 6-versiyasiga sharh)

Erta bolalikdan men qora qutilarga dosh berolmayman: men o'yinchoqlar bilan o'ynamadim, lekin ularning tuzilishini tushunishga harakat qildim (kattalar "buzilgan" so'zini ishlatishdi, lekin yovuz tillarga ishonmaydilar). Ehtimol, shuning uchun birinchi Unixning norasmiy madaniyati va zamonaviy ochiq manba harakati menga juda yaqin.

Ushbu maqolaning maqsadlari uchun o'nlab yillar davomida o'sib borayotgan strace manba kodini qismlarga ajratish mantiqiy emas. Ammo kitobxonlar uchun hech qanday sir qolmasligi kerak. Shuning uchun, bunday strace dasturlarining ishlash printsipini ko'rsatish uchun men miniatyura izlagichi kodini beraman - Piglet Trace (ptr). U qanday qilib maxsus narsani qilishni bilmaydi, lekin asosiysi dasturning tizim chaqiruvlari - u chiqadi:

$ 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 yuzlab Linux tizimi qo'ng'iroqlarini taniydi (qarang. jadval) va faqat x86-64 arxitekturasida ishlaydi. Bu ta'lim maqsadlari uchun etarli.

Keling, klonimizning ishini ko'rib chiqaylik. Linux holatida nosozliklarni tuzatuvchilar va kuzatuvchilar, yuqorida aytib o'tilganidek, ptrace tizimi chaqiruvidan foydalanadilar. U birinchi argumentda bizga faqat kerak bo'lgan buyruq identifikatorlarini o'tkazish orqali ishlaydi PTRACE_TRACEME, PTRACE_SYSCALL и PTRACE_GETREGS.

Tracer odatiy Unix uslubida boshlanadi: fork(2) bola jarayonini ishga tushiradi, bu esa o'z navbatida foydalanadi exec(3) o‘rganilayotgan dasturni ishga tushiradi. Bu erda yagona noziklik - bu qiyinchilik ptrace(PTRACE_TRACEME) oldin exec: Bola jarayoni ota-ona jarayoni uni kuzatishini kutadi:

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

Ota-ona jarayoni endi qo'ng'iroq qilishi kerak wait(2) bola jarayonida, ya'ni kuzatish rejimiga o'tish sodir bo'lganligiga ishonch hosil qiling:

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

Ayni paytda tayyorgarlik tugallandi va siz to'g'ridan-to'g'ri tizim qo'ng'iroqlarini cheksiz tsiklda kuzatishga o'tishingiz mumkin.

Qiyinchilik ptrace(PTRACE_SYSCALL) bundan keyin ham kafolat beradi wait ota-ona tizim chaqiruvi amalga oshirilishidan oldin yoki u tugallangandan so'ng darhol yakunlanadi. Ikki qo'ng'iroq o'rtasida siz istalgan amallarni bajarishingiz mumkin: qo'ng'iroqni muqobil bilan almashtiring, argumentlarni o'zgartiring yoki qiymatni qaytaring.

Biz shunchaki buyruqni ikki marta chaqirishimiz kerak ptrace(PTRACE_GETREGS)reestr holatini olish uchun rax qo'ng'iroqdan oldin (tizim qo'ng'iroq raqami) va darhol keyin (qaytish qiymati).

Aslida, tsikl:

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

Bu butun kuzatuvchi. Endi siz keyingi portlashni qaerdan boshlashni bilasiz DTraktsiya Linuxda.

Asosiy ma'lumotlar: strace bilan ishlaydigan dasturni ishga tushirish

Birinchi foydalanish holati sifatida strace, ehtimol, eng oddiy usulni eslatib o'tishga arziydi - ishlaydigan dasturni ishga tushirish strace.

Oddiy dasturning cheksiz qo'ng'iroqlari ro'yxatiga kirmaslik uchun biz yozamiz minimal dastur atrofida 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;
}

Keling, dasturni tuzamiz va uning ishlashiga ishonch hosil qilamiz:

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

Va nihoyat, keling, uni strace nazorati ostida ishga tushiramiz:

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

Juda "so'zli" va unchalik tarbiyaviy emas. Bu erda ikkita muammo bor: dastur chiqishi chiqish bilan aralashtiriladi strace va bizni qiziqtirmaydigan ko'plab tizim qo'ng'iroqlari.

Tizim qo'ng'iroqlari ro'yxatini argument fayliga yo'naltiruvchi -o kaliti yordamida dasturning standart chiqish oqimini va xato chiqishini ajratishingiz mumkin.

"Qo'shimcha" qo'ng'iroqlar muammosi bilan shug'ullanish qoladi. Bizni faqat qo'ng'iroqlar qiziqtiradi, deb faraz qilaylik write. Kalit -e tizim chaqiruvlari filtrlanadigan iboralarni belgilash imkonini beradi. Eng mashhur shart varianti, tabiiyki, trace=*, bu bilan siz faqat bizni qiziqtirgan qo'ng'iroqlarni qoldirishingiz mumkin.

Bir vaqtning o'zida ishlatilganda -o и -e biz olamiz:

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

Shunday qilib, ko'rdingizmi, o'qish ancha oson.

Shuningdek, siz tizim qo'ng'iroqlarini, masalan, xotirani ajratish va bo'shatish bilan bog'liq qo'ng'iroqlarni olib tashlashingiz mumkin:

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

Cheklangan qo'ng'iroqlar ro'yxatida qochib ketgan undov belgisiga e'tibor bering: bu buyruq qobig'i tomonidan talab qilinadi. chig'anoq).

Mening glibc versiyamda tizim chaqiruvi jarayonni tugatadi exit_group, an'anaviy emas _exit. Bu tizim chaqiruvlari bilan ishlashning qiyinligi: dasturchi ishlaydigan interfeys tizim qo'ng'iroqlari bilan bevosita bog'liq emas. Bundan tashqari, u amalga oshirish va platformaga qarab muntazam ravishda o'zgarib turadi.

Asosiy ma'lumotlar: jarayonga tezda qo'shilish

Dastlab, u qurilgan ptrace tizimi chaqiruvi strace, faqat dasturni maxsus rejimda ishga tushirishda foydalanish mumkin edi. Ushbu cheklov Unix 6-versiyasi kunlarida mantiqiy tuyulgan bo'lishi mumkin. Hozirgi kunda bu etarli emas: ba'zida siz ishlaydigan dastur muammolarini tekshirishingiz kerak. Oddiy misol - tutqichda bloklangan yoki uxlab yotgan jarayon. Shuning uchun zamonaviy strace jarayonlarga tezda qo'shilishi mumkin.

Muzlatish misoli dasturlari:

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

Keling, dasturni tuzamiz va uning muzlatilganligiga ishonch hosil qilamiz:

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

Endi unga qo'shilishga harakat qilaylik:

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

Dastur qo'ng'iroq orqali bloklangan pause. Keling, u signallarga qanday munosabatda bo'lishini ko'rib chiqaylik:

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

Biz muzlatilgan dasturni ishga tushirdik va unga qo'shildik strace. Ikki narsa aniq bo'ldi: pauza tizimi qo'ng'irog'i ishlov beruvchilarsiz signallarni e'tiborsiz qoldiradi va eng qizig'i, strace nafaqat tizim qo'ng'iroqlarini, balki kiruvchi signallarni ham kuzatib boradi.

Misol: Bolalar jarayonlarini kuzatish

Chaqiruv orqali jarayonlar bilan ishlash fork - barcha Unix-larning asosi. Keling, oddiy "naslchilik" misolidan foydalanib, strace jarayon daraxti bilan qanday ishlashini ko'rib chiqaylik. dasturlari:

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

Bu erda asl jarayon, ikkalasi ham standart chiqishga yoziladigan bola jarayonini yaratadi:

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

Odatiy bo'lib, biz faqat asosiy jarayondan tizim qo'ng'iroqlarini ko'ramiz:

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

Bayroq butun jarayon daraxtini kuzatishga yordam beradi -fqaysi bilan strace bolalar jarayonlarida tizim chaqiruvlarini kuzatib boradi. Bu chiqishning har bir qatoriga qo'shiladi pid tizim chiqishini amalga oshiradigan jarayon:

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

Shu nuqtai nazardan, tizim qo'ng'iroqlari guruhi bo'yicha filtrlash foydali bo'lishi mumkin:

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

Aytgancha, yangi jarayonni yaratish uchun qanday tizim chaqiruvi ishlatiladi?

Misol: tutqichlar o'rniga fayl yo'llari

Fayl identifikatorlarini bilish, albatta, foydalidir, lekin dastur kiradigan muayyan fayllarning nomlari ham foydali bo'lishi mumkin.

Keyingi dasturi vaqtinchalik faylga qatorni yozadi:

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

Oddiy qo'ng'iroq paytida strace tizim chaqiruviga uzatilgan deskriptor raqamining qiymatini ko'rsatadi:

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

Bayroq bilan -y Yordamchi dastur deskriptor mos keladigan faylga yo'lni ko'rsatadi:

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

Misol: Faylga kirishni kuzatish

Yana bir foydali xususiyat: faqat ma'lum bir fayl bilan bog'liq tizim qo'ng'iroqlarini ko'rsatish. Keyingisi dasturi argument sifatida berilgan ixtiyoriy faylga qator qo'shadi:

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

sukut strace juda ko'p keraksiz ma'lumotlarni ko'rsatadi. Bayroq -P argument bilan strace faqat belgilangan faylga qo'ng'iroqlarni chop etishga sabab bo'ladi:

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

Misol: Ko'p bosqichli dasturlar

Qulaylik strace ko'p tarmoqli bilan ishlashda ham yordam berishi mumkin dastur. Quyidagi dastur ikkita oqimdan standart chiqishga yozadi:

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

Tabiiyki, u bog'lovchiga - pthread bayrog'iga maxsus tabrik bilan tuzilishi kerak:

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

Bayroq -f, muntazam jarayonlarda bo'lgani kabi, har bir satrning boshiga jarayonning pidini qo'shadi.

Tabiiyki, biz POSIX Threads standartini amalga oshirish ma'nosida ip identifikatori haqida emas, balki Linuxda vazifalarni rejalashtiruvchi tomonidan ishlatiladigan raqam haqida gapiramiz. Ikkinchisi nuqtai nazaridan, jarayonlar yoki iplar yo'q - mashinaning mavjud yadrolari orasida taqsimlanishi kerak bo'lgan vazifalar mavjud.

Bir nechta mavzularda ishlashda tizim qo'ng'iroqlari juda ko'p bo'ladi:

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

O'zingizni faqat boshqaruv va tizim chaqiruvlari bilan cheklash mantiqan 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 +++

Aytgancha, savollar. Yangi mavzu yaratish uchun qanday tizim chaqiruvidan foydalaniladi? Ushbu mavzu chaqiruvi jarayonlarga qo'ng'iroq qilishdan qanday farq qiladi?

Master-klass: tizim qo'ng'irog'i vaqtidagi jarayon stek

Ulardan biri yaqinda paydo bo'ldi strace imkoniyatlar - tizim chaqiruvi vaqtida funksiya chaqiruvlari to'plamini ko'rsatish. Oddiy misol:

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

Tabiiyki, dastur chiqishi juda katta hajmga ega bo'ladi va bayroqqa qo'shimcha ravishda -k (qo'ng'iroqlar to'plamini ko'rsatish), tizim qo'ng'iroqlarini nom bo'yicha filtrlash mantiqiy:

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

Master-klass: xato in'ektsiyasi

Va yana bir yangi va juda foydali xususiyat: xatolik kiritish. Bu yerga dasturi, chiqish oqimiga ikkita qator yozish:

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

Keling, ikkala yozma qo'ng'iroqni kuzatamiz:

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

Endi biz ifodadan foydalanamiz injectxato kiritish uchun EBADF barcha yozish qo'ng'iroqlarida:

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

Qaysi xatolar qaytarilgani qiziq hamma qiyinchiliklar write, shu jumladan xato ortida yashiringan qo'ng'iroq. Faqat birinchi qo'ng'iroqlar uchun xatoni qaytarish mantiqan:

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

Yoki ikkinchisi:

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

Xato turini ko'rsatish shart emas:

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

Boshqa bayroqlar bilan birgalikda siz ma'lum bir faylga kirishni "buzishingiz" mumkin. Misol:

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

Xatolarni kiritishdan tashqari, mumkin qo'ng'iroq qilish yoki signallarni qabul qilishda kechikishlarni kiritish.

So'zdan keyin

Qulaylik strace - oddiy va ishonchli vosita. Ammo tizim qo'ng'iroqlariga qo'shimcha ravishda, dasturlar va operatsion tizimning boshqa jihatlarini tuzatish mumkin. Misol uchun, u dinamik ravishda bog'langan kutubxonalarga qo'ng'iroqlarni kuzatishi mumkin. ltrace, ular operatsion tizimning ishlashini ko'rib chiqishlari mumkin Tizimga teging и ftrace, va dastur ishlashini chuqur o'rganishga imkon beradi mukammal. Shunga qaramay, shunday strace - o'zimning va boshqa odamlarning dasturlari bilan bog'liq muammolar yuzaga kelganda birinchi himoya chizig'i va men undan haftada kamida bir necha marta foydalanaman.

Qisqasi, agar siz Unix-ni yaxshi ko'rsangiz, o'qing man 1 strace va dasturlaringizni ko'rib chiqishingiz mumkin!

Manba: www.habr.com

a Izoh qo'shish