Strace në Linux: historia, struktura dhe përdorimi

Strace në Linux: historia, struktura dhe përdorimi

NĂ« sistemet operative tĂ« ngjashme me Unix, komunikimi i njĂ« programi me botĂ«n e jashtme dhe sistemin operativ ndodh pĂ«rmes njĂ« grupi tĂ« vogĂ«l funksionesh — thirrjeve sistemike. Pra, pĂ«r qĂ«llime tĂ« debugimit, ndonjĂ«herĂ« Ă«shtĂ« e dobishme tĂ« shikoni thirrjet sistemike qĂ« po ekzekutohen nga proceset.

Për të ndjekur "jetën intime" të programeve në Linux ndihmon utilitari strace, për të cilin është shkruar ky artikull. Bashkë me shembujt e përdorimit të pajisjeve "spiun", ka një histori të shkurtër strace dhe përshkrimin e funksionimit të këtyre programeve.

Përmbajtja

Origjina e llojeve

Ndërfaqja kryesore midis programeve dhe bërthamës së sistemit në Unix janë thirrjet sistemike (angl. system calls, syscalls), ndërveprimi i programeve me botën e jashtme ndodh ekskluzivisht përmes tyre.

Por nĂ« versionin e parĂ« publik tĂ« Unix (Version 6 Unix, viti 1975) nuk kishte mĂ«nyra tĂ« pĂ«rshtatshme pĂ«r tĂ« ndjekur sjelljen e proceseve tĂ« pĂ«rdoruesve. PĂ«r tĂ« zgjidhur kĂ«tĂ« problem, Bell Labs nĂ« versionin e ardhshĂ«m (Version 7 Unix, viti 1979) propozuan njĂ« thirrje tĂ« re sistemike — ptrace.

Ptrace u zhvillua kryesisht pĂ«r debuggers interaktive, por deri nĂ« fund tĂ« viteve '80 (nĂ« epokĂ«n e tashme komerciale System V Release 4) u krijuan dhe u pĂ«rhapĂ«n gjerĂ«sisht debuggers tĂ« specializuar — ndjekĂ«sit e thirrjeve sistemike.

I pari Versioni strace u publikua nga Paul Kronenburg në listën e postës comp.sources.sun në vitin 1992 si një alternativë për utilitarin e mbyllur trace nga Sun. Si kloni ashtu edhe origjinali ishin të destinuara për SunOS, por deri në vitin 1994 strace u portua në System V, Solaris dhe Linux-in që po fitonte popullaritet.

Sot, strace mbështet vetëm Linux dhe mbështetet në të njëjtën ptrace, e cila është pasuruar me shumë zgjerime.

Maintainer-i modern (dhe shumĂ« aktiv) strace — Dmitry Levin. FalĂ« tij, utilitari ka fituar mundĂ«si tĂ« avancuara si injeksioni i gabimeve nĂ« thirrjet sistemike, mbĂ«shtetje pĂ«r njĂ« gamĂ« tĂ« gjerĂ« arkitekturash dhe, mĂ« e rĂ«ndĂ«sishmja, maskoti. Burimet jozyrtare thonĂ« se zgjedhja ishte oborri pĂ«r shkak tĂ« ngjashmĂ«risĂ« midis fjalĂ«s ruse "страус" dhe anglishtes "strace".

Një faktor i rëndësishëm është se thirrja sistemike ptrace dhe debuggers nuk u përfshin në POSIX, pavarësisht nga historia e gjatë dhe implementimi në Linux, FreeBSD, OpenBSD dhe Unix tradicionale.

Struktura e strace në dy fjalë: Piglet Trace

"Nuk pritet të kuptoni këtë" (Dennis Ritchie, koment në kodin burimor të Version 6 Unix)

Që në fëmijëri kam pasur një urrejtje për kutitë e zeza: nuk luaja me lodrat, por përpiqesha të kuptoja se si funksiononin (të rriturit e quanin "prishje", por mos iu besoni fjalëve të këqija). Ndoshta, për këtë arsye, kultura joformale e Unix-it të parë dhe e lëvizjes moderne open-source më tërheq kaq shumë.

NĂ« kuadĂ«r tĂ« kĂ«tij artikulli, Ă«shtĂ« e pamundur tĂ« analizojmĂ« kodin burimor tĂ« strace, i cili Ă«shtĂ« bĂ«rĂ« mĂ« i pĂ«rparuar gjatĂ« dekadave. Por nuk duhet tĂ« ketĂ« asnjĂ« sekret pĂ«r lexuesit. Prandaj, pĂ«r tĂ« treguar parimin e punĂ«s sĂ« programeve tĂ« ngjashme me strace, do tĂ« jap kodin e njĂ« debuggers tĂ« vogĂ«l — Piglet Trace (ptr). Ai nuk bĂ«n asgjĂ« tĂ« veçantĂ«, por e rĂ«ndĂ«sishmja — regjistron thirrjet sistemike tĂ« programit —

$ 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 u mbyll

Piglet Trace njeh rreth njëqind thirrje sistemike të Linux-it (shih tabelën) dhe funksionon vetëm në arkitekturën x86-64. Për qëllime mësimore, kjo është e mjaftueshme.

Le të analizojmë punën e klonit tonë. Në rastin e Linux-it, për debugger dhe debuggers përdoret, siç u përmend më sipër, thirrja sistemike ptrace. Ai funksionon përmes kalimit të identifikuesve të komandave në argumentin e parë, nga të cilat na duhen vetëm PTRACE_TRACEME, PTRACE_SYSCALL dhe PTRACE_GETREGS.

Puna e debuggers fillon nĂ« njĂ« stil tĂ« zakonshĂ«m Unix: fork(2) aktualizon njĂ« proces tĂ« ri, dhe ai nga ana e tij me anĂ« tĂ« exec(3) aktualizon programin e hetuar. NjĂ« e vetme marrĂ«veshje kĂ«tu — thirrja ptrace(PTRACE_TRACEME) para exec: procesi i birĂ«suar pret qĂ« procesi prind tĂ« zbatojĂ« mbikqyrjen e tij:

pid_t child_pid = fork();
switch (child_pid) {
case -1:
    err(EXIT_FAILURE, "fork");
case 0:
    /* Këtu është fëmija */
    /* Një mod klasa e ndjekjes duhet të aktivizohet. Një prind do të duhet të presë(2) për këtë
     * të ndodhë. */
    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
    /* Zëvendëson veten me një program për t'u ekzekutuar. */
    execvp(argv[1], argv + 1);
    err(EXIT_FAILURE, "exec");
}

Tani procesi-prind duhet të thërrasë wait(2) në procesin fëmijë, që do të thotë të sigurohet që kalimi në modin e ndjekjes ka ndodhur:

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

Këtu përgatitjet përfundojnë dhe mund të fillohet të ndjekim thirrjet e sistemit në një cikël të pafund.

Thirrja ptrace(PTRACE_SYSCALL) garanton që prindi prisni do të përfundojë ose para ekzekutimit të thirrjes së sistemit, ose menjëherë pas përfundimit të saj. Midis dy thirrjeve mund të bëhen disa veprime: të zëvendësohet thirrja me një alternativ, të ndryshohen argumentet ose vlera e kthyer.

Na mjafton ta thërrasim dy herë komandën ptrace(PTRACE_GETREGS), për të marrë gjendjen e regjistrit rax para thirrjes (numri i thirrjes së sistemit) dhe menjëherë pas (vlera e kthyer).

NĂ« fakt, cikli:

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

Ja dhe gjithë ndjekësi. Tani e dini nga të filloni portimin e ardhshëm DTrace në Linux.

Themeli: ekzekutimi i programit nën menaxhimin e strace

Si njĂ« shembull tĂ« parĂ« pĂ«rdorimi strace, ndoshta duhet tĂ« tregojmĂ« mĂ«nyrĂ«n mĂ« tĂ« thjeshtĂ« — tĂ« ekzekutojmĂ« njĂ« aplikacion nĂ«n menaxhimin e strace.

Për të mos u futur në një listë të pafund thirrjesh të një programi tipik, do të shkruajmë një program minimal rreth write:

int main(int argc, char *argv[])
{
    char str[] = "write me to stdoutn";
    /* write(2) është një mbështjellës i thjeshtë rreth një thirrjeje të sistemit, kështu që duhet të jetë e lehtë për ta gjetur në gjurmën e thirrjes së sistemit. */
    if (sizeof(str) != write(STDOUT_FILENO, str, sizeof(str))) {
        perror("write");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

Të ndërtuam programin dhe të sigurohemi që funksionon:

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

Dhe në fund do ta ekzekutojmë nën menaxhimin e strace:

$ strace ./write-simple
execve("./write", ["./write"], 0x7ffebd6145b0 /* 71 vars */) = 0
brk(NULL)                               = 0x55ff5489e000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (Nuk ka një skedar ose direktori të tillë)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (Nuk ka një skedar ose direktori të tillë)
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 (Nuk ka një skedar ose direktori të tillë)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF2113        3 >
Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster