Un kernel tradicional gestiona los recursos de una máquina y presta servicios a través de la interfaz de llamadas a sistema (syscalls), ¿Qué dirias si te mostrara como hacer lo segundo?. Comenzaré por algunas descripciones importantes:
- Linux ptrace: Es un mecanismo que permite controlar la ejecución, cambiar registros y modificar la memoria de un proceso hijo.
- PTRACE_SYSEMU: Es una petición soportada por ptrace en algunas arquitecturas (en el port de Linux x86 funciona perfecto, en el port x86_64 no) que permite que un proceso vigilado por su padre no sea atendido por el kernel al realizar una llamada a sistema, sino por su proceso padre, lo que facilita la emulación de un sistema.
UML
UML utiliza, si es posible, PTRACE_SYSEMU para atender los procesos que corren bajo su dominio. Una “virtual machine” con UML consiste de una versión de Linux que corre como un proceso ordinario sobre un sistema Linux. Este proceso ordinario vigila y manipula a los procesos que se corran bajo el, como un kernel. Cómo administra recursos que solo puede asignar el sistema Linux anfitrión podría ser tema para un articulo futuro, nosotros solo nos enfocaremos en la emulación de llamadas a sistema. Nuestro pequeño kernel tendra solo dos llamadas:
- print: Imprime una cadena de máximo 128 bytes y
- exit: Finaliza al proceso que hizo la llamada.
Código de vkern
int main(int c, char **v)
{
pid_t pid;
int status, syscall_nro;
long addr;
char data[5];
data[4] = 0;
if(c < 2) {
fprintf(stderr, "Use: %s <programa>\n", v[0]);
return 1;
}
if(fork() == 0) { /* Proceso hijo */
/* Cada vez que este proceso recibe una señal, se detiene y se notifica al proceso padre.
* Una syscall produce una SIGTRAP en este proceso gracias a TRACEME.
*/
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
/* Reemplazo la imagen de proceso por el archivo en v[1] */
if(execv(v[1], v + 1) == -1) {
fprintf(stderr, "Invalid program image\n");
return 1;
}
} else { /* Proceso padre */
do {
pid = wait(&status);
if(pid == -1 || WIFEXITED(status)) {
puts("+ Instancia de VKern terminada.");
return 0;
}
if(WSTOPSIG(status) == SIGTRAP) {
/* La convención dice que el numero de la llamada va en el registro EAX, ORIG_EAX es
* la posición de ese EAX en la estructura user (la copia de los registros del proceso al detenerse).
*/
syscall_nro = ptrace(PTRACE_PEEKUSER, pid, ORIG_EAX * sizeof(long), NULL);
printf("+ Numero de syscall: %d:\n", syscall_nro);
switch(syscall_nro) {
case 0:
ptrace(PTRACE_KILL, pid, NULL, NULL);@
printf("\t%d: Terminando proceso..\n", pid);
break;
case 1:
addr = ptrace(PTRACE_PEEKUSER, pid, EBX * sizeof(long), NULL);
sys_print(pid, addr);
break;
default:
puts("\tSyscall no soportada");
break;
}
/* La siguiente syscall será emulada */
/* (1) */ ptrace(PTRACE_SYSEMU, pid, NULL, NULL);
}
} while(1);
}
return 0;
}
La primera llamada que realiza el proceso hijo luego de comenzar a ser vigilado es execv y es luega de esta que el proceso padre solicita que las llamadas del hijo sean emuladas (1).
Programa de ejemplo
#define SYSCALL __asm__ ("int $0x80")
/* Wrappers a llamadas de sistema */
void print(char *message)
{
__asm__ volatile("movl %0, %%ebx\n"
"movl $1, %%eax" : "=m" (message));
SYSCALL;
}
void __exit()
{
__asm__ volatile("movl $0, %eax");
SYSCALL;
}
/* _start es el simbolo de la primera función ejecutada, el "C runtime support" declara
* y define esta función para luego llamar al estandar main.
*/
void _start()
{
print("Hola VKern!\n");
__exit();
}
p. El ejemplo escribe “Hola Vkern!” usando la llamada print y finaliza con exit.
Probando Vkern
Luego de compilar ambas partes, probemos Vkern con el ejemplo.
felipe@leibniz:~/Vkern$ make
gcc -m32 vkern.c -o vkern
gcc -m32 -c ejemplo.c -o ejemplo.o
ld -melf_i386 ejemplo.o -o ejemplo
felipe@leibniz:~/Vkern$ ./vkern ./ejemplo
+ Numero de syscall: 11:
Syscall no soportada
+ Numero de syscall: 1:
14374: print syscall: Hola VKern!
+ Numero de syscall: 0:
14374: Terminando proceso..
+ Instancia de VKern terminada.
La primera syscall (11) corresponde a execv del proceso hijo, esa llamada es tratada por el kernel anfitrion. Las posteriores son tratadas por Vkern.
Espero que les haya parecido entretenido, los invito a extenderlo y postear aquí. Para mayor información vean:
- man ptrace
- Código de fuente de UML