Comprender las llamadas al sistema en Linux con strace

La traducción del artículo fue preparada especialmente para estudiantes de cursos básicos y avanzados de Administrador Linux.






El syscall es el mecanismo por el cual los programas de usuario interactúan con el kernel de Linux, y strace es una herramienta poderosa para realizar un seguimiento de ellos. Para comprender mejor cómo funciona el sistema operativo, es útil comprender cómo funcionan.



El sistema operativo se puede dividir en dos modos de funcionamiento:



  • El modo kernel es el modo privilegiado que utiliza el kernel del sistema operativo.
  • El modo de usuario es el modo en el que se ejecutan la mayoría de las aplicaciones de usuario.




Los usuarios suelen utilizar utilidades de línea de comandos y una interfaz gráfica (GUI) para su trabajo diario. Al mismo tiempo, las llamadas al sistema funcionan de forma invisible en segundo plano, refiriéndose al kernel para hacer el trabajo.



Las llamadas al sistema son muy similares a las llamadas a funciones en el sentido de que se pasan argumentos y devuelven valores. La única diferencia es que las llamadas al sistema funcionan a nivel del kernel, pero las funciones no. El cambio del modo de usuario al modo de kernel se realiza mediante un mecanismo de interrupción especial .



La mayoría de estos detalles están ocultos al usuario en las bibliotecas del sistema (glibc en sistemas Linux). Las llamadas al sistema son universales por naturaleza, pero a pesar de esto, la mecánica de su ejecución depende en gran medida del hardware.



Este artículo explora varios ejemplos prácticos de análisis de llamadas al sistema utilizando strace. Los ejemplos usan Red Hat Enterprise Linux, pero todos los comandos también deberían funcionar en otras distribuciones de Linux:



[root@sandbox ~]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.7 (Maipo)
[root@sandbox ~]#
[root@sandbox ~]# uname -r
3.10.0-1062.el7.x86_64
[root@sandbox ~]#




Primero, asegúrese de tener las herramientas necesarias instaladas en su sistema. Puede verificar si straceestá instalado usando el siguiente comando. Para ver la versión, straceejecútela con el parámetro -V:



[root@sandbox ~]# rpm -qa | grep -i strace
strace-4.12-9.el7.x86_64
[root@sandbox ~]#
[root@sandbox ~]# strace -V
strace -- version 4.12
[root@sandbox ~]#




Si straceno está instalado, instale ejecutando:



yum install strace




Por ejemplo, cree un directorio de prueba en /tmpy dos archivos usando el comando touch:



[root@sandbox ~]# cd /tmp/
[root@sandbox tmp]#
[root@sandbox tmp]# mkdir testdir
[root@sandbox tmp]#
[root@sandbox tmp]# touch testdir/file1
[root@sandbox tmp]# touch testdir/file2
[root@sandbox tmp]#




( /tmpSolo uso un directorio porque todos tienen acceso a él, pero puede usar cualquier otro directorio ). Verifique que los archivos se hayan creado en el directorio



usando el comando :lstestdir



[root@sandbox tmp]# ls testdir/
file1  file2
[root@sandbox tmp]#




Probablemente use el comando lstodos los días sin darse cuenta de que las llamadas al sistema se están ejecutando bajo el capó. Aquí es donde entra en juego la abstracción. Así es como funciona este comando:



   ->    (glibc) ->  




El comando lsllama a funciones de las bibliotecas del sistema Linux (glibc). Estas bibliotecas, a su vez, llaman a llamadas al sistema, que hacen la mayor parte del trabajo.



Si desea saber qué funciones se llamaron desde la biblioteca glibc, utilice el comando ltraceseguido del comando ls testdir/:



ltrace ls testdir/




Si ltraceno está instalado, instale:



yum install ltrace




Habrá mucha información en la pantalla, pero no se preocupe, lo cubriremos más adelante. Estas son algunas de las funciones de biblioteca importantes de la salida ltrace:



opendir("testdir/")                                  = { 3 }
readdir({ 3 })                                       = { 101879119, "." }
readdir({ 3 })                                       = { 134, ".." }
readdir({ 3 })                                       = { 101879120, "file1" }
strlen("file1")                                      = 5
memcpy(0x1665be0, "file1\0", 6)                      = 0x1665be0
readdir({ 3 })                                       = { 101879122, "file2" }
strlen("file2")                                      = 5
memcpy(0x166dcb0, "file2\0", 6)                      = 0x166dcb0
readdir({ 3 })                                       = nil
closedir({ 3 })    




Al examinar este resultado, probablemente pueda comprender lo que está sucediendo. El directorio nombrado se testdirabre usando una función de biblioteca opendir, seguida de llamadas a funciones readdirque leen el contenido del directorio. Finalmente, se llama a una función closedirque cierra el directorio previamente abierto. Por ahora, ignore otras funciones como strleny memcpy.



Como puede ver, puede ver fácilmente las funciones de la biblioteca que se llaman, pero en este artículo nos centraremos en las llamadas al sistema que son llamadas por las funciones de la biblioteca del sistema.



Para ver las llamadas del sistema, utilice straceel comando ls testdircomo se muestra a continuación. Y nuevamente obtienes un montón de información incoherente:



[root@sandbox tmp]# strace ls testdir/
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
brk(NULL)                               = 0x1f12000
<<< truncated strace output >>>
write(1, "file1  file2\n", 13file1  file2
)          = 13
close(1)                                = 0
munmap(0x7fd002c8d000, 4096)            = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++
[root@sandbox tmp]#


Como resultado de la ejecución, stracerecibirá una lista de las llamadas al sistema realizadas durante la ejecución del comando ls. Todas las llamadas al sistema se pueden dividir en las siguientes categorías:



  • Gestión de proceso
  • Gestión de archivos
  • Gestión de directorios y sistemas de archivos
  • Otro




Existe una forma conveniente de analizar la información recibida: escriba la salida en un archivo usando la opción -o.



[root@sandbox tmp]# strace -o trace.log ls testdir/
file1  file2
[root@sandbox tmp]#




Esta vez, no habrá datos en la pantalla; el comando lsfuncionará como se esperaba, mostrando una lista de archivos y escribiendo todos los resultados straceen un archivo trace.log. Para un comando simple, el lsarchivo contiene casi 100 líneas:



[root@sandbox tmp]# ls -l trace.log
-rw-r--r--. 1 root root 7809 Oct 12 13:52 trace.log
[root@sandbox tmp]#
[root@sandbox tmp]# wc -l trace.log
114 trace.log
[root@sandbox tmp]#




Eche un vistazo a la primera línea del archivo trace.log:



execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0




  • Al principio de la línea está el nombre de la llamada al sistema que se está ejecutando: execve.
  • El texto entre paréntesis son los argumentos pasados ​​a la llamada al sistema.
  • El número después del signo = (en este caso, 0) es el valor devuelto por la llamada al sistema.




Ahora el resultado no parece demasiado aterrador, ¿verdad? Y también puede aplicar la misma lógica para otras líneas.



Preste atención al único comando que llamó - ls testdir. Conoce el nombre del directorio utilizado por el comando ls, así que ¿por qué no utilizar grepfor testdiren el archivo trace.logy ver qué encuentra? Mire de cerca el resultado:



[root@sandbox tmp]# grep testdir trace.log
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
stat("testdir/", {st_mode=S_IFDIR|0755, st_size=32, ...}) = 0
openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
[root@sandbox tmp]#




Volviendo al análisis anterior execve, ¿puede saber qué está haciendo la próxima llamada al sistema?



execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0




No es necesario que recuerde todas las llamadas al sistema y lo que hacen: todo está en la documentación. ¡Las páginas man tienen prisa por ayudar! Asegúrese de que el paquete esté instalado antes de ejecutar el comando man man-pages:



[root@sandbox tmp]# rpm -qa | grep -i man-pages
man-pages-3.53-5.el7.noarch
[root@sandbox tmp]#




Recuerde que debe agregar "2" entre el comando many el nombre de la llamada al sistema. Si lee mansobre man( man man), verá que la sección 2 está reservada para llamadas al sistema. Del mismo modo, si necesita información sobre las funciones de la biblioteca, debe agregar 3 entre many el nombre de la función de la biblioteca.



A continuación se muestran los números de sección man:



1.       .
2.   (,  ).
3.   (  ).
4.   (    /dev).




Para ver la documentación de una llamada al sistema, ejecute man con el nombre de esa llamada al sistema.



man 2 execve




Según la documentación, una llamada al sistema execveejecuta un programa que se le pasa en parámetros (en este caso lo es ls). También se le pasan parámetros adicionales para ls. En este ejemplo, lo es testdir. Por lo tanto, esta llamada al sistema simplemente se ejecuta lscon testdirun parámetro:



'execve - execute program'

'DESCRIPTION
       execve()  executes  the  program  pointed to by filename'




A la siguiente llamada al sistema statse le pasa un parámetro testdir:



stat("testdir/", {st_mode=S_IFDIR|0755, st_size=32, ...}) = 0




Para ver la documentación, utilice man 2 stat. La llamada al sistema de estadísticas devuelve información sobre el archivo especificado. Recuerde que todo en Linux es un archivo, incluidos los directorios.



A continuación, se openatabre la llamada al sistema testdir. Tenga en cuenta que el valor de retorno es 3. Este es el descriptor de archivo que se utilizará en las siguientes llamadas al sistema:



openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3




Ahora abre el archivo
trace.log
y observe la línea que sigue a la llamada al sistema openat. Verá una llamada al sistema getdentsque hace la mayor parte del trabajo necesario para ejecutar el comando ls testdir. Ahora ejecutemos grep getdentspara el archivo trace.log:



[root@sandbox tmp]# grep getdents trace.log
getdents(3, /* 4 entries */, 32768)     = 112
getdents(3, /* 0 entries */, 32768)     = 0
[root@sandbox tmp]#




La documentación ( man getdents) dice que getdentslee las entradas del directorio, que es lo que realmente necesitamos. Tenga en cuenta que el argumento para getdent3 es el descriptor de archivo obtenido anteriormente de la llamada al sistema openat.



Ahora que se ha recibido el contenido del directorio, necesitamos una forma de mostrar la información en la terminal. Entonces, lo hacemos greppara otra llamada al sistema write, que se usa para enviar a la terminal:



[root@sandbox tmp]# grep write trace.log
write(1, "file1  file2\n", 13)          = 13
[root@sandbox tmp]#




En los argumentos, puede ver los nombres de los archivos que se generarán: file1y file2. Para el primer argumento (1), recuerde que en Linux, se abren tres descriptores de archivo de forma predeterminada para cualquier proceso:



  • 0 - flujo de entrada estándar
  • 1 - flujo de salida estándar
  • 2 - flujo de error estándar




Por lo tanto, la llamada al sistema writetoma file1y file2la salida estándar, que es una terminal, denota el número 1.



Ahora que sabe qué llamadas al sistema hicieron la mayor parte del trabajo del equipo ls testdir/. Pero, ¿qué pasa con las otras 100 llamadas al sistema en el archivo trace.log?



El sistema operativo hace muchas cosas de apoyo para iniciar el proceso, por lo que mucho de lo que ve en el archivo trace.loges inicializar y limpiar el proceso. Eche un vistazo completo al archivo trace.log e intente comprender qué sucede cuando se ejecuta el comando ls.



Ahora puede analizar las llamadas al sistema para cualquier programa. La utilidad strace también proporciona muchas opciones útiles de línea de comandos, algunas de las cuales se describen a continuación.



De forma predeterminada strace, no muestra toda la información sobre las llamadas al sistema. Sin embargo, tiene una opción -v verboseque mostrará información adicional sobre cada llamada al sistema:



strace -v ls testdir




Es una buena práctica utilizar un parámetro -fpara realizar un seguimiento de los procesos secundarios creados por un proceso en ejecución:



strace -f ls testdir




Pero, ¿qué sucede si solo desea los nombres de las llamadas al sistema, la cantidad de veces que se ejecutaron y el porcentaje de tiempo dedicado a la ejecución? Puede utilizar la opción -cpara obtener estas estadísticas:



strace -c ls testdir/




Si desea rastrear una llamada al sistema específica, por ejemplo, opene ignorar otras, entonces puede usar la opción -econ el nombre de la llamada al sistema:



[root@sandbox tmp]# strace -e open ls testdir
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libacl.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpcre.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
file1  file2
+++ exited with 0 +++
[root@sandbox tmp]#




¿Qué sucede si necesita filtrar por múltiples llamadas al sistema? No se preocupe, puede usar la misma opción -ey separar las llamadas al sistema requeridas con una coma. Por ejemplo, para writey getdent:



[root@sandbox tmp]# strace -e write,getdents ls testdir
getdents(3, /* 4 entries */, 32768)     = 112
getdents(3, /* 0 entries */, 32768)     = 0
write(1, "file1  file2\n", 13file1  file2
)          = 13
+++ exited with 0 +++
[root@sandbox tmp]#




Hasta ahora, solo hemos rastreado ejecuciones de comandos explícitas. Pero, ¿qué pasa con los comandos que se ejecutaron antes? ¿Y si quieres rastrear demonios? Para hacer esto, stracetiene una opción especial -pa la que puede pasar el ID del proceso.



No iniciaremos el demonio, pero usaremos un comando catque muestra el contenido del archivo que se le pasa como argumento. Pero si no especifica un argumento, el comando catsimplemente esperará la entrada del usuario. Después de ingresar texto, se mostrará el texto ingresado en la pantalla. Y así sucesivamente hasta que el usuario haga clic Ctrl+Cpara salir.



Ejecute el comando caten una terminal.



[root@sandbox tmp]# cat




En otra terminal, busque la identificación del proceso (PID) con el comando ps:



[root@sandbox ~]# ps -ef | grep cat
root      22443  20164  0 14:19 pts/0    00:00:00 cat
root      22482  20300  0 14:20 pts/1    00:00:00 grep --color=auto cat
[root@sandbox ~]#




Ahora comience stracecon la opción -py el PID que encontró ps. Después de comenzar, stracemostrará información sobre el proceso al que se conectó, así como su PID. Ahora stracemonitorea las llamadas al sistema realizadas por el comando cat. La primera llamada al sistema que verá es de lectura, esperando la entrada del hilo 0, es decir, de la entrada estándar, que ahora es la terminal en la que se ejecuta el comando cat:



[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0,




Ahora regrese a la terminal donde dejó el comando en ejecución cate ingrese algo de texto. Para la demostración, entré x0x0. Tenga en cuenta que catsimplemente repetí lo que ingresé y x0x0la pantalla aparecerá dos veces.



[root@sandbox tmp]# cat
x0x0
x0x0




Regrese a la terminal donde se straceconectó al proceso cat. Ahora ves dos nuevas llamadas al sistema: la anterior read, que ya ha leído x0x0, y una más para escribir write, que x0x0vuelve a escribir en el terminal, y nuevamente una nueva read, que está esperando una lectura del terminal. Tenga en cuenta que la entrada estándar (0) y la salida estándar (1) están en el mismo terminal:



[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0, "x0x0\n", 65536)                = 5
write(1, "x0x0\n", 5)                   = 5
read(0,




Imagínese los beneficios del lanzamiento stracepara daemons: puede ver todo lo que se hace en segundo plano. Completa el comando
cat
haciendo click
Ctrl+C
... Esto también terminará la sesión
strace
ya que el proceso monitoreado ha sido terminado.



Para ver las marcas de tiempo de las llamadas al sistema, use la opción -t:



[root@sandbox ~]#strace -t ls testdir/

14:24:47 execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
14:24:47 brk(NULL)                      = 0x1f07000
14:24:47 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2530bc8000
14:24:47 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
14:24:47 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3




¿Qué sucede si desea saber el tiempo transcurrido entre llamadas al sistema? Existe una práctica opción -rque muestra el tiempo que se tarda en ejecutar cada llamada al sistema. Bastante útil, ¿no?



[root@sandbox ~]#strace -r ls testdir/

0.000000 execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
0.000368 brk(NULL)                 = 0x1966000
0.000073 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb6b1155000
0.000047 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
0.000119 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3




Conclusión



La utilidad es stracemuy útil para aprender llamadas al sistema en Linux. Para otras opciones de línea de comando, consulte man y la documentación en línea.






All Articles