Stiamo perfezionando il deployment del software con strace

Stiamo perfezionando il deployment del software con strace

Il mio lavoro principale consiste principalmente nel dispiegare sistemi software, quindi passo molto tempo a cercare di rispondere a domande come queste:

  • Questo software funziona per lo sviluppatore, ma non per me. Perché?
  • Ieri questo software funzionava per me, ma oggi no. Perché?

Questo è una sorta di debug che differisce un po' dal debugging tradizionale del software. Il debugging tradizionale si occupa della logica del codice, mentre il debugging del dispiegamento riguarda l'interazione tra codice e ambiente. Anche se la radice del problema è un errore logico, il fatto che funzioni su una macchina e non su un'altra implica che c'è qualcosa di problematico nell'ambiente.

Quindi, invece dei tradizionali strumenti di debug come gdb ho un altro set di strumenti per il debug del dispiegamento. E il mio strumento preferito per affrontare il problema del "Perché questo software non funziona per me?" si chiama strace.

Che cos'è strace?

strace è uno strumento per la "tracciatura delle chiamate di sistema". Originariamente progettato per Linux, ma le stesse funzionalità di debug possono essere utilizzate anche con strumenti per altri sistemi (DTrace o ktrace).

Il suo utilizzo principale è molto semplice. Basta eseguire strace con qualsiasi comando e lui restituirà un dump di tutte le chiamate di sistema (ovviamente, prima probabilmente dovrai installare strace stesso) 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 +++

Cosa sono queste chiamate di sistema? Sono una sorta di API per il kernel del sistema operativo. Tanto tempo fa, il software aveva accesso diretto all'hardware su cui operava. Ad esempio, se si doveva visualizzare qualcosa sullo schermo, interagiva con le porte e i registri mappati in memoria per i dispositivi video. Con la popolarità dei sistemi computerizzati multitasking, regnò il caos, poiché varie applicazioni si contendevano l'accesso all'hardware. Gli errori in un'applicazione potevano compromettere il funzionamento delle altre, se non dell'intero sistema. Fu allora che i CPU introdussero modalità di privilegio (o "protezione a cerchio"). Il kernel divenne il più privilegiato: ottenne accesso completo all'hardware, creando applicazioni meno privilegiate che dovevano richiedere accesso al kernel per interagire con l'hardware — tramite chiamate di sistema.

A livello binario, una chiamata di sistema differisce leggermente da una chiamata di funzione semplice, tuttavia la maggior parte dei programmi utilizza un wrapper dalla libreria standard. Cioè, la libreria standard POSIX C contiene la chiamata di funzione write(), che include tutto il codice dipendente dall'architettura per la chiamata di sistema write.

Stiamo perfezionando il deployment del software con strace

In breve, qualsiasi interazione di un'applicazione con il suo ambiente (sistemi informatici) avviene tramite chiamate di sistema. Quindi, quando il software funziona su una macchina e non su un'altra, è utile dare un'occhiata ai risultati della tracciatura delle chiamate di sistema. Se entriamo nei dettagli, ecco un elenco di aspetti tipici che possono essere analizzati tramite la tracciatura delle chiamate di sistema:

  • Input-output della console
  • Input-output di rete
  • Accesso al file system e input-output dei file
  • Gestione del ciclo di vita di processi/thread
  • Gestione a basso livello della memoria
  • Accesso ai driver di dispositivi speciali

Quando utilizzare strace?

In teoria, strace viene utilizzato con qualsiasi programma nello spazio utente, poiché ogni programma nello spazio utente deve fare chiamate di sistema. Funziona meglio con programmi compilati e a basso livello, ma può essere utilizzato anche con linguaggi di alto livello come Python, se riesci a farti strada attraverso il rumore aggiuntivo dell'ambiente di esecuzione e dell'interprete.

In tutta la sua gloria strace si manifesta durante il debugging di software che funziona bene su una macchina e improvvisamente smette di funzionare su un'altra, restituendo messaggi poco chiari riguardo a file, permessi o tentativi falliti di eseguire comandi vari o altro… Purtroppo, non si integra così bene con problemi di alto livello come gli errori di convalida del certificato. Di solito è necessaria una combinazione di strace, a volte ltrace e strumenti di livello superiore (come lo strumento da riga di comando openssl per il debug del certificato).

Per esempio, lavoriamo su un server isolato, ma la tracciatura delle chiamate di sistema può spesso essere eseguita anche su piattaforme di dispiegamento più complesse. Basta scegliere gli strumenti giusti.

Esempio di semplice debug

Diciamo che vuoi avviare un'applicazione server fantastica chiamata foo, ma ottieni questo:

$ foo
Errore durante l'apertura del file di configurazione: Nessun file o directory di questo tipo

È chiaro che non è riuscito a trovare il file di configurazione che hai scritto. Questo accade perché a volte, quando i gestori di pacchetti compilano l'applicazione, sovrascrivono la posizione prevista dei file. E se segui le istruzioni di installazione per una distribuzione, in un'altra puoi trovare i file completamente in posti diversi da quelli attesi. La questione potrebbe essere risolta in pochi secondi se il messaggio di errore indicasse dove cercare il file di configurazione, ma ovviamente non lo fa. Quindi, dove cercare?

Se hai accesso al codice sorgente, puoi leggerlo e scoprire tutto. È un buon piano B, ma non è la soluzione più rapida. Puoi ricorrere a un debugger passo passo come gdb e vedere cosa fa il programma, ma è molto più efficace utilizzare uno strumento progettato appositamente per mostrare le interazioni con l'ambiente: strace.

Risultato strace potrebbe sembrare ridondante, ma la buona notizia è che gran parte di esso può essere ignorato senza problemi. Spesso è utile utilizzare l'opzione -o per salvare i risultati della tracciatura in un file separato:

$ strace -o /tmp/trace foo
Errore nell'aprire il file di configurazione: Nessun file o directory
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL) = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (Nessun file o 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 (Nessun file o 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, "Errore nell'aprire il file di configurazione"..., 60) = 60
close(3) = 0
exit_group(1) = ?
+++ exited with 1 +++

Circa tutta la prima pagina dell'output strace — è di solito una preparazione a basso livello per l'avvio. (Molte chiamate a mmap, mprotect, brk per cose come la rilevazione della memoria a basso livello e il mapping delle librerie dinamiche.) In effetti, durante il debug, è spesso meglio leggere l'output dal fondo. In basso ci sarà la chiamata strace , che produce il messaggio di errore. Guardiamo sopra e vediamo la prima chiamata di sistema che ha fallito — la chiamata writeopenat , che restituisce un errore, che restituisce un errore ENOENT («file o directory non trovati»), cercando di aprire /etc/foo/config.json. Ecco dove dovrebbe trovarsi il file di configurazione.

Era solo un esempio, ma direi che il 90% delle volte che utilizzo strace, non ho bisogno di fare nulla di più complicato. Di seguito è riportata una guida passo passo completa per il debug:

  • Non lasciarti scoraggiare da un messaggio d'errore poco chiaro dell'applicazione
  • Riavviare l'applicazione con strace
  • Cercare nel risultato della tracciatura il messaggio d'errore
  • Andare sopra fino a trovare la prima chiamata di sistema fallita

È molto probabile che la chiamata di sistema al quarto passo mostri cosa sia andato storto.

Suggerimenti

Prima di mostrare un esempio di debug più complesso, ti darò alcuni trucchi per utilizzare efficacemente strace:

man — è il tuo amico

Su molti sistemi *nix, puoi ottenere l'elenco completo delle chiamate di sistema del kernel eseguendo man syscalls. Vedrai cose come brk(2), il che significa che puoi ottenere maggiori informazioni eseguendo man 2 brk.

Piccole insidie: man 2 fork mi mostra la pagina per la shell fork() in GNU libc, che è stata implementata utilizzando la chiamata clone(). La semantica della chiamata fork rimane la stessa, se scrivo un programma che utilizza fork(), e avvio una tracciatura — non troverò le chiamate fork, al loro posto ci saranno clone(). Queste insidie confondono se inizio a confrontare il codice sorgente con l'output strace.

Usa -o per salvare l'output in un file

strace può generare un output esteso, quindi spesso è utile conservare i risultati della tracciatura in file separati (come nell'esempio sopra). Inoltre, aiuta a non confondere l'output dell'applicazione con l'output strace nel terminale.

Usa -s per visualizzare più dati sugli argomenti

Probabilmente hai notato che la seconda metà del messaggio di errore non è stata mostrata nell'esempio di tracciatura sopra. Questo è perché strace di default mostra solo i primi 32 byte dell'argomento della stringa. Se vuoi vedere di più, aggiungi qualcosa come -s 128 alla chiamata strace.

-u facilita il tracciamento dei file socket e così via.

«Tutto è file» significa che i sistemi *nix eseguono tutte le operazioni di input/output utilizzando descurtori di file, sia che si tratti di file, rete o pipe interprocesso. Questo è comodo per la programmazione, ma rende difficile monitorare cosa stia realmente accadendo quando si vedono le uscite generali read e write nei risultati del tracciamento delle chiamate di sistema.

Aggiungendo l'operatore -u, obbligherai strace ad annotare ogni descrittore di file nell'output con una nota su cosa punta.

Collegati a un processo già avviato con -p**

Come si vedrà nell'esempio sottostante, a volte è necessario tracciare un programma già in esecuzione. Se si sa che è avviato come processo 1337 (diciamo, dagli output ps), allora si può effettuare il tracciamento in questo modo:

$ strace -p 1337
...output del tracciamento delle chiamate di sistema...

Potresti aver bisogno di privilegi di root.

Usa -f per seguire i processi figli

strace di default traccia solo un processo. Se questo processo genera processi figli, si può vedere la chiamata di sistema per la creazione di un processo figlio, ma le chiamate di sistema del processo figlio non verranno visualizzate.

Se pensi che l'errore sia nel processo figlio, utilizza l'operatore -f, questo attiverà il suo tracciamento. Il lato negativo è che l'output ti confonderà ulteriormente. Quando strace traccia un solo processo o un solo ramo, mostra un flusso unico di eventi di chiamata. Quando traccia più processi contemporaneamente, potresti vedere l'inizio di una chiamata, interrotto da un messaggio <unfinished …>, poi un gruppo di chiamate per altri rami di esecuzione, e solo dopo la conclusione del primo con <… foocall resumed>. Oppure dividi tutti i risultati del tracciamento in file diversi, usando anche l'operatore -ff (dettagli in guida per strace).

Filtra il tracciamento usando -e

Come vedi, il risultato del tracciamento è un vero groviglio di tutte le possibili chiamate di sistema. Con il flag -e puoi filtrare il tracciamento (vedi la guida per strace). Il grande vantaggio è che eseguire un tracciamento con filtraggio è più veloce che fare un tracciamento completo e poi grep`are. A dire il vero, a me interessa praticamente sempre poco.

Non tutti gli errori sono negativi

Un esempio semplice e comune è un programma che cerca un file in più posizioni, simile a una shell che cerca quale directory contiene il file eseguibile:

$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (Nessun file o directory)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (Nessun file o directory)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...

La logica del tipo «ultima richiesta non riuscita prima del messaggio di errore» è utile per trovare errori pertinenti. Ad ogni modo, è logico partire dalla fine.

Comprendere le chiamate di sistema è facilitato dai manuali di programmazione in linguaggio C

Le chiamate standard alle librerie C non sono chiamate di sistema, ma solo uno strato superficiale sottile. Quindi, se hai almeno una minima comprensione di come e cosa fare in C, ti sarà più facile destreggiarti nei risultati del tracciamento delle chiamate di sistema. Ad esempio, se hai problemi a debug di chiamate a sistemi di rete, controlla il classico «Guida alla programmazione di rete» di W. Richard Stevens.

Un esempio di debug più complesso

Ho già detto che un esempio di semplice debug è un esempio di ciò con cui spesso devo avere a che fare nel lavoro con strace. Tuttavia, a volte è necessaria un’investigazione reale, quindi ecco un vero esempio di debug più complesso.

bcron — un pianificatore di elaborazione dei compiti, un'altra implementazione di un demone *nix cron. È installato su un server, ma quando qualcuno cerca di modificare la programmazione, succede quanto segue:

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

Va bene, quindi bcron ha tentato di scrivere un certo file, ma non ci è riuscito e non ammette perché. Andiamo a esaminare 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 +++

Verso la fine c'è un messaggio di errore write, ma questa volta c'è una differenza. In primo luogo, non c'è un errore di chiamata di sistema pertinente che di solito accade prima. In secondo luogo, si vede che da qualche parte qualcuno ha già letto il messaggio di errore. Sembra che il vero problema sia da qualche altra parte e che bcrontab stia semplicemente riproducendo il messaggio.

Se guardiamo man 2 read, si può vedere che il primo argomento (3) è il descrittore di file che *nix usa per tutte le elaborazioni di input/output. Come scoprire cosa rappresenta il descrittore di file 3? In questo caso specifico, si può eseguire strace con l'operatore -u (vedi sopra), e ti dirà automaticamente, tuttavia, per calcolare simili cose, è utile sapere come leggere e analizzare i risultati del tracciamento.

L'origine del descrittore di file può essere una delle molte chiamate di sistema (dipende tutto dallo scopo del descrittore: se è per la console, un socket di rete, un file vero e proprio o altro ancora), ma in ogni caso, le chiamate che ci interessano sono quelle che restituiscono 3 (cioè cerchiamo «= 3» nei risultati della traccia). In questo risultato ce ne sono 2: , che restituisce un errore in cima e socket in mezzo. , che restituisce un errore apre un file, ma close(3) dopo mostrerà che viene chiuso di nuovo. (Nota: i descrittori di file possono essere riutilizzati quando vengono aperti e chiusi). La chiamata socket() è pertinente, poiché è l'ultima prima di read(), e quindi si scopre che bcrontab sta lavorando con qualcosa tramite un socket. La riga successiva mostra che il descrittore di file è collegato a unix domain socket al percorso /var/run/bcron-spool.

Quindi, dobbiamo trovare il processo associato a unix socket dall'altra parte. Per questo ci sono un paio di trucchi eleganti, entrambi utili per il debug delle distribuzioni server. Il primo è usare netstat o una versione più recente di ss (socket status). Entrambi i comandi mostrano le connessioni di rete attive del sistema e utilizzano l'operatore -l per descrivere i socket in ascolto, oltre all'operatore -p per visualizzare i programmi connessi al socket come client. (Ci sono molte più opzioni utili, ma per questo compito ne bastano due.)

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

Questo indica che l'ascoltatore è il comando inixserver, che funziona con l'ID processo 20629. (E, per coincidenza, utilizza il descrittore di file 3 come socket.)

Il secondo strumento davvero utile per trovare la stessa informazione si chiama lsof. Elenca tutti i file aperti (o descrittori di file) nel sistema. Oppure, si può ottenere informazioni su un file specifico:

# 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

Il processo 20629 è un server a lungo termine, quindi è possibile attaccarlo strace utilizzando qualcosa come strace -o /tmp/trace -p 20629. Se si modifica il compito cron in un altro terminale, si otterrà l'output dei risultati della traccia con errori evidenti. Ed ecco il risultato:

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 (Da riavviare se SA_RESTART è impostato)
--- 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 (Nessun processo figlio)
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 (Da riavviare se SA_RESTART è impostato)
--- 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 (Nessun processo figlio)
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)

(L'ultimo accept() non sarà completato durante la tracciatura.) E di nuovo, purtroppo, questo risultato non contiene l'errore che stiamo cercando. Non vediamo alcun messaggio che bcrontag invii al socket o riceva da esso. Invece, c'è solo gestione del processo (clone, wait4, SIGCHLD e simili.) Questo processo genera un processo figlio che, come si può intuire, esegue il lavoro reale. E se dobbiamo catturare il suo tracciamento, aggiungi alla chiamata strace -f. Ecco cosa troveremo cercando il messaggio di errore nel nuovo risultato di 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 (Accesso negato) 
21470 write(1, "32:ZImpossibile creare file temporaneo f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatale: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (Nessun file o directory)
21470 exit_group(111)                   = ?
21470 +++ uscito con 111 +++

Ecco, questo è già qualcosa. Il processo 21470 ottiene l'errore "accesso negato" quando tenta di creare un file nel percorso tmp/spool.21470.1573692319.854640 (riferito alla directory di lavoro corrente). Se solo sapessimo qual è la directory di lavoro corrente, conosceremmo anche il percorso completo e potremmo capire perché il processo non riesce a creare il suo file temporaneo. Sfortunatamente, il processo è già uscito, quindi non possiamo semplicemente usare lsof -p 21470 per trovare la directory corrente, ma possiamo lavorare all'indietro: cercare le chiamate di sistema del PID 21470 che cambiano la directory. (Se non ci sono, il PID 21470 deve averle ereditate dal genitore, e questo già tramite lsof -p non sarà possibile scoprirlo.) Questa chiamata di sistema è chdir (il che non è difficile da scoprire utilizzando moderni motori di ricerca). Ecco il risultato delle ricerche all'indietro per i risultati della traccia, fino al 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:ZImpossibile creare un file temporaneo f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatale: log:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (Nessun file o directory)
21470 exit_group(111)                   = ?
21470 +++ uscito con 111 +++

(Se ti senti perso, potresti voler leggere il mio post precedente sulla gestione dei processi *nix e delle shell.) Quindi, il server PID 20629 non ha ricevuto il permesso di creare un file nel percorso /var/spool/cron/tmp/spool.21470.1573692319.854640. Probabilmente, la causa è nelle classiche impostazioni dei permessi del file system. Controlliamo:

# 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

Ecco dove sta il problema! Il server funziona come un cron di utente, ma solo l'utente root ha il permesso di scrivere nella directory /var/spool/cron/tmp/. Un semplice comando chown cron /var/spool/cron/tmp/ farà bcron funzionare correttamente. (Se il problema non era questo, il successivo sospetto più probabile è un modulo di sicurezza del kernel come SELinux o AppArmor, quindi controllerei i log dei messaggi del kernel usando dmesg.)

Totale

Per chi è alle prime armi con i risultati della traccia delle chiamate di sistema, è facile perdersi, ma spero di aver mostrato che sono un modo rapido per debuggare una serie di problemi comuni di deployment. Immagina di dover debuggare un'applicazione multi-processo bcron, usando un debugger passo-passo.

Analizzare i risultati della traccia all'indietro lungo la catena delle chiamate di sistema richiede abilità, ma come ho già detto, quasi sempre, usando strace, ottengo semplicemente il risultato della traccia e cerco errori partendo dalla fine. In ogni caso, strace mi aiuta a risparmiare molto tempo nella fase di debug. Spero che sia utile anche a te.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS | ProHoster