
Il mio lavoro principale consiste, per lo più, nel distribuire sistemi software, il che significa che passo un sacco di tempo cercando di rispondere a domande come questa:
- Questo software funziona per gli sviluppatori, ma non per me. Perché?
- Ieri questo software funzionava per me, ma oggi non funziona. Perché?
Questa è una sorta di debug che differisce un po' dal debug tradizionale del software. Il debug tradizionale riguarda la logica del codice, mentre il debug della distribuzione riguarda l'interazione tra il codice e l'ambiente. Anche se la radice del problema è un errore logico, il fatto che funzioni su una macchina e non su un'altra implica che ci sia qualcosa che non va nell'ambiente.
Pertanto, invece dei tradizionali strumenti di debug come gdb ho un altro insieme di strumenti per il debug della distribuzione. E il mio strumento preferito per affrontare il problema del “Perché questo software non funziona per me?” si chiama strace.
Che cos'è strace?
— è uno strumento per "tracciare le chiamate di sistema". È stato originariamente creato per Linux, ma le stesse tecniche di debug possono essere utilizzate con strumenti per altri sistemi ( o ).
L'applicazione principale è molto semplice. Devi solo eseguire strace con qualsiasi comando e invierà un dump di tutte le chiamate di sistema (anche se 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 simili a un'API per il kernel del sistema operativo. Una volta, il software aveva accesso diretto all'hardware su cui funzionava. Ad esempio, se doveva visualizzare qualcosa sullo schermo, interagiva direttamente con le porte o con i registri di memoria degli strumenti video. Quando i sistemi informatici multitasca sono diventati popolari, è regnato il caos, poiché le diverse applicazioni si contendevano l'hardware. Gli errori in un'applicazione potevano compromettere il funzionamento delle altre, se non dell'intero sistema. Così, nelle CPU sono stati introdotti i privilegi (o la "protezione a anello"). 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 — attraverso le chiamate di sistema.
A livello binario, la chiamata di sistema differisce leggermente da una semplice chiamata di funzione, tuttavia la maggior parte dei programmi utilizza un wrapper nella libreria standard. In altre parole, la libreria standard POSIX C contiene la chiamata di funzione write(), che incorpora tutto il codice dipendente dall'architettura per la chiamata di sistema write.

In sintesi, qualsiasi interazione dell'applicazione con il proprio ambiente (sistemi informatici) avviene tramite chiamate di sistema. Pertanto, quando un software funziona su una macchina e su un'altra no, è utile dare un'occhiata ai risultati del tracing delle chiamate di sistema. Più specificamente, ecco un elenco di aspetti tipici che possono essere analizzati mediante il tracing delle chiamate di sistema:
- Input e output della console
- Input e output di rete
- Accesso al filesystem e input/output di file
- Gestione del ciclo di vita dei thread di processo
- 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é qualsiasi programma nello spazio utente deve effettuare chiamate di sistema. Funziona meglio con programmi compilati e a basso livello, ma può essere utilizzato anche con linguaggi ad alto livello come Python, se si riesce a superare il rumore aggiuntivo dell'ambiente di esecuzione e dell'interprete.
Nel suo splendore strace si manifesta durante il debug di software che funziona bene su una macchina, mentre su un'altra all'improvviso smette di funzionare, fornendo messaggi poco chiari su file, permessi o tentativi non riusciti di eseguire alcuni comandi o altro ancora... Purtroppo, non si adatta così bene a problemi di alto livello come gli errori di verifica dei certificati. In genere è necessaria una combinazione strace, a volte e strumenti di livello più elevato (come lo strumento da riga di comando openssl per il debug dei certificati).
Per esempio, consideriamo il lavoro su un server isolato, ma il tracing delle chiamate di sistema può spesso essere effettuato anche su piattaforme di distribuzione più complesse. È necessario semplicemente scegliere gli strumenti appropriati.
Esempio di debug semplice
Supponiamo di voler avviare un'impressionante applicazione server foo, ma ciò che otteniamo è questo:
$ foo
Errore nell'aprire il file di configurazione: Nessun file o directory di questo tipoÈ evidente 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 attesa dei file. E se segui le istruzioni di installazione per una distribuzione, in un'altra puoi trovare i file completamente in un luogo diverso da quello che ti aspettavi. Il problema potrebbe essere risolto in pochi secondi se il messaggio di errore indicasse dove cercare il file di configurazione, ma non lo fa. Allora, dove cercare?
Se hai accesso al codice sorgente, puoi leggerlo ed ottenere tutte le informazioni necessarie. Un buon piano di riserva, ma non è la soluzione più rapida. Puoi ricorrere a un debugger passo-passo come gdb e osservare cosa fa il programma, ma è molto più efficace utilizzare uno strumento progettato specificamente per mostrare le interazioni con l'ambiente: strace.
Conclusione strace può sembrare eccessivo, ma la buona notizia è che gran parte di esso può essere tranquillamente ignorata. Spesso è utile utilizzare l'opzione -o per salvare i risultati della traccia in un file separato:
$ strace -o /tmp/trace foo
Erro nell'apertura del file di configurazione: file o directory non trovati
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL) = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (file o directory non trovati)
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 (file o directory non trovati)
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, "Erro nell'apertura del file di configurazione"..., 60) = 60
close(3) = 0
exit_group(1) = ?
+++ uscito con 1 +++Circa tutta la prima pagina di output strace — è generalmente una preparazione a basso livello per l'avvio. (Molte chiamate mmap, mprotect, brk per cose come la rilevazione di memoria a basso livello e il mapping delle librerie dinamiche.) In effetti, durante il debug, le uscite strace è meglio leggerle dalla fine. In fondo ci sarà una chiamata write, che restituisce un messaggio d'errore. Guardiamo sopra, e vediamo la prima chiamata di sistema errata — la chiamata openat, che restituisce errore ENOENT ('file o directory non trovati'), che tentava di aprire /etc/foo/config.json. Qui dovrebbe trovarsi il file di configurazione.
Era solo un esempio, ma direi che il 90% delle volte che utilizzo strace, non è necessario fare nulla di molto più complicato. Qui sotto c'è una guida passo-passo completa per il debug:
- Infastidirsi per un messaggio d'errore poco chiaro da parte del programma
- Riavviare il programma con strace
- Cercare nei risultati di tracciamento il messaggio d'errore
- Andare verso l'alto, fino a trovare la prima chiamata di sistema che ha avuto esito negativo
È altamente probabile che la chiamata di sistema nel 4° passaggio mostri cosa non va.
Suggerimenti
Prima di mostrarti un esempio di debug più complesso, ti darò alcuni suggerimenti per un utilizzo efficace strace:
man — è il tuo amico
Su molti sistemi *nix, l'elenco completo delle chiamate di sistema al kernel può essere ottenuto eseguendo man syscalls. Vedrai cose come brk(2), quindi puoi ottenere ulteriori informazioni eseguendo man 2 brk.
Piccoli problemi: man 2 fork mi mostra la pagina per la shell fork() in GNU libc, che, a quanto pare, è implementato tramite la chiamata clone(). La semantica della chiamata fork rimane la stessa, se scrivi un programma che utilizza fork(), e avvii il tracing — non troverò chiamate fork, al loro posto ci saranno clone(). Questi piccoli problemi possono confondere quando inizi a confrontare il sorgente con l'output strace.
Utilizza -o per salvare l'output in un file
strace può generare un output ampio, quindi spesso è utile conservare i risultati del tracing in file separati (come nell'esempio sopra). Inoltre, aiuta a non confondere l'output del programma con quello strace nella console.
Usa -s per visualizzare più dati dell'argomento
Probabilmente hai notato che la seconda metà del messaggio di errore non è mostrata nell'esempio di tracing sopra. Questo perché strace per impostazione predefinita mostra solo i primi 32 byte dell'argomento della stringa. Se desideri vedere di più, aggiungi qualcosa come -s 128 alla chiamata strace.
-u facilita il tracciamento dei file socket e altro.
"Tutto è un file" significa che i sistemi *nix eseguono tutte le operazioni di input-output utilizzando descrittori di file, sia che si tratti di file, reti o canali di comunicazione interprocesso. Questo è comodo per la programmazione, ma rende difficile seguire ciò che sta realmente accadendo quando vedi l'output generale read e write dei risultati del tracing delle chiamate di sistema.
Aggiungendo l'opzione -u, farai sì che strace annoti ogni descrittore di file nell'output con una nota su a cosa punta.
Collegati a un processo già in esecuzione con -p**
Come si vedrà dall'esempio qui sotto, a volte è necessario tracciare un programma già in esecuzione. Se si sa che è in esecuzione come processo 1337 (diciamo, dai risultati ps), allora puoi tracciarne uno in questo modo:
$ strace -p 1337
...output del tracing delle chiamate di sistema...Potrebbe essere necessario avere i privilegi di root.
Usa -f per seguire i processi secondari
strace Per impostazione predefinita traccia solo un processo. Se questo processo genera processi figlio, sarà possibile vedere la chiamata di sistema per generare un processo figlio, ma le chiamate di sistema del processo figlio non verranno visualizzate.
Se pensate che l'errore sia nel processo figlio, usate l'operatore -f, questo attiverà la sua tracciatura. Il lato negativo è che l'output vi confonderà ulteriormente. Quando strace traccia un processo o un ramo, mostra un flusso unico di eventi di chiamate. Quando traccia più processi contemporaneamente, potreste vedere l'inizio di una chiamata, interrotto dal messaggio <unfinished …>, seguito da un insieme di chiamate per altri rami di esecuzione, e solo dopo — la fine della prima con <… foocall resumed>. Oppure dividete tutti i risultati della tracciatura in file diversi, usando anche l'operatore -ff (dettagli in da strace).
Filtra la tracciatura usando -e
Come potete vedere, il risultato della tracciatura è un vero e proprio mucchio di tutte le possibili chiamate di sistema. Con il flag -e si può filtrare la tracciatura (vedi. da strace). Il principale vantaggio è che avviare la tracciatura con filtraggio è più veloce che fare una tracciatura completa e poi grep`arla. A dire il vero, per me è quasi sempre indifferente.
Non tutti gli errori sono negativi
Un esempio semplice e comune è un programma che cerca un file in più posizioni, come una shell che cerca dove si trova il file eseguibile:
$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (File o directory non esistente)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (File o directory non esistente)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...L'euristica del tipo "l'ultima richiesta fallita prima del messaggio di errore" è utile per cercare errori pertinenti. In ogni caso, ha senso iniziare dalla fine.
Le guide alla programmazione in C aiutano a capire le chiamate di sistema
Le chiamate standard alle librerie C non sono chiamate di sistema, ma solo uno strato superficiale sottile. Quindi, se capite anche solo un po' come fare le cose in C, sarà più facile comprendere i risultati della tracciatura delle chiamate di sistema. Ad esempio, se avete problemi a debug delle chiamate ai sistemi di rete, consultate la classica .
Un esempio di debug più complesso
Ho già detto che un esempio semplice di debug è un caso con cui mi confronto per lo più nel mio lavoro con strace. Tuttavia, a volte è necessario un vero e proprio detective, quindi ecco un esempio reale di debug più complesso.
— un pianificatore di attività, un'altra implementazione di un demone *nix cron. È installato sul server, ma quando qualcuno cerca di modificare il programma, succede questo:
# crontab -e -u logs
bcrontab: Fatal: Could not create temporary fileBene, quindi bcron ha tentato di scrivere un certo file, ma non ci è riuscito e non ammette perché. Analizziamo 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 qualcosa è diverso. In primo luogo, non c'è un errore di sistema rilevante che di solito si verifica prima. In secondo luogo, sembra che qualcuno abbia già letto il messaggio di errore. Sembra che il vero problema sia altrove e che bcrontab riproduca solo il messaggio.
Se guardi a man 2 read, puoi vedere che il primo argomento (3) è un descrittore di file, che *nix utilizza per tutte le operazioni di input/output. Come scoprire cosa rappresenta il descrittore di file 3? In questo caso specifico, puoi eseguire strace con l'operatore -u (vedi sopra), e ti dirà automaticamente, tuttavia, per calcolare cose simili, è utile sapere come leggere e analizzare i risultati del tracciamento.
Il sorgente del descrittore di file può essere uno dei molti chiamati di sistema (tutto dipende da cosa è il descrittore — per la console, un socket di rete, un file stesso o qualcos'altro), ma in ogni caso, cerchiamo le chiamate che restituiscono 3 (cioè cerchiamo «= 3» nei risultati del tracciamento). In questo risultato ce ne sono 2: openat nella parte superiore e socket nel mezzo. openat apre un file, ma close(3) dopo mostra che viene chiuso di nuovo. (Attenzione: i descrittori di file possono essere riutilizzati quando vengono aperti e chiusi). La chiamata socket() è quella giusta, poiché è ultima prima di read(), e si scopre che bcrontab sta operando 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 legato a unix socket dall'altra parte. Per questo ci sono un paio di trucchi eleganti, e entrambi saranno utili per il debug delle implementazioni del server. Il primo è utilizzare netstat o un più nuovo ss (stato del socket). Entrambi i comandi mostrano connessioni di rete attive del sistema e utilizzano l'operatore -l per descrivere i socket in ascolto, così come l'operatore -p per visualizzare i programmi connessi al socket come client. (Ci sono molte più opzioni utili, ma per questo compito bastano queste 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'ascolto è un comando inixserver, che lavora con l'ID processo 20629. (E, a coincidenza, utilizza il descrittore di file 3 come socket.)
Il secondo strumento realmente utile per trovare le stesse informazioni si chiama lsof. Esso 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=STREAMIl processo 20629 è un server a lunga vita, quindi si può attaccare ad esso strace utilizzando qualcosa come strace -o /tmp/trace -p 20629. Se si modifica il cron job in un altro terminale, otterremo l'output dei risultati di tracing con l'errore che si verifica. 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 il tracing.) E di nuovo, per quanto possa dispiacere, questo risultato non contiene l'errore che stiamo cercando. Non vediamo alcun messaggio che bcrontag invia al socket o riceve da esso. Invece ci sono solo controlli di processo (clone, wait4, SIGCHLD e altro.) Questo processo genera un processo figlio che, come si può intuire, esegue il lavoro effettivo. E se è necessario tracciare il suo percorso, aggiungi alla chiamata strace -f. Ecco cosa troveremo cercando un messaggio d'errore nel nuovo risultato con 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 (Permesso negato)
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 +++Ecco, questo è già un passo avanti. Il processo 21470 riceve l'errore "accesso negato" quando tenta di creare un file nel percorso tmp/spool.21470.1573692319.854640 (relativo alla cartella di lavoro corrente). Se solo sapessimo qual è la cartella di lavoro corrente, conosceremmo anche il percorso completo e potremmo scoprire perché il processo non riesce a creare il suo file temporaneo. Sfortunatamente, il processo è già terminato, quindi non possiamo semplicemente usare lsof -p 21470 per trovare la cartella corrente, ma possiamo lavorare a ritroso — cercare chiamate di sistema del PID 21470 che cambiano directory. (Se non ci sono, il PID 21470 deve averle ereditate dal genitore, e questo è già tramite lsof -p non si può scoprire.) Questa chiamata di sistema è chdir (che non è difficile scoprire con i moderni motori di ricerca). Ecco il risultato delle ricerche retroattive in base ai risultati della tracciatura, 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 (Permesso negato)
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 perdi, potresti voler leggere il mio post precedente .) Quindi, il server PID 20629 non ha ricevuto l'autorizzazione per creare un file nel percorso /var/spool/cron/tmp/spool.21470.1573692319.854640. Probabilmente, la causa sono le classiche impostazioni delle autorizzazioni del file system. Verifichiamo:
# 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-spoolEcco dove si trova il problema! Il server gira come cron utente, ma solo root ha il permesso di scrivere nella cartella /var/spool/cron/tmp/. Un semplice comando chown cron /var/spool/cron/tmp/ risolverà bcron lavorare correttamente. (Se il problema non era questo, il prossimo sospettato più probabile è il modulo di sicurezza del kernel, tipo SELinux o AppArmor, quindi controllerei il registro dei messaggi del kernel usando dmesg.)
Totale
Un principiante nei risultati delle tracce delle chiamate di sistema può sentirsi sopraffatto, ma spero di aver dimostrato che sono un modo veloce per fare debug di tutta una serie di problemi comuni di distribuzione. Immagina di dover fare debug in un ambiente multithreaded bcron, usando un debugger a passo-passo.
Analizzare i risultati della traccia al contrario 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 un sacco di tempo nel debug. Spero che sarà utile anche per te.
Fonte: habr.com
