په لینکس کې سټریس: تاریخ، ډیزاین او کارول

په لینکس کې سټریس: تاریخ، ډیزاین او کارول

د یونیکس په څیر عملیاتي سیسټمونو کې، د بهرنۍ نړۍ او عملیاتي سیسټم سره د پروګرام اړیکه د یو کوچنیو دندو - سیسټم کالونو له لارې واقع کیږي. دا پدې مانا ده چې د ډیبګ کولو اهدافو لپاره دا ګټور کیدی شي د سیسټم تلیفونونو جاسوسي چې د پروسو لخوا اجرا کیږي.

یو افادیت تاسو سره په لینکس کې د برنامو "نږدې ژوند" نظارت کولو کې مرسته کوي strace، کوم چې د دې مقالې موضوع ده. د جاسوسۍ د وسایلو د کارولو بېلګې د لنډ تاریخ سره مل دي strace او د دې ډول پروګرامونو د ډیزاین توضیحات.

منځپانګې

د ډولونو اصل

په یونیکس کې د برنامو او OS کرنل ترمینځ اصلي انٹرفیس د سیسټم کالونه دي. سیسټم زنګونه, سییسکالونهد بهرنۍ نړۍ سره د برنامو تعامل یوازې د دوی له لارې پیښیږي.

مګر د یونیکس په لومړۍ عامه نسخه کې (نسخه 6 یونیکس, 1975) د کارن پروسو د چلند د تعقیب لپاره کومه مناسبه لار نه وه. د دې مسلې حل کولو لپاره ، بیل لابراتوار به راتلونکي نسخې ته تازه کړي (نسخه 7 یونیکس، 1979) د نوي سیسټم کال وړاندیز وکړ - ptrace.

ptrace په اصل کې د متقابل ډیبګرانو لپاره رامینځته شوی و ، مګر د 80s په پای کې (د سوداګریزې دورې کې) سیسټم V خپرول 4) په دې اساس، په لنډه توګه متمرکزې ډیبګرونه — د سیسټم کال ټریسر — څرګند شول او په پراخه کچه کارول شوي.

لومړی د سټریس ورته نسخه د پاول کرونینبرګ لخوا په 1992 کې د تړل شوي یوټیلیټ بدیل په توګه د comp.sources.sun بریښنالیک لیست کې خپره شوې وه. trace له لمر څخه کلون او اصلي دواړه د SunOS لپاره و، مګر د 1994 لخوا strace سیسټم V، سولاریس او مخ په زیاتیدونکي مشهور لینکس ته لیږدول شوی.

نن سټریس یوازې د لینکس ملاتړ کوي او په ورته تکیه کوي ptrace، د ډیری توسیعونو سره ډیر شوی.

عصري (او ډیر فعال) ساتونکی strace - دمیتري لیوین. د هغه څخه مننه، افادیت پرمختللي ب featuresې ترلاسه کړې لکه د سیسټم تلیفونونو کې د خطا انجیکشن ، د پراخه جوړښتونو ملاتړ او خورا مهم ، ماسکوټ. غیر رسمي سرچینې ادعا کوي چې انتخاب د شتر مرغ په برخه کې د روسي کلمې "ostrich" او د انګلیسي کلمې "strace" ترمنځ د همغږۍ له امله راوتلی.

دا هم مهمه ده چې په لینکس، FreeBSD، OpenBSD او دودیز یونیکس کې د اوږد تاریخ او پلي کولو سره سره، د ptrace سیسټم کال او ټریسرز هیڅکله په POSIX کې شامل نه وو.

د سټریس وسیله په لنډه توګه: Piglet Trace

"تاسو تمه نه لرئ چې پدې پوه شئ" (ډینس ریچي، د یونیکس سرچینې کوډ 6 نسخه کې تبصره)

د ماشومتوب له پیل راهیسې، زه نشم کولی تور بکسونه ودروم: ما د لوبو سره لوبې نه دي کړې، مګر هڅه مې وکړه چې د دوی جوړښت پوه کړم (لویانو د "مات" کلمه کارولې، مګر په بدو ژبو باور مه کوئ). شاید له همدې امله د لومړي یونیکس غیر رسمي کلتور او د عصري خلاص سرچینې حرکت ما ته خورا نږدې دی.

د دې مقالې د موخو لپاره، دا د سټریس د سرچینې کوډ جلا کول غیر معقول دي، کوم چې په لسیزو کې وده کړې. مګر د لوستونکو لپاره باید هیڅ راز پاتې نشي. له همدې امله ، د دې ډول سټریس برنامو د عملیاتو اصول ښودلو لپاره ، زه به د کوچني ټرسر لپاره کوډ چمتو کړم - د Piglet Trace (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 د سلګونو لینکس سیسټم زنګونه پیژني (وګورئ. ميز) او یوازې په 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 +++

په هرصورت، د نوي پروسې رامینځته کولو لپاره کوم سیسټم کال کارول کیږي؟

بېلګه: د لاسوندونو پر ځای د فایل لارې

د فایل تشریح کونکو پیژندل یقینا ګټور دي ، مګر د ځانګړو فایلونو نومونه چې برنامه ورته لاسرسی لري هم په کار کې راځي.

بله پروګرام لنډمهاله فایل ته کرښه لیکي:

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 -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، لکه څنګه چې د منظمو پروسو په حالت کې ، د هرې کرښې په پیل کې به د پروسې پیډ اضافه کړي.

په طبیعي ډول ، موږ د 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، دوی کولی شي د عملیاتي سیسټم عملیات وګوري SystemTap и ftrace، او تاسو ته اجازه درکوي د برنامې فعالیت ژوره پلټنه وکړئ کامل. په هرصورت، دا دی strace - زما د خپل او نورو خلکو برنامو سره د ستونزو په صورت کې د دفاع لومړۍ کرښه ، او زه یې په اونۍ کې لږترلږه دوه ځله کاروم.

په لنډه توګه، که تاسو د یونیکس سره مینه لرئ، ولولئ man 1 strace او ستاسو پروګرامونو ته د کتلو لپاره وړیا احساس وکړئ!

سرچینه: www.habr.com

Add a comment