Beállítjuk a szoftver telepítését strace-szel

Beállítjuk a szoftver telepítését strace-szel

Fő munkám nagyrészt szoftverrendszerek telepítése, így rengeteg időt töltök az olyan kérdések megválaszolásával, mint például:

  • A fejlesztőnél ez a szoftver működik, nálam viszont nem. Miért?
  • Tegnap még működött nálam ez a szoftver, ma pedig már nem. Miért?

Ez egyfajta hibakeresés, amely kicsit eltér a hagyományos szoftverhibakereséstől. A hagyományos hibakeresés a kód logikájáról szól, míg a telepítési hibakeresés a kód és a környezet interakciójával foglalkozik. Még ha a probléma gyökere logikai hiba is, az a tény, hogy az egyik gépen minden működik, a másikon viszont nem, azt jelenti, hogy a probléma valamilyen módon a környezettel áll összefüggésben.

Ezért a hagyományos hibakeresési eszközök helyett gdb van egy másik eszközöm a telepítési hibák elhárítására. A kedvenc eszközöm a „Miért nem működik ez a szoftver nálam?” típusú problémák kezelésére strace.

Mi is az a strace?

strace - ez egy eszköz a "rendszergondozási hívások nyomozására". Eredetileg Linux-hoz készült, de ugyanazokat a hibakeresési funkciókat más rendszerek megfelelő eszközeivel is végrehajthatjuk (DTrace vagy ktrace).

Fő alkalmazása nagyon egyszerű. Elég, ha a strace-t bármely parancs mellett futtatjuk, és az elküldi az összes rendszerhívást dumpként (persze először valószínűleg magát a strace):

$ strace echo Hello
...Kivágás sok dologból...
write(1, "Hellon", 6)                  = 6
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ 0-val kilépett +++

Mik ezek a rendszerhívások? Ez tulajdonképpen egy API az operációs rendszer magjához. Régen a szoftverek közvetlen hozzáféréssel rendelkeztek azokhoz a „vasakhoz”, amelyeken működtek. Például, ha valamit ki kellett vetíteni a képernyőre, akkor a portokkal és a videós eszközök memóriába tűnt regisztereivel játszottak. Amikor a többlépcsős számítógépes rendszerek népszerűvé váltak, káosz keletkezett, mert különböző alkalmazások „ringattak” a „vasért”. Az egyik alkalmazásban lévő hibák mások működését szétverhették, ha nem az egész rendszert. Ekkor a CPU-ban megjelentek a privilégiumszintek (vagy „gyűrűvédelmi” rendszerek). A legmagasabb privilégiumot a kernel kapta: ez teljes hozzáférést nyert a „vasakhoz”, létrehozva olyan, kevésbé privilegizált alkalmazásokat, amelyek már kérniük kellett a magtól a „vashoz” való hozzáférést - rendszergondozási hívásokon keresztül.

Binaris szinten a rendszerhívás kissé eltér a sima függvényhívástól, azonban a legtöbb program a standard könyvtár wrapperét használja. Azaz az POSIX C szabványos könyvtár tartalmazza a rendszerhívás architektúrafüggő kódját. write(), которая содержит весь зависящий от архитектуры код для системного вызова write.

Beállítjuk a szoftver telepítését strace-szel

Röviden, bármilyen interakció az alkalmazás és a környezete (számítógépes rendszerek) között rendszerhívásokon keresztül történik. Ezért amikor a szoftver egy gépen működik, a másikon pedig nem, érdemes megnézni a rendszerhívások nyomkövetési eredményeit. Ha pedig konkrétabban akarjuk, itt van egy tipikus pillanatok listája, amelyet a rendszerhívások nyomkövetésével elemezhetünk:

  • Konzol bemenet-kimenet
  • Hálózati bemenet-kimenet
  • Fájl rendszerhez való hozzáférés és fájl bemenet-kimenet
  • Szálak élettartamának kezelése
  • Alacsony szintű memória kezelés
  • Hozzáférés speciális eszközmeghajtókhoz

Mikor használjuk a strace-t?

Elméletileg strace minden felhasználói térben levő programmal használható, hiszen bármelyik programnak a felhasználói térben rendszerhívásokat kell végeznie. Hatékonyabban működik a lefordítható, alacsony szintű programokkal, de a magas szintű nyelvekkel, mint a Python is működik, ha sikerül áthatolni a futtatási környezet és az interpreter által generált zajon.

Teljes fényében strace a szoftver hibakeresésekor mutatkozik meg, amikor valami jól működik egy gépen, de a másikon hirtelen leáll, érthetetlen fájlokkal, jogosultságokkal vagy sikertelen parancs-kivitelezési próbálkozásokkal találkozva... Kár, de nem annyira jól működik a magas szintű problémák esetén, például a tanúsítvány ellenőrzési hibák. Ilyenkor általában kombinációra van szükség strace, néha ltrace és magasabb szintű eszközökre (mint például a tanúsítvány hibakereső eszközre). openssl Példaként vegyük az elszigetelt szerveren való munkát, de a rendszerhívások nyomkövetését gyakran lehet végezni bonyolultabb telepítési platformokon is. Csak a megfelelő eszközkészletet kell kiválasztani.

Egyszerű hibakeresési példa

Tegyük fel, hogy egy lenyűgöző szerveralkalmazást, a foo-t szeretné futtatni, de az alábbi hibaüzenetet kapja:

$ foo Hiba a konfigurációs fájl megnyitásakor: Nincs ilyen fájl vagy könyvtár

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

Nyilvánvaló, hogy nem sikerült megtalálnia az ön által írt konfigurációs fájlt. Ez azért történik meg, mert néha, amikor a csomagkezelők egy alkalmazást fordítanak, felülírják a fájlok várt elhelyezkedését. Ha az egyik disztribúció telepítési útmutatóját követjük, a másikban teljesen máshol találhatók a fájlok, amit vártunk. Ezt a problémát másodpercek alatt meg lehetne oldani, ha a hibaüzenet megmondaná, hol kell keresni a konfigurációs fájlt, de sajnos nem mondja el. Szóval hol keressünk?

Ha van hozzáférés a forráskódhoz, el lehet olvasni és minden kiderül. Ez egy jó alternatív megoldás, de nem a leggyorsabb. Használhatunk lépésről lépésre történő hibakeresőt, mint például gdb és megnézhetjük, mit csinál a program, de sokkal hatékonyabb egy olyan eszköz használata, amelyet kifejezetten az interakciók környezetének megjelenítésére terveztek: strace.

Következtetés strace túlzottnak tűnhet, de a jó hír az, hogy a nagy részét nyugodtan figyelmen kívül lehet hagyni. Gyakran hasznos az -o opció használata, hogy a nyomozási eredményeket külön fájlba mentse:

$ strace -o /tmp/trace foo
Hiba a konfigurációs fájl megnyitásakor: Nincs ilyen fájl vagy könyvtár
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 változó */) = 0
brk(NULL)                               = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (Nincs ilyen fájl vagy könyvtár)
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 (Nincs ilyen fájl vagy könyvtár)
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, "Hiba a konfigurációs fájl megnyitásakor"..., 60) = 60
close(3)                                = 0
exit_group(1)                           = ?
+++ exited with 1 +++

Körülbelül az összes első lap kimeneti része strace — ez általában az alacsony szintű előkészületekhez való indításhoz. (Sok hívás mmap, mprotect, brk az olyan dolgok számára, mint az alacsony szintű memória és a dinamikus könyvtárak megjelenítése.) Valójában, a hibakeresés során a kimeneteket strace jobban olvassák a legvégéről. Lent lesz egy hívás write, ami hibaüzenetet ad. Nézzünk feljebb, és látjuk az első hibás rendszerhívást - a hívást openat, ami hibát ad ENOENT ("fájl vagy könyvtár nem található"), próbálkozva megnyitni /etc/foo/config.json. Itt kellene lennie a konfigurációs fájlnak.

Ez csak egy példa volt, de azt mondanám, hogy az időm 90%-ában, amikor használom strace, nem kell ennél sokkal bonyolultabbat csinálnom. Lent - egy teljes lépésről lépésre útmutató a hibakereséshez:

  • Frusztrálódni egy homályos rendszerhiba üzenet miatt a programtól
  • Újraindítani a programot a strace
  • Megkeresni a hibaüzenetet a nyomkövetési eredményekben
  • Feljebb menni, míg rá nem ütközik az első sikertelen rendszerhívásra

Valószínű, hogy a 4. lépésben a rendszerhívás megmutatja, hogy mi ment rosszul.

Útmutatók

Mielőtt bemutatnék egy bonyolultabb hibakeresési példát, osztok meg veletek néhány trükköt a hatékony használathoz. strace:

man — a barátod

Sok *nix rendszeren a teljes rendszerhívások listáját elérhetjük a kernelhez a következő parancs végrehajtásával: man syscalls. Itt olyan dolgokat láthatsz, mint a brk(2), így több információt szerezhetsz a parancs vizsgálatával: man 2 brk.

Kis csapdák: man 2 fork megmutatja a shell számára a lapot fork() be. GNU libc, ami, mint kiderült, a következő hívás megvalósításával készült: clone(). A hívás fork szemantikai értelme változatlan marad, ha írsz egy programot, amely ezt használja, és elindítod a nyomozást — nem fogom megtalálni a hívásokat, fork(), helyette csak fork. Ilyen csapdák zavaróak lehetnek, ha elkezded összehasonlítani a forrást a kimenettel. clone()Az -o opció használatával mentheted a kimenetet egy fájlba. strace.

Ez sok kimenetet generálhat, így gyakran hasznos, ha a nyomozási eredményeket külön fájlokban tárolod (ahogy azt a fenti példa is mutatja). Ezenkívül segít elkerülni a programkimenet és a

strace konzolos kimenet összekeveredését. strace Használj -s-t, hogy többet láthass az argumentum adatokból.

Valószínűleg észrevetted, hogy az hibaüzenet második fele nem volt látható a fenti nyomozási példában. Ennek az az oka, hogy

alapértelmezés szerint csak az első 32 bájtjait mutatja az argumentum stringnek. Ha többet szeretnél látni, adj hozzá olyasmit, mint strace -s 128 a híváshoz. -u megkönnyíti a fájlok, socketek és más dolgok nyomozását. strace.

"Minden fájl" azt jelenti, hogy a *nix rendszerek minden bemeneti és kimeneti műveletet fájlleírókon keresztül hajtanak végre, függetlenül attól, hogy fájlra, hálózatra vagy köztes folyamatcsatornákra vonatkozik. Ez kényelmes a programozás szempontjából, de megnehezíti a valós események követését, amikor közönséges dolgokat látsz a rendszerhívás kimenetében.

Ha hozzáadod a -u opciót, azzal megkérdezed, hogy a kimenetben minden fájlleíró annotálva legyen azzal a megjegyzéssel, hogy mire mutat. read és write Csatlakozz már futó folyamathoz -p-vel.

Ahogy a következő példából látható, néha nyomozni kell egy programot, amely már fut. Ha tudod, hogy a 1337-es folyamatként fut (mondjuk, a kimenetekből), akkor így lehet nyomozni: $ strace -p 1337 ...system call trace output...Lehetséges, hogy root jogosultságokra van szükséged. strace Használj -f-t, hogy kövesd a gyermekfolyamatokat.

Прикрепите к уже запущенному процессу с -p**

Как будет видно из приведенного ниже примера, иногда нужно производить трассировку программы, которая уже запущена. Если известно, что она запущена как процесс 1337 (скажем, из выводов A folyamatok listájának megjelenítése.), то можно осуществить трассировку ее вот так:

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

Возможно, вам нужны root-права.

Используйте -f, чтобы следить за дочерними процессами

strace Alapértelmezés szerint csak egy folyamatot követ. Ha ez a folyamat gyermek folyamatokat hoz létre, akkor láthatja a gyermek folyamat létrehozásához szükséges rendszerhívást, de a gyermek folyamat rendszerhívásai nem fognak megjelenni.

Ha úgy gondolja, hogy a hiba a gyermek folyamatban van, használja a parancsot -f, ez bekapcsolja annak követését. Ennek hátránya, hogy a kimenet még zavaróbbá válik. Amikor strace egy folyamatot vagy egy ágat követ, egy eseménysorozatot mutat a hívásokról. Amikor egyszerre több folyamatot követ, előfordulhat, hogy látja a hívás kezdetét, majd egy üzenetet, ami megszakítja, <unfinished …>, aztán pedig egy halom hívást más végrehajtási ágakból, és csak ezután a megszakított hívás végét <… foocall resumed>. Vagy szétválaszthatja az összes követési eredményt külön fájlokba, szintén a parancs segítségével, -ff (részletek a kézikönyvben hogy strace).

Szűrje a követést az -e

Láthatja, hogy a követés eredménye valójában egy halom minden lehetséges rendszerhívás. Az -e zászlóval szűrheti a követést (lásd: a kézikönyvet hogy strace). A fő előnye az, hogy a követést szűréssel gyorsabban lehet futtatni, mint egy teljes követést végezni, majd grep`t. Ha őszinte akarok lenni, szinte mindig mindegy.

Nem minden hiba rossz

Egy egyszerű és elterjedt példa egy olyan program, amely egy fájlt keres több helyen, például egy shell, amely azt keresi, hol található az végrehajtható fájl a lomtárban:

$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (Nincs ilyen fájl vagy könyvtár)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (Nincs ilyen fájl vagy könyvtár)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...

A "legutolsó sikertelen kérés az hibaüzenet előtt" típusú heurisztika segít a releváns hibák keresésében. Mindazonáltal logikus, hogy a végéről kell kezdeni.

A rendszerhívások megértésében jól segítenek a C nyelv programozási útmutatói.

A C könyvtárak standard hívásai nem rendszerhívások, hanem csak egy vékony felszíni réteg. Tehát, ha egy kicsit is érti, hogyan működik a C, könnyebb lesz megértenie a rendszerhívások követésének eredményeit. Például, ha problémái vannak a hálózati rendszerhívások hibakeresésével, nézze át a klasszikus „Hálózati programozás kézikönyve” Bidge.

Bonyolultabb hibakeresési példa

Már mondtam, hogy a legegyszerűbb hibakeresési példa az, amivel a munkám során a legnagyobb részt foglalkozom. straceDe néha valódi nyomozásra van szükség, így itt van egy valós példa egy bonyolultabb hibakeresésre.

bcron — feladatkezelő, a *nix démon egy másik megvalósítása. cronTelepítve van a szerveren, de amikor valaki próbálja szerkeszteni az ütemtervet, a következő történik:

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

Rendben van, tehát bcron megpróbált egy fájlt írni, de nem sikerült, és nem is hajlandó megmondani, miért. Kinyitjuk 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 +++

Körülbelül a legvégén találunk egy hibaüzenetet write, de ezúttal valami eltérő. Először is, nincs releváns hibás rendszerhívás, amely általában előtte történik. Másodszor, látható, hogy valahol valaki már olvasta a hibaüzenetet. Úgy tűnik, hogy a valódi probléma valahol máshol van, és bcrontab csak reprodukálja az üzenetet.

Ha megnézzük a man 2 read, láthatjuk, hogy az első paraméter (3) — egy fájlleíró, amelyet a *nix használ minden bemeneti-kimeneti feldolgozáshoz. Hogyan tudhatjuk meg, mit képvisel a 3-as fájlleíró? Ebben az esetben futtathatjuk strace a parancsal $ strace -p 1337 ...system call trace output... (lásd fent), és automatikusan elmondja, de ahhoz, hogy hasonló dolgokat kiszámoljunk, hasznos tudni, hogyan lehet olvasni és elemezni a nyomozási eredményeket.

A fájlleíró forrása lehet a sok rendszerhívás egyike (minden attól függ, hogy mire szolgál a leíró — konzolra, hálózati socketre, fájlra vagy valami másra), de mindenesetre a hívásokat keressük, megvizsgálva a 3-at (tehát a „= 3”-at a nyomozási eredményekben). Ebben az eredményben kettő van: openat a legfelső és socket a középső. openat Megnyit egy fájlt, de close(3) ezután azt mutatja, hogy ismét zárva van. (Szorongás: a fájlleírók újra felhasználhatók, amikor megnyitják és bezárják őket). A socket() hívás megfelelő, mert ez az utolsó előtte, és kiderül, hogy a bcrontab egy socketen keresztül dolgozik. A következő sor mutatja, hogy a fájlleíró kapcsolódik egy read()unix domain socket -hez az útvonalon Így meg kell találni a folyamatot, amely a /var/run/bcron-spool.

unix socket másik oldalán van. Ehhez van néhány elegáns trükk, és mindkettő hasznos lesz a szerverek hibakeresésénél. Az első — használni a vagy újabb netstat ss ss (socket állapot). Mindkét parancs a rendszer aktív hálózati kapcsolatait mutatja és az operátor -l a hallgató socketek leírásához, valamint az operátor parancsban a sockethez kliensként kapcsolódó programok megjelenítésére. (Sok hasznos opció van, de ehhez a feladathoz elég ez a kettő.)

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

Ez azt jelzi, hogy a hallgató — ez a parancs inixserver, amely a 20629-es folyamat ID-vel működik. (És véletlenül a 3-as fájlleírót használja socketként.)

A második valóban hasznos eszköz, amellyel ugyanezt az információt megtalálhatjuk, az lsof. Ez összes nyitott fájlt (vagy fájlleírókat) listáz a rendszerben. Vagy információt kaphatunk egy konkrét fájlról is:

# 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

A 20629-es folyamat egy hosszú életű szerver, tehát csatlakozhatunk hozzá strace valami ilyesmivel strace -o /tmp/trace -p 20629. Ha egy másik terminálban szerkesztjük a cron feladatot — megkapjuk a hiba által generált nyomkövetési eredményeket. Íme az eredmény:

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 (újraindítandó, ha az SA_RESTART be van állítva)
--- 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 (Nincs gyermek folyamat)
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 (újraindítandó, ha az SA_RESTART be van állítva)
--- 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 (Nincs gyermek folyamat)
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

(Az utolsó accept() nem fejeződik be a nyomkövetéskor.) Sajnos az eredmény nem tartalmazza a keresett hibát. Nem látunk egyetlen üzenetet sem, amit a bcrontag küldött a socketnek vagy azt fogadta tőle. Csak a folyamatkezelés folyik (clone, wait4, SIGCHLD Ez a folyamat létrehoz egy gyermek folyamatot, amely, ahogy kitalálható, végzi a tényleges munkát. Ha nyomon akarjuk követni, adjunk hozzá a híváshoz strace -fItt van, amit találunk, ha keresünk egy hibaüzenetet az új strace eredményben -f -o /tmp/trace -p 20629:

21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Hozzáférés megtagadva) 
21470 write(1, "32:ZNem lehetett létrehozni a doboz f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatális: logok:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (Nincs ilyen fájl vagy könyvtár)
21470 exit_group(111)                   = ?
21470 +++ kilépett 111 +++

Na, ez már valami. A 21470-es folyamat „hozzáférés megtagadva” hibát kap, amikor próbálkozik fájl létrehozásával ezen az elérési úton tmp/spool.21470.1573692319.854640 (a jelenlegi munkakönyvtárra vonatkozik). Ha csak tudnánk a jelenlegi munkakönyvtárat, akkor tudnánk a teljes elérési utat, és ki tudnánk találni, miért nem tudja a folyamat létrehozni a saját ideiglenes fájlját. Sajnos a folyamat már kilépett, így nem lehet egyszerűen használni lsof -p 21470 az aktuális könyvtár megtalálásához, de visszafelé is lehet dolgozni — keresni a 21470 PID rendszervívásait, amelyek megváltoztatják a könyvtárat. (Ha ilyenek nincsenek, a 21470 PID-nek valószínűleg örökölte azokat a szülőtől, és ez már túl van lsof -p nem tudható.) Ez a rendszervívás — chdir (amit nem nehéz kideríteni modern keresőmotorok segítségével). És itt van az eredmény a visszafordított keresésből a 20629 PID szerverén:

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 (Hozzáférés megtagadva) 
21470 write(1, "32:ZNem lehetett létrehozni a doboz f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatális: logok:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (Nincs ilyen fájl vagy könyvtár)
21470 exit_group(111)                   = ?
21470 +++ kilépett 111 +++

(Ha elveszik, talán érdemes lenne elolvasnod az előző bejegyzésemet a *nix folyamatok és shell-ek kezeléséről.) Tehát a 20629 PID szerver nem kapott engedélyt fájl létrehozására ezen az elérési úton /var/spool/cron/tmp/spool.21470.1573692319.854640. Valószínűleg ennek oka a klasszikus fájlrendszer jogosultsági beállítások. Ellenőrizzük:

# 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

Itt van, ahol a kutya elásva! A szerver felhasználói cronként működik, de csak a root-nak van engedélye írni a könyvtárba /var/spool/cron/tmp/. Egy egyszerű parancs chown cron /var/spool/cron/tmp/ megoldja bcron helyesen működjön. (Ha a probléma nem ebben rejlik, akkor a következő legvalószínűbb gyanúsított a SELinux vagy AppArmor típusú kernel biztonsági modul, ezért érdemes megnézni a kernel üzenetek naplóját a dmesg.)

Összesen

Kezdőként a rendszerhívások nyomozásának eredményeiben könnyű elveszni, de remélem, hogy megmutattam, hogy ezek gyors módot kínálnak egy egész osztály általános telepítési problémáinak hibakeresésére. Képzelje el, hogy próbálja nyomozni a többprocesszoros bcron, lépésről lépésre haladva a hibakeresővel.

A nyomozási eredmények visszafelé történő elemzése a rendszerhívások láncán ügyességet igényel, de ahogy már említettem, szinte mindig, ha használom strace, egyszerűen megkapom a nyomozási eredményt, és a végéről kezdve keresem a hibákat. Mindenesetre, strace segít rengeteg időt megtakarítani a hibakeresés során. Remélem, hogy önnek is hasznos lesz.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster