
Moj glavni posao većinom se sastoji od implementacije softverskih sustava, što znači da provodim mnogo vremena pokušavajući odgovoriti na ovakva pitanja:
- Kod programera ovaj softver radi, a kod mene ne. Zašto?
- Juče je ovaj softver radio kod mene, a danas ne. Zašto?
Ovo je neka vrsta debagiranja koja se malo razlikuje od uobičajenog debagiranja softvera. Uobičajeno debagiranje odnosi se na logiku koda, dok debagiranje implementacije posmatra interakciju koda i okruženja. Čak i ako je uzrok problema logička greška, činjenica da na jednom računaru sve radi, a na drugom ne, znači da je nešto vezano za okolinu.
Zato umesto uobičajenih alata za debagiranje kao što su gdb imam drugi set alata za debagiranje implementacije. A moj omiljeni alat za rešavanje problema tipa "Zašto ovaj softver ne radi?" zove se strace.
Šta je strace?
— to je alat za "praćenje sistemskih poziva". Prvobitno je razvijen za Linux, ali iste funkcije debagiranja možete primeniti i sa alatima za druge sisteme ( ili ).
Osnovna primena je vrlo jednostavna. Samo treba pokrenuti strace sa bilo kojom komandom i on će poslati u dump sve sistemske pozive (mada će verovatno prvo morati da se instalira sam strace):
$ strace echo Hello
...Snip lots of stuff...
write(1, "Hellon", 6) = 6
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++Šta su ti sistemski pozivi? To je nešto poput API-ja za jezgro operativnog sistema. Davno su softveri imali direktan pristup "hardveru" na kojem su radili. Na primer, kada je bilo potrebno nešto prikazati na ekranu, manipulirali su sa portovima ili registrima koji su se prikazivali u memoriji za video uređaje. Kada su postali popularni višezadaćni računarski sistemi, nastao je haos jer su se različite aplikacije borile za "hardver". Greške u jednom programu mogle su srušiti rad drugih, pa čak i celog sistema. Tada su u CPU-u uvedeni privilegovani režimi (ili "prstenasta zaštita"). Najprivilegovanije je postalo jezgro: dobijalo je puni pristup "hardveru", proizvodeći manje privilegovane aplikacije koje su zatim morale tražiti pristup od jezgra za interakciju sa "hardverom" — putem sistemskih poziva.
Na binarnoj razini, sistemski poziv se malo razlikuje od običnog poziva funkcije, ali većina programa koristi omotač iz standardne biblioteke. Odnosno, standardna biblioteka POSIX C sadrži poziv funkcije write(), koja sadrži sav arhitekturi ovisan kod za sistemski poziv write.

Ukratko, bilo koja interakcija aplikacije sa svojim okruženjem (računalnim sustavima) ostvaruje se putem sistemskih poziva. Stoga, kada softver na jednom stroju radi, a na drugom ne, dobro bi bilo pogledati rezultate praćenja sistemskih poziva. Ako budemo točniji, evo popisa tipičnih stvari koje se mogu analizirati pomoću praćenja sistemskog poziva:
- Konzolni ulaz-izlaz
- Mrežni ulaz-izlaz
- Pristup datotečnom sustavu i datotečni ulaz-izlaz
- Upravljanje životnim ciklusom procesnih niti
- Niskonivojno upravljanje memorijom
- Pristup vozačima posebnih uređaja
Kada koristiti strace?
U teoriji, strace koristi se s bilo kojim programima u korisničkom prostoru, jer svaki program u korisničkom prostoru mora raditi sistemske pozive. Efikasnije radi s kompajliranim, niskonivojnim programima, ali isto tako radi i s visokointernim jezicima poput Pythona, ako se može probiti kroz dodatni šum iz okoline izvršavanja i interpretatora.
U svom punom sjaju strace pokazuje se tijekom debagiranja softvera koji dobro radi na jednom stroju, a na drugom iznenada prestaje raditi, izdajući nejasne poruke o datotekama, dozvolama ili neuspješnim pokušajima izvršavanja nekih komandi ili slično... Šteta, ali ne funkcionira dobro s visokointernim problemima poput greške provjere certifikata. Obično je potrebna kombinacija strace, ponekad i alata višeg nivoa (poput alatne trake openssl za debagiranje certifikata).
Za primjer uzmimo rad na izoliranom serveru, ali praćenje sistemskih poziva često se može izvršiti i na složenijim platformama za implementaciju. Samo je potrebno odabrati odgovarajući alat.
Primjer jednostavnog debagiranja
Recimo, želite pokrenuti nevjerojatnu serversku aplikaciju foo, a dobijate ovo:
$ foo
Greška pri otvaranju konfiguracijske datoteke: Nema takve datoteke ili direktorijaOčigledno, nije uspio pronaći konfiguracijski datoteku koju ste napisali. To se događa jer ponekad, kada upravitelji paketa kompiliraju aplikaciju, izmjene očekivanu lokaciju datoteka. I ako se slijedi vodič za instalaciju za jednu distribuciju, u drugoj se datoteke nalaze potpuno drugdje nego što se očekivalo. Problem bi se mogao riješiti za nekoliko sekundi da je poruka o pogrešci govorila gdje treba tražiti konfiguracijski datoteku, ali ona ne kaže. Gdje tražiti?
Ako postoji pristup izvornoj kodu, možete ga pročitati i sve saznati. Dobar rezervni plan, ali nije najbrže rješenje. Možete koristiti korak-po-korak debager poput gdb i vidjeti što program radi, ali daleko je učinkovitije koristiti alat koji je posebno dizajniran za prikazivanje interakcije s okolinom: strace.
Izvještaj strace može izgledati suvišno, ali dobra vijest je da većinu njega možete slobodno ignorirati. Često je korisno koristiti opciju -o za spremanje rezultata praćenja u zasebnu datoteku:
$ strace -o /tmp/trace foo
Greška prilikom otvaranja konfiguracijske datoteke: Nema takvog datoteka ili direktorija
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL) = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (Nema takvog datoteka ili direktorija)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=25186, ...}) = 0
mmap(NULL, 25186, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2f12cf1000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF2113 3 > 1 260A2 "..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1824496, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2f12cef000
mmap(NULL, 1837056, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2f12b2e000
mprotect(0x7f2f12b50000, 1658880, PROT_NONE) = 0
mmap(0x7f2f12b50000, 1343488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f2f12b50000
mmap(0x7f2f12c98000, 311296, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16a000) = 0x7f2f12c98000
mmap(0x7f2f12ce5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b6000) = 0x7f2f12ce5000
mmap(0x7f2f12ceb000, 14336, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2f12ceb000
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7f2f12cf0500) = 0
mprotect(0x7f2f12ce5000, 16384, PROT_READ) = 0
mprotect(0x56363b08b000, 4096, PROT_READ) = 0
mprotect(0x7f2f12d1f000, 4096, PROT_READ) = 0
munmap(0x7f2f12cf1000, 25186) = 0
openat(AT_FDCWD, "/etc/foo/config.json", O_RDONLY) = -1 ENOENT (Nema takvog datoteka ili direktorija)
dup(2) = 3
fcntl(3, F_GETFL) = 0x2 (flags O_RDWR)
brk(NULL) = 0x56363b3fb000
brk(0x56363b41c000) = 0x56363b41c000
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x8), ...}) = 0
write(3, "Greška prilikom otvaranja konfiguracijske datoteke"..., 60) = 60
close(3) = 0
exit_group(1) = ?
+++ izašao s 1 +++Otprilike cijela prva stranica ispisa strace — to obično predstavlja niskonivo pripremu za pokretanje. (Mnogo poziva mmap, mprotect, brk za stvari poput otkrivanja niskonivo memorije i mapiranja dinamičkih biblioteka.) U pravilu, tijekom debagiranja ispisi strace bolje je čitati od samog kraja. Na dnu će biti poziv write, koji ispisuje poruku o grešci. Proučimo iznad, i vidimo prvi neuspjeli sistemski poziv — poziv openat, koji daje grešku ENOENT ("datoteka ili direktorij nisu pronađeni"), pokušavajući otvoriti /etc/foo/config.json. E, ovdje bi trebao biti konfiguracijski datoteka.
To je bio samo primjer, ali rekao bih da 90% vremena, kada koristim strace, ništa mnogo složenije od ovoga se ne radi. Ispod je potpuni vodič korak po korak za debagiranje:
- Ne razočarati se zbog nejasne poruke o sistemskoj grešci od programa
- Ponovno pokrenuti program s strace
- Pronaći u rezultatima praćenja poruku o grešci
- Ići iznad, sve dok ne naiđete na prvi neuspjeli sistemski poziv
Vrlo je vjerojatno da će sistemski poziv u 4. koraku pokazati što je pošlo po zlu.
Savjeti
Prije nego što pokažem primjer složenijeg debagiranja, otkriću vam nekoliko trikova za efikasnije korištenje strace:
man — vaš prijatelj
Na mnogim *nix sustavima cjelovit popis sistemskih poziva jezgre može se dobiti pokretanjem man syscalls. Vidjet ćete stvari poput brk(2), što znači da možete dobiti više informacija pokretanjem man 2 brk.
Mali problemi: man 2 fork prikazuje mi stranicu za ljusku fork() u GNU libc, koja se, ispostavlja, implementira putem poziva clone(). Semantika poziva fork ostaje ista, ako napišem program koji koristi fork(), i pokrenem debagiranje — neću pronaći pozive fork, umjesto njih bit će clone(). Takve sitnice samo zbunjuju ako počnete uspoređivati izvor s ishodom strace.
Koristite -o da sačuvate ishod u datoteku
strace može generirati opširne ishodne podatke, pa je često korisno čuvati rezultate debagiranja u zasebnim datotekama (kao u gornjem primjeru). Također pomaže da se ne pomiješaju izlaz programa s izlazom strace u konzoli.
Koristite -s da pregledate više podataka argumenta
Sigurno ste primijetili da druga polovica poruke o grešci nije prikazana u gornjem primjeru debagiranja. To je zato što strace po defaultu pokazuje samo prvih 32 bajta argumenta. Ako želite vidjeti više, dodajte nešto poput -s 128 k pozivu strace.
-u olakšava praćenje datoteka, soketa i slično.
„Sve je datoteka“ znači da *nix sustavi izvršavaju sve ulaze i izlaze koristeći deskriptore datoteka, bez obzira da li se radi o datoteci, mreži ili međuprocesnim kanalima. Ovo je zgodno za programiranje, ali otežava praćenje onoga što se zapravo događa kada vidite opću read i write u ishodu sistemskog poziva.
Dodavanje operatora -u, natjerat će vas strace da annotirate svaki deskriptor datoteke u izlazu s napomenom na što upućuje.
Priključite se već pokrenutom procesu s -p**
Kao što je vidljivo iz donjeg primjera, ponekad je potrebno debagirati program koji je već pokrenut. Ako se zna da se pokreće kao proces 1337 (recimo, iz izlaza ps), debagiranje se može izvršiti ovako:
$ strace -p 1337
...izlaz praćenja sistemskog poziva...Možda će vam trebati root privilegije.
Koristite -f da pratite podprocese
strace po defaultu prati samo jedan proces. Ako taj proces stvara podprocese, možete vidjeti sistemski poziv za stvaranje podprozora, ali sistemski pozivi podprozora neće se prikazati.
Ako smatrate da je greška u podprocesu, koristite operator -f, to će omogućiti njegovo praćenje. Minus je u tome što će vas izlaz još više zbuniti. Kada strace prati jedan proces ili jednu granu, prikazuje jedinstveni tok događaja poziva. Kada prati više procesa, možete vidjeti početak poziva, prekinut porukom <unfinished …>, zatim - hrpu poziva za druge izvođačke grane, i tek kasnije - završetak prvog s <… foocall resumed>. Ili podijelite sve rezultate praćenja po različitim datotekama, koristeći također operator -ff (detalji - u o strace).
Filtrirajte praćenje pomoću -e
Kao što vidite, rezultat praćenja je stvarna gomila svih mogućih sistemskih poziva. Flagom -e možete filtrirati praćenje (vidi. o strace). Glavna prednost je u tome što se praćenje s filtriranjem pokreće brže nego raditi cjelovito praćenje, a zatim grep`ati. Ako budemo iskreni, gotovo uvijek mi je svejedno.
Nisu sve greške loše
Jednostavan i uobičajen primjer je program koji traži datoteku na više mjesta, poput ljuske koja traži gdje se izvršna datoteka nalazi u kantici s kasnom:
$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (Nema takve datoteke ili direktorija)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (Nema takve datoteke ili direktorija)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...Heuristika tipa 'zadnji neuspješni upit prije poruke o grešci' dobra je za pronalaženje relevantnih grešaka. Bez obzira što, logično je početi s kraja.
Razumijevanje sistemskih poziva dobro pomažu priručnici za programiranje u jeziku C
Standardni pozivi biblioteka C nisu sistemski pozivi, već samo tanka površinska sloj. Dakle, ako barem malo razumijete kako i što raditi u C, bit će vam lakše razumjeti rezultate praćenja sistemskih poziva. Na primjer, imate problem s debagiranjem poziva na mrežne sustave, pregledajte isto klasično .
Primjer složenijeg debagiranja
Već sam spomenuo da je primjer jednostavne analize slučaj kada se većinom susrećem u svom radu sa strace. Međutim, ponekad je potrebno pravo istraživanje, pa evo pravog primjera složenije analize.
— planer za obradu zadataka, još jedna implementacija demona *nix cron. Instaliran je na serveru, ali kada netko pokuša urediti raspored, događa se sljedeće:
# crontab -e -u logs
bcrontab: Fatal: Could not create temporary fileU redu, znači, bcron pokušao je napisati neki datoteku, ali mu nije uspjelo i ne priznaje zašto. Razvijamo strace:
# strace -o /tmp/trace crontab -e -u logs
bcrontab: Fatal: Could not create temporary file
# cat /tmp/trace
...
openat(AT_FDCWD, "bcrontab.14779.1573691864.847933", O_RDONLY) = 3
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
read(3, "#Ansible: logsaggn20 14 * * * lo"..., 8192) = 150
read(3, "", 8192) = 0
munmap(0x7f82049b4000, 8192) = 0
close(3) = 0
socket(AF_UNIX, SOCK_STREAM, 0) = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/bcron-spool"}, 110) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
write(3, "156:Slogs #Ansible: logsaggn20 1"..., 161) = 161
read(3, "32:ZCould not create temporary f"..., 8192) = 36
munmap(0x7f82049b4000, 8192) = 0
close(3) = 0
write(2, "bcrontab: Fatal: Could not creat"..., 49) = 49
unlink("bcrontab.14779.1573691864.847933") = 0
exit_group(111) = ?
+++ exited with 111 +++Otprilike na kraju pojavljuje se poruka o grešci write, ali ovaj put nešto se razlikuje. Prvo, nema relevantne greške sistemskog poziva koja obično dolazi prije toga. Drugo, može se vidjeti da je netko već pročitao poruku o grešci. Izgleda kao da je pravi problem negdje drugdje, a bcrontab samo ponavlja poruku.
Ako pogledate man 2 read, možete vidjeti da je prvi argument (3) — to je datotečni deskriptor koji *nix koristi za sve operacije ulaza/izlaza. Kako saznati što predstavlja datotečni deskriptor 3? U ovom konkretnom slučaju možete pokrenuti strace s operatorom -u (vidi gore), i on će automatski ispričati, međutim, da biste izračunali slične stvari, korisno je znati kako čitati i analizirati rezultate praćenja.
Izvor datotečnog deskriptora može biti jedan od mnogih sistemskih poziva (sve ovisi o tome čemu je deskriptor namijenjen — za konzolu, mrežni soket, samu datoteku ili nešto drugo), ali kako god bilo, pozive tražimo, vraćajući 3 (t.j. tražimo „= 3” u rezultatima praćenja). U ovom rezultatu ih ima 2: openat na samom vrhu i socket u sredini. openat otvara datoteku, ali close(3) nakon toga pokazat će da se ponovno zatvara. (Zamke: datotečni deskriptori mogu se ponovo koristiti kada se otvaraju i zatvaraju). Poziv socket() je adekvatan, budući da je posljednji prije read(), i ispada da bcrontab radi s nečim preko soketa. Sljedeći redak pokazuje da je datotečni deskriptor povezan s unix domain socket na putu /var/run/bcron-spool.
Dakle, potrebno je pronaći proces vezan za unix socket s druge strane. Za taj cilj postoje par zgodnih trikova, a oboje će biti korisni za analizu servernih implementacija. Prvi — koristiti netstat ili noviji ss (status soketa). Obje komande prikazuju aktivne mrežne povezane sistema i uzimaju operator -l za opisivanje slušajućih soketa, kao i operator -p za prikazivanje programa povezanih na soket kao klijent. (Koristnih opcija ima mnogo više, ali za ovu temu dovoljno je i ovih dvoje.)
# ss -pl | grep /var/run/bcron-spool
u_str LISTEN 0 128 /var/run/bcron-spool 1466637 * 0 users:(("unixserver",pid=20629,fd=3))To ukazuje da je slušajući — to je komanda inixserver, koja radi sa ID procesom 20629. (I, usput, koristi deskriptor datoteke 3 kao soket.)
Drugi stvarno koristan alat za pronalaženje istih informacija naziva se lsof. Nabraja sve otvorene datoteke (ili deskriptore datoteka) u sistemu. Ili se može dobiti informacija o jednoj konkretnoj datoteci:
# lsof /var/run/bcron-spool
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
unixserve 20629 cron 3u unix 0x000000005ac4bd83 0t0 1466637 /var/run/bcron-spool type=STREAMProces 20629 — to je dugotrajni server, tako da ga možemo vezati strace pomoću nečega poput strace -o /tmp/trace -p 20629. Ako se urednik zadatka cron u drugom terminalu — dobićemo ishod rezultata praćenja s nastalom greškom. A evo rezultata:
accept(3, NULL, NULL) = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21181
close(4) = 0
accept(3, NULL, NULL) = ? ERESTARTSYS (Ponovo će se pokrenuti ako je SA_RESTART postavljen)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21181, si_uid=998, si_status=0, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED, NULL) = 21181
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (Nema procesa dece)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]}) = 43
accept(3, NULL, NULL) = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21200
close(4) = 0
accept(3, NULL, NULL) = ? ERESTARTSYS (Ponovo će se pokrenuti ako je SA_RESTART postavljen)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21200, si_uid=998, si_status=111, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 111}], WNOHANG|WSTOPPED, NULL) = 21200
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (Nema procesa dece)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]}) = 43
accept(3, NULL, NULL(Zadnje accept() neće biti završeno tokom praćenja.) I opet, koliko god žalili, ovaj rezultat ne sadrži grešku koju tražimo. Ne vidimo nijednu poruku koju bi bcrontag slao soketu ili primao od njega. Umesto njih samo upravljanje procesom (clone, wait4, SIGCHLD i ostalo.) Ovaj proces generira dijete procesa, koji, kao što možete pretpostaviti, obavlja stvarni posao. I ako trebate uhvatiti njegov trag, dodajte pozivu strace -f. Ovo što ćemo pronaći, pretražujući poruku o grešci u novom rezultatu sa strace -f -o /tmp/trace -p 20629:
21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Odbijen pristup)
21470 write(1, "32:ZNije moguće stvoriti privremeni f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logovi:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (Nema takve datoteke ili direktorija)
21470 exit_group(111) = ?
21470 +++ izašao s 111 +++Evo, ovo je već nešto. Proces 21470 prima grešku "pristup odbijen" prilikom pokušaja stvaranja datoteke na putu tmp/spool.21470.1573692319.854640 (koji se odnosi na trenutni radni katalog). Da smo samo znali trenutni radni katalog, znali bismo i puni put i mogli otkriti zašto proces ne može stvoriti svoju privremenu datoteku. Nažalost, proces je već završio, pa ne možemo jednostavno koristiti lsof -p 21470 da bismo pronašli trenutni katalog, ali možemo raditi unatrag — pretražiti sistemske pozive PID 21470 koji mijenjaju katalog. (Ako takvih nema, PID 21470, morali su naslijediti od roditelja, i to je već preko lsof -p ne možemo otkriti.) Ovaj sistemski poziv — chdir (što nije teško otkriti pomoću modernih pretraživača). I evo rezultata obrnutih pretraživanja prema rezultatima trasa, sve do samog servera PID 20629:
20629 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21470
...
21470 execve("/usr/sbin/bcron-spool", ["bcron-spool"], 0x55d2460807e0 /* 27 vars */) = 0
...
21470 chdir("/var/spool/cron") = 0
...
21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Odbijen pristup)
21470 write(1, "32:ZNije moguće stvoriti privremeni f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logovi:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (Nema takve datoteke ili direktorija)
21470 exit_group(111) = ?
21470 +++ izašao s 111 +++(Ako se izgubite, možda biste trebali pročitati moj prethodni post .) Dakle, server PID 20629 nije dobio dozvolu da stvori datoteku na putu /var/spool/cron/tmp/spool.21470.1573692319.854640. Najvjerojatnije je uzrok tome — klasične postavke dozvola datotečnog sustava. Provjerimo:
# ls -ld /var/spool/cron/tmp/
drwxr-xr-x 2 root root 4096 Nov 6 05:33 /var/spool/cron/tmp/
# ps u -p 20629
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
cron 20629 0.0 0.0 2276 752 ? Ss Nov14 0:00 unixserver -U /var/run/bcron-spool -- bcron-spoolEvo gdje je pas zakopao! Server radi kao korisnički cron, ali samo root ima dozvolu pisanja u direktorij /var/spool/cron/tmp/. Jednostavna komanda chown cron /var/spool/cron/tmp/ izvršit će bcron raditi ispravno. (Ako problem nije bio u tome, sljedeći najvjerojatniji osumnjičenik je sigurnosni modul jezgre poput SELinuxa ili AppArmor-a, stoga bih provjerio zapisnik poruka jezgre pomoću dmesg.)
U konačnici
Početniku u rezultatima praćenja sistemskih poziva može biti teško snaći se, ali se nadam da sam pokazao da su oni brz način za rješavanje cijelog niza uobičajenih problema s implementacijom. Zamislite kako pokušavate otkloniti višedretvene bcron, koristeći korak-po-korak debager.
Analiziranje rezultata praćenja unazad duž lanca sistemskih poziva zahtijeva vještinu, ali kao što sam već rekao, gotovo uvijek, koristeći strace, jednostavno dobijem rezultat praćenja i tražim greške, počinjući s kraja. U svakom slučaju, strace pomaže mi da uštedim mnogo vremena na otklanjanju grešaka. Nadam se da će i vama biti koristan.
Izvor: habr.com
