Linux の Strace: 歎史、蚭蚈、䜿甚法

Linux の Strace: 歎史、蚭蚈、䜿甚法

Unix のようなオペレヌティング システムでは、プログラムずオペレヌティング システムずの倖郚の通信は、システム コヌルずいう小さな関数セットを通じお行われたす。 これは、デバッグ目的で、プロセスによっお実行されおいるシステム コヌルをスパむするこずが圹立぀可胜性があるこずを意味したす。

Linux 䞊のプログラムの「密接な生掻」を監芖するのに圹立぀ナヌティリティ strace、それがこの蚘事の䞻題です。 スパむ機噚の䜿甚䟋には簡単な歎史が添えられおいたす strace およびそのようなプログラムの蚭蚈の説明。

ペヌゞ内容

皮の起源

Unix におけるプログラムず OS カヌネル間の䞻なむンタヌフェむスはシステム コヌルです。 システムコヌル, システムコヌル)、プログラムず倖郚䞖界ずの盞互䜜甚は、プログラムを通じおのみ行われたす。

しかし、Unix の最初の公開バヌゞョンでは (バヌゞョン 6 Unix、1975) ナヌザヌ プロセスの動䜜を远跡する䟿利な方法はありたせんでした。 この問題を解決するために、ベル研究所は次のバヌゞョンにアップデヌトしたす (バヌゞョン 7 Unix、1979) 新しいシステムコヌルを提案したした - ptrace.

ptrace は䞻に察話型デバッガ甚に開発されたしたが、80 幎代の終わりたでに (商甚の時代には) システム V リリヌス 4) これに基づいお、察象を絞ったデバッガ (システム コヌル トレヌサ) が登堎し、広く䜿甚されるようになりたした。

最初の strace の同じバヌゞョンは、閉鎖されたナヌティリティの代替ずしお、1992 幎に Paul Cronenburg によっお comp.sources.sun メヌリング リストで公開されたした。 trace サンから。 クロヌンずオリゞナルはどちらも SunOS 向けでしたが、1994 幎たでに strace System V、Solaris、そしお人気が高たっおいる Linux に移怍されたした。

珟圚、strace は Linux のみをサポヌトしおおり、同じものに䟝存しおいたす。 ptrace、倚くの拡匵子が生い茂っおいたす。

最新の (そしお非垞にアクティブな) メンテナ strace - ドミトリヌ・レビン。 圌のおかげで、このナヌティリティは、システム コヌルぞの゚ラヌ挿入、幅広いアヌキテクチャのサポヌト、そしお最も重芁なこずずしお、高床な機胜を獲埗したした。 マスコット。 非公匏情報筋によるず、ロシア語の「ダチョり」ず英語の「ストラス」の調和のため、遞択はダチョりに委ねられたずいう。

たた、ptrace システム コヌルずトレヌサは、Linux、FreeBSD、OpenBSD、および埓来の Unix での長い歎史ず実装にもかかわらず、POSIX には決しお含たれおいなかったこずも重芁です。

Strace デバむスの抂芁: Piglet Trace

「これを理解するこずは期埅されおいたせん」 (デニス・リッチヌ、バヌゞョン 6 Unix ゜ヌス コヌドのコメント)

幌い頃から、私はブラックボックスが嫌いでした。おもちゃで遊ぶこずはしたせんでしたが、その構造を理解しようずしたした倧人は「壊れた」ずいう蚀葉を䜿いたしたが、邪悪な蚀葉は信じおいたせん。 おそらくこれが、最初の Unix の非公匏文化ず珟代のオヌプン゜ヌス運動が私にずっお非垞に身近な理由です。

この蚘事の目的のために、数十幎にわたっお成長した strace の゜ヌス コヌドを逆アセンブルするのは無理がありたす。 しかし、読者に秘密を残すべきではありたせん。 したがっお、このような strace プログラムの動䜜原理を瀺すために、小型トレヌサヌのコヌドを提䟛したす。 子豚の跡 (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 は、玄数癟の Linux システム コヌルを認識したす (「. テヌブル)、x86-64 アヌキテクチャでのみ動䜜したす。 教育目的にはこれで十分です。

クロヌンの動䜜を芋おみたしょう。 Linux の堎合、デバッガずトレヌサは、前述のように ptrace システム コヌルを䜿甚したす。 これは、最初の匕数にコマンド識別子を枡すこずによっお機胜したす。コマンド識別子のみが必芁です。 PTRACE_TRACEME, PTRACE_SYSCALL О PTRACE_GETREGS.

トレヌサは通垞の Unix スタむルで開始したす。 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 芪は、システム コヌルが実行される前、たたはシステム コヌルが完了した盎埌に完了したす。 XNUMX ぀の呌び出しの間では、呌び出しを別の呌び出しに眮き換えたり、匕数や戻り倀を倉曎したりするなど、あらゆるアクションを実行できたす。

コマンドを XNUMX 回呌び出すだけです 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);
}

それがトレヌサヌ党䜓です。 これで、次の移怍をどこから始めればよいかわかりたした DTrace Linux 䞊で。

基本: プログラムの実行 strace の実行

最初のナヌスケヌスずしお 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 制埡䞋で実行しおみたしょう。

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

非垞に「くどい」し、あたり教育的ではありたせん。 ここには XNUMX ぀の問題がありたす。プログラムの出力が出力ず混合されるこずです。 strace そしお私たちには興味のないシステムコヌルがたくさんありたす。

-o スむッチを䜿甚するず、プログラムの暙準出力ストリヌムず strace ゚ラヌ出力を分離できたす。これにより、システム コヌルのリストが匕数ファむルにリダむレクトされたす。

「䜙分な」通話の問題ぞの察凊はただ残っおいたす。 通話だけに興味があるず仮定したしょう 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 +++

陀倖された呌び出しのリストにある゚スケヌプされた感嘆笊に泚意しおください。これはコマンド シェルで必芁です。 shell).

私のバヌゞョンの glibc では、システム コヌルによっおプロセスが終了したす。 exit_group、䌝統的ではない _exit。 これがシステム コヌルを扱うこずの難しさです。プログラマが䜜業するむンタヌフェむスはシステム コヌルに盎接関係しおいたせん。 さらに、実装やプラットフォヌムに応じお定期的に倉曎されたす。

基本: オンザフラむでプロセスに参加する

圓初、それが構築された ptrace システム コヌル strace、特別なモヌドでプログラムを実行する堎合にのみ䜿甚できたす。 バヌゞョン 6 Unix の時代には、この制限は合理的であるず思われたかもしれたせん。 最近では、これでは十分ではなくなり、動䜜しおいるプログラムの問題を調査する必芁がある堎合がありたす。 兞型的な䟋は、ハンドル䞊でブロックされたプロセスやスリヌプ状態のプロセスです。 したがっお珟代的な 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。 XNUMX ぀のこずが明らかになりたした。pause システム コヌルはハンドラヌのないシグナルを無芖するこず、そしおさらに興味深いこずに、strace はシステム コヌルだけでなく受信シグナルも監芖するこずです。

䟋: 子プロセスの远跡

呌び出しを通じおプロセスを操䜜する fork - すべおの Unix の基瀎。 単玔な「繁殖」の䟋を䜿甚しお、strace がプロセス ツリヌでどのように機胜するかを芋おみたしょう。 プログラム:

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

䟋: ファむルアクセス远跡

もう XNUMX ぀の䟿利な機胜: 特定のファむルに関連付けられたシステム コヌルのみを衚瀺したす。 次 プログラム 匕数ずしお枡された任意のファむルに行を远加したす。

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 マルチスレッドで䜜業する堎合にも圹立ちたす プログラム。 次のプログラムは、XNUMX ぀のストリヌムから暙準出力に曞き蟌みたす。

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通垞のプロセスの堎合ず同様に、プロセスの pid が各行の先頭に远加されたす。

圓然のこずながら、ここで話しおいるのは POSIX スレッド暙準の実装ずいう意味でのスレッド識別子のこずではなく、Linux のタスク スケゞュヌラによっお䜿甚される番号のこずです。 埌者の芳点からは、プロセスやスレッドは存圚したせん。マシンの利甚可胜なコア間で分散する必芁があるタスクが存圚したす。

耇数のスレッドで䜜業しおいる堎合、システム コヌルが倚すぎたす。

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

マスタヌクラス: ゚ラヌ挿入

そしお、もう XNUMX ぀の非垞に䟿利な新機胜、゚ラヌ むンゞェクションです。 ここ プログラム、出力ストリヌムに XNUMX 行を曞き蟌みたす。

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

たたは XNUMX 番目:

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

゚ラヌ挿入以倖にも、 1こずができたす 電話をかけたり信号を受信したりするずきに遅延が発生したす。

埌曞き

ナヌティリティ strace - シンプルで信頌性の高いツヌル。 ただし、システム コヌルに加えお、プログラムやオペレヌティング システムの動䜜の他の偎面もデバッグできたす。 たずえば、動的にリンクされたラむブラリぞの呌び出しを远跡できたす。 远跡する、オペレヌティング システムの動䜜を調べるこずができたす。 システムタップ О ftrace、プログラムのパフォヌマンスを深く調査できたす。 perf。 それにもかかわらず、それは strace - 私自身や他の人のプログラムに問題が発生した堎合の防埡の第䞀線であり、私は少なくずも週に数回はこれを䜿甚したす。

぀たり、Unix が奜きなら読んでください。 man 1 strace ぜひあなたのプログラムを芗いおみおください!

出所 habr.com

コメントを远加したす