Strace Linuxis: ajalugu, seade ja kasutamine

Strace Linuxis: ajalugu, seade ja kasutamine

Unix-sarnastes operatsioonisĂŒsteemides suhtlevad programmid vĂ€lismaailma ja operatsioonisĂŒsteemiga vĂ€ikese funktsioonide komplekti — sĂŒsteemikutsungite kaudu. SeetĂ”ttu on silmas pidades tĂ”hus jĂ€lgida, mis protsessid sĂŒsteemikutsungite kaudu toimuvad.

Linuxis programmide "intimaalse elu" jĂ€lgimise abi pakub utiliit strace, millele see artikkel on pĂŒhendatud. "Vakoidpalaste" seadmete kasutamise nĂ€idetele lisatakse lĂŒhike ajalugu strace ja sarnaste programmide seadmed kirjeldus.

Sisu

Liikide pÀritolu

Peamine liides programmide ja Unix OS tuuma vahel on sĂŒsteemikutsungid (ingl. system calls, syscalls), programmide suhtlemine vĂ€lismaailmaga toimub ainult nende kaudu.

Kuid esimeses avalikus Unix versioonis (Version 6 Unix, 1975. aastal puudusid mugavad viisid kasutajaprotsesside kĂ€itumise jĂ€lgimiseks. Selle probleemi lahendamiseks pakkusid Bell Labs jĂ€rgmise versiooni (Version 7 Unix, 1979. aastal) uut sĂŒsteemikutsungit — ptrace.

ptrace töötati vĂ€lja peamiselt interaktiivsete silurite jaoks, kuid 80-ndate lĂ”puks (kaubandusliku ajal System V Release 4) selle alusel ilmusid ja said laialdaselt levinud kitsalt suunatud silurid — sĂŒsteemikutsungite jĂ€lgijad.

Esimene Strace'i versioon avaldas Paul Kronenburg 1992. aastal comp.sources.sun jaotuses alternatiivina suletud utiliidile trace Sunilt. Nii kloon kui ka originaal olid mÔeldud SunOS-i jaoks, kuid 1994. aastaks strace portiti need System V-le, Solaris'ele ja kiiresti populaarsust koguvatele Linux'ile.

TÀnapÀeval toetab strace ainult Linuxi ja toetub endiselt sellele ptrace, mis on saanud hulga laiendusi.

Kaasaegne (ja vĂ€ga aktiivne) hooldaja strace — Dmitri Levin. TĂ€nu temale on utiliit saanud edasijĂ”udnud vĂ”imalusi, nagu vigade sĂŒstimine sĂŒsteemikutsungitesse, toetades laia arhitektuuride spektrit ja mis kĂ”ige tĂ€htsam, maskot.. Mitte ametlikud allikad vĂ€idavad, et valik langes inimeste hÀÀldamise tĂ”ttu sĂ”nale «straus» ja ingliskeelsele "strace".

Oluline on ka see, et sĂŒsteemi kutsed ptrace ning jĂ€lgimisprogrammid ei ole kunagi olnud POSIX-is, hoolimata pikaajalisest ajaloost ning olemasolust Linuxis, FreeBSD-s, OpenBSD-s ja traditsioonilistes Unix-sĂŒsteemides.

Strace'i seade lĂŒhidalt: Piglet Trace

"You are not expected to understand this" (Dennis Ritchie, kommentaar Version 6 Unix'i lÀhtekoodis)

LastepĂ”lvest alates pole ma kunagi mustade kastidega leppinud: ma ei mĂ€nginud mĂ€nguasjadega, vaid ĂŒritasin mĂ”ista nende struktuuri (tĂ€iskasvanud kasutasid sĂ”na «murdsin», kuid Ă€rge uskuge kurjakuulutavatesse keeltedesse). VĂ”ib-olla seetĂ”ttu on mulle nii lĂ€hedane esimeste Unixi ja kaasaegse avatud lĂ€htekoodiga liikumise mitteformaalne kultuur.

KĂ€esolevas artiklis ei ole mĂ”istlik kĂ€sitleda aastate jooksul arenenud strace lĂ€htekoodi. Kuid lugemiseks ei tohiks saladusi jÀÀda. SeetĂ”ttu, et nĂ€idata sarnaste strace programmide tööprintsiipi, toon vĂ€lja miniatuurse jĂ€lgija koodi — Piglet Trace (ptr). Ta ei oska midagi erilist teha, kuid oluline — programmist vĂ€ljuvad sĂŒsteemi kutsed — vĂ€ljendab:

$ 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 tuvastab umbes sada Linuxi sĂŒsteemikutsungit (vt tabel) ja töötab ainult x86-64 arhitektuuril. ÕppeeesmĂ€rgil on sellest piisavalt.

Vaatame, kuidas meie kloon töötab. Linuxi puhul kasutatakse silumise ja jĂ€lgimise jaoks, nagu eelnevalt mainitud, sĂŒsteemikutsungit ptrace. See töötab, edastades esimeses argumendis kĂ€skude tuvid, millest meid huvitavad ainult PTRACE_TRACEME, PTRACE_SYSCALL ja PTRACE_GETREGS.

JĂ€lgija töö algab tavapĂ€rases Unix'i stiilis: fork(2) kĂ€ivitab lapsprotsessi, kes omakorda kasutab exec(3) kĂ€ivitab uuritava programmi. Ainult nĂŒanss on siin - vĂ€ljakutse ptrace(PTRACE_TRACEME) enne exec: tĂŒtarprotsess ootab, et vanemprotsess jĂ€lgiks teda:

pid_t child_pid = fork();
switch (child_pid) {
case -1:
    err(EXIT_FAILURE, "fork");
case 0:
    /* Laps siin */
    /* JĂ€lgimismood peab olema lubatud. Vanem peab ootama(2), et see
     * juhtuks. */
    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
    /* Asendab end programmiga, mida on vaja kÀitada. */
    execvp(argv[1], argv + 1);
    err(EXIT_FAILURE, "exec");
}

Vanemprotsess peab nĂŒĂŒd kutsuma wait(2) tĂŒtarprotsessis, et veenduda, et jĂ€lgimisse reĆŸiimi ĂŒleminek on toimunud:

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

Sellega on ettevalmistused lĂ”ppenud ja saab otse alustada sĂŒsteemikutsungite jĂ€lgimist lĂ”pmatus tsĂŒklis.

Kutse ptrace(PTRACE_SYSCALL) tagab, et jĂ€rgnev wait vanem lĂ”ppeb kas enne sĂŒsteemikutsungi tĂ€itmist vĂ”i kohe pĂ€rast selle lĂ”petamist. Kahte vĂ€ljakutset vĂ”ib vahepeal kasutada mis tahes tegude tegemiseks: vĂ€ljakutse asendamiseks alternatiivsega, argumentide vĂ”i tagastatava vÀÀrtuse muutmiseks.

Meie jaoks piisab, kui kutsume kĂ€sku kaks korda ptrace(PTRACE_GETREGS), et saada registri olek rax enne vĂ€ljakutset (sĂŒsteemikutsungi number) ja kohe pĂ€rast (tagastatav vÀÀrtus).

Tegelikult, tsĂŒkkel:

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

Siin on kogu jĂ€lgimisprogramm. NĂŒĂŒd teate, kust alustada jĂ€rgmise portimisega DTrace Linuxil.

Alused: programmi kÀivitamine strace'i haldamisel

Esimese nĂ€itena kasutamiseks strace, vĂ”ib-olla on mĂ”istlik tuua vĂ€lja kĂ”ige lihtsam viis – programmi kĂ€itamine strace.

Et mitte kaevuda tavalise programmi lĂ”putusse vĂ€ljakutsumise nimekirja, kirjutame minimaalse programmi ĂŒmber write:

int main(int argc, char *argv[])
{
    char str[] = "write me to stdoutn";
    /* write(2) on lihtne wrapper, mis ĂŒmbritseb sĂŒsteemikutsumist, seega peaks see olema lihtne
     * leida sĂŒsteemikutsumise jĂ€lgimisest. */
    if (sizeof(str) != write(STDOUT_FILENO, str, sizeof(str))){
        perror("write");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

Kogume programmi ja veendume, et see töötab:

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

Ja lÔpuks kÀivitame selle strace'i all:

$ strace ./write-simple
execve("./write", ["./write"], 0x7ffebd6145b0 /* 71 vars */) = 0
brk(NULL)                               = 0x55ff5489e000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (Ei leitud sellist faili vÔi katalooge)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (Ei leitud sellist faili vÔi katalooge)
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 (Ei leitud sellist faili vÔi katalooge)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF2113        3 >
Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster