Llega la última entrega de esta serie de tres capítulos que nos llega desde Linux Voice en la que se explica los entresijos que hay detrás de los procesos de Linux. En la primera parte se vio cómo se organizan los programas en el disco, en la segunda cómo lo hacen en la memoria y en esta última parte veremos cómo se comportan los procesos «por dentro».
Hasta el momento hemos visto los archivos ejecutables desde el punto de vista estático. Merece la pena conocer todas estas cosas pero hay momentos en los que se tiene que mirar cómo viven los procesos.
Imagina la siguiente situación. Tienes una aplicación creada por un tercero, quizás incluso como un binario precompilado. Cuando intentas ejecutarlo lo único que consigue es un vago mensaje como «El programa hace boo boo» (luego finaliza con el código de salida 1). Sospechas que no puede encontrar algún dato o archivo de configuración pero ¿cómo sabes qué está buscando exactamente?
Hay dos herramientas importantes que pueden ayudar. Son similares tanto en el nombre como en la forma de funcionar. La primera es ‘strace’ y rastrea las llamadas de sistema que hace el programa. La segunda es ‘ltrace’ que rastrea las llamadas a bibliotecas dinámicas.
Ambas herramientas descansan en un solo mecanismo de rastreo: ptrace(2) que también es el caballo de batalla de depuradores como GDB. ‘strace’ hace que salte cuando hay llamadas de sistema. ‘ltrace’ es un poco más enrevesado. Este pone puntos de interrupción en la sección .plt. Las llamadas a funciones en bibliotecas compartidas se envían a través de .plt por lo que la siguiente vez que ocurre, ‘ltrace’ tiene la oportunidad de intervenir. Es posible arrancar el proceso que necesitas rastrear o adjuntar cualquiera de estas herramientas a uno en ejecución (es necesario permiso de root).
Cuando se atrapa la llamada, ‘strace’ y ‘ltrace’ necesitan descodificarla antes de mostrarte cualquier cosa. Las llamadas de sistema tienen firmas conocidas por lo que es tedioso pero bastante sencillo. Para una biblioteca arbitraria de un tercero, se recomienda usar un archivo de configuración especial (normalmente en /etc/ltrace.conf). De otra manera, ‘ltrace’ no será capaz de descodificar los argumentos de las llamadas y las mostrará como números hexadecimales. Introdujimos ‘strace’ como nuestro comando del mes en LV016 así que concentrémonos hoy en ‘ltrace’. Con el modificador – de la linea de comandos también se pueden atrapar llamadas de sistema con lo que consigues lo mejor de ambos mundos. También puedes obtener nombres C++ con el modificador -C. Úsalo para ver a nuestro conejillo de indias pwd bajo el microscopio:
$ltrace /bin/pwd __libc_start_main(0x401840, 1, 0x7ffd777e0e58, 0x404a80 <unfinished ...> getenv("POSIXLY_CORRECT") = nil strrchr("/bin/pwd", '/') = "/pwd" setlocale(LC_ALL, "") = "es_ES.UTF-8" bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale" textdomain("coreutils") = "coreutils" __cxa_atexit(0x402430, 0, 0, 0x736c6974756572) = 0 getopt_long(1, 0x7ffd777e0e58, "LP", 0x606cc0, nil) = -1 getcwd(0, 0) = "" puts("/home/alex"/home/alex ) = 11 free(0xb0a030) = <void> exit(0 <unfinished ...>
(Las funciones que incluyen guiones bajos son las rutinas de servicio de glibc y las hemos omitido por brevedad). No hay nada demasiado sorprendente ahí: pwd(1) llama a getcwp(3) y pone los resultados en la consola. Sin embargo, con el argumento -S obtendrás información mucho más elaborada. Es larga y no las reproduciremos aquí así que, por favor, pruébalo. Las llamadas de sistema empiezan con el prefijo SYS_. Puedes observar cómo se mapean las localizaciones (comprueba que realmente están en /proc/<PID>/maps) y cómo se extiende la pila a través de brk(2). Todas estas acciones se hace en glibc por lo que puedes programar sin preocuparte demasiado de los detalles a bajo nivel.
Hoy, muchas aplicaciones están escritas en lenguajes interpretados de alto nivel como Python o Ruby. Aún así, puedes ejecutarlas de la misma forma en que lo hacen los binarios ELF. Puedes escribir /usr/bin/soffice para iniciar LibreOffice sin ni siquiera darte cuenta de que esto es un script. ¿Cómo sabe el kernel a qué intérprete llamar en cada caso?
La respuesta es Shebang. El shebang es ‘#!’ y si un ejecutable se inicia con estos dos caracteres, el kernel sabe que realmente debe ejecutar el programa cuya ruta absoluta los sigue, pasando el nombre del script como un argumento de la linea de comandos. Esto no está limitado a intérpretes: #!/bin/cat probablemente es uno de esos pequeños programas que se muestran a si mismos. Dennis Ritchie, «el padre de Unix», introdujo el shebang en 1980. Fíjate que # denota un comentario para muchos intérpretes por lo que shebang no impide ejecutar los scripts directamente como python script.py.
Los diferentes sistemas puede tener los intérpretes instalados en lugares distintos, por lo que a menudo encuentras una construcción #!/usr/bin/env python que persigue la portabilidad, al menos en Linux. El shebang está en prácticamente todos los Unix pero no en el estándar POSIX: en www.inulm.de/~mascheck/various/shebang se resumen los problemas de portabilidad existentes.
· Primera parte: Elfos de Linuxoria
· Segunda parte: Rememorando sus recuerdos