Настройваме разполагането на софтуер със strace

Настройваме разполагането на софтуер със strace

Основната ми работа е да внедрявам системи за софтуер, т.е. прекарвам много време в опити да отговоря на подобни въпроси:

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

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

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

Какво е strace?

strace — това е инструмент за "проследяване на системни повиквания". Първоначално е създаден за Linux, но същите трикове за отстраняване на проблеми могат да се извършват и с инструменти за други системи (DTrace или ktrace).

Основното приложение е много просто. Необходимо е просто да стартирате 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 (flags 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 трасира един процес или една гъвка, той показва единен поток от събития на повиквания. Когато трасира едновременно няколко процеса, вероятно ще видите началото на повикване, прекъснато от съобщение <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Не можа да създаде временен f"..., 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Не можа да създаде временен f"..., 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