
I Unix-liknande operativsystem sker kommunikationen mellan program och vĂ€rlden samt operativsystemet genom en liten uppsĂ€ttning funktioner â systemanrop. Det innebĂ€r att det kan vara anvĂ€ndbart att övervaka de systemanrop som utförs av processerna för felsökningssyften.
Att följa programmens "intima liv" pÄ Linux underlÀttas av verktyget för att kontrollera paketöverföringen Ät bÄda hÄll, som denna artikel Àr tillÀgnad. DÀr ges Àven en kort historia och beskrivning av anvÀndningen av "spionerings"-program. för att kontrollera paketöverföringen Ät bÄda hÄll En enhetsbeskrivning av sÄdana program.
InnehÄll
Arternas ursprung
Det huvudsakliga grÀnssnittet mellan programmen och kÀrnan i Unix Àr systemanrop (eng. system calls, syscalls.), interaktionen mellan programmen och omvÀrlden sker uteslutande genom dem.
Men i den första offentliga versionen av Unix (, 1975) fanns det inga praktiska sĂ€tt att övervaka anvĂ€ndarprocessernas beteende. För att lösa detta problem föreslog Bell Labs i nĂ€sta version (, 1979) en ny systemanrop â ptrace.
Ptrace utvecklades frĂ€mst för interaktiva felsökare, men i slutet av 80-talet (i den kommersiella redan ) baserades det och fick stort spridning i specialiserade felsökare â systemanropsspĂ„rare.
Den samma version av strace publicerades av Paul Kronenburg i mailinglistan comp.sources.sun 1992 som ett alternativ till det stÀngda verktyget trace frÄn Sun. BÄde klonen och originalet var avsedda för SunOS, men Är 1994 för att kontrollera paketöverföringen Ät bÄda hÄll portades den till System V, Solaris och det populÀra Linux.
Idag stöder strace endast Linux och bygger fortfarande pÄ samma ptrace, nu med mÄnga utökningar.
Den moderna (och mycket aktiva) underhĂ„llaren för att kontrollera paketöverföringen Ă„t bĂ„da hĂ„ll â . Tack vare honom har verktyget försetts med avancerade funktioner som felinjicering i systemanrop, stöd för ett brett spektrum av arkitekturer och, viktigast av allt, Oofficiella kĂ€llor hĂ€vdar att valet föll pĂ„ strutsen pĂ„ grund av likheten mellan det ryska ordet "ŃŃŃаŃŃ" och det engelska "strace".
Det Àr ocksÄ viktigt att notera att systemanropet ptrace och debuggarna aldrig blev en del av POSIX, trots sin lÄnga historia och förekomsten av implementationer i Linux, FreeBSD, OpenBSD och traditionella Unix.
Strukturen hos strace i korthet: Piglet Trace
"Du förvÀntas inte förstÄ detta" (Dennis Ritchie, kommentar i koden för Version 6 Unix)
FrÄn tidig barndom har jag alltid ogillat svarta lÄdor: jag lekte inte med leksakerna utan försökte förstÄ deras konstruktion (vuxna anvÀnde ordet "bröt sönder", men tro inte pÄ illvilliga sprÄk). Kanske Àr det dÀrför jag kÀnner mig sÄ nÀra den informella kulturen hos de första Unix och den moderna open-source-rörelsen.
Inom ramen för denna artikel Ă€r det oklokt att gĂ„ igenom koden för den vĂ€lvuxna strace som har Ă„ldrats i decennier. Men det ska inte finnas nĂ„gra hemligheter för lĂ€sarna. DĂ€rför, för att visa hur sĂ„dana strace-program fungerar, kommer jag att ge koden för en miniatyrtracer - (ptr). Den gör ingenting sĂ€rskilt, men det viktigaste â systemanropen frĂ„n programmet â skriver ut:
$ 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 termineradePiglet Trace kÀnner igen cirka hundra systemanrop i Linux (se ) och fungerar endast pÄ x86-64-arkitekturen. För undervisningssyften Àr detta tillrÀckligt.
LÄt oss gÄ igenom hur vÄr klon fungerar. I fallet med Linux anvÀnds, som nÀmnts ovan, systemanropet ptrace för debugger och tracers. Det fungerar genom att överföra kommandon i det första argumentet, av vilka vi bara behöver PTRACE_TRACEME, PTRACE_SYSCALL och PTRACE_GETREGS.
Arbetet för debuggern börjar pÄ vanligt Unix-manér: fork(2) startar en barnprocess, som i sin tur startar det undersökta programmet med hjÀlp av exec(3) . Den enda detaljen hÀr Àr anropet ptrace(PTRACE_TRACEME) före exec: barnprocessen vÀntar pÄ att förÀldraprocessen ska övervaka den:
pid_t child_pid = fork();
switch (child_pid) {
case -1:
err(EXIT_FAILURE, "fork");
case 0:
/* Barn hÀr */
/* En spÄrad lÀge mÄste aktiveras. En förÀlder mÄste vÀnta(2) pÄ att det
* ska ske. */
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
/* ErsÀtt sig sjÀlv med ett program som ska köras. */
execvp(argv[1], argv + 1);
err(EXIT_FAILURE, "exec");
}Nu mÄste förÀldraprocessen anropa wait(2) i barnprocessen, alltsÄ sÀkerstÀlla att bytet till spÄrningslÀge har skett:
/* 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");Nu Àr förberedelserna klara och vi kan gÄ vidare till att spÄra systemanrop i en oÀndlig loop.
Anrop ptrace(PTRACE_SYSCALL) garanterar att efterföljande vÀnta förÀldern avslutas antingen före utförandet av systemanropet, eller omedelbart efter att det har slutförts. Mellan de tvÄ anropen kan man utföra ÄtgÀrder: byta anrop mot ett alternativt, Àndra argument eller det returnerade vÀrdet.
Vi behöver bara anropa kommandot ptrace(PTRACE_GETREGS), tvÄ gÄnger för att hÀmta tillstÄndet hos registret rax före anropet (nummer pÄ systemanropet) och omedelbart efter (det returnerade vÀrdet).
Egentligen, cykeln:
/* 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);
}Och sÄ Àr hela spÄrningsprogrammet klart. Nu vet du hur du ska börja nÀsta portering till Linux.
Grunderna: starta programmet under strace
Som ett första exempel pĂ„ anvĂ€ndning för att kontrollera paketöverföringen Ă„t bĂ„da hĂ„ll, kanske det Ă€r vĂ€rt att ge det enklaste sĂ€ttet â att starta en applikation under kontrollerad för att kontrollera paketöverföringen Ă„t bĂ„da hĂ„ll.
För att inte grÀva ner oss i en oÀndlig lista av anrop frÄn ett typiskt program, lÄt oss skriva omkring skriv:
int main(int argc, char *argv[])
{
char str[] = "write me to stdoutn";
/* write(2) Àr ett enkelt omslag runt en syscall, sÄ det borde vara enkelt att
* hitta i syscall-tracingen. */
if (sizeof(str) != write(STDOUT_FILENO, str, sizeof(str))){
perror("write");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
LÄt oss kompilera programmet och sÀkerstÀlla att det fungerar:
$ gcc examples/write-simple.c -o write-simple
$ ../write-simple
write me to stdoutOch slutligen lÄt oss köra det under strace:
$ strace ./write-simple
execve("./write", ["./write"], 0x7ffebd6145b0 /* 71 vars */) = 0
brk(NULL) = 0x55ff5489e000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (Inga sÄdana filer eller kataloger)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (Inga sÄdana filer eller kataloger)
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 (Inga sÄdana filer eller kataloger)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF2113 3 >