Rregullojmë shpërndarjen e software-it me strace

Rregullojmë shpërndarjen e software-it me strace

Puna ime kryesore është, për shumicën e rasteve, shpërndarja e sistemeve të software-it, pra kaloj shumë kohë duke u përpjekur të përgjigjem në pyetje si këto:

  • Tek zhvilluesi ky software funksionon, por te unë jo. Pse?
  • Dje ky software funksiononte te unë, por sot jo. Pse?

Kjo është një lloj debug-u që pak dallon nga debug-u i zakonshëm të software-it. Debug-u i zakonshëm ka të bëjë me logjikën e kodit, ndërsa debug-u i shpërndarjes ka të bëjë me ndërveprimin e kodit dhe ambientit. Edhe nëse problemi i origjinës është një gabim logjik, fakti që në një makinë gjithçka funksionon, ndërsa në një tjetër jo, do të thotë që ka diçka që lidhet me ambientin.

Prandaj, në vend të mjeteve të zakonshme për debug të tillë si gdb kam një grup tjetër mjetesh për debug të shpërndarjes. Dhe mjeti im i preferuar për të luftuar problemin si "Pse ky software nuk funksionon për mua?" quhet strace.

Çfarë është strace?

strace - është një mjet për "ndjekjen e thirrjeve të sistemit". Fillimisht ishte krijuar për Linux, por të njëjtat truke për debug mund të përdoren edhe me mjete për sisteme të tjera (DTrace ose ktrace).

Përdorimi kryesor është shumë i thjeshtë. Thjesht duhet të nisni strace me çdo komandë dhe ai do të dërgojë në dump të gjitha thirrjet sistemore (në fakt, ndoshta, duhet fillimisht të instaloni atë 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 sistemore? Ato janë diçka si një API për bërthamën e sistemit operativ. Njëherë, programet kishin qasje të drejtpërdrejtë në "harduerin" mbi të cilin punonin. Nëse, për shembull, do të duhej të shfaqnin diçka në ekran, ato luanin me portet ose regjistrat e shfaqur në kujtesë për pajisjet video. Kur sistemet kompjuterike me shumë detyra u bënë të njohura, kaosi mbizotëroi, sepse aplikacione të ndryshme garonin për "harduerin". Gabimet në një aplikacion mund të dëmtonin punën e të tjerëve, madje edhe të gjithë sistemin. Atëherë, në CPU u shfaqën modet e privilegjeve (ose "mbrojtja në rreth"). Bërthama bëhej e privilegjuar më së shumti: ajo merrte qasje të plotë në "harduer", duke krijuar aplikacione më pak të privilegjuara, të cilat duhej të kërkonin qasje nga bërthama për të ndërvepruar me "harduerin" — përmes thirrjeve sistemore.

Në nivelin binar, thirrja sistemore paksa ndryshon nga një thirrje e zakonshme funksioni, megjithatë shumica e programeve përdorin një mbështjellës në bibliotekën standarde. Kështu, biblioteka standarde POSIX C përmban thirrjen e funksionit write(), e cila përmban tërë kodin e varur nga arkitektura për thirrjet sistemike shkruaj.

Rregullojmë shpërndarjen e software-it me strace

Në përmbledhje, çdo ndërveprim i aplikacionit me mjedisin e tij (sistemet kompjuterike) zhvillohet përmes thirrjeve sistemike. Prandaj, kur programi punon në një makinë, por jo në një tjetër, do të ishte e mençur të shikoni rezultatet e gjurmimit të thirrjeve sistemike. Për më specifik, ja lista e çështjeve tipike që mund të analizoni përmes gjurmimit të thirrjeve sistemike:

  • Hyrje-dalje konzolle
  • Hyrje-dalje rrjeti
  • Qasje në sistemin e skedarëve dhe hyrje-dalje skedarësh
  • Menaxhimi i jetës së procesit/rrjedhës
  • Menaxhimi i kujtesës në nivel të ulët
  • Qasje në drejtuesit e pajisjeve speciale

Kur të përdorni 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 sistemike. Ai punon më efektivisht me programet e kompozuara dhe me nivel të ulët, por gjithashtu funksionon me gjuhë të nivelit të lartë si Python, nëse mund të kaloni zhurmën shtesë nga mjedisi i ekzekutimit dhe interpreti.

Në gjithë shkëlqimin strace shfaqet gjatë procesit të debug-imit të softuerit, që funksionon mirë në një makinë, por në një tjetër papritmas ndalon së funksionuari, duke dhënë mesazhe të paqarta rreth skedarëve, lejeve ose përpjekjeve të dështuar për të ekzekutuar ndonjë komandë apo diçka tjetër... Të vjen keq, por nuk i përshtatet aq mirë problemeve të nivelit të lartë siç janë gabimet e verifikimit të certifikatave. Zakonisht kërkohet një kombinim strace, ndonjëherë ltrace dhe mjeteve të nivelit më të lartë (siç është mjeti i komandës openssl për debug-imin e certifikatave).

Për shembull, marrim punën në një server të izoluar, por ndjekja e thirrjeve sistemike shpesh mund të kryhet dhe në platforma më komplekse të implementimit. Thjesht duhet të gjejmë mjetin e duhur.

Shembull i thjeshtë debug-imi

Marrim rastin se doni të ekzekutoni një aplikacion serveri fantastik foo, por rezultati është ky:

$ foo
Gabim në hapjen e skedarit të konfigurimit: Skedar ose drejtor nuk ekziston

Duket se ai nuk arriti të gjejë skedarin e konfigurimit të shkruar nga ju. Kjo ndodh ndonjëherë sepse menaxherët e paketave, duke kompiliuar aplikacionin, tejkalojnë vendndodhjen e pritur të skedareve. Dhe nëse ndjekni udhëzimet e instalimit për një shpërndarje, në tjetrën gjeni skedarët krejt ndryshe nga atë që prisnit. Problemi mund të zgjidhet brenda disa sekondash, nëse mesazhi i gabimit do të tregonte se ku duhet të kërkoheni për skedarin e konfigurimit, por ai nuk e bën. Pra, ku duhet të kërkoni?

Nëse keni qasje në kodin burimor, mund ta lexoni atë dhe ta zbardhni gjithçka. Është një plan i mirë rezervë, por nuk është zgjidhja më e shpejtë. Mund të përdorni një debuggues hap pas hapi si gdb dhe të shihni se çfarë bën programa, por është më efektive të përdorni një mjet që është projektuar posaçërisht për të treguar ndërveprimin me mjedisin: strace.

Përfundim strace mund të duket i tepërt, por lajmi i mirë është se shumicën e tij mund ta injoroni me besim. 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
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 +++

Shembuj i të gjitha faqeve të para të daljes strace — zakonisht është një përgatitje e ulët për nisjen. (Shumë thirrje mmap, mprotect, brk për gjëra si identifikimi i memories së ulët dhe paraqitja e biblioteka dinamike.) Në përgjithësi, gjatë de-bugimit, rezultatet strace lexohen më mirë nga fundi. Në fund do të ketë një thirrje shkruaj, që jep një mesazh gabimi. Shikoni lart dhe do të shihni thirrjen e parë sistemore me gabim — thirrja openat, që jep gabimin ENOENT ("file ose katalog nuk u gjet") që përpiqet të hapë /etc/foo/config.json. Ja, këtu duhet të jetë skedari i konfigurimit.

Ishte thjesht një shembull, por do të thosha se 90% të kohës që po përdor strace, nuk ka ndonjë gjë më të komplikuar për të kryer. Më poshtë është një udhëzues i plotë hap pas hapi për de-bugimin:

  • Të shqetësohesh për një mesazh të paqartë gabimi nga programi
  • Rinis programin me strace
  • Gjej në rezultatet e gjurmimit mesazhin e gabimit
  • Shkonte lart derisa të hasni thirrjen e parë sistemore që dështoi

Mundësi e madhe që thirrja sistemore në hapin 4 do të tregojë se çfarë shkoi keq.

Këshilla

Përpara se të tregoj një shembull më të komplikuar të de-bugimit, do t'ju tregoj disa truke për përdorimin efikas. strace:

man — mikku juaj

Në shumë sisteme *nix, lista e plotë e thirrjeve sistemike për bërthamën mund të merret duke hapur man syscalls. Do të shihni gjëra si brk(2), dhe më shumë informacion mund të merret duke hapur man 2 brk.

Disa gjëra të vogla: man 2 fork më tregon faqen për fork()GNU libc, e cila, siç del, implementohet përmes thirrjes clone(). Semantika e thirrjes fork mbetet e njëjtë, nëse shkruaj një program që përdor fork(), dhe nëse bëj gjurmimin — nuk do të gjej thirrje fork, në vend të tyre do të jenë clone(). Këto gjëra të vogla vetëm e ngatërrojnë, kur fillon të krahasosh kodin burimor me rezultatin strace.

Përdorni -o për të ruajtur rezultatin në një skedar

strace mund të gjenerojë një rezultat të gjerë, kështu që shpesh është e dobishme të ruash rezultatet e gjurmimit në skedarë të veçantë (si në shembullin e lartpërmendur). Po ashtu, ndihmon që të mos ngatërrosh rezultatin e programit me rezultatin strace në konsolë.

Përdorni -s për të parë më shumë të dhëna për argumentin

Me siguri keni vënë re se gjysma e dytë e mesazhit të gabimit nuk është treguar në shembullin e mësipërm të gjurmimit. Kjo është sepse strace mënyra e parazgjedhur tregon vetëm 32 bajtët e parë të argumentit të vargut. Nëse dëshiron të shohësh më shumë, shto diçka si -s 128 në thirrje strace.

-u lehtëson gjurmimin e skedarëve dhe socket-eve të tjerë.

«Të gjitha janë skedar» do të thotë se sistemet *nix kryejnë të gjitha hyrjet-daljet duke përdorur skedarin e descriptorëve, e cila është e aplikueshme me skedarë, ose rrjete, ose kanale ndërproçesh. Kjo është e përshtatshme për programim, por pengon në ndjekjen e asaj që po ndodh në të vërtetë, kur sheh përmbledhjet read dhe shkruaj në rezultatet e gjurmimit të thirrjes së sistemit.

Duke shtuar operatorin -u, do ta bësh strace të annotosh çdo descriptor skedari në daljen me një vërejtje se çfarë tregon.

Për t'u lidhur me një proces të nisur tashmë me -p**

Siç do të shihet nga shembulli i mëposhtëm, ndonjëherë është e nevojshme të gjurmohet një program që është tashmë i nisur. Nëse dihet se është nisur si procesi 1337 (të themi, nga daljet ps), atëherë mund të kryhet gjurmimi i tij kështu:

$ strace -p 1337
...dalja e gjurmimit të thirrjes së sistemit...

Ndoshta, do të kërkosh privilegjet e root.

Përdor -f për të ndjekur proceset fëmijë.

strace për default ndjek vetëm një proces. Nëse ky proces krijon procese të reja, atëherë mund të shihni thirrjen sistemike për krijimin e procesit të ri, por thirrjet sistemike të procesit të ri nuk do të shfaqen.

Nëse mendoni se gabimi është në procesin e ri, përdorni operatorin -f, kjo do ta përfshijë ndjekjen e tij. Minus i këtij është se rezultati do t'ju ngatërronte edhe më shumë. Kur strace ndjek një proces ose një degë, tregon një rrjedhë të vetme ngjarjesh thirrjesh. Kur ndjek disa procese në të njëjtën kohë, mund të shihni fillimin e thirrjes, të ndërprerë nga mesazhi <unfinished …>, pastaj — një grup thirrjesh për degë të tjera ekzekutimi, dhe vetëm më pas — përfundimi i parë me <… foocall resumed>. Ose ndani të gjithë rezultatet e ndjekjes në skedarë të ndryshëm, duke përdorur gjithashtu operatorin -ff (detajet janë në udhëzimin për strace).

Filttoni ndjekjen duke përdorur -e

Siç e shihni, rezultati i ndjekjes është një grumbull real i të gjitha thirrjeve sistemike të mundshme. Me flagun -e mund të filtroni ndjekjen (shih. një udhëzues për strace). Avantazhi kryesor është se të drejtat për të ndjekur me filtrimin janë më të shpejta se të bësh një ndjekje të plotë dhe pastaj grep`atë. Nëse e pranoj, mua pothuajse gjithmonë më intereson pak.

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

Një shembull i thjeshtë dhe i zakonshëm është një program që kërkon një skedar menjëherë në disa vende, si një shell që kërkon se cilat dosje përmbajnë skedarin ekzekutues:

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

Heuristika si «kërkesa e fundit e dështuar para mesazhit të gabimit» është e mirë për të gjetur gabime përkatëse. Sidoqoftë, është logjike të fillohet nga fundi.

Të kuptuarit e thirrjeve sistemore ndihmojnë shumë udhëzimet për programimin në gjuhën C

Thirrjet standarde në bibliotekat C — nuk janë thirrje sistemore, por thjesht një shtresë e hollë. Pra, nëse ju e kuptoni disi se si dhe çfarë të bëni në C, do t'ju jetë më e lehtë të kuptoni rezultatet e gjurmimit të thirrjeve sistemore. Për shembull, nëse keni probleme me debuggimin e thirrjeve në sistemet rrjetërore, shikoni të njëjtin klasik «Udhëzimi për programimin rrjetor» i Bidgës.

Një shembull më i komplikuar i debuggimit

Kam thashë, shembulli i thjesht i shkëputjes është një shembull i asaj me të cilën, në shumicën e rasteve, merrem në punën me strace. Megjithatë, ndonjëherë nevojitet një hetim i vërtetë, prandaj ja një shembull real i një shkëputjeje më të komplikuar.

bcron — planifikuesi i detyrave, një implementim tjetër i demonit *nix cron. Ai është instaluar në server, por kur dikush përpiqet të editojë orarin, ndodh kjo:

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

Mirë, pra, bcron përpiqej të shkruante një skedar, por nuk iu doli, dhe ai nuk e pranon pse. Le të hulumtojmë 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 +++

Afro në fund ka një mesazh gabimi shkruaj, por këtë herë diçka është ndryshe. Së pari, nuk ka gabim të lidhur me thirrjen e sistemit, që zakonisht ndodh para kësaj. Së dyti, duket se diku dikush e ka lexuar tashmë mesazhin e gabimit. Duket se problemet e vërteta janë diku tjetër, por bcrontab thjesht e riprodhon mesazhin.

Nëse shohim man 2 read, mund të shohim se argumenti i parë (3) është një deshifrues skedari, i cili *nix e përdor për të gjitha operacionet e hyrjes dhe daljes. Si të mësojmë se çfarë përfaqëson deshifruesi i skedarit 3? Në këtë rast specifik, mund të ekzekutojmë 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 se si të lexoni dhe analizoni rezultatet e gjurmimit.

Burimi i përshkruesve të skedarëve mund të jetë një nga shumë thirrje sistemike (varet nga se për çfarë është përshkruesi - për konsolën, socket-in rrjetit, skedarin vetë ose diçka tjetër), por siç qoftë, ne po kërkojmë thirrjet, duke kthyer 3 (dmth, po kërkojmë "= 3" në rezultatet e gjurmimit). Në këtë rezultat ka 2: openat në krye dhe socket në mes. openat hap skedarin, por close(3) pas kësaj do të tregojë se ai përsëri mbyllet. (Kujdes: përshkruesit e skedarëve mund të rishfrytëzohen kur hapen dhe mbyllen). Thirrja socket() është e përshtatshme, pasi është e fundit para read(), dhe rezulton se bcrontab punon me diçka përmes një socket-i. Rreshti i ardhshëm tregon se përshkruesi i skedarëve është i lidhur me unix domain socket në rrugën /var/run/bcron-spool.

Pra, duhet të gjeni procesin e lidhur me unix socket nga ana tjetër. Për këtë qëllim ka disa truke elegante, dhe të dyja do të jenë të dobishme për debug-imin e implementimeve të serverëve. E para - përdorni netstat ose një më të ri ss (socket status). Të dy komandat tregojnë lidhjet aktive të rrjetit të sistemit dhe marrin operator -l për përshkrimin e soketëve që dëgjojnë, si dhe operatorin -p për të paraqitur programet që lidhen me soketin si klient. (Ka shumë më tepër mundësi 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ëgjojësi është komandë inixserver, duke punuar me ID-në e procesit 20629. (Dhe, për rastësi, ajo përdor një deshifrues skedari 3 si soket.)

Një mjet realisht i dobishëm për të gjetur të njëjtën informacion quhet lsof. Ai liston të gjitha skedarët e hapur (ose deshifruesit e skedarëve) në sistem. Ose mund të merrni informacion për një skedar të veçantë:

# 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 i gjatë jetësor, kështu që mund të lidhni me të strace duke përdorur diçka si strace -o /tmp/trace -p 20629. Nëse e redaktoni detyrën cron në një terminal tjetër - do të merrni daljen e rezultateve të ndjekjes me gabim që shfaqet. Dhe ky është 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 (Të rinisni nëse SA_RESTART është 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 të fëmijëve)
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 (Të rinisni nëse SA_RESTART është 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 të fëmijëve)
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

(E fundit accept() nuk do të përfundojë gjatë gjurmimit.) Në këtë rast, ashtu siç është keq, rezultati nuk përmban gabimin që po kërkojmë. Nuk shohim asnjë mesazh që bcrontag dërgon ndihmësit ose merr prej tij. Në vend të tyre, është vetëm menaxhimi i procesit (clone, wait4, SIGCHLD etj.) Ky proces krijon një proces nën, i cili, siç mund ta imagjinoni, kryen punën e vërtetë. Dhe, nëse duhet të kapni gjurmën e tij, shtoni në thirrjen strace -f. Këtu është ajo që do të gjejmë duke kërkuar 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 (Qasja e ndaluar) 
21470 write(1, "32:ZNuk ishte e mundur të krijohej skedari i përkohshëm 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ë skedar ose direktor) 
21470 exit_group(111)                   = ?
21470 +++ doli me 111 +++

Ja, kjo është diçka. Procesi 21470 merr gabimin "qasje e ndaluar" kur përpiqet të krijojë një skedar në rrugën tmp/spool.21470.1573692319.854640 (i lidhur me katalognin aktual të punës). Po të dinim thjesht katalognin aktual të punës, do të dinim gjithashtu edhe rrugën e plotë dhe do të mund të zbulonim se përse procesi nuk mund të krijojë skedarin e tij të përkohshëm atje. Fatkeqësisht, procesi ka përfunduar tashmë, kështu që nuk është e mundur që thjesht të përdorim lsof -p 21470 për të gjetur katalogun aktuell, por mund të punojmë në anën tjetër – të kërkojmë thirrjet sistemike të PID 21470 që ndryshojnë katalogun. (Nëse nuk ka të tillë, PID 21470, ka trashëguar ndoshta ato nga prindi, dhe kjo është tashmë përmes lsof -p nuk do të zbulohet.) Kjo thirrje sistemike – chdir (çka nuk është e vështirë të zbulohet me ndihmën e motorëve të sotëm të kërkimit në internet). Ja dhe rezultati i kërkimeve të kundërt sipas rezultateve të gjurmimit, deri te vetë serveri 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 (Leje e refuzuar)
21470 write(1, "32:ZNuk mund të krijohet skedar i përkohshëm 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ë skedari ose katalog të tillë)
21470 exit_group(111)                   = ?
21470 +++ doli me 111 +++

(Nëse jeni të humbur, ndoshta duhet të lexoni postimin tim të mëparshëm rreth menaxhimit të procesit *nix dhe shell-eve.) Pra, serveri PID 20629 nuk mori lejen për të krijuar një skedar në rrugën /var/spool/cron/tmp/spool.21470.1573692319.854640. Probabilisht, arsyeja është konfigurimi klasik i lejeve të sistemit të skedarëve. Le të verifikojmë:

# 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 qëndron problemi! Serveri funksionon si cron i përdoruesit, por vetëm root ka leje për të shkruar në katalogun /var/spool/cron/tmp/. Një komandë e thjeshtë chown cron /var/spool/cron/tmp/ do të bëjë bcron që të funksionojë siç duhet. (Nëse problemi nuk ishte këtu, atëherë dyshimi tjetër më i mundshëm është moduli i sigurisë së bërthamës si SELinux ose AppArmor, kështu që do të kontrolloja regjistrin e mesazheve të bërthamës me ndihmën e dmesg.)

Në përfundim

Për fillestarët, rezultatet e gjurmimeve të thirrjeve të sistemit mund të jenë të përmbysura, por shpresoj se kam treguar se ato janë një mënyrë e shpejtë për të riparuar një klasë të zakonshme problemesh me implementimin. Imagjinoni si të përpiqeni të shpërndani një aplikacion mnoçproçesor bcron, duke përdorur një debugguer hap pas hapi.

Analizimi i rezultateve të gjurmimeve mbrapsht nëpër zinxhirin e thirrjeve të sistemit kërkon aftësi, por siç e thashë, pothuajse gjithmonë, duke përdorur strace, thjesht po marr rezultatin e gjurmimit dhe gjej gabimet, duke filluar nga fundi. Në çdo rast, strace më ndihmon të kursej shumë kohë në debuggim. Shpresoj që të jetë i dobishëm edhe për ju.

Burimi: habr.com

Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS 🔥 Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS | ProHoster