Implementación de software de depuración con strace

Implementación de software de depuración con strace

O meu traballo diario é principalmente a implementación de software, o que significa que paso moito tempo intentando responder preguntas como:

  • Este software funciona para o programador, pero non para min. Por que?
  • Onte funcionou para min este software, pero hoxe non. Por que?

Este é un tipo de depuración que é lixeiramente diferente da depuración de software normal. A depuración regular trata sobre a lóxica do código, pero a depuración de despregamento trata sobre a interacción entre o código e o ambiente. Aínda que a raíz do problema sexa un erro lóxico, o feito de que todo funcione nunha máquina e non noutra significa que o problema está dalgún xeito no ambiente.

Entón, no canto das ferramentas de depuración habituais como gdb Teño un conxunto diferente de ferramentas para a implementación de depuración. E a miña ferramenta favorita para xestionar o problema como "Por que este software non funciona para min?" chamado strace.

Que é strace?

strace é unha ferramenta para o "rastrexo de chamadas do sistema". Foi creado orixinalmente para Linux, pero os mesmos trucos de depuración pódense facer con ferramentas para outros sistemas (DTrace ou ktrace).

A aplicación básica é moi sinxela. Só tes que executar strace con calquera comando e volcará todas as chamadas do sistema (aínda que primeiro probablemente teñas que instalalo ti mesmo 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 +++

Cales son estas chamadas de sistema? Isto é algo así como unha API para o núcleo do sistema operativo. Érase unha vez, o software tiña acceso directo ao hardware no que funcionaba. Se, por exemplo, necesitaba mostrar algo na pantalla, xogaba con portos ou rexistros mapeados de memoria para dispositivos de vídeo. Cando os sistemas informáticos multitarefa se fixeron populares, o caos reinou mentres varias aplicacións pelexaban polo hardware. Os erros nunha aplicación poden derrubar outras, se non todo o sistema. Entón apareceron os modos de privilexio (ou "protección de anel") na CPU. O kernel converteuse no máis privilexiado: recibiu acceso total ao hardware, xerando aplicacións menos privilexiadas que xa tiñan que solicitar acceso ao kernel para interactuar co hardware mediante chamadas ao sistema.

A nivel binario, unha chamada de sistema é lixeiramente diferente dunha simple chamada de función, pero a maioría dos programas usan un envoltorio na biblioteca estándar. Eses. a biblioteca estándar POSIX C contén unha chamada de función write (), que contén todo o código específico da arquitectura para a chamada do sistema escribir.

Implementación de software de depuración con strace

En definitiva, calquera interacción entre unha aplicación e o seu entorno (sistemas informáticos) realízase mediante chamadas ao sistema. Polo tanto, cando o software funciona nunha máquina pero non noutra, sería bo mirar os resultados do rastrexo das chamadas do sistema. Máis concretamente, aquí tes unha lista de puntos típicos que se poden analizar mediante un rastrexo de chamadas ao sistema:

  • E/S da consola
  • E/S de rede
  • Acceso ao sistema de ficheiros e E/S de ficheiros
  • Xestionar a vida útil dun fío de proceso
  • Xestión de memoria de baixo nivel
  • Acceso a controladores de dispositivos específicos

Cando usar strace?

En teoría, strace usado con calquera programa no espazo de usuario, porque calquera programa no espazo de usuario debe facer chamadas ao sistema. Funciona de forma máis eficiente con programas compilados de baixo nivel, pero tamén funciona con linguaxes de alto nivel como Python se pode cortar o ruído adicional do tempo de execución e do intérprete.

En todo o esplendor strace maniféstase durante a depuración de software que funciona ben nunha máquina, pero que de súpeto deixa de funcionar noutra, producindo mensaxes vagas sobre ficheiros, permisos ou intentos infrutuosos de executar algúns comandos ou outra cousa... É unha mágoa, pero non o fai. combinar tan ben con problemas de alto nivel como erros de verificación de certificados. Normalmente, isto require unha combinación strace, ás veces ltrace e ferramentas de nivel superior (como a ferramenta de liña de comandos openssl para depurar o certificado).

Usaremos un servidor autónomo como exemplo, pero o rastrexo de chamadas do sistema a miúdo pódese facer en plataformas de implantación máis complexas. Só tes que escoller as ferramentas correctas.

Exemplo sinxelo de depuración

Digamos que queres executar a incrible aplicación de servidor foo, e isto é o que acabas:

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

Ao parecer, non puido atopar o ficheiro de configuración que escribiu. Isto ocorre porque ás veces, cando os xestores de paquetes compilan unha aplicación, anulan as localizacións de ficheiros esperadas. E se segues a guía de instalación dunha distribución, noutra atoparás ficheiros completamente diferentes dos que esperabas. O problema podería resolverse nun par de segundos se a mensaxe de erro indicaba onde buscar o ficheiro de configuración, pero non é así. Entón, onde mirar?

Se tes acceso ao código fonte, podes lelo e descubrir todo. Un bo plan de copia de seguridade, pero non a solución máis rápida. Podes recorrer a un depurador paso a paso como gdb e vexa o que fai o programa, pero é moito máis eficaz utilizar unha ferramenta deseñada especificamente para mostrar a interacción co medio: strace.

Saída strace pode parecer redundante, pero a boa noticia é que a maioría pode ignorarse con seguridade. Moitas veces é útil usar o operador -o para gardar os resultados do rastrexo nun ficheiro separado:

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

Aproximadamente toda a primeira páxina da saída strace - Esta é normalmente unha preparación de baixo nivel para o lanzamento. (Moitas chamadas mmap, mprotect, pluma para cousas como detectar memoria de baixo nivel e mostrar bibliotecas dinámicas.) En realidade, durante a depuración da saída strace É mellor ler desde o final. A continuación haberá un desafío escribir, que mostra unha mensaxe de erro. Miramos arriba e vemos a primeira chamada de sistema errónea: a chamada aberto, que arroxa un erro ENOENT ("non se atopou o ficheiro ou directorio") tentando abrir /etc/foo/config.json. Aquí é onde debería estar o ficheiro de configuración.

Este foi só un exemplo, pero eu diría que o 90% do tempo que uso strace, non hai nada máis difícil de facer que isto. A continuación móstrase unha guía completa de depuración paso a paso:

  • Molétese por mor dunha mensaxe vaga sobre un erro do sistema e dun programa
  • Reinicie o programa con strace
  • Busca a mensaxe de erro nos resultados do rastrexo
  • Vaia máis arriba ata acadar a primeira chamada do sistema fallida

É moi probable que a chamada do sistema no paso 4 revele o que fallou.

Consellos

Antes de mostrarche un exemplo de depuración máis complexa, mostrareiche algúns trucos para un uso eficaz strace:

o home é o teu amigo

En moitos sistemas *nix, pódese obter unha lista completa de chamadas ao sistema ao núcleo executando home syscalls. Verás cousas como freo (2), o que significa que se pode obter máis información executando home 2 brk.

Rastrillo pequeno: home 2 garfo móstrame a páxina para o shell garfo () в GNU libc, que, ao parecer, se implementa chamando clonar (). Semántica de chamadas garfo segue sendo o mesmo se escribe un programa usando garfo (), e realiza un rastrexo: non atoparei ningunha chamada garfo, no canto deles haberá clonar (). Estes rastrillos só te confunden se comezas a comparar a fonte coa saída strace.

Use -o para gardar a saída nun ficheiro

strace pode xerar unha saída extensa, polo que adoita ser útil almacenar os resultados de rastrexo en ficheiros separados (como no exemplo anterior). Isto tamén axuda a evitar confundir a saída do programa coa saída strace na consola.

Use -s para ver máis datos dos argumentos

Quizais teña notado que a segunda metade da mensaxe de erro non se mostra no trazo de exemplo anterior. É porque strace por defecto mostra só os primeiros 32 bytes do argumento cadea. Se queres ver máis, engade algo así -S 128 á chamada strace.

-y facilita o seguimento de ficheiros, sockets, etc.

"Todo é ficheiro" significa que os sistemas *nix fan todas as E/S usando descritores de ficheiros, tanto se se aplica a un ficheiro como a unha rede ou canalizacións entre procesos. Isto é conveniente para a programación, pero dificulta o seguimento do que realmente está pasando cando ves común ler и escribir nos resultados do rastrexo da chamada do sistema.

Engadindo un operador , forzarás strace anote cada descritor de ficheiro na saída cunha nota do que apunta.

Anexo a un proceso que xa está en execución con -p**

Como verás no seguinte exemplo, ás veces cómpre rastrexar un programa que xa se está a executar. Se se sabe que se está a executar como o proceso 1337 (por exemplo, dende a saída ps), entón podes rastrexalo así:

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

Pode que necesites dereitos de root.

Use -f para supervisar procesos fillos

strace Por defecto, só rastrexa un proceso. Se este proceso xera procesos fillos, pódese ver a chamada ao sistema para xerar o proceso fillo, pero as chamadas ao sistema do proceso fillo non se mostrarán.

Se pensas que o erro está nun proceso fillo, utiliza a declaración -f, isto permitirá o seu rastrexo. A desvantaxe disto é que a saída o confundirá aínda máis. Cando strace rastrexa un proceso ou un fío, mostra un único fluxo de eventos de chamada. Cando rastrexa varios procesos á vez, podes ver o inicio dunha chamada interrompido por unha mensaxe , entón - unha morea de chamadas para outras ramas de execución, e só entón - o final da primeira <...foocall retomada>. Ou divida todos os resultados da traza en ficheiros diferentes, tamén usando o operador -ff (detalles en liderado en strace).

Filtrar trazos usando -e

Como podes ver, o resultado do rastrexo é unha verdadeira pila de todas as posibles chamadas ao sistema. Bandeira -e Pode filtrar o rastro (ver liderado en strace). A principal vantaxe é que é máis rápido executar un trazo filtrado que facer un trazo completo e despois grep'ás. Para ser sincero, case sempre non me importa.

Non todos os erros son malos

Un exemplo sinxelo e común é un programa que busca un ficheiro en varios lugares á vez, como un shell que busca un directorio que contén un ficheiro executable:

$ 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
...

Heurísticas como "última solicitude fallida antes de informar un erro" son boas para atopar erros relevantes. Sexa como for, é lóxico comezar dende o final.

Os tutoriais de programación en C poden axudarche a comprender as chamadas do sistema.

As chamadas estándar ás bibliotecas C non son chamadas de sistema, senón só unha capa superficial delgada. Entón, se entendes polo menos un pouco como e que facer en C, será máis fácil comprender os resultados do rastrexo de chamadas do sistema. Por exemplo, tes problemas para depurar chamadas a sistemas de rede, mira o mesmo clásico Guía de Bija para a programación en rede.

Un exemplo de depuración máis complexo

Xa dixen que o exemplo de depuración simple é un exemplo do que teño que tratar sobre todo cando traballo strace. Non obstante, ás veces é necesaria unha investigación real, polo que aquí tes un exemplo real de depuración máis avanzada.

bcron - planificador de procesamento de tarefas, outra implementación do daemon *nix cron. Está instalado no servidor, pero cando alguén intenta editar a programación, isto é o que ocorre:

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

Vale, iso significa bcron intentou escribir un determinado ficheiro, pero non funcionou, e non admitirá por que. Descubrindo 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 +++

Hai unha mensaxe de erro preto do final escribir, pero esta vez algo é diferente. En primeiro lugar, non hai ningún erro de chamada do sistema relevante, que normalmente ocorre antes. En segundo lugar, está claro que nalgún lugar alguén xa leu a mensaxe de erro. Parece que o verdadeiro problema está noutro lugar, e bcrontab simplemente reproduce a mensaxe.

Se miras home 2 ler, podes ver que o primeiro argumento (3) é un descritor de ficheiros, que *nix usa para todo o procesamento de E/S. Como podo saber que representa o descritor de ficheiro 3? Neste caso particular, pode executar strace con operador (ver arriba) e indicarache automaticamente, pero para descubrir cousas como esta, é útil saber como ler e analizar os resultados do rastrexo.

A fonte dun descritor de ficheiro pode ser unha das moitas chamadas ao sistema (todo depende do que sexa o descritor: unha consola, unha toma de rede, o propio ficheiro ou outra cousa), pero sexa como for, buscamos chamadas devolvendo 3 (é dicir, buscamos "= 3" nos resultados do trazado). Neste resultado hai 2 deles: aberto no máis alto e socket No medio. aberto abre o ficheiro pero pechar(3) mostrará entón que se pecha de novo. (Rake: os descritores de ficheiros pódense reutilizar cando se abren e se pechan). Chamar socket() adecuado porque é o último de antes ler (), e resulta que bcrontab funciona con algo a través dun socket. A seguinte liña mostra que o descritor do ficheiro está asociado socket de dominio unix de camiño /var/run/bcron-spool.

Polo tanto, necesitamos atopar o proceso asociado socket unix do outro lado. Hai un par de trucos sinxelos para este fin, os dous son útiles para depurar implementacións de servidores. O primeiro é usar netstat ou máis recente ss (estado do enchufe). Ambos os comandos mostran as conexións de rede activas do sistema e toman a declaración -l para describir as tomas de escoita, así como o operador -p para mostrar programas conectados ao socket como cliente. (Hai moitas máis opcións útiles, pero estas dúas son suficientes para esta tarefa.)

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

Isto suxire que o oínte é o comando inixserver, en execución co ID de proceso 20629. (E, casualmente, usa o descritor de ficheiro 3 como socket).

A segunda ferramenta realmente útil para atopar a mesma información chámase lsof. Lista todos os ficheiros abertos (ou descritores de ficheiros) no sistema. Ou pode obter información sobre un ficheiro específico:

# 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

O proceso 20629 é un servidor de longa duración, polo que podes anexalo strace usando algo así strace -o /tmp/trace -p 20629. Se editas un traballo cron noutro terminal, recibirás unha saída de rastrexo cun erro. E aquí está o resultado:

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

(Último aceptar () non se completará ao rastrexar.) De novo, por desgraza, este resultado non contén o erro que estamos a buscar. Non vemos ningunha mensaxe que bcrontag envía ou recibe dende o socket. En cambio, controle o proceso completo (clonar, agarda 4, SIGCHLD etc.) Este proceso xera un proceso fillo que, como podes adiviñar, fai o verdadeiro traballo. E se precisas seguir o seu rastro, engádese á chamada strace -f. Isto é o que atoparemos cando busquemos a mensaxe de erro no novo resultado 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 (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 +++

Agora, iso é algo. O proceso 21470 recibe un erro de "acceso denegado" ao tentar crear un ficheiro na ruta tmp/spool.21470.1573692319.854640 (relativo ao directorio de traballo actual). Se só coñecemos o directorio de traballo actual, tamén coñeceriamos a ruta completa e poderiamos descubrir por que o proceso non pode crear o seu ficheiro temporal nel. Desafortunadamente, o proceso xa rematou, polo que non podes usar lsof -p 21470 para atopar o directorio actual, pero pode traballar na dirección oposta: busque chamadas de sistema PID 21470 que cambien o directorio. (Se non os hai, o PID 21470 debe ter herdados do seu pai, e xa está lsof -p non se pode descubrir.) Esta chamada ao sistema é chdir (que é fácil de descubrir coa axuda dos modernos buscadores en liña). E aquí está o resultado das buscas inversas baseadas nos resultados do rastrexo, ata o PID do servidor 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 estás perdido, podes ler a miña publicación anterior sobre a xestión de procesos e shells *nix.) Polo tanto, o PID do servidor 20629 non recibiu permiso para crear un ficheiro na ruta /var/spool/cron/tmp/spool.21470.1573692319.854640. O máis probable é que a razón sexa a clásica configuración de permisos do sistema de ficheiros. Comprobamos:

# 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

¡Aí está enterrado o can! O servidor execútase como un usuario cron, pero só o root ten permiso para escribir no directorio /var/spool/cron/tmp/. Comando sinxelo chown cron /var/spool/cron/tmp/ forzará bcron funcionar correctamente. (Se ese non fose o problema, entón o seguinte sospeitoso máis probable é un módulo de seguridade do núcleo como SELinux ou AppArmor, polo que comprobaría o rexistro de mensaxes do núcleo con dmesg.)

En total

Os rastrexos das chamadas do sistema poden ser abrumadores para un principiante, pero espero demostrar que son unha forma rápida de depurar toda unha clase de problemas comúns de implementación. Imaxina tentar depurar un multiproceso bcronusando un depurador paso a paso.

Analizar os resultados da traza cara atrás ao longo da cadea de chamadas do sistema require habilidade, pero como dixen, case sempre, usar strace, acabo de obter o resultado do rastrexo e busco erros comezando polo final. De todos os xeitos, strace axúdame a aforrar moito tempo na depuración. Espero que vos sexa útil tamén.

Fonte: www.habr.com

Engadir un comentario