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
strace
está instalado usando el siguiente comando. Para ver la versión, strace
ejecú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
strace
no está instalado, instale ejecutando:
yum install strace
Por ejemplo, cree un directorio de prueba en
/tmp
y 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]#
(
/tmp
Solo 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 :
ls
testdir
[root@sandbox tmp]# ls testdir/
file1 file2
[root@sandbox tmp]#
Probablemente use el comando
ls
todos 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
ls
llama 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
ltrace
seguido del comando ls testdir/
:
ltrace ls testdir/
Si
ltrace
no 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
testdir
abre usando una función de biblioteca opendir
, seguida de llamadas a funciones readdir
que leen el contenido del directorio. Finalmente, se llama a una función closedir
que cierra el directorio previamente abierto. Por ahora, ignore otras funciones como strlen
y 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
strace
el comando ls testdir
como 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,
strace
recibirá 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
ls
funcionará como se esperaba, mostrando una lista de archivos y escribiendo todos los resultados strace
en un archivo trace.log
. Para un comando simple, el ls
archivo 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 grep
for testdir
en el archivo trace.log
y 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
man
y el nombre de la llamada al sistema. Si lee man
sobre 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 man
y 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
execve
ejecuta 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 ls
con testdir
un parámetro:
'execve - execute program'
'DESCRIPTION
execve() executes the program pointed to by filename'
A la siguiente llamada al sistema
stat
se 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
openat
abre 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 getdents
que hace la mayor parte del trabajo necesario para ejecutar el comando ls testdir
. Ahora ejecutemos grep getdents
para 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 getdents
lee las entradas del directorio, que es lo que realmente necesitamos. Tenga en cuenta que el argumento para getdent
3 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
grep
para 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:
file1
y 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
write
toma file1
y file2
la 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.log
es 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 verbose
que mostrará información adicional sobre cada llamada al sistema:
strace -v ls testdir
Es una buena práctica utilizar un parámetro
-f
para 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
-c
para obtener estas estadísticas:
strace -c ls testdir/
Si desea rastrear una llamada al sistema específica, por ejemplo,
open
e ignorar otras, entonces puede usar la opción -e
con 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
-e
y separar las llamadas al sistema requeridas con una coma. Por ejemplo, para write
y 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,
strace
tiene una opción especial -p
a la que puede pasar el ID del proceso.
No iniciaremos el demonio, pero usaremos un comando
cat
que muestra el contenido del archivo que se le pasa como argumento. Pero si no especifica un argumento, el comando cat
simplemente 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+C
para salir.
Ejecute el comando
cat
en 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
strace
con la opción -p
y el PID que encontró ps
. Después de comenzar, strace
mostrará información sobre el proceso al que se conectó, así como su PID. Ahora strace
monitorea 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
cat
e ingrese algo de texto. Para la demostración, entré x0x0
. Tenga en cuenta que cat
simplemente repetí lo que ingresé y x0x0
la pantalla aparecerá dos veces.
[root@sandbox tmp]# cat
x0x0
x0x0
Regrese a la terminal donde se
strace
conectó 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 x0x0
vuelve 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
strace
para daemons: puede ver todo lo que se hace en segundo plano. Completa el comandocat
haciendo click Ctrl+C
... Esto también terminará la sesiónstrace
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
-r
que 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
strace
muy ú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.