لینکس میں اسٹریس: تاریخ، ڈیزائن اور استعمال

لینکس میں اسٹریس: تاریخ، ڈیزائن اور استعمال

یونکس جیسے آپریٹنگ سسٹمز میں، پروگرام کا بیرونی دنیا اور آپریٹنگ سسٹم کے ساتھ رابطہ ایک چھوٹے سے فنکشنز یعنی سسٹم کالز کے ذریعے ہوتا ہے۔ اس کا مطلب یہ ہے کہ ڈیبگنگ کے مقاصد کے لیے یہ سسٹم کالز کی جاسوسی کرنا کارآمد ثابت ہو سکتا ہے جو عمل کے ذریعے کی جا رہی ہیں۔

ایک افادیت لینکس پر پروگراموں کی "مباشرت زندگی" کی نگرانی میں آپ کی مدد کرتی ہے۔ strace، جو اس مضمون کا موضوع ہے۔ جاسوسی آلات کے استعمال کی مثالیں مختصر تاریخ کے ساتھ ہیں۔ strace اور اس طرح کے پروگراموں کے ڈیزائن کی تفصیل۔

مواد

پرجاتیوں کی اصل

یونکس میں پروگراموں اور OS کرنل کے درمیان بنیادی انٹرفیس سسٹم کالز ہے۔ سسٹم کالز, سسکلزبیرونی دنیا کے ساتھ پروگراموں کا تعامل خصوصی طور پر ان کے ذریعے ہوتا ہے۔

لیکن یونکس کے پہلے عوامی ورژن میں (ورژن 6 یونکس، 1975) صارف کے عمل کے رویے کو ٹریک کرنے کے کوئی آسان طریقے نہیں تھے۔ اس مسئلے کو حل کرنے کے لیے، بیل لیبز اگلے ورژن (ورژن 7 یونکس، 1979) نے ایک نیا سسٹم کال تجویز کیا - ptrace.

ptrace بنیادی طور پر انٹرایکٹو ڈیبگرز کے لیے تیار کیا گیا تھا، لیکن 80 کی دہائی کے آخر تک (تجارتی دور میں سسٹم وی ریلیز 4) اس بنیاد پر، تنگ توجہ والے ڈیبگرز—سسٹم کال ٹریسر—نمودار ہوئے اور بڑے پیمانے پر استعمال ہونے لگے۔

سب سے پہلے سٹریس کا وہی ورژن پال کروننبرگ نے 1992 میں comp.sources.sun میلنگ لسٹ پر ایک بند یوٹیلیٹی کے متبادل کے طور پر شائع کیا تھا۔ trace سورج سے. کلون اور اصل دونوں کا مقصد SunOS کے لیے تھا، لیکن 1994 تک strace سسٹم V، سولاریس اور تیزی سے مقبول لینکس پر پورٹ کیا گیا تھا۔

آج اسٹریس صرف لینکس کو سپورٹ کرتا ہے اور اسی پر انحصار کرتا ہے۔ ptrace, بہت سے توسیعات کے ساتھ overgrown.

جدید (اور بہت فعال) دیکھ بھال کرنے والا strace - دمتری لیون. اس کی بدولت، یوٹیلیٹی نے جدید خصوصیات حاصل کیں جیسے سسٹم کالز میں ایرر انجیکشن، وسیع رینج کے آرکیٹیکچرز کے لیے سپورٹ اور سب سے اہم بات، شوبنکر. غیر سرکاری ذرائع کا دعویٰ ہے کہ یہ انتخاب شتر مرغ پر پڑا کیونکہ روسی لفظ "ostrich" اور انگریزی لفظ "strace" کے درمیان ہم آہنگی ہے۔

یہ بھی اہم ہے کہ لینکس، فری بی ایس ڈی، اوپن بی ایس ڈی اور روایتی یونکس میں طویل تاریخ اور نفاذ کے باوجود ptrace سسٹم کال اور ٹریسر کو کبھی بھی POSIX میں شامل نہیں کیا گیا۔

مختصر طور پر اسٹریس ڈیوائس: Piglet ٹریس

"آپ سے یہ سمجھنے کی توقع نہیں ہے" (ڈینس رچی، ورژن 6 یونکس سورس کوڈ میں تبصرہ)

ابتدائی بچپن سے، میں بلیک بکس کو برداشت نہیں کر سکتا: میں کھلونوں سے نہیں کھیلتا تھا، لیکن ان کی ساخت کو سمجھنے کی کوشش کرتا تھا (بڑوں نے لفظ "بروک" استعمال کیا تھا، لیکن بری زبانوں پر یقین نہیں کرتے)۔ شاید یہی وجہ ہے کہ پہلے یونکس کی غیر رسمی ثقافت اور جدید اوپن سورس موومنٹ میرے بہت قریب ہے۔

اس مضمون کے مقاصد کے لیے، اسٹریس کے ماخذ کوڈ کو الگ کرنا غیر معقول ہے، جو کئی دہائیوں سے بڑھتا چلا گیا ہے۔ لیکن قارئین کے لیے کوئی راز نہیں چھوڑنا چاہیے۔ لہذا، اس طرح کے اسٹریس پروگراموں کے آپریشن کے اصول کو ظاہر کرنے کے لیے، میں ایک چھوٹے ٹریسر کے لیے کوڈ فراہم کروں گا - Piglet ٹریس (ptr) یہ کچھ خاص کرنے کا طریقہ نہیں جانتا ہے، لیکن سب سے اہم چیز پروگرام کی سسٹم کالز ہے - یہ آؤٹ پٹ کرتا ہے:

$ gcc examples/piglet-trace.c -o ptr
$ ptr echo test > /dev/null
BRK(12) -> 94744690540544
ACCESS(21) -> 18446744073709551614
ACCESS(21) -> 18446744073709551614
unknown(257) -> 3
FSTAT(5) -> 0
MMAP(9) -> 140694657216512
CLOSE(3) -> 0
ACCESS(21) -> 18446744073709551614
unknown(257) -> 3
READ(0) -> 832
FSTAT(5) -> 0
MMAP(9) -> 140694657208320
MMAP(9) -> 140694650953728
MPROTECT(10) -> 0
MMAP(9) -> 140694655045632
MMAP(9) -> 140694655070208
CLOSE(3) -> 0
unknown(158) -> 0
MPROTECT(10) -> 0
MPROTECT(10) -> 0
MPROTECT(10) -> 0
MUNMAP(11) -> 0
BRK(12) -> 94744690540544
BRK(12) -> 94744690675712
unknown(257) -> 3
FSTAT(5) -> 0
MMAP(9) -> 140694646390784
CLOSE(3) -> 0
FSTAT(5) -> 0
IOCTL(16) -> 18446744073709551591
WRITE(1) -> 5
CLOSE(3) -> 0
CLOSE(3) -> 0
unknown(231)
Tracee terminated

Piglet ٹریس لینکس سسٹم کی سینکڑوں کالوں کو پہچانتا ہے (دیکھیں۔ ٹیبل) اور صرف 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);
}

یہ سارا ٹریسر ہے۔ اب آپ جانتے ہیں کہ اگلی پورٹنگ کہاں سے شروع کرنی ہے۔ Drace لینکس پر.

بنیادی باتیں: سٹریس چلانے والا پروگرام چلانا

پہلے استعمال کے معاملے کے طور پر 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);
}

قدرتی طور پر، اسے لنکر کے لیے ایک خصوصی سلام کے ساتھ مرتب کیا جانا چاہیے - the -pthread پرچم:

$ gcc examples/thread-write.c -pthread -o thread-write
$ ./thread-write
/thread-write
Initial thread: launching a thread
Initial thread: joining a thread
Secondary thread: working
Secondary thread: done
Initial thread: done
$

پرچم -fجیسا کہ ریگولر پروسیسز کے معاملے میں ہوتا ہے، ہر سطر کے شروع میں عمل کی pid کو شامل کرے گا۔

قدرتی طور پر، ہم POSIX تھریڈز کے معیار کے نفاذ کے معنی میں تھریڈ شناخت کنندہ کے بارے میں بات نہیں کر رہے ہیں، بلکہ لینکس میں ٹاسک شیڈیولر کے استعمال کردہ نمبر کے بارے میں بات کر رہے ہیں۔ مؤخر الذکر کے نقطہ نظر سے، کوئی عمل یا دھاگہ نہیں ہے - ایسے کام ہیں جن کو مشین کے دستیاب کوروں میں تقسیم کرنے کی ضرورت ہے۔

متعدد تھریڈز میں کام کرتے وقت، سسٹم کالز بہت زیادہ ہو جاتی ہیں:

$ 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، وہ آپریٹنگ سسٹم کے آپریشن کو دیکھ سکتے ہیں۔ سسٹم ٹیپ и ftrace، اور آپ کو پروگرام کی کارکردگی کی گہرائی سے تفتیش کرنے کی اجازت دیتا ہے۔ مکمل. بہر حال، یہ ہے strace - میرے اپنے اور دوسرے لوگوں کے پروگراموں میں مسائل کی صورت میں دفاع کی پہلی لائن، اور میں اسے ہفتے میں کم از کم ایک دو بار استعمال کرتا ہوں۔

مختصر یہ کہ اگر آپ یونکس سے محبت کرتے ہیں تو پڑھیں man 1 strace اور اپنے پروگراموں میں جھانکنے کے لئے آزاد محسوس کریں!

ماخذ: www.habr.com

نیا تبصرہ شامل کریں