Debugging deployin e softuerit me strace

Debugging deployin e softuerit me strace

Puna ime është kryesisht shpërndarja e sistemeve të softuerit, pra, kaloj shumë kohë duke u përpjekur të përgjigjem për pyetje të tilla:

  • Pse tek zhvilluesi ky softuer punon, ndërsa tek unë nuk punon?
  • Kjo punonte dje, por sot nuk punon. Pse?

Ky është një lloj i depurimit që dallon pak nga depurimi i zakonshëm i softuerit. Depurimi i zakonshëm ka të bëjë me logjikën e kodit, ndersa depurimi i shpërndarjes ka të bëjë me ndërveprimin ndërmjet kodit dhe mjedisit. Edhe nëse problemi fillestar është një gabim logjik, fakti që në një makinë gjithçka punon, ndërsa në tjetrën jo, tregon se diçka ka të bëjë me mjedisin.

Prandaj, në vend të mjeteve të zakonshme të depurimit si gdb , kam një grup tjetër mjetesh për depurimin e shpërndarjes. Dhe mjeti im i preferuar për të trajtuar problemin si 'Pse ky softuer nuk funksionon për mua?' quhet strace.

Çfarë është strace?

strace — është një mjet për 'ndjekjen e thirrjeve sistemike'. Fillimisht u krijua për Linux, por të njëjtat karakteristika për depurim mund të arrihen edhe me mjete për sisteme të tjera (DTrace ose ktrace).

Përdorimi i tij është shumë i thjeshtë. Thjesht nisni strace me çdo komandë dhe ai do të dërgojë në një dump të gjitha thirrjet sistemike (përveç se, ndoshta duhet së pari ta instalohet vetë) 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 +++

Çfarë janë këto thirrje sistemike? Ato janë diçka si API për bërthamën e sistemit operativ. Një herë, softuerit iu dha qasje e drejtpërdrejtë në 'harduerin' ku ai punonte. Nëse, për shembull, duhet të paraqiste diçka në ekran, ai interaktonte me portet apo regjistrat e pamjes në память për pajisjet video. Kur sistemet kompjuterike shumë-funksionale u bënë të njohura, kaosi mbizotëroi, sepse aplikacione të ndryshme po luftonin për 'harduerin'. Gabimet në një aplikacion mund të shkatërronin funksionimin e aplikacioneve të tjera, nëse jo të gjithë sistemin. Atëherë në CPU u shfaqën modet e privilegjeve (ose 'mbrojtja me unazë'). Bërthama bëhej më privilegjuar: ajo kishte qasje të plotë në 'harduer', duke krijuar aplikacione më pak të privilegjuara, të cilat tani duhej të kërkonin qasje nga bërthama për të komunikuar me 'harduerin' — përmes thirrjeve sistemike.

Në nivelin binar, thirrja sistemore ndryshon pak nga thirrja e zakonshme e funksionit, megjithatë shpesh programet përdorin një mbështjellës në bibliotekën standarde. Pra, biblioteka standarde POSIX C përmban thirrjen e funksionit write(), e cila përmban të gjithë kodin që varet nga arkitektura për thirrjen sistemore. write.

Debugging deployin e softuerit me strace

Në përmbledhje, çdo ndërveprim i aplikacionit me ambientin e tij (sistemet kompjuterike) realizohet përmes thirrjeve sistemore. Prandaj, kur softueri punon në një makinë, por në një tjetër jo, do ishte mirë të shihnit rezultatet e gjurmimit të thirrjeve sistemore. Nëse jemi më specifikë, ja një listë momentesh tipike që mund të analizohet përmes gjurmimit të thirrjes sistemore:

  • Hyrja-dalja në konsolë
  • Hyrja-dalja në rrjet
  • Qasja në sistemin e skedarëve dhe hyrja-dalja e skedarëve
  • Menaxhimi i jetës së procesit/të fijeve
  • Menaxhimi me nivel të ulët të memories
  • Qasja në drejtuese të pajisjeve specifike

Kur të përdorim strace?

Në teori, strace përdoret me çdo program në hapësirën e përdoruesit, pasi çdo program në hapësirën e përdoruesit duhet të bëjë thirrje sistemore. Ai punon më efektivisht me programe të kompilueshme, me nivel të ulët, por funksionon edhe me gjuhë më të larta si Python, për sa kohë që arrin të kalojë zhurmën shtesë nga mjedisi i ekzekutimit dhe interpretuese.

Në gjithë shkëlqimin e tij strace shfaqet gjatë defektimit të softuerit, i cili funksionon mirë në një makinë, por në një tjetër papritmas ndalon së funksionuari, duke dhënë mesazhe të paqartë rreth skedarëve, lejeve ose përpjekjeve të dështuara për të ekzekutuar komanda të caktuara apo edhe diçka tjetër... Fatkeqësisht, ai nuk kombinohet mirë me probleme më të larta si gabimi i verifikimit të certifikatave. Zakonisht, këtu kërkohet një kombinim strace, ndonjëherë ltrace dhe mjete më të nivelit të lartë (siç është mjeti i komandës openssl për defektimin e certifikatave).

Si shembull, po merremi me punën në një server të izoluar, por gjurmimi i thirrjeve sistemore shpesh mund të kryhet edhe në platforma më të komplikuara të shpërndarjes. Thjesht duhet të zgjidhni mjetin e duhur.

Shembulli i defektimit të thjeshtë

Le të themi, dëshironi të ekzekutoni aplikacionin e shkëlqyer të serverit foo, dhe rezultati është ky:

$ foo
Gabim në hapjen e skedarit të konfigurimit: Nuk ekziston një skedar ose drejtori e tillë

Evidentisht, ai nuk arriti të gjente skedarin e konfigurimit të shkruar nga ju. Kjo ndodh sepse ndonjëherë, kur menaxherët e pakove, duke e kompiluar aplikacionin, riemërojnë vendin e pritur të skedave. Dhe nëse ndiqni udhëzimin e instalimit për një shpërndarje, në një tjetër gjeni skedat krejt ndryshe nga sa prisnit. Problemi mund të zgjidhej brenda disa sekondash nëse mesazhi i gabimit do të tregonte se ku duhet të kërkoni skedarin e konfigurimit, por ai nuk thotë asgjë. Atëherë, ku duhet kërkuar?

Nëse ka akses në kodin burimor, mund ta lexoni dhe të çclaroni gjithçka. Një plan rezervë i mirë, por jo zgjidhja më e shpejtë. Mund të përdorni një debugguer hap pas hapi, si gdb dhe të shihni se çfarë bën programi, por është shumë më efektive të përdorni një mjet që është projektuar veçanërisht për të treguar ndërveprimin me mjedisin: strace.

Përfundimi strace mund të duket e tepruar, por lajmi i mirë është se pjesa më e madhe e tij mund të injorohet pa frikë. Shpesh është e dobishme të përdorni operatorin -o për të ruajtur rezultatet e gjurmimit në një skedar të veçantë:

$ strace -o /tmp/trace foo
Gabim në hapjen e skedës së konfigurimit: Nuk është gjetur asnjë skedë ose direktor
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL)                               = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (Nuk është gjetur asnjë skedë ose direktor)
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 (Nuk është gjetur asnjë skedë ose direktor)
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, "Gabim në hapjen e skedës së konfigurimit"..., 60) = 60
close(3)                                = 0
exit_group(1)                           = ?
+++ doli me 1 +++

Pothuajse të gjithë faqen e parë të daljes strace — zakonisht është një përgatitje me nivel të ulët për ekzekutim. (Shumë thirrje mmap, mprotect, brk për gjërat si zbulimi i memories së ulët dhe mapimi i bibliotekave dinamike.) Në të vërtetë, gjatë diagnostikimit, daljet strace lexohen më mirë nga fundi. Në fund do të ketë një thirrje write, që jep një mesazh gabimi. Shikojmë lart dhe shohim thirrjen e parë të sistemit që dështoi — thirrja openat, që jep gabimin ENOENT ("skeda ose direktor nuk u gjet"), duke u përpjekur të hapë /etc/foo/config.json. Ja ku duhet të jetë skeda e konfigurimit.

Ishte thjesht një shembull, por do të thoja se 90% e kohës që unë përdor strace, nuk ka shumë më të komplikuar se kaq për të kryer. Më poshtë është një udhëzues hap pas hapi për diagnostikimin:

  • Të shqetësohesh për një mesazh të paqartë gabimi të sistemit nga programi
  • Rinstalo programin me strace
  • Gjej në rezultatet e gjurmimit mesazhin gabim
  • Shkoni lart, derisa të hasni thirrjen e parë të dështuar të sistemit

Eshte tejet e mundshme që thirrja sistemike në hapin 4 do të tregojë se çfarë ka shkuar keq.

Këshilla

Para se antes de mostrar um exemplo de depuração mais complexa, vou lhe apresentar algumas dicas para um uso eficaz strace:

man — seu amigo

Em muitas sistemas *nix, a lista completa das chamadas de sistema para o núcleo pode ser obtida executando man syscalls. Você verá coisas como brk(2), o que significa que mais informações podem ser obtidas ao executar man 2 brk.

Pequenos obstáculos: man 2 fork me mostra a página para o shell fork() në GNU libc, que, afinal, foi implementada através da chamada clone(). A semântica da chamada fork permanece a mesma se você escrever um programa usando fork(), e executar o rastreamento — não encontrarei as chamadas fork, em vez disso, haverá clone(). Esses obstáculos apenas confundem se você começar a comparar o código fonte com a saída strace.

Use -o para salvar a saída em um arquivo

strace pode gerar uma saída extensa, então muitas vezes é útil armazenar os resultados do rastreamento em arquivos separados (como no exemplo acima). Além disso, isso ajuda a não confundir a saída do programa com a saída strace no console.

Use -s para visualizar mais dados do argumento

Você certamente notou que a segunda metade da mensagem de erro não foi mostrada no exemplo de rastreamento acima. Isso ocorre porque strace por padrão mostra apenas os primeiros 32 bytes do argumento da string. Se você quiser ver mais, adicione algo como -s 128 para a chamada strace.

-u facilita o rastreamento de arquivos, soquetes e similares.

"Tudo é um arquivo" significa que os sistemas *nix realizam todas as entradas e saídas usando descritores de arquivos, aplicáveis seja a um arquivo ou rede, ou canais de comunicação entre processos. Isto é conveniente para programação, mas dificulta o acompanhamento do que está realmente acontecendo quando você vê os gerais read dhe write nos resultados do rastreamento de chamadas de sistema.

Adicionando o operador -u, você fará com que strace anote cada descritor de arquivo na saída com uma nota sobre o que ele aponta.

Anexe a um processo já em execução com -p**

Como será visto no exemplo abaixo, às vezes é necessário rastrear um programa que já está em execução. Se você souber que ele está em execução como o processo 1337 (digamos, a partir de saídas ps), você pode rastreá-lo assim:

$ strace -p 1337
...saída da rastreação de chamadas de sistema...

Você pode precisar de privilégios de root.

Use -f para acompanhar processos filhos

strace me të parazgjedhur ndjek vetëm një proces. Nëse ky proces krijon procese fëmijë, mund të shihni thirrjen sistemike për krijimin e procesit fëmijë, por thirrjet sistemike të procesit fëmijë nuk do të shihen.

Nëse mendoni se gabimi është në procesin fëmijë, përdorni operatorin --follow, kjo do ta përfshijë atë në ndjekje. Mina e kësaj është se dalja do t'ju konfuzojë edhe më shumë. Kur strace ndjek një proces ose një degë, tregon një rrjedhë të vetme ngjarjesh thirrjesh. Kur ndjek disa procese njëherësh, ju ndoshta do të shihni fillimin e thirrjes, e cila ndalohet nga një mesazh <unfinished …>, pastaj një grumbull thirrjesh për degë të tjera ekzekutimi, dhe vetëm më pas përfundimin e parë me <… foocall resumed>. Ose ndani të gjitha rezultatet e ndjekjes në skedarë të ndryshëm, duke përdorur gjithashtu operatorin -ff (detajet - në udhëzimin sipër strace).

Filtrojeni ndjekjen duke përdorur -e

Siç e shihni, rezultati i ndjekjes është një masë e vërtetë të gjitha thirrjeve sistemike të mundshme. Me flamurin -e mund të filtrohet ndjekja (shih. manual sipër strace). Avantazhi kryesor është se ekzekutimi i ndjekjes me filtrimin është më i shpejtë sesa të bëni një ndjekje të plotë e pastaj grep`e. Nëse jam sinqertë, mua pothuajse gjithmonë më vehement.

Jo të gjitha gabimet janë të këqija

Një shembull i thjeshtë dhe i zakonshëm është një program që kërkon një skedar në disa vende, si një shell që kërkon, në cilin kosh-katalog ndodhet skedari ekzekutiv:

$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (Nuk ka skedar ose katalog)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (Nuk ka skedar ose katalog)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...

Heuristika e tipit "kërkesa e fundit e dështuara para mesazhit të gabimit" është e mirë për gjetjen e gabimeve relevante. Megjithatë, është logjike të filloni nga fundi.

Të kuptuarit e thirrjeve sistemike ndihmojnë mirë udhëzimet për programim në gjuhën C

Thirrjet standarde në bibliotekat C nuk janë thirrje sistemike, por vetëm një sipërfaqe e hollë. Pra, nëse kuptoni pak sesi dhe çfarë të bëni në C, do t'ju jetë më e lehtë të kuptoni rezultatet e ndjekjes së thirrjeve sistemike. Për shembull, nëse keni probleme me debugin e thirrjeve në sistemet rrjetësore, shikoni po atë klasik "Udhëzimi për programimin e rrjeteve" të Bidg.

Një shembull më i gjatë i debugin

Kupta se kam, një shembull i thjeshtë i debug është një shembull me të cilin, në shumicën e rasteve, kam për të punuar. strace. Megjithatë, ndonjëherë kërkohet një hetim i vërtetë, kështu që po ju jap një shembull real të një debug më të ndërlikuar.

bcron — menaxheri i punëve të planifikuara, një implementim tjetër i demonit *nix. cronAi është instaluar në server, por kur dikush përpiqet të editojë orarin, ndodh ky rezultat:

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

Mirë, kështu që bcron ai përpiqet të shkruajë një skedë, por nuk ia del, dhe nuk e pranon pse. Të heqim 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 +++

Pikërisht në fund ka një mesazh gabimi write, por këtë herë diçka ndryshon. Së pari, nuk ka një gabim përkatës nga thirrja e sistemit që zakonisht ndodh para kësaj. Së dyti, duket se dikush e ka lexuar tashmë mesazhin e gabimit. Kjo tregon se problemi i vërtetë është diku tjetër, ndërsa bcrontab thjesht përsërit mesazhin.

Nëse shikoni në man 2 read, do të shihni se argumenti i parë (3) është një përshkrues skedari që *nix e përdor për të gjitha operacionet e hyrjes-daljes. Si ta dini se çfarë paraqet përshkruesi i skedarit 3? Në këtë rast, mund të запускате strace me operatorin -u (shih më lart), dhe ai do t'ju tregojë automatikisht, megjithatë, për të llogaritur gjëra të tilla, është e dobishme të dini si të lexoni dhe analizoni rezultatet e gjurmimit.

Burimi i përshkruesit të skedarit mund të jetë një nga shumë thirrje sistemesh (varet nga për çfarë është përshkruesi — për konsolë, socket rrjeti, vetë skedarin ose diçka tjetër), por megjithatë, ne kërkojmë thirrjet që kthejnë 3 (dmth. kërkojmë «= 3» në rezultatet e gjurmimit). Në këtë rezultat ka 2: openat në krye dhe socket në mes. openat hap një skedë, por close(3) pas kësaj do të tregojë se ajo po mbyllet përsëri. (Hile: përshkruesit e skedarëve mund të riblenohen kur hapen dhe mbyllen). Thirrja socket() është e përshtatshme, pasi është e fundit para read(), dhe duket se bcrontab po punon me diçka përmes socket-it. Rreshti tjetër tregon se përshkruesi i skedarit është i lidhur me unix domain socket. në rrugën /var/run/bcron-spool.

Pra, duhet të gjejmë procesin e lidhur me unix socket nga ana tjetër. Për këtë qëllim ka një çift hilesh raffinante, dhe të dy do t'u duhen për debugging të implementimeve në server. E para — përdorimi i netstat ose më e reja ss (statusi i socket). Të dy komandat tregojnë lidhjet aktive të rrjetit të sistemit dhe marrin operatorin --selector për të përshkruar socket-et që po dëgjohen, si dhe operatorin -p për të shfaqur programet e lidhura me socket-in si klient. (Ka shumë më tepër opcione të dobishme, por për këtë detyrë, këto dy janë të mjaftueshme.)

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

Kjo tregon se dëgjimi është komandë inixserver, që punon me ID-në e procesit 20629. (Dhe, për rastësi, ajo përdor një deskriptues skedari 3 si socket.)

Instrumenti i dytë realisht i dobishëm për gjetjen e të njëjtave informacione quhet lsof. Ai liston të gjithë skedarët e hapur (ose deskriptuesit e skedarëve) në sistem. Ose mund të merrni informacion mbi një skedar specifik:

# 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

Procesi 20629 është një server afatgjatë, kështu që mund të lidhen me të strace me anë të diçkaje si strace -o /tmp/trace -p 20629. Nëse redaktoni detyrën cron në një terminal tjetër, do të merrni daljen e rezultateve të ndjekjes me gabimin e ndodhur. Ja rezultati:

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 (Do të rinstalohet nëse SA_RESTART është e vendosur)
--- 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 (Nuk ka procese fëmijësh)
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 (Do të rinstalohet nëse SA_RESTART është e vendosur)
--- 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 (Nuk ka procese fëmijësh)
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

(Accepti i fundit accept() nuk do të përfundojë gjatë ndjekjes.) Dhe përsëri, sa keq, por ky rezultat nuk përmban gabimin që po kërkojmë. Ne nuk shohim asnjë mesazh që bcrontag i dërgonte socket-it ose merrte nga ai. Në vend të tyre, ka vetëm menaxhim procesi (clone, wait4, SIGCHLD Dhe shihni, ky proces krijon një proces bir, i cili, siç mund të hamendësojmë, kryen punën e vërtetë. Dhe nëse duhet të kapim gjurmën e tij, shtoni në thirrje strace -f. Ky është informacioni që do të gjejmë duke kërkuar për mesazhin e gabimit në rezultatin e ri me 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 (Qasje e refuzuar) 
21470 write(1, "32:ZNuk mund të krijoj dosje të përkohshme f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (Nuk ka një dosje ose katalog të tillë)
21470 exit_group(111) = ?
21470 +++ doli me 111 +++

Ja, kjo është diçka. Procesi 21470 merr gabimin "qasje e refuzuar" kur përpiqet të krijojë një skedë në rrugën tmp/spool.21470.1573692319.854640 (e lidhur me katalogun aktual të punës). Nëse do të dinim thjesht katalogun aktual të punës, do të dinim gjithashtu rrugën e plotë dhe do të mund të kuptonim pse procesi nuk mund të krijojë dosjen e tij të përkohshme. Fatkeqësisht, procesi tashmë ka dalë, kështu që nuk mund të përdorim thjesht lsof -p 21470 për të gjetur katalogun aktual, por mund të punojmë në drejtim të kundërt — të kërkojmë thirrjet sistemike të PID 21470 që ndryshojnë katalogun. (Nëse nuk ka të tilla, PID 21470 duhet ta kenë trashëguar atë nga prindi, dhe kjo tashmë është përmes lsof -p nuk jep një përfundim.) Kjo thirrje sistemike është chdir (çka nuk është e vështirë të zbulohet me ndihmën e motorëve të kërkimit modernë). Ja dhe rezultati i kërkimeve të kundërta nga rezultatet e përzgjedhjes, deri në serverin 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 (Qasje e refuzuar) 
21470 write(1, "32:ZNuk mund të krijoj dosje të përkohshme f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (Nuk ka një dosje ose katalog të tillë)
21470 exit_group(111) = ?
21470 +++ doli me 111 +++

(Nëse humbisni, ndoshta duhet të lexoni postimin tim të mëparshëm në menaxhimin e proceseve *nix dhe shell-eve.) Pra, serveri PID 20629 nuk mori lejen për të krijuar skedën në rrugën /var/spool/cron/tmp/spool.21470.1573692319.854640. Shumë e mundshme, arsyeja është konfigurimet klasike të lejeve të skedarëve. Le të kontrollojmë:

# 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

Ja ku është problemi! Serveri punon si cron i përdoruesit, por vetëm root ka lejen për të shkruar në katalogun /var/spool/cron/tmp/. Një komandë e thjeshtë chown cron /var/spool/cron/tmp/ do ta detyrojë bcron puneshe mirë. (Nëse problemi nuk ishte këtu, atëherë të dyshuarin tjetër më të mundshëm është moduli i sigurisë së bërthamës si SELinux ose AppArmor, prandaj do të kontrolloja regjistrin e mesazheve të bërthamës me ndihmën e dmesg.)

Përveç kësaj

Një fillestar në rezultatet e gjurmimit të thirrjeve të sistemit mund të humbasë, por shpresoj se kam treguar se ato janë një mënyrë e shpejtë për të debug një grup të tërë problemesh të zakonshme të implementimit. Imagjinoni si po përpiqeni të debugoni një aplikacion me shumë procese bcron, duke përdorur një debuger hap pas hapi.

Analiza e rezultateve të gjurmimit prapa nëpër zinxhirin e thirrjeve të sistemit kërkon aftësi, por siç thashë, pothuajse gjithmonë, duke përdorur strace, thjesht marr rezultatin e gjurmimit dhe kërkoj gabime duke filluar nga fundi. Në çdo rast, strace më ndihmon të kursej shumë kohë në debugim. Shpresoj se do t'ju ndihmojë edhe juve.

Burimi: habr.com

Blini hosting të besueshëm për faqe interneti me mbrojtje nga DDoS, serverë VPS VDS 🔥 Blini hosting të besueshëm për faqe interneti me mbrojtje nga DDoS, serverë VPS VDS | ProHoster