Lo más importante a la hora de desarrollar módulos para el Kernel de Linux es sin duda configurar un entorno de desarrollo para el Kernel con Eclipse o con cualquier otro IDE a tu elección y con el que te sientas cómodo. Yo me siento muy cómodo con Eclipse, así que voy a hacerlo con él, puedes utilizar la información en este artículo uses Eclipse o no, lo único que cambia es el modo de depurar.
Doy por hecho que el lector conoce la forma de instalar Eclipse en su sistema a la vez que el Kernel de Linux. Para el objetivo del artículo es indiferente si desarrollamos sobre un Kernel descargado desde Kernel.org o es un Kernel específico con parches aplicados de una distribución.
Para realizar este artículo voy a utilizar un Kernel 3.0 descargado desde Kernel.org y la versión Indigo de Eclipse. Por supuesto, tú puedes utilizar el Kernel que más rabia te de o el que más te guste, no afecta para nada el desarrollo y desenlace del mismo.
Compilando el Kernel con Eclipse
He descargado la versión 3.0 del Kernel a mi directorio de desarrollo en mi linux box. Yo estoy desarrollando directamente desde una Gentoo Linux de 64 bits pero no es necesario usar una distribución de Linux para desarrollar el Kernel de Linux, los usuarios de Windows y Mac OS X pueden hacer uso de VirtualBox y una carpeta compartida para desarrollar aunque esa temática se aleja de la finalidad de este artículo y no voy a explicar como configurar ese tipo de entorno no es algo demasiado complejo, investiga, experimenta.
Para compilar código C en Eclipse es necesario instalar el plugin CDT o haber instalado Eclipse for C/C++ Developers que lleva ese plugin instalado por defecto. Abre una consola o abre la vista de consola en Eclipse y dirígete hacia donde descargamos nuestro tarball del Kernel, en mi caso un linux-3.0.tar.bz2
y lo descomprimimos con tar jxvf linux-X.Y.Z.tar.bz2
(X Y y Z corresponden a los números de versión del tarball que hayas descargado).
Una vez hemos descomprimido el tarball nos movemos dentro del directorio con el comando cd linux-X.Y.Z
. Usa cualquiera de las formas explicadas anteriormente en este blog para configurar tu Kernel a través de make menuconfig
y despliega la sección Kernel Hacking. Selecciona Compile the kernel with debug info y Compile the kernel with frame pointers como se ve en la imagen.
Guarda tu configuración e inicia Eclipse. Desactiva la opción de construcción automática a través del menú Wiindow->Preferences->General->Workspace desmarcando la opción Build autmatically si está marcada. Si no quieres indexar el código del Kernel (494M) puedes desactivar el indexador a través de Window->Preferences->C/C++->Indexer y desmarcando Enable Indexer.
Ahora vamos a crear el proyecto para nuestro Kernel creando un nuevo proyecto de C de tipo Makefile y con la toolchain de Linux GCC tal y como se muestra en la captura de pantalla.
Le ponemos el nombre que queramos, aunque lo lógico es que sea algo así como Kernel o algo similar. Desmarcamos la opción Use default location y seleccionamos el directorio raíz del Kernel que hemos descomprimido en pasos anteriores. Hacemos click en Finish. Ya tenemos nuestro entorno de desarrollo listo en Eclipse.
Vamos a comprobar que nuestro Kernel compila. Para ello vamos al menú Project y seleccionamos Build Project, podremos ver el proceso de compilación en la consola de Eclipse. Dependiendo de la velocidad de tu máquina este proceso puede tardar entre cuatro y treinta minutos en completarse. Podemos mejorar la velocidad del proceso de compilación dependiendo de la cantidad de núcleos de los que dispongamos en el procesador. En mi caso tengo ocho por lo tanto puedo usar el parámetro -j9
con el comando make
para crear nueve hilos de compilación.
La opción -j[n]
de make
permite que se lancen n
compiladores en paralelo. La regla es utilizar el número total de núcleos disponibles en el sistema más uno que servirá para el balanceo, de ahí mi -j9
, ocho núcleos más un proceso de balanceo. Para modificar esto debemos ir a las preferencias del proyecto y en C/C++ Build desmarcar la opción Use default build command para añadir nuestra configuración tal y como se muestra en la imagen.
Una vez nuestro Kernel se ha compilado, dispondremos de una imagen comprimida del Kernel en el directorio arch/x86/boot/bzImage. Ahora podremos ejecutar directamente este Kernel usando QEMU con el simple comando qemu -kernel arch/x86/boot/bzImage
o con qemu-system-x86_64
en caso de que hayas compilado un Kernel para sistemas x86 de 64 bits. Obviamente esta acción resultará en un Kernel Panic puesto que no hemos definido un directorio raíz para el sistema en arranque.
Creando un módulo en nuestro entorno de desarrollo
Para crear un nuevo módulo en nuestro entorno de desarrollo, tan solo debemos crear un nuevo directorio en nuestro proyecto dentro del directorio drivers con el nombre que queramos para nuestro módulo y crear dos archivos en su interior. Uno que contendrá nuestro código C y un archivo Makefile con reglas de compilación. Siguiendo nuestro ejemplo en el artículo anterior llamaremos al directorio tronco y añadiremos este contenido para tronco.c
:
/* * tronco.c – Un ejemplo completamente inútil de modulo para el Kernel */Y este otro en el archivo Makefile:include <linux/module.h> /* Todos los modulos lo necesitan */
include <linux/kernel.h> /* Ofrece la macro KERN_INFO */
include <linux/init.h> /* Ofrece las macros de inicio y fin */
static int __init tronco_start(void) { printk(KERN_INFO "Que pasa tronco!\n"); /* * Si no se devuelve 0 significa que init_module ha fallado y no ha podido cargarse. / return 0; } static void __exit tronco_cleanup(void) { printk(KERN_INFO "Me voy dando el piro!\n"); } / * Indicamos cuales son las funciones de inicio y fin / module_init(tronco_start); module_exit(tronco_cleanup); / * Documentacion del modulo */ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Genbeta Dev - https://www.genbetadev.com"); MODULE_DESCRIPTION("Este modulo no vale para absolutamente nada"); MODULE_SUPPORTED_DEVICE("eltiodelavara");
obj-m:=tronco.o
Esto compilará nuestro código siempre como un módulo, si queremos que se compile de forma integrada dentro del Kernel debemos cambiar obj-m
por obj-y
(ya aprenderemos a añadir configuraciones a nuestros módulos). Añadimos estas líneas al final del archivo Makefile
del directorio drivers:
# tronco kernel module
obj-y += tronco/
Ahora podemos volver a compilar nuestro Kernel y obtendremos un módulo en drivers/tronco llamado tronco.ko
.
Configurando QEMU para depuración del Kernel
Lo primero es descargar los módulos kvm_intel/kvm_amd y kvm en nuestra box si están cargados por que hacen que el depurador se comporte de forma extraña cuando fijamos puntos de ruptura en el Kernel:
$ rmmod kvm_intel
$ rmmod kvm_amd
$ rmmod kvm
Podemos arrancar QEMU con la máquina en estado de pausa y con un servidor GDB escuchando en un puerto a nuestra elección para poder depurar el proceso. La opción -S le indica a QEMU que inicie la máquina en estado de pausa y la opción -s que inicie un servidor GDB:
$ qemu -s -S /dev/zero -kernel arch/x86/boot/bzImage
Depurando el Kernel con Eclipse
Para depurar el Kernel con Eclipse tenemos dos opciones:
-
Compilar el módulo como parte integrada en el kernel y usar un hardware breakpoint en la función de inicio
-
Crear un rootfs para QEMU, arrancar el Kernel con un depurador vinculado a él, cargar nuestro modulo en la máquina virtual y depurarlo
Cualquiera de las dos opciones es válida y dependiendo del tipo de módulo que hayamos creado nos vendrá mejor utilizar una u otra. Para mantener este artículo sencillo vamos a optar por la primera opción puesto que nuestro módulo no interactúa ni con el hardware ni con el sistema de ninguna manera.
Para poder fijar puntos de ruptura debemos fijar la opción de gdb set breakpoint auto-hw
vamos a crear un archivo llamado .gdbinit en la raiz de nuestro Kernel con ese contenido. GDB lo cargará de forma automática:
set breakpoint auto-hw
Ahora debemos configurar el depurador de Eclipse para que funcione con nuestro depurador remoto levantado por QEMU. Vamos al menú Run->Run Configurations hacemos doble click en C/C++ Remote Application eso creará una nueva configuración de depurado. En el input box de la aplicación a depurar hacemos click en el botón Browse... y seleccionamos vmlinux del directorio raíz del Kernel ese es el binario de nuestro Kernel con símbolos de depurado.
Abrimos la pestaña Debugger en la opción Stop on startup at: escribimos tronco_start
para que el depurador pare el proceso de arranque del Kernel al llegar a nuestro módulo que recordemos vamos a compilar como parte integrada del mismo cambiando mod-m
por mod-y
en el Makefile del directorio tronco.
Pasamos a la pestaña Connection y fijamos Host name a localhost
y Port number a 1234
que es el puerto por defecto que inicia QEMU al pasarle el parámetro -s
sin argumentos.
También podemos usar la configuración de de Attach to process si la configuración de Remote no funciona (en algunos casos pasa). Abrimos la perspectiva de depurado y pulsamos F11 para iniciar la depuración (ten en cuenta que tienes que tener QEMU levantado y con el gdbserver escuchando ya el puerto 1234) nuestro kernel arrancará y entraremos en modo depuración.
Conclusión
Si queremos depurar la carga de módulos y queremos tener un sistema de depuración y desarrollo en condiciones, debemos de instalar un sistema Linux en una máquina virtual usando QEMU (y KVM si es posible para las tareas de instalación) para poder arrancar el Kernel dentro de un sistema real y realizar nuestras pruebas en ese sistema.
Más en Genbeta Dev | Programando módulos para el kernel de linux