
NĂ« sistemet operativĂ« tĂ« ngjashme me Unix, komunikimi i programeve me botĂ«n e jashtme dhe me sistemin operativ ndodh pĂ«rmes njĂ« grushti tĂ« vogĂ«l funksionesh â thirrjeve sistemike. Prandaj, pĂ«r qĂ«llime debbugimi, Ă«shtĂ« e dobishme tĂ« hedhĂ«sh njĂ« sy mbi proceset qĂ« ekzekutohen me thirrje sistemike.
Për të ndjekur "jetën intime" të programeve në Linux, ndihmon utilita strace, e cila i dedikohet këtij artikulli. Me shembujt e përdorimit të pajisjeve "spiun" ofrohet një histori e shkurtër strace dhe përshkrimi i pajisjeve të tilla të programeve.
Përmbajtja
Origjina e specieve
Interfisi kryesor mes programeve dhe kerneli tĂ« OS-sĂ« nĂ« Unix â thirrjet sistemike (ang. system calls, syscalls), interaksioni i programeve me botĂ«n e jashtme ndodh ekskluzivisht pĂ«rmes tyre.
Por nĂ« versionin e tij tĂ« parĂ« publik tĂ« Unixit (, viti 1975) nuk kishte mĂ«nyra tĂ« pĂ«rshtatshme pĂ«r tĂ« ndjekur sjelljen e proceseve tĂ« pĂ«rdoruesit. PĂ«r tĂ« zgjidhur kĂ«tĂ« problem, Bell Labs nĂ« versionin e ardhshĂ«m (, viti 1979) propozuan njĂ« thirrje tĂ« re sistemike â ptrace.
Ptrace u zhvillua kryesisht pĂ«r debbugers interaktive, por deri nĂ« fund tĂ« viteve '80 (nĂ« epokĂ«n comerciale tĂ« ) bazuar nĂ« kĂ«tĂ« u shfaqĂ«n dhe morĂ«n njĂ« pĂ«rhapje tĂ« gjĂ«rĂ« debbugers tĂ« specializuar â ndjekĂ«sit e thirrjeve sistemike.
Versioni i strace u publikua nga Paul Kronenburg në listën e shpërndarjes comp.sources.sun në vitin 1992 si një alternativë ndaj utilit të mbyllur trace nga Sun. Si kloni ashtu dhe origjinali ishin të destinuara për SunOS, por deri në vitin 1994 strace u portua në System V, Solaris dhe Linux-in në rritje të popullaritetit.
Sot strace mbështet vetëm Linux-in dhe mbështetet në të njëjtin ptrace, i cili është pasuruar me një shumëllojshmëri zgjerimesh.
Maintainer-i modern (dhe mjaft aktiv) Ă«shtĂ« strace â . FalĂ« tij, utilita Ă«shtĂ« pajisur me mundĂ«si tĂ« avancuara si injektimi i gabimeve nĂ« thirrjet sistemike, mbĂ«shtetje pĂ«r njĂ« gamĂ« tĂ« gjerĂ« arkitekturash dhe, mĂ« e rĂ«ndĂ«sishmja, . Burime jo zyrtare pretendojnĂ« se zgjidhja ra mbi strucin pĂ«r shkak tĂ« ngjashmĂ«risĂ« sĂ« fjalĂ«s ruse "ŃŃŃаŃŃ" me anglishten "strace".
Po ashtu në mënyrë të rëndësishme, thirrja sistemike ptrace dhe ndjekësit kurrë nuk u pëfshinë në POSIX, pavarësisht historisë së saj të gjatë dhe realizimit në Linux, FreeBSD, OpenBSD dhe Unix tradicionale.
Struktura e strace me dy fjalë: Piglet Trace
"You are not expected to understand this" (Dennis Ritchie, koment në kodin burimor të Version 6 Unix)
Që nga fëmijëria, unë nuk kam duruar kutitë e zeza: nuk luaja me lodra, por nuk përpiqesha të kuptoja se si ishin të ndërtuara (të rriturit përdornin fjalën "kisha", por mos besoni fjalët e këqija). Ndoshta, kjo është arsyeja pse kultura joformale e Unix-it të parë dhe e levizjes aktuale open-source më është e afërt.
NĂ« kuadĂ«r tĂ« kĂ«tij artikulli, analiza e kodit tĂ« strace, e cila Ă«shtĂ« bĂ«rĂ« e njohur gjatĂ« dekadave, Ă«shtĂ« e paarsyeshme. Por asnjĂ« mister nuk duhet tĂ« mbetet pĂ«r lexuesit. Prandaj, pĂ«r tĂ« ilustruar parimin e funksionimit tĂ« programeve tĂ« tilla strace, do tĂ« ofroj kodin e njĂ« ndjekĂ«si miniaturĂ« â (ptr). Nuk dihet asgjĂ« e veçantĂ«, por gjĂ«ja kryesore â thirrjet sistemike tĂ« programit â e tregon:
$ 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 ndaluaPiglet Trace njeh rreth njëqind thirrje sistemike të Linux (shih ) dhe punon vetëm në arkitekturën x86-64. Për qëllime mësimore, kjo është e mjaftueshme.
Le të analizojmë funksionimin e klonit tonë. Në rastin e Linux, për debbugers dhe ndjekësit, përdoret, siç u përmend më parë, thirrja sistemike ptrace. Ai funksionon përmes kalimit në argumentin e parë të identifikuesve të komandave, nga të cilat na nevojiten vetëm PTRACE_TRACEME, PTRACE_SYSCALL dhe PTRACE_GETREGS.
Punimi i ndjekësit fillon në stilin e zakonshëm Unix: fork(2) nis një proces fëmijë, dhe ai nga ana e tij, përmes exec(3) nis programin e hulumtimit. Vetëm një hollësi - thirrja ptrace(PTRACE_TRACEME) para exec: procesi i pasardhës pret që procesi prind të jetë duke e ndjekur:
pid_t child_pid = fork();
switch (child_pid) {
case -1:
err(EXIT_FAILURE, "fork");
case 0:
/* Fëmija këtu */
/* Një mënyrë e ndjekjes duhet të aktivizohet. Një prind do të duhet të presë(2) që kjo
* të ndodhë. */
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
/* Zëvendëson veten me një program që do të ekzekutohet. */
execvp(argv[1], argv + 1);
err(EXIT_FAILURE, "exec");
}Procesi-prind tani duhet të thërrasë wait(2) në procesin e fëmijës, domethënë të sigurohet që kalimi në mënyrën e ndjekjes ndodhi:
/* 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");Me këtë, përgatitjet përfundojnë dhe mund të fillojmë në mënyrë të drejtpërdrejtë të ndjekim thirrjet sistemike në një cikël të pafund.
Thirrja ptrace(PTRACE_SYSCALL) garanton se prindit do t'i jepet një sinjal para ekzekutimit të thirrjes sistemike, ose menjëherë pas përfundimit të saj. Ndërmjet dy thirrjeve mund të kryhen disa veprime: zëvendësimi i thirrjes me një alternativ, ndërrimi i argumenteve ose vlerës së kthimit. wait Na mjafton të thërrasim dy herë komandën
ptrace(PTRACE_GETREGS) , për të marrë gjendjen e regjistritrax para thirrjes (numri i thirrjes sistemike) dhe menjëherë pas (vlera e kthyer). Në fakt, cikli:
Këtu është tërë ndjekësi. Tani e dini nga të filloni një portim të ri
/* 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, ®isters) == -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, ®isters) == -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);
}në Linux. Si një shembull të parë përdorimi
Bazat: nisja e programit nën menaxhimin e strace
, ndoshta ia vlen tĂ« pĂ«rmendim mĂ«nyrĂ«n mĂ« tĂ« thjeshtĂ« â ekzekutimin e aplikacionit nĂ«n menaxhimin e stracePĂ«r tĂ« mos u ngjitur nĂ« njĂ« listĂ« tĂ« pafund thirrjesh tĂ« njĂ« programi tipik, le tĂ« shkruajmĂ« strace.
një program minimal int main(int argc, char *argv[]) { char str[] = "shkruajmë në stdoutn"; /* write(2) është një mbështjellës i thjeshtë rreth një thirrjeje të sistemit, kështu që duhet të jetë e lehtë të * gjejmë në gjurmimin e thirrjes sistemike. */ if (sizeof(str) != write(STDOUT_FILENO, str, sizeof(str))){ perror("write"); return EXIT_FAILURE; } return EXIT_SUCCESS; } shkruaj:
Le të ndërtoshim programin dhe të sigurohemi që funksionon:
$ gcc examples/write-simple.c -o write-simple $ ./write-simple shkruajmë në stdout
Dhe më në fund, ta ekzekutojmë atë nën menaxhimin e strace:$ strace ./write-simple pexecve("./write", ["./write"], 0x7ffebd6145b0 /* 71 vars */) = 0 brk(NULL) = 0x55ff5489e000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (Nuk ekziston një skedar ose direktori me të tillë) access("/etc/ld.so.preload", R_OK) = -1 ENOENT (Nuk ekziston një skedar ose direktori me 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 ekziston një skedar ose direktori me të tillë) openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "177ELF2113 3 <
$ strace ./write-simple
pexecve("./write", ["./write"], 0x7ffebd6145b0 /* 71 vars */) = 0
brk(NULL) = 0x55ff5489e000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (Nuk është një fajl ose direktor i tillë)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (Nuk është një fajl ose direktor i 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 është një fajl ose direktor i tillë)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF2113 3 >