TP0: Traza de llamadas en JOS1

AVISO: antes de comenzar, verificar que se tiene instalado el software necesario.

Índice

Introducción a JOS

Los TPs de este cuatrimestre se implementarán sobre JOS, el exokernel educativo con licencia libre del grupo de Sistemas Operativos Distribuidos del MIT.

En cada consigna, se referenciará el contenido original equivalente (en inglés) en la página de su materia 6.828: Operating Systems Engineering. Se recomienda su uso como complemento a la consigna en castellano.

The programming assignments […] are not easy. It is far from unusual for a single one-line bug to take two days to track down.

— Carnegie Mellon University, Operating System Design and Implementation.

Código fuente

La copia de JOS para la materia se encuentra en el repositorio fisop/jos de GitHub. El esqueleto para el TP0 se encuentra en la rama tp0, que deberá ser integrada en la rama master del repositorio grupal.

IMPORTANTE: Para una guía sobre el manejo de repositorios e integraciones, consultar la página de entregas y descargas.

Compilación y ejecución

La compilación se realiza mediante make. En el directorio obj/kern se puede encontrar:

  • kernel — el binario ELF con el kernel
  • kernel.asm — assembler asociado al binario

Para correr JOS, se puede usar make qemu o make qemu-nox.

Depurado

El Makefile de JOS incluye dos reglas para correr QEMU junto con GDB. En dos terminales distintas:

1
2
3
4
5
$ make qemu-gdb
***
*** Now run 'make gdb'.
***
qemu-system-i386 ...

y:

1
2
3
4
5
6
$ make gdb
gdb -q -ex 'target remote ...' -n -x .gdbinit
Reading symbols from obj/kern/kernel...done.
Remote debugging using 127.0.0.1:...
0x0000fff0 in ?? ()
(gdb)

Para más información, consultar esta guía.

Resumen del TP

Código

En este TP se pide la implementación de una única función mon_backtrace(), en el archivo kern/monitor.c. La salida de git diff --stat que sigue indica, a modo de guía, la cantidad de código en la solución de los docentes al TP. En este caso son solamente 14 líneas de código, todas en el archivo monitor.c.

1
2
3
$ git diff --stat tp0..tp0_sol -- kern
 kern/monitor.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

Documentación

Algunos ejercicios de los TPs de JOS consisten en responder en prosa ciertas preguntas breves. Estas se deben responder en el archivo TP0.md, TP1.md, … del repositorio.

1
2
3
$ git diff --stat tp0..tp0_sol -- *.md
 TP0.md | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

Pruebas automáticas

La mayoría de los TPs incluyen un auto-corrector (make grade) que informa al estudiante sobre su progreso. Para el TP0, el resultado de la auto-corrección tiene este aspecto:

1
2
3
4
5
6
7
8
$ make grade
./grade-lab1
running JOS: (1.0s)
  backtrace count: OK
  backtrace arguments: OK
  backtrace symbols: OK
  backtrace lines: OK
Score: 4/4

Guía para el TP

Convención de llamadas

Dada la convención de llamadas en x86, es posible saber la secuencia de llamadas anidadas que condujo al estado actual de ejecución. En particular, mediante la instrucción estándar:

1
push %ebp

cada función almacena en el stack una referencia al marco de ejecución de la función inmediatamente anterior. Se puede obtener el punto de retorno y los parámetros de cada llamada “saltando” hacia atrás cada frame pointer. (Algo parecido hace el comando bt de GDB.)

Se proporcionan dos funciones auxiliares:

  • read_ebp(): devuelve el valor actual del registro %ebp.

  • debuginfo_eip(): usa la información de debug ELF para determinar, dada la dirección de una instrucción, en qué función reside. Proporciona información sobre dicha función en un struct Eipdebuginfo:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    struct Eipdebuginfo {
        const char *eip_file;   // Source code filename for EIP
        int eip_line;           // Source code linenumber for EIP
    
        const char *eip_fn_name;  // Name of function containing EIP
                                  //     Note: not null terminated!
        int eip_fn_namelen;       // Length of function name
        uintptr_t eip_fn_addr;    // Address of start of function
        int eip_fn_narg;          // Number of function arguments
    };
    

La función mon_backtrace() visita hacia atrás los sucesivos marcos de ejecución, imprimiendo información sobre cada uno de ellos.

Tarea: backtrace_eip

Implementar una primera versión de mon_backtrace() que, para cada marco de ejecución, imprima en hexadecimal con cprintf() el frame pointer, instruction pointer y los cinco primeros argumentos de la función, en este formato:2

1
2
3
  ebp f0109e58  eip f0100a62  args 00000001 f0109e80 f0109e98 f0100ed2 00000031
  ebp f0109ed8  eip f01000d6  args 00000000 00000000 f0100058 f0109f28 00000061
␣␣   ␣        ␣␣   ␣        ␣␣

Para cada función, ebp se refiere al valor del registro %ebp (esto es, el valor del stack pointer al entrar a la función), y eip a la instrucción de retorno.

La primera línea se referirá siempre a la función actualmente en ejecución, esto es, siempre será mon_backtrace(). La segunda línea refleja quién llamó a mon_backtrace(), y así sucesivamente.

El auto-corrector comprueba el comportamiento de esta función llamándola al final de una serie de llamadas recursivas a test_backtrace():

1
2
3
4
5
6
$ make grade
running JOS: (0.8s)
  backtrace count: OK
  backtrace arguments: OK
  backtrace symbols: FAIL
  backtrace lines: FAIL

Ayuda: para saber cuándo se llega al primer marco de ejecución, averiguar si JOS inicializa %ebp durante el arranque a algún valor pre-establecido. (Usar, por ejemplo, el comando git grep -F %ebp.)

Tarea: backtrace_cmd

Añadir en el archivo monitor.c un comando backtrace que llame a la función mon_backtrace(). Así, en el monitor interactivo de JOS será posible escribir:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ make qemu-nox
...
Welcome to the JOS kernel monitor!
Type 'help' for a list of commands.

K> help
help - Display this list of commands
kerninfo - Display information about the kernel
backtrace - Display the current backtrace

K> backtrace
  ebp f010ff48  eip f0100a03  args  00000001 f010ff70 00000000 0000000a 00000009
  ebp f010ffc8  eip f0100a48  args  00010094 00010094 f010fff8 f01000e1 00000000
  ebp f010ffd8  eip f01000e1  args  00000000 00001aac 00000644 00000000 00000000
  ebp f010fff8  eip f010003e  args  00111021 00000000 00000000 00000000 00000000

Tarea: backtrace_func_names

La función auxiliar debuginfo_eip() también proporciona el nombre y ubicación de la función a que corresponde %eip.

Se pide ampliar la salida de mon_backtrace() para que, usando campos adicionales de Eipdebuginfo, muestre el archivo y número de línea en que se halla la instrucción, así como el offset de eip respecto a la dirección de comienzo de la función:

1
2
3
  ebp f010ff48  eip f0100a01  args  00000001 f010ff70 00000000 0000000a 00000009
         kern/monitor.c:116: runcmd+261
  ...

Escribir en el archivo TP0.md la salida del comando backtrace al arrancar JOS.

Ayuda: la familia de funciones printf permite forzar un ancho para el valor a imprimir; por ejemplo:

1
printf("%04d", n)

imprime el entero n con un ancho de al menos 4; y:

1
printf("%.4f", r)

imprime el float r con cuatro dígitos de precisión.

El ancho y/o la precisión pueden aplicarse vía variables mediante el modificador '*', por ejemplo: printf("%0*d", ancho, n) y printf("%.*4f", precision, r).

Esto último es útil para cadenas no terminadas en '\0' como eip_fn_name. Para más detalles, consultar la página de manual printf(3).

  1. Material original en inglés: Lab 1, part 3: The Kernel↩︎

  2. Quizás la función tome menos de cinco argumentos. En ese caso, se imprimirá igualmente las posiciones del stack donde estarían esos argumentos. ↩︎