
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 (, 1975. aastal puudusid mugavad viisid kasutajaprotsesside kĂ€itumise jĂ€lgimiseks. Selle probleemi lahendamiseks pakkusid Bell Labs jĂ€rgmise versiooni (, 1979. aastal) uut sĂŒsteemikutsungit â ptrace.
ptrace töötati vĂ€lja peamiselt interaktiivsete silurite jaoks, kuid 80-ndate lĂ”puks (kaubandusliku ajal ) selle alusel ilmusid ja said laialdaselt levinud kitsalt suunatud silurid â sĂŒsteemikutsungite jĂ€lgijad.
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 â . 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, . 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 â (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 terminatedPiglet Trace tuvastab umbes sada Linuxi sĂŒsteemikutsungit (vt ) 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, ®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);
}Siin on kogu jĂ€lgimisprogramm. NĂŒĂŒd teate, kust alustada jĂ€rgmise portimisega 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 ĂŒ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 stdoutJa 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 >