Debug della distribuzione del software con strace

Debug della distribuzione del software con strace

Il mio lavoro quotidiano riguarda principalmente la distribuzione di software, il che significa che passo molto tempo cercando di rispondere a domande come:

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

Questo è un tipo di debug leggermente diverso dal normale debug del software. Il debug regolare riguarda la logica del codice, ma 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 tutto funzioni su una macchina e non su un'altra significa che il problema è in qualche modo nell'ambiente.

Quindi, invece dei soliti strumenti di debug come gdb Ho un diverso set di strumenti per il debug della distribuzione. E il mio strumento preferito per affrontare il problema è “Perché questo software non funziona per me?” chiamato straccio.

Cos'è lo strace?

straccio è uno strumento per il “tracciamento delle chiamate di sistema”. È stato originariamente creato per Linux, ma gli stessi trucchi di debug possono essere eseguiti con strumenti per altri sistemi (DTrace o ktrace).

L'applicazione di base è molto semplice. Devi solo eseguire strace con qualsiasi comando e scaricherà tutte le chiamate di sistema (anche se prima probabilmente dovrai installarlo tu stesso straccio):

$ 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? Questo è qualcosa come un'API per il kernel del sistema operativo. Una volta il software aveva accesso diretto all’hardware su cui veniva eseguito. Se, ad esempio, aveva bisogno di visualizzare qualcosa sullo schermo, giocava con le porte o i registri mappati in memoria per i dispositivi video. Quando i sistemi informatici multitasking divennero popolari, regnò il caos mentre varie applicazioni si contendevano l’hardware. Gli errori in un'applicazione potrebbero causare il collasso di altre, se non dell'intero sistema. Quindi nella CPU sono apparse le modalità privilegio (o "protezione dell'anello"). Il kernel è diventato il più privilegiato: ha ricevuto pieno accesso all'hardware, generando applicazioni meno privilegiate che già dovevano richiedere l'accesso al kernel per interagire con l'hardware tramite chiamate di sistema.

A livello binario, una chiamata di sistema è leggermente diversa da una semplice chiamata di funzione, ma la maggior parte dei programmi utilizza un wrapper nella libreria standard. Quelli. la libreria standard POSIX C contiene una chiamata di funzione write (), che contiene tutto il codice specifico dell'architettura per la chiamata di sistema scrivere.

Debug della distribuzione del software con strace

In breve, qualsiasi interazione tra un'applicazione e il suo ambiente (sistemi informatici) viene effettuata tramite chiamate di sistema. Pertanto, quando il software funziona su una macchina ma non su un'altra, sarebbe opportuno esaminare i risultati del tracciamento delle chiamate di sistema. Più nello specifico, ecco un elenco di punti tipici che possono essere analizzati utilizzando una traccia di chiamata di sistema:

  • I/O della console
  • I/O di rete
  • Accesso al file system e I/O dei file
  • Gestire la durata di un thread di processo
  • Gestione della memoria di basso livello
  • Accesso a driver di dispositivo specifici

Quando usare strace?

In teoria, straccio utilizzato con qualsiasi programma nello spazio utente, perché qualsiasi programma nello spazio utente deve effettuare chiamate di sistema. Funziona in modo più efficiente con programmi compilati di basso livello, ma funziona anche con linguaggi di alto livello come Python se riesci a eliminare il rumore aggiuntivo proveniente dal runtime e dall'interprete.

In tutto il suo splendore straccio si manifesta durante il debug di un software che funziona bene su una macchina, ma improvvisamente smette di funzionare su un'altra, producendo vaghi messaggi su file, permessi o tentativi falliti di eseguire alcuni comandi o qualcos'altro... È un peccato, ma non è così si combinano così bene con problemi di alto livello come errori di verifica del certificato. Di solito questo richiede una combinazione straccioqualche volta traccia e strumenti di livello superiore (come lo strumento da riga di comando OpenSSL per eseguire il debug del certificato).

Utilizzeremo un server autonomo come esempio, ma il tracciamento delle chiamate di sistema può spesso essere eseguito su piattaforme di distribuzione più complesse. Devi solo scegliere gli strumenti giusti.

Semplice esempio di debug

Diciamo che vuoi eseguire la fantastica applicazione server foo, e questo è ciò che otterrai:

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

Apparentemente non è riuscito a trovare il file di configurazione che hai scritto. Ciò accade perché a volte, quando i gestori di pacchetti compilano un'applicazione, sovrascrivono le posizioni dei file previste. E se segui la guida di installazione di una distribuzione, in un'altra trovi file completamente diversi da dove ti aspettavi. Il problema potrebbe essere risolto in un paio di secondi se il messaggio di errore indicasse dove cercare il file di configurazione, ma non è così. Allora dove guardare?

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

conclusione straccio può sembrare ridondante, ma la buona notizia è che la maggior parte di essi può essere tranquillamente ignorata. Spesso è utile utilizzare l'operatore -o per salvare i risultati della traccia in un file separato:

$ strace -o /tmp/trace foo
Error opening configuration file: No such file or directory
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL)                               = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=25186, ...}) = 0
mmap(NULL, 25186, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2f12cf1000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF2113 3 > 1 260A2 "..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1824496, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2f12cef000
mmap(NULL, 1837056, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2f12b2e000
mprotect(0x7f2f12b50000, 1658880, PROT_NONE) = 0
mmap(0x7f2f12b50000, 1343488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f2f12b50000
mmap(0x7f2f12c98000, 311296, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16a000) = 0x7f2f12c98000
mmap(0x7f2f12ce5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b6000) = 0x7f2f12ce5000
mmap(0x7f2f12ceb000, 14336, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2f12ceb000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f2f12cf0500) = 0
mprotect(0x7f2f12ce5000, 16384, PROT_READ) = 0
mprotect(0x56363b08b000, 4096, PROT_READ) = 0
mprotect(0x7f2f12d1f000, 4096, PROT_READ) = 0
munmap(0x7f2f12cf1000, 25186)           = 0
openat(AT_FDCWD, "/etc/foo/config.json", O_RDONLY) = -1 ENOENT (No such file or directory)
dup(2)                                  = 3
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR)
brk(NULL)                               = 0x56363b3fb000
brk(0x56363b41c000)                     = 0x56363b41c000
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x8), ...}) = 0
write(3, "Error opening configuration file"..., 60) = 60
close(3)                                = 0
exit_group(1)                           = ?
+++ exited with 1 +++

Circa l'intera prima pagina di output straccio - Di solito si tratta di una preparazione di basso livello per il lancio. (Molte chiamate mmap, mproteggere, brk per cose come il rilevamento della memoria di basso livello e la visualizzazione delle librerie dinamiche.) In realtà, durante il debug dell'output straccio È meglio leggere dalla fine. Ci sarà una sfida qui sotto scrivere, che visualizza un messaggio di errore. Guardiamo sopra e vediamo la prima chiamata di sistema errata: la chiamata aperto, che genera un errore ENOENTE (“file o directory non trovato”) tentativo di apertura /etc/foo/config.json. Qui è dove dovrebbe trovarsi il file di configurazione.

Questo era solo un esempio, ma direi che lo utilizzo il 90% delle volte straccio, non c'è niente di più difficile da fare di questo. Di seguito è riportata una guida completa al debug passo passo:

  • Arrabbiarsi a causa di un vago messaggio su un errore di sistema da un programma
  • Riavviare il programma con straccio
  • Trova il messaggio di errore nei risultati della traccia
  • Vai più in alto finché non raggiungi la prima chiamata di sistema fallita

È molto probabile che la chiamata di sistema al punto 4 riveli cosa è andato storto.

suggerimenti

Prima di mostrarti un esempio di debug più complesso, ti mostrerò alcuni trucchi per un utilizzo efficace straccio:

l'uomo è tuo amico

Su molti sistemi *nix, è possibile ottenere un elenco completo delle chiamate di sistema al kernel eseguendo man chiamate di sistema. Vedrai cose come freno(2), il che significa che è possibile ottenere più informazioni eseguendo uomo 2 posti.

Rastrello piccolo: uomo 2 forchetta mi mostra la pagina per la shell forchetta() в GNU libc, che, a quanto pare, viene implementato chiamando clone(). Semantica delle chiamate forcella rimane lo stesso se scrivi un programma usando forchetta()ed esegui una traccia: non troverò nessuna chiamata forcella, al loro posto ci saranno clone(). Tali rastrelli ti confondono solo se inizi a confrontare la fonte con l'output straccio.

Utilizzare -o per salvare l'output in un file

straccio può generare output estesi, quindi è spesso utile archiviare i risultati della traccia in file separati (come nell'esempio sopra). Ciò aiuta anche a evitare di confondere l'output del programma con l'output straccio nella consolle.

Utilizzare -s per visualizzare più dati sugli argomenti

Potresti aver notato che la seconda metà del messaggio di errore non è mostrata nella traccia di esempio sopra. È perchè straccio default mostra solo i primi 32 byte dell'argomento stringa. Se vuoi vedere di più, aggiungi qualcosa come -s128 alla chiamata straccio.

-y rende più semplice tenere traccia di file, socket, ecc.

"Tutto è file" significa che i sistemi *nix eseguono tutti gli I/O utilizzando descrittori di file, sia che ciò si applichi a un file o a una rete o a pipe interprocessi. Ciò è utile per la programmazione, ma rende difficile tenere traccia di ciò che sta realmente accadendo quando si vede comune read и scrivere nei risultati della traccia delle chiamate di sistema.

Aggiungendo un operatore y, forzerai straccio annotare ciascun descrittore di file nell'output con una nota di ciò a cui punta.

Collega a un processo già in esecuzione con -p**

Come vedrai dall'esempio seguente, a volte è necessario tracciare un programma già in esecuzione. Se è noto che è in esecuzione come processo 1337 (ad esempio, dall'output ps), quindi puoi tracciarlo in questo modo:

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

Potrebbero essere necessari i diritti di root.

Utilizzare -f per monitorare i processi figli

straccio Per impostazione predefinita, traccia un solo processo. Se questo processo genera processi figli, allora la chiamata di sistema per generare il processo figlio potrà essere vista, ma le chiamate di sistema del processo figlio non verranno visualizzate.

Se ritieni che l'errore sia in un processo figlio, utilizza l'istruzione -f, ciò ne consentirà il tracciamento. Lo svantaggio è che l'output ti confonderà ancora di più. Quando straccio traccia un processo o un thread, mostra un singolo flusso di eventi di chiamata. Quando traccia più processi contemporaneamente, potresti vedere l'inizio di una chiamata interrotta da un messaggio , quindi - una serie di chiamate per altri rami di esecuzione e solo allora - la fine del primo <…ripresa della chiamata>. Oppure dividere tutti i risultati della traccia in file diversi, anche utilizzando l'operatore -ff (dettagli in guida su straccio).

Filtra le tracce utilizzando -e

Come puoi vedere, il risultato della traccia è un vero e proprio mucchio di tutte le possibili chiamate di sistema. Bandiera -e È possibile filtrare la traccia (vedi guida su straccio). Il vantaggio principale è che è più veloce eseguire una traccia filtrata piuttosto che eseguire una traccia completa e poi grep"a. Ad essere sincero, quasi sempre non mi interessa.

Non tutti gli errori sono negativi

Un esempio semplice e comune è un programma che cerca un file in più posti contemporaneamente, come una shell che cerca una directory che contiene un file eseguibile:

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

Euristiche come “ultima richiesta non riuscita prima di segnalare un errore” sono utili per trovare errori rilevanti. Comunque sia, è logico iniziare dalla fine.

I tutorial di programmazione C possono aiutarti a comprendere le chiamate di sistema.

Le chiamate standard alle librerie C non sono chiamate di sistema, ma solo un sottile strato superficiale. Quindi, se capisci almeno un po' come e cosa fare in C, sarà più facile per te comprendere i risultati della traccia delle chiamate di sistema. Ad esempio, se hai problemi con il debug delle chiamate ai sistemi di rete, guarda lo stesso classico Guida di Bija alla programmazione di rete.

Un esempio di debug più complesso

Ho già detto che l'esempio del debug semplice è un esempio di ciò con cui devo principalmente occuparmi quando lavoro straccio. Tuttavia, a volte è necessaria un'indagine reale, quindi ecco un esempio reale di debug più avanzato.

bcron - pianificatore di elaborazione delle attività, un'altra implementazione del demone *nix cron. È installato sul server, ma quando qualcuno tenta di modificare la pianificazione, ecco cosa succede:

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

Ok, questo significa bcron ha provato a scrivere un certo file, ma non ha funzionato e non ammette il motivo. Scoprire straccio:

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

C'è un messaggio di errore verso la fine scrivere, ma questa volta qualcosa è diverso. In primo luogo, non vi è alcun errore di chiamata di sistema rilevante, che di solito si verifica prima. In secondo luogo, è chiaro che da qualche parte qualcuno ha già letto il messaggio di errore. Sembra che il vero problema sia altrove, e brontab riproduce semplicemente il messaggio.

Se guardi uomo 2 leggi, puoi vedere che il primo argomento (3) è un descrittore di file, che *nix utilizza per tutta l'elaborazione I/O. Come faccio a scoprire cosa rappresenta il descrittore di file 3? In questo caso particolare, puoi scappare straccio con operatore y (vedi sopra) e te lo dirà automaticamente, ma per capire cose come queste, è utile sapere come leggere e analizzare i risultati della traccia.

La fonte di un descrittore di file può essere una delle tante chiamate di sistema (tutto dipende da cosa serve il descrittore: una console, un socket di rete, un file stesso o qualcos'altro), ma comunque sia, cerchiamo chiamate restituendo 3 (ovvero cerchiamo “= 3” nei risultati del tracciamento). In questo risultato ce ne sono 2: aperto in alto e presa di corrente Nel mezzo. aperto apre il file ma close(3) mostrerà quindi che si chiude di nuovo. (Rake: i descrittori di file possono essere riutilizzati quando vengono aperti e chiusi). Chiamata socket () adatto perché è l'ultimo prima read ()e si scopre che bcrontab funziona con qualcosa tramite un socket. La riga successiva mostra a quale descrittore di file è associato presa di dominio unix lungo la strada /var/esegui/bcron-spool.

Quindi, dobbiamo trovare il processo associato a presa Unix Dall'altro lato. Esistono un paio di trucchi utili a questo scopo, entrambi utili per il debug delle distribuzioni del server. Il primo è usare netstat o più recente ss (stato della presa). Entrambi i comandi mostrano le connessioni di rete attive del sistema e accettano l'estratto conto -l per descrivere le prese di ascolto, nonché l'operatore -p per visualizzare i programmi collegati al socket come client. (Ci sono molte altre opzioni utili, ma queste due sono sufficienti per questo compito.)

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

Ciò suggerisce che l'ascoltatore è il comando inixserver, in esecuzione con l'ID processo 20629. (E, per coincidenza, utilizza il descrittore di file 3 come socket.)

Il secondo strumento davvero utile per trovare le stesse informazioni si chiama lsof. Elenca tutti i file aperti (o descrittori di file) sul sistema. Oppure puoi 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 di lunga durata, quindi puoi collegarlo straccio usando qualcosa di simile strace -o /tmp/trace -p 20629. Se modifichi un processo cron in un altro terminale, riceverai un output di traccia con un errore. E questo è 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 (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21181, si_uid=998, si_status=0, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED, NULL) = 21181
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL)                   = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21200
close(4)                                = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21200, si_uid=998, si_status=111, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 111}], WNOHANG|WSTOPPED, NULL) = 21200
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL

(Scorso accettare () non verrà completato durante la tracciatura.) Ancora una volta, sfortunatamente, questo risultato non contiene l'errore che stiamo cercando. Non vediamo alcun messaggio che bcrontag invia o riceve dal socket. Invece, il controllo completo del processo (clonare, aspetta4, SIGCHLD ecc.) Questo processo genera un processo figlio che, come puoi immaginare, fa il vero lavoro. E se hai bisogno di seguire le sue tracce, aggiungi alla chiamata strace -f. Questo è ciò che troveremo quando cercheremo il messaggio di errore nel nuovo risultato con strace -f -o /tmp/traccia -p 20629:

21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

Ora, è già qualcosa. Il processo 21470 riceve un errore di "accesso negato" quando tenta di creare un file nel percorso tmp/spool.21470.1573692319.854640 (relativo alla directory di lavoro corrente). Se conoscessimo solo la directory di lavoro corrente, conosceremmo anche il percorso completo e saremmo in grado di capire perché il processo non può creare il suo file temporaneo al suo interno. Sfortunatamente, il processo è già terminato, quindi non puoi semplicemente usarlo lsof -p 21470 per trovare la directory corrente, ma puoi lavorare nella direzione opposta: cerca le chiamate di sistema PID 21470 che cambiano la directory. (Se non ce ne sono, il PID 21470 deve averli ereditati dal genitore, e questo è già stato fatto lsof -p non può essere trovato.) Questa chiamata di sistema lo è chdir (che è facile da scoprire con l'aiuto dei moderni motori di ricerca online). Ed ecco il risultato delle ricerche inverse basate sui 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:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

(Se ti sei perso, potresti leggere il mio post precedente sulla gestione dei processi e sulle shell *nix.) Pertanto, il server PID 20629 non ha ricevuto l'autorizzazione per creare un file nel percorso /var/spool/cron/tmp/spool.21470.1573692319.854640. Molto probabilmente, la ragione di ciò sono le 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

È lì che è sepolto il cane! Il server viene eseguito come utente cron, ma solo root ha il permesso di scrivere nella directory /var/spool/cron/tmp/. Comando semplice chowncron /var/spool/cron/tmp/ farà bcron funzionare correttamente. (Se non fosse questo il problema, il prossimo sospettato più probabile è un modulo di sicurezza del kernel come SELinux o AppArmor, quindi controllerei il registro dei messaggi del kernel con dmesg.)

In totale

Le tracce delle chiamate di sistema possono essere travolgenti per un principiante, ma spero di aver dimostrato che sono un modo rapido per eseguire il debug di un'intera classe di problemi di distribuzione comuni. Immagina di provare a eseguire il debug di un multiprocesso bcronutilizzando un debugger passo passo.

L'analisi dei risultati della traccia all'indietro lungo la catena delle chiamate di sistema richiede abilità, ma come ho detto, quasi sempre, utilizzo straccio, ottengo solo il risultato della traccia e cerco gli errori a partire dalla fine. Comunque, straccio mi aiuta a risparmiare molto tempo nel debug. Spero che sarà utile anche a te.

Fonte: habr.com

Aggiungi un commento