Strace ing Linux: sejarah, desain lan panggunaan

Strace ing Linux: sejarah, desain lan panggunaan

Ing sistem operasi kaya Unix, komunikasi program karo donya njaba lan sistem operasi dumadi liwat sawetara fungsi cilik - panggilan sistem. Iki tegese kanggo tujuan debugging bisa migunani kanggo Spy telpon sistem sing dileksanakake dening pangolahan.

Utilitas mbantu sampeyan ngawasi "urip intim" program ing Linux strace, sing dadi topik artikel iki. Conto panggunaan peralatan mata-mata kasebut diiringi riwayat singkat strace lan gambaran saka rancangan program kuwi.

Isi

Asal usul spesies

Antarmuka utama antarane program lan kernel OS ing Unix yaiku panggilan sistem. telpon sistem, syscalls), interaksi program karo donya njaba dumadi sacara eksklusif liwat.

Nanging ing versi umum pisanan Unix (Versi 6 Unix, 1975) ora ana cara sing trep kanggo nglacak prilaku pangolahan pangguna. Kanggo ngatasi masalah iki, Bell Labs bakal nganyari menyang versi sabanjure (Versi 7 Unix, 1979) ngusulake panggilan sistem anyar - ptrace.

ptrace dikembangake utamane kanggo debugger interaktif, nanging ing pungkasan taun 80-an (ing jaman komersial Sistem V release 4) ing basis iki, narrowly fokus debuggers-system call tracers-muncul lan dadi digunakake digunakake.

Kaping pisanan versi strace sing padha diterbitake dening Paul Cronenburg ing mailing list comp.sources.sun ing taun 1992 minangka alternatif kanggo sarana tertutup. trace saka Sun. Klone lan asline ditujokake kanggo SunOS, nanging ing taun 1994 strace ditransfer menyang Sistem V, Solaris lan Linux sing saya populer.

Dina iki strace mung ndhukung Linux lan gumantung ing padha ptrace, overgrown karo akeh ekstensi.

Modern (lan aktif banget) maintainer strace - Dmitry Levin. Thanks kanggo dheweke, sarana kasebut entuk fitur canggih kayata injeksi kesalahan menyang panggilan sistem, dhukungan kanggo macem-macem arsitektur lan, sing paling penting, maskot. Sumber ora resmi nyatakake yen pilihan kasebut tiba ing manuk unta amarga konsonansi antarane tembung Rusia "ungu" lan tembung Inggris "strace".

Sampeyan uga penting manawa telpon lan tracer sistem ptrace ora tau dilebokake ing POSIX, sanajan ana sejarah lan implementasine ing Linux, FreeBSD, OpenBSD lan Unix tradisional.

Piranti Strace kanthi ringkes: Piglet Trace

"Sampeyan ora samesthine ngerti iki" (Dennis Ritchie, komentar ing Versi 6 kode sumber Unix)

Wiwit awal kanak-kanak, aku ora bisa ngadeg kothak ireng: Aku ora muter karo Toys, nanging nyoba kanggo ngerti struktur sing (wong diwasa nggunakake tembung "nyuwil," nanging ora pracaya basa ala). Mungkin iki sebabe budaya informal Unix pisanan lan gerakan open-source modern dadi cedhak karo aku.

Kanggo tujuan artikel iki, ora ana alesan kanggo mbongkar kode sumber strace, sing wis tuwuh sajrone pirang-pirang dekade. Nanging ora ana rahasia sing isih ana kanggo para pamaca. Mulane, kanggo nuduhake prinsip operasi program strace kasebut, aku bakal menehi kode kanggo tracer miniatur - Jejak Babi (ptr). Ora ngerti carane nindakake apa-apa khusus, nanging sing utama yaiku panggilan sistem program kasebut - ngasilake:

$ 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 ngenali babagan atusan telpon sistem Linux (ndeleng. meja) lan mung dianggo ing arsitektur x86-64. Iki cukup kanggo tujuan pendidikan.

Ayo ndeleng karya kloning kita. Ing kasus Linux, debugger lan tracer nggunakake, kaya kasebut ing ndhuwur, telpon sistem ptrace. Kerjane kanthi ngliwati argumentasi pisanan pengenal printah, sing mung perlu PTRACE_TRACEME, PTRACE_SYSCALL ΠΈ PTRACE_GETREGS.

Tracer diwiwiti kanthi gaya Unix biasa: fork(2) miwiti proses anak, kang siji nggunakake exec(3) ngluncurake program sing diteliti. Mung subtlety ing kene yaiku tantangan ptrace(PTRACE_TRACEME) sadurunge exec: Proses anak ngarepake proses wong tuwa kanggo ngawasi:

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

Proses induk saiki kudu nelpon wait(2) ing proses anak, yaiku, priksa manawa ngalih menyang mode trace wis kedadeyan:

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

Ing jalur iki, persiapan wis rampung lan sampeyan bisa nerusake langsung menyang telpon sistem nelusuri ing daur ulang tanpa wates.

Telpon ptrace(PTRACE_SYSCALL) njamin sing sakteruse wait wong tuwa bakal rampung sadurunge telpon sistem dieksekusi utawa sanalika sawise rampung. Ing antarane rong telpon, sampeyan bisa nindakake tumindak apa wae: ngganti telpon karo alternatif, ngganti argumen utawa nilai bali.

Kita mung kudu nelpon printah kaping pindho ptrace(PTRACE_GETREGS)kanggo njaluk negara registrasi rax sadurunge telpon (nomer telpon sistem) lan sanalika sawise (nilai bali).

Ing kasunyatan, siklus:

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

Iku kabeh tracer. Saiki sampeyan ngerti ngendi arep miwiti porting sabanjure DTrace ing Linux.

Dasar: mlaku program mlaku strace

Minangka kasus panggunaan pisanan strace, mbok menawa ana cara sing paling gampang - ngluncurake aplikasi sing mlaku strace.

Supaya ora kanggo delve menyang dhaftar telas telpon saka program khas, kita nulis program minimal sekitar 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;
}

Ayo gawe program kasebut lan priksa manawa bisa digunakake:

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

Lan pungkasane, ayo mbukak ing kontrol 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)                           = ?

Banget "wordy" lan ora banget pendidikan. Ana rong masalah ing kene: output program dicampur karo output strace lan turah mbrawah saka sistem telpon sing ora kapentingan kita.

Sampeyan bisa misahake stream output standar program lan output kesalahan strace nggunakake saklar -o, sing pangalihan dhaptar telpon sistem menyang file argumen.

Iku tetep kanggo menehi hasil karo masalah "ekstra" telpon. Ayo kita nganggep yen kita mung kasengsem ing telpon write. kunci -e ngidini sampeyan nemtokake ekspresi sing bakal disaring telpon sistem. Pilihan kondisi sing paling populer yaiku, kanthi alami, trace=*, karo sampeyan mung bisa ninggalake telpon sing narik kawigaten kita.

Nalika digunakake bebarengan -o ΠΈ -e kita bakal entuk:

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

Dadi, sampeyan ndeleng, luwih gampang maca.

Sampeyan uga bisa mbusak telpon sistem, contone sing ana hubungane karo alokasi memori lan freeing:

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

Elinga tandha seru sing lolos ing dhaptar panggilan sing ora kalebu: iki dibutuhake dening cangkang printah. Nihan).

Ing versi glibc, telpon sistem mungkasi proses kasebut exit_group, ora tradisional _exit. Iki minangka kangelan nggarap panggilan sistem: antarmuka sing dianggo programer ora langsung ana hubungane karo panggilan sistem. Kajaba iku, owah-owahan kanthi rutin gumantung saka implementasine lan platform.

Dasar: nggabungake proses kanthi cepet

Kaping pisanan, sistem ptrace nelpon sing dibangun strace, mung bisa digunakake nalika mbukak program ing mode khusus. Watesan iki bisa uga muni cukup ing dina Versi 6 Unix. Saiki, iki ora cukup maneh: kadhangkala sampeyan kudu nyelidiki masalah program kerja. Conto khas yaiku proses sing diblokir ing gagang utawa turu. Mulane modern strace bisa nggabungake pangolahan ing fly.

Tuladha beku program:

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

Ayo gawe program kasebut lan priksa manawa wis beku:

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

Saiki ayo nyoba gabung:

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

Program diblokir dening telpon pause. Ayo ndeleng carane dheweke nanggepi sinyal kasebut:

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

Kita miwiti program beku lan gabung nggunakake strace. Rong perkara dadi jelas: telpon sistem ngaso ora nggatekake sinyal tanpa panangan lan, sing luwih menarik, monitor strace ora mung telpon sistem, nanging uga sinyal sing mlebu.

Tuladha: Nelusuri Proses Anak

Nggarap pangolahan liwat telpon fork - basis kabeh Unixes. Ayo ndeleng cara kerjane strace karo wit proses nggunakake conto "breeding" sing prasaja program:

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

Ing kene proses asli nggawe proses anak, loro-lorone nulis menyang output standar:

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

Kanthi gawan, kita mung bakal weruh telpon sistem saka proses induk:

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

GendΓ©ra mbantu sampeyan nglacak kabeh wit proses -f, kang strace ngawasi telpon sistem ing pangolahan anak. Iki nambah saben baris output pid proses sing nggawe output sistem:

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

Ing konteks iki, nyaring miturut klompok panggilan sistem bisa migunani:

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

Miturut cara, panggilan sistem apa sing digunakake kanggo nggawe proses anyar?

Conto: path file tinimbang gagang

Ngerti deskriptor file mesthi migunani, nanging jeneng file tartamtu sing diakses program uga bisa migunani.

Sabanjure program kasebut nulis baris menyang file sementara:

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

Sajrone telpon normal strace bakal nuduhake nilai nomer deskriptor sing dikirim menyang telpon sistem:

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

Kanthi gendΓ©ra -y Utilitas kasebut nuduhake path menyang file sing cocog karo deskriptor:

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

Tuladha: Nelusuri Akses File

Fitur liyane sing migunani: mung nampilake panggilan sistem sing ana gandhengane karo file tartamtu. Sabanjure program kasebut nambahake baris menyang file arbitrer sing diterusake minangka argumen:

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

standar strace nampilake akeh informasi sing ora perlu. GendΓ©ra -P kanthi argumen nyebabake strace mung nyithak telpon menyang file sing ditemtokake:

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

Tuladha: Program Multithreaded

Utilitas strace uga bisa bantuan nalika nggarap multi-Utas program. Program ing ngisor iki nulis menyang output standar saka rong aliran:

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

Mesthine, kudu disusun kanthi salam khusus kanggo linker - flag -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
$

Bendera -f, kaya ing kasus pangolahan biasa, bakal nambah pid proses ing awal saben baris.

Alamiah, kita ora ngomong babagan pengenal thread ing pangertèn implementasine standar Utas POSIX, nanging babagan nomer sing digunakake dening panjadwal tugas ing Linux. Saka sudut pandang sing terakhir, ora ana proses utawa benang - ana tugas sing kudu disebarake ing antarane inti mesin sing kasedhiya.

Nalika nggarap macem-macem utas, panggilan sistem dadi akeh banget:

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

Iku ndadekake pangertèn kanggo matesi dhewe kanggo proses Manajemen lan telpon sistem mung 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 +++

Miturut cara, pitakonan. Panggilan sistem apa sing digunakake kanggo nggawe utas anyar? Kepiye carane telpon iki beda karo panggilan kanggo proses?

Kelas master: tumpukan proses nalika nelpon sistem

Salah siji sing mentas muncul strace kemampuan - nampilake tumpukan telpon fungsi ing wektu telpon sistem. Prasaja conto:

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

Alamiah, output program dadi banget voluminous, lan, saliyane flag -k (tampilan tumpukan telpon), nggawe akal kanggo nyaring panggilan sistem kanthi jeneng:

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

Kelas master: injeksi kesalahan

Lan siji fitur anyar lan migunani banget: injeksi kesalahan. kene program kasebut, nulis rong baris menyang stream output:

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

Ayo nglacak loro-lorone nulis panggilan:

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

Saiki kita nggunakake ekspresi kasebut injectkanggo nglebokake kesalahan EBADF ing kabeh telpon nulis:

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

Iku menarik apa kasalahan bali kabeh tantangan write, kalebu telpon didhelikake konco perror. Iku mung masuk akal kanggo ngasilake kesalahan kanggo telpon pisanan:

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

Utawa sing kapindho:

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

Ora perlu nemtokake jinis kesalahan:

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

Ing kombinasi karo gendera liyane, sampeyan bisa "break" akses menyang file tartamtu. Tuladha:

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

Saliyane kesalahan injeksi, bisa introduce wektu tundha nalika nelpon utawa nampa sinyal.

Afterword

Utilitas strace - alat prasaja lan dipercaya. Nanging saliyane kanggo telpon sistem, aspèk liya saka operasi program lan sistem operasi bisa debugged. Contone, bisa nglacak telpon menyang perpustakaan sing disambung kanthi dinamis. nglacak, padha bisa ndeleng menyang operasi saka sistem operasi SistemTap и ngambah, lan ngidini sampeyan nliti kinerja program kanthi jero parfum. Nanging, iku strace - baris pisanan saka nimbali ing cilik saka masalah karo program dhewe lan wong liya, lan aku nggunakake ing paling saperangan saka kaping minggu.

Ing cendhak, yen sampeyan tresna Unix, maca man 1 strace lan bebas mriksa program sampeyan!

Source: www.habr.com

Add a comment