Implementación de software de depuración con strace

Implementación de software de depuración con strace

Mi trabajo diario es principalmente la implementación de software, lo que significa que paso mucho tiempo tratando de responder preguntas como:

  • Este software funciona para el desarrollador, pero no para mí. ¿Por qué?
  • Ayer este software me funcionó, pero hoy no. ¿Por qué?

Este es un tipo de depuración ligeramente diferente de la depuración de software normal. La depuración regular tiene que ver con la lógica del código, pero la depuración de implementación tiene que ver con la interacción entre el código y el entorno. Incluso si la raíz del problema es un error lógico, el hecho de que todo funcione en una máquina y no en otra significa que el problema está de alguna manera en el entorno.

Entonces, en lugar de las herramientas de depuración habituales como gdb Tengo un conjunto diferente de herramientas para la implementación de depuración. Y mi herramienta favorita para abordar problemas como "¿Por qué este software no funciona para mí?" llamado rastro.

¿Qué es la traza?

rastro es una herramienta para el “seguimiento de llamadas del sistema”. Fue creado originalmente para Linux, pero se pueden realizar los mismos trucos de depuración con herramientas para otros sistemas (DTrace o ktrace).

La aplicación básica es muy sencilla. Sólo necesitas ejecutar strace con cualquier comando y volcará todas las llamadas al sistema (aunque primero probablemente tendrás que instalarlo tú mismo). rastro):

$ strace echo Hello
...Snip lots of stuff...
write(1, "Hellon", 6)                  = 6
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

¿Cuáles son estas llamadas al sistema? Esto es algo así como una API para el kernel del sistema operativo. Érase una vez, el software tenía acceso directo al hardware en el que se ejecutaba. Si, por ejemplo, necesitaba mostrar algo en la pantalla, jugaba con puertos o registros mapeados en memoria para dispositivos de video. Cuando los sistemas informáticos multitarea se hicieron populares, reinó el caos mientras varias aplicaciones luchaban por el hardware. Los errores en una aplicación podrían hacer caer otras, si no todo el sistema. Luego aparecieron los modos de privilegio (o “protección de anillo”) en la CPU. El kernel se convirtió en el más privilegiado: recibió acceso completo al hardware, generando aplicaciones menos privilegiadas que ya tenían que solicitar acceso al kernel para interactuar con el hardware a través de llamadas al sistema.

A nivel binario, una llamada al sistema es ligeramente diferente de una simple llamada a una función, pero la mayoría de los programas utilizan un contenedor en la biblioteca estándar. Aquellos. la biblioteca estándar POSIX C contiene una llamada de función escribir(), que contiene todo el código específico de la arquitectura para la llamada al sistema escribir.

Implementación de software de depuración con strace

En definitiva, cualquier interacción entre una aplicación y su entorno (sistemas informáticos) se realiza a través de llamadas al sistema. Por lo tanto, cuando el software funciona en una máquina pero no en otra, sería bueno observar los resultados del seguimiento de llamadas del sistema. Más específicamente, aquí hay una lista de puntos típicos que se pueden analizar mediante un seguimiento de llamadas al sistema:

  • E/S de consola
  • E/S de red
  • Acceso al sistema de archivos y E/S de archivos
  • Gestionar la vida útil de un hilo de proceso
  • Gestión de memoria de bajo nivel
  • Acceso a controladores de dispositivos específicos

¿Cuándo usar strace?

En teoria, rastro Se utiliza con cualquier programa en el espacio del usuario, porque cualquier programa en el espacio del usuario debe realizar llamadas al sistema. Funciona de manera más eficiente con programas compilados de bajo nivel, pero también funciona con lenguajes de alto nivel como Python si puede eliminar el ruido adicional del tiempo de ejecución y el intérprete.

En todo su esplendor rastro se manifiesta durante la depuración de software que funciona bien en una máquina, pero de repente deja de funcionar en otra, produciendo mensajes vagos sobre archivos, permisos o intentos fallidos de ejecutar algunos comandos u otra cosa... Es una lástima, pero no es así. combinan muy bien con problemas de alto nivel, como errores de verificación de certificados. Generalmente esto requiere una combinación rastroa veces traza y herramientas de nivel superior (como la herramienta de línea de comando openssl para depurar el certificado).

Usaremos un servidor independiente como ejemplo, pero el seguimiento de llamadas al sistema a menudo se puede realizar en plataformas de implementación más complejas. Sólo necesitas elegir las herramientas adecuadas.

Ejemplo de depuración sencillo

Digamos que quieres ejecutar la increíble aplicación de servidor foo, y esto es con lo que terminas:

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

Aparentemente no pudo encontrar el archivo de configuración que escribiste. Esto sucede porque a veces, cuando los administradores de paquetes compilan una aplicación, anulan las ubicaciones esperadas de los archivos. Y si sigues la guía de instalación de una distribución, en otra encontrarás archivos completamente diferentes a los que esperabas. El problema podría resolverse en un par de segundos si el mensaje de error indicara dónde buscar el archivo de configuración, pero no es así. Entonces, ¿dónde buscar?

Si tienes acceso al código fuente, puedes leerlo y descubrirlo todo. Un buen plan de respaldo, pero no la solución más rápida. Puedes recurrir a un depurador paso a paso como gdb y vea lo que hace el programa, pero es mucho más efectivo usar una herramienta que esté diseñada específicamente para mostrar la interacción con el entorno: rastro.

conclusión rastro Puede parecer redundante, pero la buena noticia es que la mayor parte se puede ignorar con seguridad. A menudo resulta útil utilizar el operador -o para guardar los resultados del seguimiento en un archivo independiente:

$ 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 la primera página del resultado. rastro - Suele ser una preparación de bajo nivel para el lanzamiento. (Muchas llamadas mmap, mproteger, freno para cosas como detectar memoria de bajo nivel y mostrar bibliotecas dinámicas). En realidad, durante la depuración la salida rastro Es mejor leer desde el final. Habrá un desafío a continuación. escribir, que muestra un mensaje de error. Miramos arriba y vemos la primera llamada al sistema errónea: la llamada abierto en, que arroja un error ENOENTE (“archivo o directorio no encontrado”) intentando abrir /etc/foo/config.json. Aquí es donde debería estar el archivo de configuración.

Este fue sólo un ejemplo, pero yo diría que el 90% del tiempo que uso rastro, no hay nada mucho más difícil de hacer que esto. A continuación se muestra una guía completa de depuración paso a paso:

  • Molestarse por un mensaje vago sobre un error del sistema de un programa
  • Reinicie el programa con rastro
  • Encuentre el mensaje de error en los resultados del seguimiento.
  • Vaya más alto hasta llegar a la primera llamada fallida al sistema

Es muy probable que la llamada al sistema en el paso 4 revele qué salió mal.

consejos

Antes de mostrarte un ejemplo de depuración más compleja, te mostraré algunos trucos para un uso eficaz. rastro:

el hombre es tu amigo

En muchos sistemas *nix, se puede obtener una lista completa de llamadas del sistema al kernel ejecutando hombre llama al sistema. Verás cosas como brk (2), lo que significa que se puede obtener más información ejecutando hombre 2 habitaciones.

Rastrillo pequeño: hombre 2 tenedor me muestra la pagina del shell tenedor() в librería GNU, que resulta que se implementa llamando clon(). Semántica de llamadas tenedor sigue siendo el mismo si escribes un programa usando tenedor()y ejecute un seguimiento; no encontraré ninguna llamada tenedor, en lugar de ellos habrá clon(). Estos rastrillos sólo te confunden si empiezas a comparar la fuente con la salida. rastro.

Utilice -o para guardar la salida en un archivo

rastro puede generar una gran cantidad de resultados, por lo que suele resultar útil almacenar los resultados del seguimiento en archivos separados (como en el ejemplo anterior). Esto también ayuda a evitar confundir la salida del programa con la salida. rastro en la consola.

Utilice -s para ver más datos de argumentos

Es posible que haya notado que la segunda mitad del mensaje de error no se muestra en el seguimiento de ejemplo anterior. Eso es porque rastro El valor predeterminado muestra solo los primeros 32 bytes del argumento de cadena. Si quieres ver más, agrega algo como -s 128 a la llamada rastro.

-y facilita el seguimiento de archivos, sockets, etc.

"Todo es archivo" significa que los sistemas *nix realizan todas las E/S utilizando descriptores de archivos, ya sea que se aplique a un archivo, una red o tuberías entre procesos. Esto es conveniente para la programación, pero dificulta realizar un seguimiento de lo que realmente sucede cuando ves cosas comunes. leer и escribir en los resultados del seguimiento de llamadas al sistema.

Al agregar un operador Si, lo obligarás rastro anote cada descriptor de archivo en la salida con una nota de lo que apunta.

Adjuntar a un proceso que ya se está ejecutando con -p**

Como verá en el ejemplo siguiente, a veces es necesario rastrear un programa que ya se está ejecutando. Si se sabe que se está ejecutando como el proceso 1337 (digamos, desde la salida ps), entonces puedes rastrearlo así:

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

Es posible que necesite derechos de root.

Utilice -f para monitorear procesos secundarios

rastro De forma predeterminada, rastrea solo un proceso. Si este proceso genera procesos secundarios, entonces se puede ver la llamada al sistema para generar el proceso secundario, pero no se mostrarán las llamadas al sistema del proceso secundario.

Si cree que el error está en un proceso hijo, utilice la declaración -f, esto permitirá su rastreo. La desventaja de esto es que el resultado te confundirá aún más. Cuando rastro rastrea un proceso o un hilo, muestra un único flujo de eventos de llamada. Cuando rastrea varios procesos a la vez, es posible que vea el inicio de una llamada interrumpida por un mensaje , luego - un montón de llamadas para otras ramas de ejecución, y solo entonces - el final de la primera <…reanudación de la llamada focal>. O divida todos los resultados del seguimiento en diferentes archivos, también utilizando el operador -ff (detalles en guia en rastro).

Filtrar rastros usando -e

Como puede ver, el resultado del seguimiento es un montón real de todas las llamadas posibles al sistema. Bandera -e Puede filtrar el seguimiento (ver руководство en rastro). La principal ventaja es que es más rápido ejecutar un seguimiento filtrado que realizar un seguimiento completo y luego grep`en. Para ser honesto, casi siempre no me importa.

No todos los errores son malos

Un ejemplo simple y común es un programa que busca un archivo en varios lugares a la vez, como un shell que busca un directorio que contiene un archivo ejecutable:

$ 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 solicitud fallida antes de informar un error" son buenas para encontrar errores relevantes. Sea como fuere, lo lógico es empezar desde el final.

Los tutoriales de programación en C pueden ayudarle a comprender las llamadas al sistema.

Las llamadas estándar a bibliotecas C no son llamadas al sistema, sino sólo una fina capa superficial. Entonces, si comprende al menos un poco cómo y qué hacer en C, le resultará más fácil comprender los resultados del seguimiento de llamadas al sistema. Por ejemplo, tiene problemas para depurar llamadas a sistemas de red, mire el mismo clásico Guía de Bija para la programación de redes.

Un ejemplo de depuración más complejo

Ya dije que el ejemplo de depuración simple es un ejemplo de lo que más tengo que afrontar cuando trabajo con rastro. Sin embargo, a veces se requiere una investigación real, por lo que a continuación se muestra un ejemplo real de depuración más avanzada.

bcron - programador de procesamiento de tareas, otra implementación del demonio *nix cron. Está instalado en el servidor, pero cuando alguien intenta editar el horario, esto es lo que sucede:

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

Está bien, eso significa bcron Intentó escribir cierto archivo, pero no funcionó y no admite por qué. Descubriendo rastro:

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

Hay un mensaje de error cerca del final. escribir, pero esta vez algo es diferente. En primer lugar, no hay ningún error de llamada al sistema relevante, que suele ocurrir antes de esto. En segundo lugar, está claro que en algún lugar alguien ya leyó el mensaje de error. Parece que el verdadero problema está en otra parte y bcrontab simplemente reproduce el mensaje.

Si nos fijamos en hombre 2 leer, puede ver que el primer argumento (3) es un descriptor de archivo, que *nix utiliza para todo el procesamiento de E/S. ¿Cómo puedo saber qué representa el descriptor de archivo 3? En este caso particular, puedes ejecutar rastro con operador Si (ver arriba) y se lo dirá automáticamente, pero para descubrir cosas como esta, es útil saber cómo leer y analizar los resultados del seguimiento.

La fuente de un descriptor de archivo puede ser una de muchas llamadas al sistema (todo depende de para qué sirve el descriptor: una consola, un socket de red, el archivo en sí u otra cosa), pero sea como sea, buscamos llamadas devolviendo 3 (es decir, buscamos "= 3" en los resultados del seguimiento). En este resultado hay 2 de ellos: abierto en en lo más alto y enchufe En el medio. abierto en abre el archivo pero Cerrar(3) mostrará entonces que se cierra nuevamente. (Rake: los descriptores de archivos se pueden reutilizar cuando se abren y cierran). Llamar zócalo () adecuado porque es el último antes leer(), y resulta que bcrontab trabaja con algo a través de un socket. La siguiente línea muestra que el descriptor de archivo está asociado con socket de dominio unix en el camino / var / run / bcron-spool.

Entonces, necesitamos encontrar el proceso asociado con enchufe unix Por otro lado. Hay un par de trucos interesantes para este propósito, los cuales son útiles para depurar implementaciones de servidores. La primera es utilizar netstat o mas nuevo ss (estado del enchufe). Ambos comandos muestran las conexiones de red activas del sistema y toman la declaración -l para describir las tomas de escucha, así como el operador -p para mostrar programas conectados al socket como cliente. (Hay muchas más opciones útiles, pero estas dos son suficientes para esta tarea).

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

Esto sugiere que el oyente es el comando. servidor inix, ejecutándose con el ID de proceso 20629. (Y, coincidentemente, utiliza el descriptor de archivo 3 como socket).

La segunda herramienta realmente útil para encontrar la misma información se llama lsof. Enumera todos los archivos abiertos (o descriptores de archivos) en el sistema. O puede obtener información sobre un archivo 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

El proceso 20629 es un servidor de larga duración, por lo que puede adjuntarlo a rastro usando algo como ruta -o /tmp/trace -p 20629. Si edita un trabajo cron en otra terminal, recibirá un resultado de seguimiento con un error. Y aqui esta el 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 () no se completará durante el seguimiento). Nuevamente, desafortunadamente, este resultado no contiene el error que estamos buscando. No vemos ningún mensaje que bcrontag envíe o reciba del socket. En su lugar, control completo del proceso (clonar, espera4, SEÑAL etc.) Este proceso genera un proceso hijo que, como se puede imaginar, hace el trabajo real. Y si necesitas seguir su rastro, agrégalo a la llamada. trazo -f. Esto es lo que encontraremos cuando busquemos el mensaje de error en el nuevo 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 +++

Ahora, eso es algo. El proceso 21470 recibe un error de "acceso denegado" al intentar crear un archivo en la ruta tmp/spool.21470.1573692319.854640 (relativo al directorio de trabajo actual). Si supiéramos el directorio de trabajo actual, también sabríamos la ruta completa y podríamos descubrir por qué el proceso no puede crear su archivo temporal en él. Desafortunadamente, el proceso ya finalizó, por lo que no puedes simplemente usar lsof-p 21470 para encontrar el directorio actual, pero puede trabajar en la dirección opuesta: busque las llamadas al sistema PID 21470 que cambian el directorio. (Si no hay ninguno, el PID 21470 debe haberlos heredado de su padre, y esto ya es a través de lsof-p no se puede encontrar.) Esta llamada al sistema es chdir (que es fácil de descubrir con la ayuda de los modernos motores de búsqueda en línea). Y aquí está el resultado de las búsquedas inversas basadas en los resultados del seguimiento, hasta el servidor 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 +++

(Si estás perdido, quizás quieras leer mi publicación anterior sobre *nix gestión de procesos y shells.) Entonces, el servidor PID 20629 no recibió permiso para crear un archivo en la ruta /var/spool/cron/tmp/spool.21470.1573692319.854640. Lo más probable es que la razón de esto sea la configuración de permisos del sistema de archivos clásico. Vamos a revisar:

# 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

¡Ahí es donde está enterrado el perro! El servidor se ejecuta como un cron de usuario, pero solo root tiene permiso para escribir en el directorio /var/spool/cron/tmp/. Comando sencillo chown cron /var/spool/cron/tmp/ hará bcron funcionar correctamente. (Si ese no fuera el problema, entonces el siguiente sospechoso más probable es un módulo de seguridad del kernel como SELinux o AppArmor, por lo que verificaría el registro de mensajes del kernel con dmesg.)

En total

Los seguimientos de llamadas al sistema pueden resultar abrumadores para un principiante, pero espero haber demostrado que son una forma rápida de depurar toda una clase de problemas de implementación comunes. Imagínese intentar depurar un multiproceso bcronusando un depurador paso a paso.

Analizar los resultados del rastreo hacia atrás a lo largo de la cadena de llamadas al sistema requiere habilidad, pero como dije, casi siempre, usar rastro, simplemente obtengo el resultado del seguimiento y busco errores comenzando desde el final. De todos modos, rastro me ayuda a ahorrar mucho tiempo en la depuración. Espero que te sea útil a ti también.

Fuente: habr.com

Añadir un comentario