لينڪس ۾ اسٽريس: تاريخ، ڊيزائن ۽ استعمال

لينڪس ۾ اسٽريس: تاريخ، ڊيزائن ۽ استعمال

يونڪس جهڙو آپريٽنگ سسٽم ۾، ٻاهرين دنيا ۽ آپريٽنگ سسٽم سان هڪ پروگرام جو ڪميونيڪيشن، ڪم جي هڪ ننڍڙي سيٽ ذريعي ٿئي ٿو - سسٽم ڪالز. هن جو مطلب اهو آهي ته ڊيبگنگ جي مقصدن لاء اهو ڪارائتو ٿي سگهي ٿو جاسوسي ڪرڻ لاء سسٽم ڪالن تي عمل ڪيو پيو وڃي پروسيس ذريعي.

هڪ افاديت توهان کي لينڪس تي پروگرامن جي ”انٽم لائف“ مانيٽر ڪرڻ ۾ مدد ڪري ٿي strace، جيڪو هن مضمون جو موضوع آهي. جاسوسي سامان جي استعمال جا مثال مختصر تاريخ سان گڏ آهن strace ۽ اهڙن پروگرامن جي ڊيزائن جو تفصيل.

Contents

ذات جو اصل

يونڪس ۾ پروگرامن ۽ او ايس ڪنيل جي وچ ۾ مکيه انٽرفيس سسٽم ڪالز آهي. سسٽم ڪالون, اسڪيمون)، ٻاهرين دنيا سان پروگرامن جو رابطو خاص طور تي انهن جي ذريعي ٿئي ٿو.

پر يونڪس جي پهرين عوامي ورزن ۾ (نسخو 6 يونڪس, 1975) صارف جي عملن جي رويي کي ٽريڪ ڪرڻ لاء ڪو به آسان طريقا نه هئا. ھن مسئلي کي حل ڪرڻ لاءِ، Bell Labs ايندڙ ورزن ۾ تازه ڪاري ڪندو (نسخو 7 يونڪس، 1979) تجويز ڪيل نئين سسٽم ڪال - ptrace.

ptrace بنيادي طور تي انٽرايڪٽو ڊيبگرز لاءِ ترقي ڪئي وئي، پر 80 جي ڏهاڪي جي آخر تائين (تجارتي دور ۾ سسٽم V رليز 4) هن بنياد تي، تنگ طور تي مرڪوز ڊيبگرز-سسٽم ڪال ٽريڪرز-ظاهر ٿيا ۽ وڏي پيماني تي استعمال ڪيا ويا.

پهرين اسٽريس جو ساڳيو نسخو پال ڪروننبرگ طرفان 1992 ۾ comp.sources.sun ميلنگ لسٽ تي شايع ڪيو ويو هو هڪ بند يوٽيلٽي جي متبادل طور. trace سج کان. ٻئي ڪلون ۽ اصل سنوس لاءِ هئا، پر 1994 تائين strace سسٽم V، سولاريس ۽ وڌندڙ مقبول لينڪس ڏانهن پورٽ ڪيو ويو.

اڄ strace صرف لينڪس کي سپورٽ ڪري ٿو ۽ ساڳئي تي ڀاڙي ٿو ptrace, گھڻن واڌارن سان overgrown.

جديد (۽ تمام سرگرم) سنڀاليندڙ strace - دمتري ليون. هن جي مهرباني، يوٽيلٽي ترقي يافته خاصيتون حاصل ڪيون جهڙوڪ سسٽم ڪالن ۾ غلطي انجڻ، وسيع رينج جي تعمير لاء سپورٽ ۽، سڀ کان اهم، ماسڪوٽ. غير سرڪاري ذريعن جو چوڻ آهي ته چونڊ شتر مرغ تي ٿي، ڇاڪاڻ ته روسي لفظ "ostrich" ۽ انگريزي لفظ "strace" جي وچ ۾ مطابقت جي ڪري.

اهو پڻ اهم آهي ته لينڪس، فري بي ايس ڊي، اوپن بي ايس ڊي ۽ روايتي يونڪس ۾ هڪ ڊگهي تاريخ ۽ عمل جي باوجود، ptrace سسٽم ڪال ۽ ٽريڪرز ڪڏهن به POSIX ۾ شامل نه ڪيا ويا.

اسٽريس ڊيوائس مختصر ۾: Piglet Trace

"توهان کي اهو سمجهڻ جي اميد نه آهي" (ڊينس رچي، ورزن 6 ۾ تبصرو يونڪس سورس ڪوڊ)

ننڍپڻ کان وٺي، مان ڪارو باڪس برداشت نه ٿو ڪري سگهان: مون رانديڪن سان نه کيڏيو، پر انهن جي ساخت کي سمجهڻ جي ڪوشش ڪئي (بالغ لفظ "بروڪ" استعمال ڪيو، پر برائي زبانن تي يقين نه رکو). شايد اهو ئي سبب آهي جو پهريون يونڪس جو غير رسمي ڪلچر ۽ جديد اوپن سورس تحريڪ مون کي تمام گهڻو ويجهو آهي.

هن آرٽيڪل جي مقصدن لاءِ، اسٽريس جي سورس ڪوڊ کي ڌار ڪرڻ غير معقول آهي، جيڪو ڏهاڪن کان وڌي چڪو آهي. پر پڙهندڙن لاءِ ڪو به راز نه ڇڏڻ گهرجي. تنهن ڪري، اهڙن اسٽريس پروگرامن جي آپريشن جا اصول ڏيکارڻ لاءِ، مان هڪ ننڍڙي ٽريڪٽر لاءِ ڪوڊ مهيا ڪندس. سور جو نشان (پي ٽي آر). اها خبر ناهي ته ڪجهه خاص ڪيئن ڪجي، پر بنيادي شيء آهي پروگرام جي سسٽم ڪالز - اهو نڪتو:

$ 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 اٽڪل سوين لينڪس سسٽم ڪالن کي سڃاڻي ٿو (ڏسو. ٽيبل) ۽ صرف x86-64 فن تعمير تي ڪم ڪري ٿو. اهو تعليمي مقصدن لاء ڪافي آهي.

اچو ته اسان جي ڪلون جي ڪم کي ڏسو. لينڪس جي صورت ۾، ڊيبگرز ۽ ٽريڪرز استعمال ڪندا آهن، جيئن مٿي ڄاڻايل آهي، ptrace سسٽم ڪال. اهو ڪم ڪري ٿو پهرين دليلن ۾ گذرڻ سان ڪمانڊ جي سڃاڻپ ڪندڙ، جن مان اسان کي صرف ضرورت آهي PTRACE_TRACEME, PTRACE_SYSCALL и PTRACE_GETREGS.

ٽريسر عام يونڪس انداز ۾ شروع ٿئي ٿو: 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);
}

اهو سڄو ٽريڪٽر آهي. هاڻي توهان کي خبر آهي ته ايندڙ پورٽنگ ڪٿي شروع ڪجي ڊي ٽريس لينڪس تي.

بنياديات: هڪ پروگرام هلائڻ وارو اسٽريس

پهرين استعمال جي صورت ۾ 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 ./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 يونڪس جي ڏينهن ۾ اها حد مناسب لڳي سگهي ٿي. اڄڪلهه، اهو هاڻي ڪافي ناهي: ڪڏهن ڪڏهن توهان کي ڪم ڪندڙ پروگرام جي مسئلن جي تحقيق ڪرڻ جي ضرورت آهي. ھڪڙو عام مثال ھڪڙو عمل آھي جيڪو ھٿ يا ننڊ تي بند ٿيل آھي. تنهن ڪري جديد 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. ٻه شيون واضح ٿي ويون: روڪ سسٽم ڪال هينڊلر کان سواءِ سگنلن کي نظرانداز ڪري ٿو ۽ وڌيڪ دلچسپ ڳالهه اها آهي ته اسٽريس مانيٽر نه رڳو سسٽم ڪالون، پر ايندڙ سگنل پڻ.

مثال: ٽريڪنگ چائلڊ پروسيس

ڪال ذريعي عمل سان ڪم ڪرڻ fork - سڀني يونڪس جو بنياد. اچو ته ڏسو ڪيئن اسٽريس هڪ سادي ”نسل“ جو مثال استعمال ڪندي پروسيس ٽري سان ڪم ڪري ٿي. پروگرام:

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

رستي جي ذريعي، نئين پروسيسنگ ٺاهڻ لاء ڪهڙي سسٽم ڪال استعمال ڪئي وئي آهي؟

مثال: فائيل رستا بدران handles

ڄاڻڻ وارا فائل بيان ڪندڙ ضرور ڪارائتو آهن، پر مخصوص فائلن جا نالا جيڪي پروگرام تائين رسائي ڪري سگھن ٿا پڻ هٿ ۾ اچي سگهن ٿيون.

اڳيون پروگرام هڪ عارضي فائل ڏانهن لڪير لکي ٿو:

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

قدرتي طور تي، ان کي لازمي طور تي لنڪر کي خاص سلام سان گڏ ڪيو وڃي - پيٿڊ پرچم:

$ 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، جيئن باقاعده عملن جي صورت ۾، هر لڪير جي شروعات ۾ عمل جي پِڊ شامل ڪندو.

قدرتي طور تي، اسان POSIX Threads جي معيار تي عمل ڪرڻ جي معني ۾ هڪ موضوع جي سڃاڻپ ڪندڙ بابت نه ڳالهائي رهيا آهيون، پر لينڪس ۾ ٽاسڪ شيڊولر پاران استعمال ڪيل نمبر بابت. پوئين نقطي نظر کان، ڪو به عمل يا سلسلو نه آهي - اهڙا ڪم آهن جيڪي مشين جي دستياب ڪور ۾ ورهائڻ جي ضرورت آهي.

جڏهن ڪيترن ئي موضوعن ۾ ڪم ڪري رهيا آهن، سسٽم ڪالون تمام گهڻا ٿي ويندا آهن:

$ 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، اهي آپريٽنگ سسٽم جي آپريشن ۾ نظر ڪري سگهن ٿا سسٽم ٽيپ и سڌو، ۽ توهان کي پروگرام جي ڪارڪردگي جي تمام گهڻي تحقيق ڪرڻ جي اجازت ڏئي ٿي عجيب. تنهن هوندي به، اهو آهي strace - دفاع جي پهرين لائن منهنجي پنهنجي ۽ ٻين ماڻهن جي پروگرامن سان مسئلن جي صورت ۾، ۽ آئون ان کي هفتي ۾ گهٽ ۾ گهٽ ٻه ڀيرا استعمال ڪريان ٿو.

مختصر ۾، جيڪڏھن توھان يونڪس سان پيار ڪريو، پڙھو man 1 strace ۽ توهان جي پروگرامن کي ڏسڻ لاء آزاد محسوس ڪريو!

جو ذريعو: www.habr.com

تبصرو شامل ڪريو