Отстраняваме внедряването на софтуер с strace

Отстраняваме внедряването на софтуер с strace

Основната ми работа е по-голямата част от времето внедряване на софтуерни системи, така че прекарвам много време, опитвайки се да отговоря на такива въпроси:

  • При разработчика този софтуер работи, а при мен не. Защо?
  • Вчера този софтуер работеше при мен, а днес не. Защо?

Това е вид отстраняване на грешки, което малко се различава от обикновеното отстраняване на софтуерни грешки. Обикновеното отстраняване на грешки е свързано с логиката на кода, а отстраняването на внедряването - с взаимодействието на кода и околната среда. Дори ако коренът на проблема е логическа грешка, фактът, че на една машина всичко работи, а на друга - не, означава, че проблемът е свързан по някакъв начин с околната среда.

Затова вместо обикновените инструменти за отстраняване на грешки като gdb имам друг набор от инструменти за отстраняване на внедряването. И любимият ми инструмент за справяне с проблема тип „Защо този софтуер не работи при мен?“ се нарича strace.

Какво е strace?

strace Това е инструмент за „отслеждане на системни повиквания“. Първоначално е създаден за Linux, но същите функции могат да се извършат и с инструменти за други системи (DTrace или ktrace).

Основното му приложение е много простo. Необходимо е само да стартирате strace с коя да е команда и той ще извлече всички системни повиквания (въпреки че вероятно първо ще трябва да инсталирате самия 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 +++

Какво представляват тези системни повиквания? Те са нещо като API за ядрото на операционната система. Отдавна софтуерът имаше директен достъп до 'железото', върху което работеше. Например, ако искате да изобразите нещо на екрана, той манипулираше портовете или регистрите, видими в паметта, за видео устройства. Когато многозадачните компютърни системи станаха популярни, настъпи хаос, защото различни приложения се състезаваха за 'железото'. Грешки в едно приложение можеха да сринат работата на други, ако не и цялата система. Тогава в CPU се появиха режимите на привилегии (или „кольцева защита“). Най-привилегированото стана ядрото: то получи пълен достъп до 'железото', произвеждайки по-малко привилегировани приложения, които вече трябваше да искат достъп от ядрото, за да взаимодействат с 'железото' - чрез системни повиквания.

На бинарно ниво системният повик малко се различава от простия повик на функция, но повечето програми използват обвивка в стандартната библиотека. Т.е. стандартната библиотека POSIX C съдържа повик на функция write(), която съдържа целия архитектурно зависим код за системно повикване write.

Отстраняваме внедряването на софтуер с strace

Ако накратко, всяко взаимодействие на приложението със своето обкръжение (компютърните системи) се осъществява чрез системни повикове. Затова, когато софтуерът на едната машина работи, а на друга не, добре е да се погледне в резултатите от трасировката на системните повикове. По-конкретно, ето списък с типичните моменти, които можете да анализирате с помощта на трасировка на системните повикове:

  • Конзолен вход-изход
  • Мрежов вход-изход
  • Достъп до файловата система и файлов вход-изход
  • Управление на житейския цикъл на процеса-поток
  • Нискониво управление на паметта
  • Достъп до драйвери на специални устройства

Кога да използвате strace?

В теорията, strace се използва с всякакви програми в потребителското пространство, тъй като всяка програма в потребителското пространство трябва да прави системни повикове. Той работи по-ефективно с компилирани, нискониво програми, но работи и с високо ниво езици като Python, ако успеете да се справите с допълнителния шум от средата на изпълнение и интерпретатора.

В цялото си величие strace се проявява по време на отстраняване на грешки в софтуера, който работи добре на една машина, а на друга изведнъж спира да работи, предоставяйки неясни съобщения за файлове, разрешения или неуспешни опити за изпълнение на команди или нещо друго... Уви, но не толкова добре се комбинира с високо ниво проблеми като грешки при проверка на сертификати. Обикновено тук е необходима комбинация strace, понякога ltrace и инструменти с по-високо ниво (като инструмент на командния ред openssl за отстраняване на грешки със сертификати).

Например, вземаме работа на изолиран сървър, но трасировката на системни повикове често може да бъде извършена и на по-сложни платформи за внедряване. Просто трябва да подберете подходящия инструментариум.

Пример за просто отстраняване на грешки

Да предположим, че искате да стартирате страхотно сървърно приложение foo, а получавате следното:

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

Очевидно, не успявахте да намерите конфигурационния файл, написан от вас. Това се случва, защото понякога, когато мениджърите на пакети компилират приложението, те пренасочват очакваното местоположение на файловете. И ако следвате инструкциите за инсталация за едно дистрибуция, в другата можете да намерите файлове на напълно различни места. Проблемът може да бъде решен за секунди, ако съобщението за грешка указваше къде трябва да се търси конфигурационния файл, но то не го прави. Та къде да търсите?

Ако имате достъп до изходния код, можете да го прочетете и всичко да стане ясно. Добър резервен план, но не най-бързото решение. Можете да използвате стъпков дебъгер като gdb и да видите какво прави програмата, но много по-ефективно е да се използва инструмент, който е специално проектиран да показва взаимодействието със средата: strace.

Извод strace може да изглежда излишно, но добрата новина е, че голяма част от него може да бъде игнорирана. Често е полезно да се използва операторът -o, за да се запазят резултатите от трасировката в отделен файл:

$ strace -o /tmp/trace foo
Грешка при отваряне на конфигурационния файл: Няма такъв файл или директория
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL) = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (Няма такъв файл или директория)
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 (Няма такъв файл или директория)
dup(2) = 3
fcntl(3, F_GETFL) = 0x2 (флагове O_RDWR)
brk(NULL) = 0x56363b3fb000
brk(0x56363b41c000) = 0x56363b41c000
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x8), ...}) = 0
write(3, "Грешка при отваряне на конфигурационния файл"..., 60) = 60
close(3) = 0
exit_group(1) = ?
+++ излезе с 1 +++

Примерно цялата първа страница от извода strace — това обикновено е низко ниво подготовка за стартиране. (Много повиквания mmap, mprotect, brk за неща от типа откритие на низко ниво памет и отображение на динамични библиотеки.) Всъщност, по време на отстраняване на грешки, изходите strace е по-добре да се четат от края. Вдолу ще бъде повикването write, излъчващо съобщение за грешка. Гледаме нагоре и виждаме първото грешно системно повикване — повикването openat, даващо грешка ENOENT („файл или директория не намерени“), опитвайки се да отвори /etc/foo/config.json. Ето, тук трябва да е конфигурационния файл.

Това беше просто пример, но бих казал, че 90% от времето, което използвам strace, не е нужно да правя нищо по-сложно от това. По-долу — пълно ръководство стъпка по стъпка за отстраняване на грешки:

  • Не се тревожете за неясно съобщение за системна грешка от програмата
  • Повторно стартирайте програмата с strace
  • Намерете в резултатите от трасировката съобщението за грешка
  • Гледайте нагоре, докато не стигнете до първото неуспешно системно повикване

Възможно е, че системното повикване на 4-та стъпка ще покаже какво е сбъркало.

Подсказки

Преди да покажа пример за по-сложна отладка, ще ви разкажа няколко трика за ефективно използване strace:

man — ваш приятел

В много *nix системи пълният списък с системни повиквания към ядрото може да бъде получен, като се изпълни man syscalls. Ще видите неща като brk(2), така че повече информация може да бъде получена, като напишете man 2 brk.

Някои малки капани: man 2 fork ми показва страницата за черупката fork() в GNU libc, която, оказва се, е реализирана чрез повикването clone(). Семантиката на повикването fork остава същата, ако напишете програма, която използва fork(), и пуснете трасировка — няма да намеря повиквания fork, вместо тях ще бъдат clone(). Такива капани само объркват, ако започнете да сравнявате изходния код с изхода strace.

Използвайте -о, за да запазите изхода в файл

strace може да генерира обширен изход, така че често е полезно да съхранявате резултатите от трасировката в отделни файлове (както в посочения по-горе пример). Освен това, това помага да не се обърка софтуерният изход с изхода strace в конзолата.

Използвайте -s, за да видите повече данни за аргумента

Вероятно сте заб注意ли, че втората половина на съобщението за грешка не беше показана в примера за трасировка по-горе. Това е защото strace по подразбиране показва само първите 32 байта от аргумента на реда. Ако искате да видите повече, добавете нещо като -s 128 к повикването strace.

-у улеснява проследяването на файлове, сокети и др.

"Всичко е файл" означава, че *nix системи извършват всички входно-изходни операции, използвайки файлови дескриптори, независимо дали става дума за файл, мрежа или междупроцесни канали. Това е удобно за програмиране, но затруднява проследяването на това, което наистина се случва, когато видите общите read и write в резултатите от трасировката на системните повиквания.

Добавяйки оператора -у, ще накарате strace да анотира всеки файлов дескриптор в изхода с бележка за това, на какво сочи.

Прикрепете се към вече стартиран процес с -p**

Както ще видите в следния пример, понякога трябва да трасировате програма, която вече е стартирана. Ако знаете, че тя е стартирана като процес 1337 (да речем, от изходите ps), можете да направите трасировка по този начин:

$ strace -p 1337
...изход от трасировката на системни повиквания...

Може да са ви нужни root права.

Използвайте -f, за да следите за дочерни процеси

strace по подразбиране трасира само един процес. Ако този процес създава дъщерни процеси, ще можете да видите системния повик за създаване на дъщерния процес, но системните повици на дъщерния процес няма да бъдат показани.

Ако смятате, че грешката е в дъщерния процес, използвайте оператора -f, това ще активира трасировката му. Недостатъкът е, че изходът ще бъде още по-объркващ. Когато strace трасирa един процес или едно клон, той показва едно единствено протичане на събитията. Когато трасирa наведнъж няколко процеса, вие може да видите началото на повикването, прекъснато от съобщение <unfinished …>, след това - куп повиквания за други клонове на изпълнението, и едва след това - завършването на първото с <… foocall resumed>. Или разделете всички резултати от трасировката в различни файлове, като също използвате оператора -ff (детайли - в ръководството по strace).

Филтрирайте трасировката с -e

Както виждате, резултатът от трасировката е действителен куп от всички възможни системни повиквания. С флага -e може да филтрирате трасировката (вж. ръководство по strace). Основното предимство е, че стартирането на трасировка с филтриране е по-бързо, отколкото да направите пълна трасировка и след това grep`ате. Честно казано, на мен почти винаги ми е все едно.

Не всички грешки са лоши

Прост и често срещан пример е програма, която търси файл едновременно на няколко места, например черупка, която търси дали в кошчето има изпълняем файл:

$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (Няма такъв файл или директория)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (Няма такъв файл или директория)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...

Евристиката от типа „последно неуспешно запитване преди съобщението за грешка“ е добра за намиране на релевантни грешки. Както и да е, логично е да започнете от самия край.

Разбирането на системните повиквания добре се подпомага от ръководствата за програмиране на C

Стандартните повиквания към библиотеки на C не са системни повиквания, а само тънък повърхностен слой. Така че, ако поне малко разбирате какво и как да правите в C, ще ви бъде по-лесно да се ориентирате в резултатите от трасировката на системните повиквания. Например, ако имате проблем с отстраняването на грешки в повикванията към мрежови системи, прегледайте същото класическо „Ръководство по мрежовото програмиране“ на Бидж.

Примерът за отстраняване на грешки е по-сложен

Казвал съм, че примерът за проста отладка е представителен за това, с което най-често се сблъсквам в работата си с strace. Все пак понякога се изисква истинско разследване, така че ето ви реален пример за по-сложна отладка.

bcron — планировчик на задачи, още една реализация на демона *nix cron. Той е инсталиран на сървъра, но когато някой опита да редактира графика, се случва следното:

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

Добре, значи bcron е опитвал да запише определен файл, но не му се е получило и не признава защо. Разглеждаме 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 +++

Приблизително в самия край има съобщение за грешка write, но този път нещо е различно. Първо, няма релевантна грешка от системния повик, която обикновено се случва преди това. На второ място, видно е, че някой вече е прочел съобщението за грешка. Изглежда, че истинският проблем е някъде другаде, а bcrontab просто възпроизвежда съобщението.

Ако погледнете на man 2 read, ще видите, че първият аргумент (3) е файлов дескриптор, който *nix използва за всички входно-изходни операции. Как да узнаем какво представлява файловият дескриптор 3? В конкретния случай може да се стартира strace с оператор -у (вж. по-горе), и той автоматично ще разкаже, но за да изчислявате подобни неща, е полезно да знаете как да четете и анализирате резултатите от трасировката.

Източникът на файловия дескриптор може да бъде едно от многото системни повиквания (всичко зависи от това каква е целта на дескриптора — за конзола, мрежов сокет, самия файл или нещо друго), но каквото и да е, повикванията търсим, връщайки 3 (т.е. търсейки „= 3“ в резултатите от трасировката). В този случай са 2: openat най-отгоре и socket в средата. openat отваря файл, но close(3) след това ще покаже, че той отново се затваря. (Предупреждение: файловите дескриптори могат да се преподадат, когато се отварят и затварят). Повикването socket() е уместно, тъй като е последното преди read(), и излиза, че bcrontab работи с нещо през сокет. Следващият ред показва, че файловият дескриптор е свързан с unix domain socket по пътя /var/run/bcron-spool.

Така че, трябва да намерим процеса, свързан с unix socket от другата страна. За тази цел има няколко изтънчени трика, и двата ще бъдат полезни за отстраняването на проблеми с разгръщането на сървъри. Първият — да се използва netstat или по-новия ss (статус сокета). Обе команды показывают активни мрежови връзки на системата и вземат оператор -l за описание на слушащите сокети, както и оператора -p за показване на програмите, свързани със сокета като клиент. (Полезни опции има много, но за тази задача тези две са достатъчни.)

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

Това показва, че слушащият е команда inixserver, работеща с ID на процеса 20629. (И, за съвпадение, тя използва файлов дескриптор 3 като сокет.)

Вторият наистина полезен инструмент за намиране на същата информация се нарича lsof. Той изброява всички отворени файлове (или файлови дескриптори) в системата. Или може да получите информация за конкретен файл:

# 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

Процес 20629 е дълготрайния сървър, така че можете да се прикрепите към него strace чрез нещо като strace -o /tmp/trace -p 20629. Ако редактирате задачата cron в друг терминал — получавате изход на резултатите от трасировката с възникваща грешка. А ето и резултатът:

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 (Да бъде възобновен, ако SA_RESTART е зададен)
--- 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 (Няма дъщерни процеси)
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 (Да бъде възобновен, ако SA_RESTART е зададен)
--- 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 (Няма дъщерни процеси)
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

(Последният accept() няма да бъде завършен при трасировката.) И отново, как да съжалявам, но този резултат не съдържа грешката, която търсим. Не виждаме нито едно съобщение, което bcrontag е изпратил на сокета или е получило от него. Вместо това имаме само управление на процеса (clone, wait4, SIGCHLD и проч.) Този процес създава дъщерен процес, който, както може да се предположи, извършва действителната работа. И ако трябва да уловим следите му, добавете към извикването strace -f. Ето какво ще намерим, ако търсим съобщение за грешка в новия резултат със 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 (Отказано разрешение) 
21470 write(1, "32:ZНе може да се създаде временно файл"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Фатално: логове:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (Няма такъв файл или директория)
21470 exit_group(111)                   = ?
21470 +++ излезе с 111 +++

Ето, това вече е нещо. Процес 21470 получава грешка „отказано разрешение“ при опит да създаде файл на пътя tmp/spool.21470.1573692319.854640 (свързано с текущата работна директория). Ако просто знаехме текущата работна директория, щяхме да знаем и пълния път и можехме да установим защо процесът не може да създаде временното си файл. За съжаление, процесът вече е приключил, така че не можем просто да използваме lsof -p 21470 за да намерим текущата директория, но можем да работим в обратна посока — да търсим системни повиквания на PID 21470, които променят директорията. (Ако такива няма, PID 21470, вероятно, са наследили от родителя, и това вече през lsof -p не може да се установи.) Това системно повикване — chdir (което не е трудно да се установи с помощта на съвременни мрежови търсачки). А ето и резултатите от обратните търсения по следите, до самия сървър 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 (Отказано разрешение) 
21470 write(1, "32:ZНе може да се създаде временно файл"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Фатално: логове:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (Няма такъв файл или директория)
21470 exit_group(111)                   = ?
21470 +++ излезе с 111 +++

(Ако се затруднявате, може би е добре да прочетете моя предишен пост за управление на процеса *nix и черупките.) Така че, сървър PID 20629 не е получил разрешение да създаде файл на пътя /var/spool/cron/tmp/spool.21470.1573692319.854640. Вероятно причина за това са класическите настройки на разрешенията на файловата система. Да проверим:

# 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

Ето къде е заровена кучето! Сървърът работи като потребителски cron, но само root има разрешение да записва в директорията /var/spool/cron/tmp/. Определена команда chown cron /var/spool/cron/tmp/ ще реши проблема bcron да работи правилно. (Ако проблемът не е в това, следващият най-вероятен заподозрян е модулът за сигурност на ядрото, като SELinux или AppArmor, така че бих проверил ядрения журнал с помощта на dmesg.)

Общо

Начинаещ в анализа на системните повиквания може да се потопи, но се надявам, че показах, че те са бърз начин за отстраняване на цял клас разпространени проблеми с внедряването. Представете си как се опитвате да отстраните грешките в многопроцесното bcron, използвайки стъпков отладчик.

Разборът на резултатите от трасировката по веригата на системните повиквания изисква умение, но както вече споменах, почти винаги, използвайки strace, просто получавам резултата от трасировката и търся грешките, започвайки от края. Във всеки случай, strace ми помага да спестя много време при отстраняването на грешки. Надявам се и на вас да бъде полезно.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster