Ladění nasazení softwaru pomocí strace

Ladění nasazení softwaru pomocí strace

Moje každodenní práce je většinou zavádění softwaru, což znamená, že trávím spoustu času hledáním odpovědí na otázky jako:

  • Tento software funguje pro vývojáře, ale ne pro mě. Proč?
  • Včera mi tento software fungoval, ale dnes už ne. Proč?

Jedná se o druh ladění, který se mírně liší od běžného softwarového ladění. Pravidelné ladění je o logice kódu, ale ladění nasazení je o interakci mezi kódem a prostředím. I když je kořenem problému logická chyba, skutečnost, že vše funguje na jednom počítači a na jiném ne, znamená, že problém je nějak v prostředí.

Takže místo obvyklých ladicích nástrojů jako gdb Mám jinou sadu nástrojů pro nasazení ladění. A můj oblíbený nástroj pro řešení problému typu „Proč mi tento software nefunguje?“ volal obejmout.

Co je strace?

obejmout je nástroj pro „sledování systémových volání“. Původně byl vytvořen pro Linux, ale stejné ladicí triky lze provést pomocí nástrojů pro jiné systémy (DTrace nebo ktrace).

Základní aplikace je velmi jednoduchá. Stačí spustit strace s libovolným příkazem a vypíše všechna systémová volání (ačkoli nejprve budete pravděpodobně muset nainstalovat sami obejmout):

$ strace echo Hello
...Snip lots of stuff...
write(1, "Hellon", 6)                  = 6
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

Co jsou tato systémová volání? To je něco jako API pro jádro operačního systému. Kdysi měl software přímý přístup k hardwaru, na kterém běžel. Pokud například potřeboval něco zobrazit na obrazovce, pohrál si s porty nebo paměťově mapovanými registry pro video zařízení. Když se multitaskingové počítačové systémy staly populárními, zavládl chaos, protože různé aplikace bojovaly o hardware. Chyby v jedné aplikaci by mohly zničit ostatní, ne-li celý systém. Poté se v CPU objevily režimy oprávnění (neboli „ring protection“). Jádro se stalo nejprivilegovanějším: získalo plný přístup k hardwaru a vytvořilo méně privilegované aplikace, které již musely vyžadovat přístup z jádra, aby mohly interagovat s hardwarem prostřednictvím systémových volání.

Na binární úrovni se systémové volání mírně liší od jednoduchého volání funkce, ale většina programů používá obal ve standardní knihovně. Tito. standardní knihovna POSIX C obsahuje volání funkce napsat(), který obsahuje veškerý kód specifický pro architekturu pro systémové volání zapsat.

Ladění nasazení softwaru pomocí strace

Stručně řečeno, jakákoli interakce mezi aplikací a jejím prostředím (počítačovými systémy) se provádí prostřednictvím systémových volání. Proto, když software funguje na jednom počítači, ale ne na jiném, bylo by dobré podívat se na výsledky trasování systémových volání. Konkrétněji zde je seznam typických bodů, které lze analyzovat pomocí trasování systémových volání:

  • I/O konzoly
  • Síťový I/O
  • Přístup k souborovému systému a souborový I/O
  • Správa životnosti procesního vlákna
  • Správa paměti na nízké úrovni
  • Přístup ke konkrétním ovladačům zařízení

Kdy použít strace?

Teoreticky, obejmout používá se s jakýmkoli programem v uživatelském prostoru, protože jakýkoli program v uživatelském prostoru musí provádět systémová volání. Funguje efektivněji s kompilovanými programy na nízké úrovni, ale funguje také s jazyky na vysoké úrovni, jako je Python, pokud dokážete odstranit další šum z běhového prostředí a interpretu.

V celé své kráse obejmout projevuje se při ladění softwaru, který na jednom stroji funguje dobře, ale na jiném najednou přestane fungovat a produkuje nejasné zprávy o souborech, oprávněních nebo neúspěšných pokusech o provedení některých příkazů nebo něčeho jiného... Škoda, ale nejde tak dobře kombinovat s problémy na vysoké úrovni, jako jsou chyby ověření certifikátu. Obvykle to vyžaduje kombinaci obejmoutněkdy ltrace a nástroje vyšší úrovně (jako je nástroj příkazového řádku OpenSSL ladit certifikát).

Jako příklad použijeme samostatný server, ale trasování systémových volání lze často provádět na složitějších platformách nasazení. Stačí si vybrat ty správné nástroje.

Jednoduchý příklad ladění

Řekněme, že chcete spustit úžasnou serverovou aplikaci foo, a skončíte s tímto:

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

Zřejmě nemohl najít konfigurační soubor, který jste napsali. K tomu dochází, protože někdy správci balíčků kompilují aplikaci, přepíší očekávaná umístění souborů. A pokud budete postupovat podle instalační příručky pro jednu distribuci, v jiné najdete soubory úplně jiné, než jste očekávali. Problém by mohl být vyřešen během několika sekund, pokud by chybová zpráva řekla, kde hledat konfigurační soubor, ale ne. Kde tedy hledat?

Pokud máte přístup ke zdrojovému kódu, můžete si jej přečíst a vše zjistit. Dobrý plán zálohování, ale ne nejrychlejší řešení. Můžete se uchýlit k krok za krokem debugger jako gdb a uvidíte, co program dělá, ale mnohem efektivnější je použít nástroj, který je speciálně navržen tak, aby ukazoval interakci s prostředím: obejmout.

Výkon obejmout se může zdát nadbytečné, ale dobrou zprávou je, že většinu z nich lze bezpečně ignorovat. K uložení výsledků trasování do samostatného souboru je často užitečné použít operátor -o:

$ 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 +++

Přibližně celá první stránka výstupu obejmout - Toto je obvykle příprava na spuštění na nízké úrovni. (Spousta hovorů mmap, mprotect, brk pro věci, jako je detekce nízkoúrovňové paměti a zobrazení dynamických knihoven.) Ve skutečnosti během ladění výstupu obejmout Je lepší číst od konce. Níže bude výzva zapsat, který zobrazí chybovou zprávu. Podíváme se výše a vidíme první chybné systémové volání – volání openat, což vyvolá chybu ENOENT („soubor nebo adresář nenalezen“) při pokusu o otevření /etc/foo/config.json. Zde by měl být konfigurační soubor.

To byl jen příklad, ale řekl bych, že 90% času, který používám obejmout, není nic mnohem obtížnějšího než toto. Níže je kompletní průvodce laděním krok za krokem:

  • Rozčilujte se kvůli vágní zprávě o systémové chybě z programu
  • Restartujte program pomocí obejmout
  • Najděte chybovou zprávu ve výsledcích trasování
  • Jděte výš, dokud nenarazíte na první neúspěšné systémové volání

Je velmi pravděpodobné, že systémové volání v kroku 4 odhalí, co se pokazilo.

Tipy

Než vám ukážu příklad složitějšího ladění, ukážu vám pár triků pro efektivní použití obejmout:

muž je tvůj přítel

Na mnoha systémech *nix lze úplný seznam systémových volání jádra získat spuštěním muž systémové volání. Uvidíte věci jako brk(2), což znamená, že více informací lze získat spuštěním muž 2 brk.

Malé hrábě: muž 2 vidlice ukazuje mi stránku pro shell Vidlička() в GNU libc, který, jak se ukázalo, je implementován voláním klon(). Sémantika volání vidlice zůstane stejný, pokud napíšete program pomocí Vidlička()a spustit trasování – nenajdu žádné hovory vidlice, místo nich tam bude klon(). Takové hrábě vás zmátnou pouze v případě, že začnete porovnávat zdroj s výstupem obejmout.

Použijte -o pro uložení výstupu do souboru

obejmout může generovat rozsáhlý výstup, takže je často užitečné ukládat výsledky trasování do samostatných souborů (jako v příkladu výše). To také pomáhá vyhnout se záměně výstupu programu s výstupem obejmout v konzole.

Pomocí -s zobrazíte další data argumentů

Možná jste si všimli, že ve výše uvedeném příkladu trasování není zobrazena druhá polovina chybové zprávy. To je Protože obejmout default zobrazuje pouze prvních 32 bajtů argumentu řetězce. Pokud chcete vidět víc, přidejte něco jako -s 128 k hovoru obejmout.

-y usnadňuje sledování souborů, soketů atd.

"Vše je soubor" znamená, že systémy *nix provádějí všechny I/O pomocí deskriptorů souborů, ať už se to týká souboru nebo sítě nebo meziprocesových kanálů. To je vhodné pro programování, ale ztěžuje sledování toho, co se skutečně děje, když vidíte společné číst и zapsat ve výsledcích trasování systémových volání.

Přidáním operátora y, donutíš obejmout označte každý deskriptor souboru ve výstupu poznámkou, na co ukazuje.

Připojte k již běžícímu procesu pomocí -p**

Jak uvidíte z níže uvedeného příkladu, někdy je potřeba vysledovat program, který již běží. Pokud je známo, že běží jako proces 1337 (řekněme z výstupu ps), pak to můžete sledovat takto:

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

Možná budete potřebovat práva root.

Použijte -f ke sledování podřízených procesů

obejmout Ve výchozím nastavení sleduje pouze jeden proces. Pokud tento proces vytvoří podřízené procesy, pak lze vidět systémové volání pro vytvoření podřízeného procesu, ale systémová volání podřízeného procesu se nezobrazí.

Pokud si myslíte, že chyba je v podřízeném procesu, použijte příkaz -f, umožní to jeho trasování. Nevýhodou je, že vás výstup ještě více zmate. Když obejmout sleduje jeden proces nebo jedno vlákno, zobrazuje jeden proud událostí volání. Když sleduje více procesů najednou, můžete vidět, že začátek hovoru byl přerušen zprávou , pak - hromada výzev pro další exekuční větve a teprve potom - konec té první <…foocall obnoven>. Nebo rozdělit všechny výsledky trasování do různých souborů, také pomocí operátoru -ff (podrobnosti v průvodce na obejmout).

Filtrujte stopy pomocí -e

Jak vidíte, výsledkem trasování je skutečná hromada všech možných systémových volání. Vlajka -e Trasu můžete filtrovat (viz průvodce na obejmout). Hlavní výhodou je, že je rychlejší spustit filtrované trasování než provést úplné trasování a poté grep'v. Abych byl upřímný, je mi to skoro vždy jedno.

Ne všechny chyby jsou špatné

Jednoduchým a běžným příkladem je program, který hledá soubor na několika místech najednou, jako shell hledající adresář, který obsahuje spustitelný soubor:

$ 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
...

Heuristiky jako „poslední neúspěšný požadavek před nahlášením chyby“ jsou dobré při hledání relevantních chyb. Ať je to jak chce, je logické začít od úplného konce.

Kurzy programování v C vám mohou pomoci porozumět systémovým voláním.

Standardní volání do C knihoven nejsou systémová volání, ale pouze tenká povrchová vrstva. Pokud tedy alespoň trochu rozumíte tomu, jak a co dělat v C, bude pro vás snazší pochopit výsledky trasování systémových volání. Například máte problém s laděním volání do síťových systémů, podívejte se na stejnou klasiku Bija's Guide to Network Programming.

Složitější příklad ladění

Již jsem řekl, že příklad jednoduchého ladění je ukázkou toho, s čím se většinou musím při práci potýkat obejmout. Někdy je však potřeba skutečné vyšetřování, takže zde je reálný příklad pokročilejšího ladění.

bcron - plánovač zpracování úloh, další implementace démona *nix cron. Je nainstalován na serveru, ale když se někdo pokusí upravit plán, stane se toto:

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

Dobře, to znamená bcron pokusil se napsat určitý soubor, ale nevyšlo to a nepřizná proč. Odkrývání obejmout:

# 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 +++

Na samém konci je chybová zpráva zapsat, ale tentokrát je něco jinak. Za prvé, neexistuje žádná relevantní chyba systémového volání, která se obvykle vyskytuje před tímto. Za druhé je jasné, že někde už někdo chybovou hlášku četl. Vypadá to, že skutečný problém je někde jinde a bcrontab jednoduše přehraje zprávu.

Když se podíváte na muž 2 četl, můžete vidět, že první argument (3) je deskriptor souboru, který *nix používá pro veškeré I/O zpracování. Jak zjistím, co představuje deskriptor souboru 3? V tomto konkrétním případě můžete běžet obejmout s оператором y (viz výše) a automaticky vám to řekne, ale pro zjištění takových věcí je užitečné vědět, jak číst a analyzovat výsledky trasování.

Zdrojem deskriptoru souboru může být jedno z mnoha systémových volání (vše záleží na tom, k čemu deskriptor slouží - konzole, síťový soket, samotný soubor nebo něco jiného), ale ať je to jak chce, hledáme volá vrácením 3 (tj. ve výsledcích trasování hledáme „= 3“). V tomto výsledku jsou 2 z nich: openat úplně nahoře a zásuvka Uprostřed. openat otevře soubor ale zavřít(3) pak ukáže, že se opět zavře. (Rake: deskriptory souborů mohou být znovu použity, když jsou otevřeny a zavřeny). Volání zásuvka () vhodné, protože je to poslední předtím číst(), a ukázalo se, že bcrontab pracuje s něčím přes zásuvku. Další řádek ukazuje, že je přidružen popisovač souboru soket unixové domény při cestě /var/run/bcron-spool.

Musíme tedy najít související proces unixová zásuvka na druhé straně. Pro tento účel existuje několik elegantních triků, z nichž oba jsou užitečné pro ladění nasazení serveru. První je použít netstat nebo novější ss (stav zásuvky). Oba příkazy ukazují aktivní síťová připojení systému a přebírají výpis -l k popisu naslouchacích zásuvek a také operátora -p k zobrazení programů připojených k soketu jako klient. (Existuje mnoho dalších užitečných možností, ale tyto dvě jsou pro tento úkol dostačující.)

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

To naznačuje, že posluchač je příkaz inixserver, běžící s ID procesu 20629. (A shodou okolností používá jako soket deskriptor souboru 3.)

Druhým opravdu užitečným nástrojem pro hledání stejných informací je tzv také. Vypisuje všechny otevřené soubory (nebo deskriptory souborů) v systému. Nebo můžete získat informace o jednom konkrétním souboru:

# 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

Process 20629 je server s dlouhou životností, takže jej můžete připojit obejmout pomocí něčeho takového strace -o /tmp/trace -p 20629. Pokud upravíte úlohu cron v jiném terminálu, obdržíte trasovací výstup s chybou. A tady je výsledek:

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

(Poslední akceptovat() nebude při trasování dokončena.) Tento výsledek opět bohužel neobsahuje chybu, kterou hledáme. Nevidíme žádné zprávy, které bcrontag posílá nebo přijímá ze soketu. Místo toho kompletní řízení procesu (klonovat, počkejte 4, SIGCHLD atd.) Tento proces vytvoří podřízený proces, který, jak asi tušíte, dělá skutečnou práci. A pokud potřebujete zachytit její stopu, přidejte se k hovoru strace -f. To je to, co najdeme, když budeme hledat chybovou zprávu v novém výsledku pomocí 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 (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 +++

Tak to je něco. Proces 21470 při pokusu o vytvoření souboru na cestě obdrží chybu „přístup odepřen“. tmp/spool.21470.1573692319.854640 (vztahuje se k aktuálnímu pracovnímu adresáři). Pokud bychom znali pouze aktuální pracovní adresář, znali bychom také úplnou cestu a byli bychom schopni zjistit, proč v něm proces nemůže vytvořit svůj dočasný soubor. Bohužel proces již skončil, takže nemůžete jen používat lsof -p 21470 abyste našli aktuální adresář, ale můžete pracovat i opačným směrem - vyhledejte systémová volání PID 21470, která mění adresář. (Pokud žádné nejsou, PID 21470 je musel zdědit od svého rodiče, a to je již přes lsof -p nelze zjistit.) Toto systémové volání je chdir (což lze snadno zjistit pomocí moderních online vyhledávačů). A zde je výsledek zpětného vyhledávání na základě výsledků trasování až po server 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 +++

(Pokud se ztratíte, možná si budete chtít přečíst můj předchozí příspěvek o *nix správě procesů a shellech.) Server PID 20629 tedy neobdržel oprávnění k vytvoření souboru na cestě /var/spool/cron/tmp/spool.21470.1573692319.854640. Důvodem je s největší pravděpodobností klasické nastavení oprávnění souborového systému. Pojďme zkontrolovat:

# 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

V tom je zakopaný pes! Server běží jako uživatelský cron, ale pouze root má oprávnění zapisovat do adresáře /var/spool/cron/tmp/. Jednoduchý příkaz chown cron /var/spool/cron/tmp/ donutí bcron pracovat správně. (Pokud to nebyl problém, pak dalším nejpravděpodobnějším podezřelým je modul zabezpečení jádra jako SELinux nebo AppArmor, takže bych zkontroloval protokol zpráv jádra pomocí dmesg.)

Celkem

Trasování systémových volání může být pro začátečníka zdrcující, ale doufám, že jsem ukázal, že jde o rychlý způsob, jak odladit celou třídu běžných problémů s nasazením. Představte si, že se pokoušíte ladit multiproces bcronpomocí ladicího programu krok za krokem.

Analýza výsledků trasování zpětně v řetězci systémových volání vyžaduje dovednost, ale jak jsem řekl, téměř vždy používat obejmout, dostanu výsledek trasování a hledám chyby od konce. Tak jako tak, obejmout pomáhá mi ušetřit spoustu času při ladění. Doufám, že to bude užitečné i pro vás.

Zdroj: www.habr.com

Přidat komentář