Otklanjanje pogrešaka implementacije softvera sa strace

Otklanjanje pogrešaka implementacije softvera sa strace

Moj svakodnevni posao uglavnom je implementacija softvera, što znači da provodim mnogo vremena pokušavajući odgovoriti na pitanja poput:

  • Ovaj softver radi za programera, ali ne i za mene. Zašto?
  • Jučer mi je ovaj softver radio, ali danas ne. Zašto?

Ovo je vrsta otklanjanja pogrešaka koja se malo razlikuje od uobičajenog softverskog otklanjanja pogrešaka. Redovito otklanjanje pogrešaka odnosi se na logiku koda, ali ispravljanje pogrešaka implementacije odnosi se na interakciju između koda i okoline. Čak i ako je korijen problema logička pogreška, činjenica da sve radi na jednom računalu, a na drugom ne znači da je problem nekako u okruženju.

Dakle, umjesto uobičajenih alata za otklanjanje pogrešaka poput gdb Imam drugačiji skup alata za otklanjanje pogrešaka implementacije. I moj omiljeni alat za rješavanje problema poput "Zašto mi ovaj softver ne radi?" nazvao strace.

Što je strace?

strace je alat za “traženje poziva sustava”. Izvorno je stvoren za Linux, ali isti trikovi za otklanjanje pogrešaka mogu se izvesti s alatima za druge sustave (DTrace ili ktrace).

Osnovna primjena je vrlo jednostavna. Samo trebate pokrenuti strace s bilo kojom naredbom i ona će ispisati sve sistemske pozive (iako ćete je prvo vjerojatno morati sami instalirati 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 +++

Koji su to sistemski pozivi? Ovo je nešto poput API-ja za jezgru operativnog sustava. Nekoć je softver imao izravan pristup hardveru na kojem je radio. Ako je, primjerice, trebao prikazati nešto na zaslonu, igrao se s priključcima ili memorijsko mapiranim registrima za video uređaje. Kad su multitasking računalni sustavi postali popularni, zavladao je kaos dok su se razne aplikacije borile oko hardvera. Pogreške u jednoj aplikaciji mogle bi srušiti druge, ako ne i cijeli sustav. Tada su se u CPU-u pojavili privilegirani načini rada (ili "zaštita prstena"). Kernel je postao najpovlašteniji: dobio je puni pristup hardveru, iznjedrivši manje privilegirane aplikacije koje su već morale zahtijevati pristup od kernela za interakciju s hardverom putem sistemskih poziva.

Na binarnoj razini, sistemski poziv malo se razlikuje od jednostavnog poziva funkcije, ali većina programa koristi omotač u standardnoj biblioteci. Oni. standardna knjižnica POSIX C sadrži poziv funkcije pisati(), koji sadrži sav kôd specifičan za arhitekturu za sistemski poziv pisati.

Otklanjanje pogrešaka implementacije softvera sa strace

Ukratko, svaka interakcija između aplikacije i njezine okoline (računalnih sustava) provodi se putem sistemskih poziva. Stoga, kada softver radi na jednom računalu, ali ne i na drugom, bilo bi dobro pogledati rezultate praćenja poziva sustava. Točnije, ovdje je popis tipičnih točaka koje se mogu analizirati pomoću praćenja poziva sustava:

  • I/O konzole
  • Mrežni I/O
  • Pristup datotečnom sustavu i ulaz/izlaz datoteka
  • Upravljanje vijekom trajanja procesne niti
  • Upravljanje memorijom niske razine
  • Pristup određenim upravljačkim programima uređaja

Kada koristiti strace?

U teoriji, strace koristi se s bilo kojim programom u korisničkom prostoru, jer svaki program u korisničkom prostoru mora upućivati ​​sistemske pozive. Djeluje učinkovitije s kompiliranim programima niske razine, ali također radi s jezicima visoke razine poput Pythona ako možete smanjiti dodatnu buku iz vremena izvođenja i tumača.

U svoj svojoj raskoši strace manifestira se tijekom otklanjanja pogrešaka softvera koji dobro radi na jednom računalu, ali odjednom prestane raditi na drugom, proizvodeći nejasne poruke o datotekama, dozvolama ili neuspješnim pokušajima izvršavanja nekih naredbi ili nečeg trećeg... Šteta, ali nije tako dobro kombinirati s problemima visoke razine kao što su pogreške pri provjeri certifikata. Obično je za to potrebna kombinacija strace, Ponekad ltrace i alate više razine (poput alata naredbenog retka openssl za otklanjanje pogrešaka certifikata).

Koristit ćemo samostalni poslužitelj kao primjer, ali praćenje poziva sustava često se može izvesti na složenijim platformama za implementaciju. Vi samo trebate odabrati prave alate.

Jednostavan primjer otklanjanja pogrešaka

Recimo da želite pokrenuti nevjerojatnu poslužiteljsku aplikaciju foo, a ovo je ono što na kraju dobijete:

$ foo
Error opening configuration file: No such file or directory

Očigledno nije mogao pronaći konfiguracijsku datoteku koju ste napisali. To se događa zato što ponekad kad upravitelji paketa kompajliraju aplikaciju, nadjačaju očekivane lokacije datoteka. A ako slijedite instalacijski vodič za jednu distribuciju, u drugoj ćete pronaći datoteke potpuno drugačije od onih koje ste očekivali. Problem bi se mogao riješiti za nekoliko sekundi ako bi poruka o pogrešci rekla gdje tražiti konfiguracijsku datoteku, ali nije. Pa gdje tražiti?

Ako imate pristup izvornom kodu, možete ga pročitati i saznati sve. Dobar rezervni plan, ali ne i najbrže rješenje. Možete pribjeći korak-po-korak debuggeru kao što je gdb i vidjeti što program radi, ali puno je učinkovitije koristiti alat koji je posebno dizajniran za prikaz interakcije s okolinom: strace.

Izlaz strace može se činiti suvišnim, ali dobra je vijest da se većina toga može sigurno zanemariti. Često je korisno koristiti -o operator za spremanje rezultata praćenja u zasebnu datoteku:

$ strace -o /tmp/trace foo
Error opening configuration file: No such file or directory
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL)                               = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
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 (No such file or directory)
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, "Error opening configuration file"..., 60) = 60
close(3)                                = 0
exit_group(1)                           = ?
+++ exited with 1 +++

Otprilike cijela prva stranica ispisa strace - Ovo je obično niska razina pripreme za lansiranje. (Mnogo poziva mmap, mprotect, pero za stvari poput otkrivanja memorije niske razine i prikazivanja dinamičkih biblioteka.) Zapravo, tijekom otklanjanja pogrešaka izlaz strace Bolje je čitati od samog kraja. Ispod će biti izazov pisati, koji prikazuje poruku o pogrešci. Pogledamo iznad i vidimo prvi pogrešan sistemski poziv - poziv openat, što daje pogrešku ENOENT ("datoteka ili direktorij nije pronađen") pokušava se otvoriti /etc/foo/config.json. Ovdje bi trebala biti konfiguracijska datoteka.

Ovo je bio samo primjer, ali rekao bih 90% vremena koje koristim strace, ne postoji ništa puno teže učiniti od ovoga. Ispod je potpuni vodič za uklanjanje pogrešaka korak po korak:

  • Uzrujati se zbog nejasne poruke o sistemskoj pogrešci iz programa
  • Ponovno pokrenite program s strace
  • Pronađite poruku o pogrešci u rezultatima praćenja
  • Idite više dok ne pogodite prvi neuspjeli poziv sustava

Vrlo je vjerojatno da će poziv sustava u koraku 4 otkriti što je pošlo po zlu.

savjeti

Prije nego što vam pokažem primjer složenijeg otklanjanja pogrešaka, pokazat ću vam nekoliko trikova za učinkovitu upotrebu strace:

čovjek je tvoj prijatelj

Na mnogim *nix sustavima, potpuni popis sistemskih poziva kernelu može se dobiti pokretanjem man syscalls. Vidjet ćete stvari poput brk(2), što znači da se više informacija može dobiti trčanjem čovjek 2 brk.

Mali rake: čovjek 2 vilica pokazuje mi stranicu za školjku vilica () в GNU libc, koji se, pokazalo se, implementira pozivom klon(). Semantika poziva viljuška ostaje isti ako napišete program koristeći vilica (), i pokrenite praćenje - neću pronaći nijedan poziv viljuška, umjesto njih bit će klon(). Takve vas grablje zbunjuju samo ako počnete uspoređivati ​​izvor s izlazom strace.

Koristite -o za spremanje izlaza u datoteku

strace može generirati opsežan izlaz, pa je često korisno pohraniti rezultate praćenja u zasebne datoteke (kao u gornjem primjeru). Ovo također pomaže u izbjegavanju brkanja izlaza programa s izlazom strace u konzoli.

Koristite -s za prikaz više podataka o argumentima

Možda ste primijetili da druga polovica poruke o pogrešci nije prikazana u gornjem primjeru praćenja. To je zato što strace default prikazuje samo prva 32 bajta argumenta niza. Ako želite vidjeti više, dodajte nešto poput -s 128 na poziv strace.

-y olakšava praćenje datoteka, utičnica itd.

"Sve je datoteka" znači da *nix sustavi rade sve I/O koristeći deskriptore datoteka, bilo da se to odnosi na datoteku ili mrežu ili međuprocesne cijevi. Ovo je zgodno za programiranje, ali otežava praćenje onoga što se stvarno događa kada vidite uobičajeno čitati и pisati u rezultatima praćenja poziva sustava.

Dodavanjem operatora , prisilit ćeš strace označite svaki deskriptor datoteke u izlazu s napomenom na što ukazuje.

Povežite se s već pokrenutim procesom s -p**

Kao što ćete vidjeti iz donjeg primjera, ponekad morate pratiti program koji je već pokrenut. Ako je poznato da se izvodi kao proces 1337 (recimo, iz izlaza ps), onda to možete pratiti ovako:

$ strace -p 1337
...system call trace output...

Možda će vam trebati root prava.

Koristite -f za nadzor podređenih procesa

strace Prema zadanim postavkama prati samo jedan proces. Ako ovaj proces rađa podređene procese, tada se može vidjeti sistemski poziv za stvaranje podređenog procesa, ali sistemski pozivi podređenog procesa neće biti prikazani.

Ako mislite da je pogreška u podređenom procesu, upotrijebite naredbu -f, to će omogućiti njegovo praćenje. Loša strana ovoga je da će vas rezultat još više zbuniti. Kada strace prati jedan proces ili jednu nit, prikazuje jedan tok događaja poziva. Kada prati više procesa odjednom, možete vidjeti početak poziva prekinut porukom , zatim - hrpa poziva za druge izvršne grane, pa tek onda - kraj prve . Ili podijelite sve rezultate praćenja u različite datoteke, također pomoću operatora -ff (detalji u rukovodstvo na strace).

Filtrirajte tragove koristeći -e

Kao što vidite, rezultat praćenja je prava hrpa svih mogućih sistemskih poziva. Zastava -e Možete filtrirati trag (pogledajte rukovodstvo na strace). Glavna prednost je što je brže pokrenuti filtrirano praćenje nego napraviti potpuno praćenje i zatim grep`na. Da budem iskren, gotovo uvijek me nije briga.

Nisu sve pogreške loše

Jednostavan i uobičajen primjer je program koji traži datoteku na nekoliko mjesta odjednom, poput ljuske koja traži direktorij koji sadrži izvršnu datoteku:

$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...

Heuristike poput "posljednji neuspjeli zahtjev prije prijave pogreške" dobre su u pronalaženju relevantnih pogrešaka. Bilo kako bilo, logično je krenuti od samog kraja.

Udžbenici za C programiranje mogu vam pomoći u razumijevanju sistemskih poziva.

Standardni pozivi C knjižnicama nisu sistemski pozivi, već samo tanki površinski sloj. Dakle, ako barem malo razumijete kako i što raditi u C-u, bit će vam lakše razumjeti rezultate praćenja poziva sustava. Na primjer, imate problema s otklanjanjem pogrešaka poziva mrežnim sustavima, pogledajte isti klasik Bijin vodič za mrežno programiranje.

Složeniji primjer otklanjanja pogrešaka

Već sam rekao da je primjer jednostavnog otklanjanja pogrešaka primjer onoga s čime se uglavnom moram susresti kada radim strace. Međutim, ponekad je potrebna stvarna istraga, pa evo primjera naprednijeg otklanjanja pogrešaka iz stvarnog života.

bcron - planer obrade zadataka, još jedna implementacija *nix demona cron. Instaliran je na poslužitelju, ali kada netko pokuša urediti raspored, događa se sljedeće:

# crontab -e -u logs
bcrontab: Fatal: Could not create temporary file

U redu, to znači bcron pokušao napisati određenu datoteku, ali nije išlo, a on neće priznati zašto. Razotkrivanje 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 +++

Postoji poruka o pogrešci pri samom kraju pisati, ali ovaj put nešto je drugačije. Prvo, ne postoji relevantna pogreška poziva sustava, koja se obično javlja prije ovoga. Drugo, jasno je da je negdje netko već pročitao poruku o pogrešci. Čini se da je pravi problem negdje drugdje, i bcrontab jednostavno reproducira poruku.

Ako pogledate čovjek 2 čitati, možete vidjeti da je prvi argument (3) deskriptor datoteke, koji *nix koristi za sve I/O obrade. Kako mogu saznati što deskriptor datoteke 3 predstavlja? U ovom konkretnom slučaju, možete trčati strace s operaterom (vidi gore) i automatski će vam reći, ali da biste shvatili ovakve stvari, korisno je znati kako čitati i analizirati rezultate praćenja.

Izvor deskriptora datoteke može biti jedan od mnogih sistemskih poziva (sve ovisi o tome čemu deskriptor služi - konzoli, mrežnoj utičnici, samoj datoteci ili nečem drugom), ali kako god bilo, mi tražimo poziva vraćajući 3 (tj. tražimo "= 3" u rezultatima praćenja). U ovom rezultatu ima ih 2: openat na samom vrhu i utičnica U sredini. openat otvara datoteku ali blizu(3) će tada pokazati da se ponovno zatvara. (Rake: deskriptori datoteka mogu se ponovno koristiti kada se otvaraju i zatvaraju). Poziv utičnica () prikladan jer je posljednji prije čitati(), i ispada da bcrontab radi s nečim preko socketa. Sljedeći redak pokazuje da je deskriptor datoteke povezan s utičnica unix domene na putu /var/run/bcron-spool.

Dakle, moramo pronaći proces povezan s unix utičnica na drugoj strani. Za tu svrhu postoji nekoliko zgodnih trikova, a oba su korisna za otklanjanje pogrešaka u postavljanju poslužitelja. Prvi je koristiti netstat ili noviji ss (status utičnice). Obje naredbe prikazuju aktivne mrežne veze sustava i preuzimaju izjavu -l opisati prislušne utičnice, kao i operatera -p za prikaz programa spojenih na utičnicu kao klijenta. (Postoji još mnogo korisnih opcija, ali ove dvije su dovoljne za ovaj zadatak.)

# ss -pl | grep /var/run/bcron-spool
u_str LISTEN 0   128   /var/run/bcron-spool 1466637   * 0   users:(("unixserver",pid=20629,fd=3))

Ovo sugerira da je slušatelj naredba inixserver, koji se izvodi s procesom ID 20629. (I, slučajno, koristi deskriptor datoteke 3 kao utičnicu.)

Drugi stvarno koristan alat za pronalaženje istih informacija zove se Također. Navodi sve otvorene datoteke (ili deskriptore datoteka) na sustavu. Ili možete dobiti informacije o jednoj određenoj 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=STREAM

Proces 20629 je dugotrajni poslužitelj, tako da ga možete priključiti strace koristeći nešto poput strace -o /tmp/trace -p 20629. Ako uredite cron posao na drugom terminalu, primit ćete izlaz praćenja s pogreškom. I 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 (To be restarted if SA_RESTART is set)
--- 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 (No child processes)
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 (To be restarted if SA_RESTART is set)
--- 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 (No child processes)
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

(Posljednji prihvatiti () neće biti dovršen prilikom praćenja.) Ponovno, nažalost, ovaj rezultat ne sadrži pogrešku koju tražimo. Ne vidimo nikakve poruke koje bcrontag šalje ili prima od utičnice. Umjesto toga, potpuna kontrola procesa (klon, čekaj4, SIGCHLD itd.) Ovaj proces rađa proces dijete, koji, kao što možete pretpostaviti, radi pravi posao. A ako joj trebaš uhvatiti trag, dodaj pozivu strace -f. To je ono što ćemo pronaći kada tražimo poruku o pogrešci u novom rezultatu sa strace -f -o /tmp/trag -p 20629:

21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

E, to je nešto. Proces 21470 prima pogrešku "pristup odbijen" kada pokušava stvoriti datoteku na stazi tmp/spool.21470.1573692319.854640 (odnosi se na trenutni radni imenik). Kad bismo samo znali trenutni radni direktorij, također bismo znali punu stazu i mogli bismo shvatiti zašto proces ne može stvoriti svoju privremenu datoteku u njemu. Nažalost, proces je već izašao, tako da ne možete samo koristiti lsof -p 21470 kako biste pronašli trenutni imenik, ali možete raditi i u suprotnom smjeru - potražite PID 21470 sistemske pozive koji mijenjaju imenik. (Ako ih nema, PID 21470 mora da ih je naslijedio od svog roditelja, a to je već kroz lsof -str ne može se saznati.) Ovaj sistemski poziv je chdir (što je lako saznati uz pomoć modernih online tražilica). A evo i rezultata obrnutih pretraga na temelju rezultata praćenja, sve do poslužitelja 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 (Permission denied) 
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

(Ako ste se izgubili, možda biste trebali pročitati moj prethodni post o *nix upravljanju procesima i školjkama.) Dakle, poslužitelj PID 20629 nije dobio dopuštenje za stvaranje datoteke na stazi /var/spool/cron/tmp/spool.21470.1573692319.854640. Najvjerojatnije su razlog tome klasične postavke dopuštenja 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-spool

Tamo je pas zakopan! Poslužitelj radi kao korisnički cron, ali samo root ima dopuštenje za pisanje u direktorij /var/spool/cron/tmp/. Jednostavna naredba chown cron /var/spool/cron/tmp/ će prisiliti bcron raditi ispravno. (Ako to nije problem, onda je sljedeći najvjerojatniji sumnjivac sigurnosni modul jezgre kao što je SELinux ili AppArmor, pa bih provjerio zapisnik poruka jezgre s dmesg.)

Ukupno

Tragovi sistemskih poziva mogu biti neodoljivi za početnike, ali nadam se da sam pokazao da su oni brzi način za otklanjanje pogrešaka cijele klase uobičajenih problema s implementacijom. Zamislite da pokušavate ispraviti pogreške višeprocesnog procesa bcronpomoću alata za uklanjanje pogrešaka korak po korak.

Raščlanjivanje rezultata praćenja unatrag duž lanca sistemskih poziva zahtijeva vještinu, ali kao što sam rekao, gotovo uvijek, koristeći strace, samo dobivam rezultat praćenja i tražim pogreške počevši od kraja. svejedno, strace pomaže mi uštedjeti puno vremena na otklanjanju pogrešaka. Nadam se da će i vama biti od koristi.

Izvor: www.habr.com

Dodajte komentar