Acerca de
Tutoriales
Comunidad
Actualidad
Enlaces





En macprogramadores.org
En Internet

Trucos

¿Qué es un Kernel Panic?



Cuando el núcleo de Mac OS X falla, nos aparece un mensaje que se llama Kernel Panic. Cuando esto ocurre no hay más remedio que resetear el ordenador. ¿Cómo podemos saber que es lo que produjo este error?

Este truco explica que son los kernel panic y como podemos depurar el código que causó el error.


¿Qué es un Kernel Panic?

En el mundo de UNIX, un kernel panic es el equivalente a una excepción en un proceso de usuario, sólo que la excepción en vez de producirse en un proceso de usuario, en cuyo caso el sistema la trataría (p.e. cerrando el proceso que produjo la excepción), la excepción se produce en el propio kernel.

Aunque es posible producir un kernel panic desde el núcleo llamando a la función panic() que se encuentra definida en el fichero de cabecera sys/systm.h, lo más normal es que se produzca como resultado de una excepción del procesador no controlada, como p.e. una referencia a una dirección de memoria no válida. Un kernel panic es típicamente un indicador de que, o bien algo en el núcleo tiene un bug, o bien alguna parte del hardware está fallando.

Conceptos básicos del manejo de excepciones en PowerPC

Una excepción es una condición que encuentra el procesador y que requiere un tratamiento especial. PowerPC trata las excepciones cambiando al modo supervisor, guardando el estado del procesador en determinados registros, para después saltar a una rutina de manejo de excepciones. Las excepciones se agrupan por tipos (acceso a memoria, alineamiento incorrecto,...), y cada tipo de excepción tiene su propio vector de manejo de excepciones, localizado en una dirección de memoria absoluta que está definida en la arquitectura de PowerPC.

Los tipos de excepciones más comunes son:

DSI (Data Storage Interrupt) también llamada Data Memory Access. Son excepciones causadas por un intento de acceder a un dato localizado en una zona de memoria no válida. Por ejemplo, al indireccionar un puntero NULL.

ISI (Instruction Storage Interrupt). Causada por un intento de ejecutar una instrucción localizada en una dirección de memoria no válida. P.e. se produce después de hacer un salto a la dirección de memoria 0.

IIE (Illegal Instruction Exception). Causada al intentar ejecutar una instrucción con un opcode no válido.

Cuando se ejecuta una excepción que produce un mensaje de panic, se muestra el contenido de varios registros. Estos registros son:

Registro
Descripción

DSISR

Identifica la causa de la DSI, en caso de un acceso a un dato colocado en una dirección de memoria no válido, o el operando que causo la excepción en caso de un acceso a un dato no alineado en memoria.
DAR
(Data Access Register) Contiene la dirección efectiva del elemento de memoria que causo la DSI, o la excepción de alineamiento.
MSR
(Machine State Register) El registro describe en una serie de flags el estado del microprocesador cuando se produjo la excepción. Esto incluye aspectos como: Si estaban habilitadas las excepciones, el nivel de privilegio, bits de traducción de direcciones, etc...
SRR0
(Machine Status Save/Restore 0) Contiene la dirección que se usa para calcular donde continuar ejecutando después de tratar la excepción. Dependiendo del tipo de excepción, esta puede ser la dirección efectiva de la instrucción que causó la excepción, o bien, la siguiente instrucción en el flujo de programa. Este registro se muestra en el mensaje de panic como PC (Program Counter).
SRR1
Contiene información específica de la excepción, así como determinados bits del registro MSR. Este registro se muestra en el mensaje de panic como MSR.
LR
Contiene el valor del registro LR (Link Register) cuando se produjo la excepción. Este registro contiene la dirección de retorno de la función que se estaba ejecutando.
GPR1
(General Purpose Register 1) Este registro lo usa PowerPC como puntero a pila. Se muestra en el mensaje de panic como R1.

 

El núcleo de Darwin sigue el siguiente flujo de llamadas a funciones cuando se produce una excepción de sistema en PowerPC:

  1. xnu/osfmk/ppc/lowmem_vectors.s: L_handlerXXXX() (XXXX Indica el vector de manejo de excepción, el cual va desde 100 a 2FFF. Actualmente sólo se usa de 100 a 2000)
  2. xnu/osfmk/ppc/lowmem_vectors.s: L_exception_entry()
  3. xnu/osfmk/ppc/hw_exception.s: thandler()
  4. xnu/osfmk/ppc/trap.c: trap()
  5. xnu/osfmk/ppc/trap.c: unresolved_kernel_trap()

La función unresolved_kernel_trap() es la que finalmente muestra el mensaje de panic.

¿Qué aspecto tiene un mensaje de panic?

Cuando se produce un kernel panic, aparece en pantalla un mensaje como el siguiente. Se han añadido números a cada línea del mensaje para una posterior explicación.

1 Unresolved kernel trap(cpu 0): 0x300 - Data access DAR=0xdeadbeef PC=0x0e692550
2 Latest crash info for cpu 0:
3    Exception state (sv=0x0EB5DA00)
4       PC=0x0E692550; MSR=0x00009030; DAR=0xDEADBEEF; DSISR=0x42000000;
        LR=0x0E692530; R1=0x081DBC20; XCP=0x0000000C (0x300 - Data access)
5       Backtrace:
6          0x0E6924A8 0x00213A88 0x00213884 0x002141D4 0x00214830
           0x00204CB0 0x00204C74
7       Kernel loadable modules in backtrace (with dependencies):
8          com.apple.dts.driver.PanicDriver(1.0)@0xe691000
9             dependency: com.apple.iokit.IOUSBFamily(1.9.2)@0xed9c000
10 Proceeding back via exception chain:
11    Exception state (sv=0x0EB5DA00)
12       previously dumped as "Latest" state. skipping...
13    Exception state (sv=0x0EB64A00)
14       PC=0x00000000; MSR=0x0000D030; DAR=0x00000000;
15       DSISR=0x00000000; LR=0x00000000; R1=0x00000000; XCP=0x00000000 (Unknown)
16 Kernel version:
17 Darwin Kernel Version 6.1:
18 Fri Sep  6 23:24:34 PDT 2002; root:xnu/xnu-344.2.obj~2/RELEASE_PPC
19
20
21 Memory access exception (1,0,0)
22 ethernet MAC address: 00:0a:11:22:33:44
23 ip address: 169.254.180.203
24
25 Waiting for remote debugger connection.

 

Además en el directorio /Library/logs se crea un fichero llamado panic.log con el mismo mensaje que aparece en pantalla.

Para cada línea del mensaje de panic vamos a detallar el fichero fuente y función donde se produce, así como una explicación de la información que da la línea.

Línea 1: xnu/osfmk/ppc/trap.c: unresolved_kernel_trap()

Unresolved kernel trap Descripción textual de la causa del error. Este es el parámetro pasado a la función panic()

(cpu 0) Número de la CPU donde se produjo la excepción. Útil cuando trabajamos en máquinas multiprocesador. Obsérvese que en este caso es posible que un procesador sufra un kernel panic, mientras que el otro sigue trabajando.

0x300 - Data access Nombre del trap. Los nombres de los traps se encuentran en el array xnu/osfmk/ppc/trap.c: trap_type. El código de excepción hardware trapno (definido en xnu/osfmk/ppc/exception.h) inicialmente lo fija el gestor de excepciones xnu/osfmk/ppc/lowmem_vectors.s: L_handlerXXXX() (donde XXXX es el vector de excepciones de PowerPC). El índice del gestor de excepción en el array trap_type se calcula dividiendo trapno entre T_VECTOR_SIZE, que es una constante que está también definida en el fichero xnu/osfmk/ppc/exception.h que vale 4. 0x300 indica el principio del vector de excepciones de PowerPC.

DAR Contenido del Data Access Register

PC Contenido del registro SRR0

La interpretación del contenido de los registros DAR y PC varía dependiendo de la excepción que se haya producido.

Línea 2,3: xnu/osfmk/ppc/model_dep.c: print_backtrace()

Los estados de las excepciones se almacenan en estructuras del tipo savearea (definido en xnu/osfmk/ppc/exception.h). sv es un puntero a una estructura savearea con información de la última excepción.

Línea 4: xnu/osfmk/ppc/model_dep.c: dump_savearea()

Se describe el contenido de los registros almacenados en la savearea:

Registro Descripción
PC
Contenido del registro SRR0
MSR
Contenido del registro SRR1
DAR
Contenido del Data Access Register
DSISR
Contenido del DSISR
LR
Contenido del Link Register
R1
Contenido del GPR1
XCP
Este no es un registro, si no el código de excepción de la excepción actual almacenado en savearea. Véase la línea 1

Línea 5,6: xnu/osfmk/ppc/model_dep.c: dump_backtrace()

Esta es la traza de llamadas a funciones de la pila. El primer número es el valor del puntero a pila GPR1, el siguiente es el valor del LR (Link Register) y los demás valores se extraen de la pila.

La pila de llamadas a funciones es posiblemente la información más importante del mensaje de panic porque se puede usar para reconstruir la cadena de llamadas a funciones que produjeron la excepción. Esto se describirá más detalladamente en la siguiente sección.

Línea 7,8,9: xnu/osfmk/kern/kmod.c: kmod_dump()

Aquí aparece el nombre del módulo del núcleo, versión y dirección de inicio de la pila de llamadas a módulos donde se produjo a excepción. Para conocer esta información kmod_dump() mira la dirección de memoria en la que se carga cada módulo.

Un módulo del núcleo (kernel loadable module) no es más que una parte ejecutable del kernel, también llamado kernel extension o kext. También aparecen las dependencias entre módulos (si las hubiera). P.e. en nuestro ejemplo com.apple.dts.driver.PanicDriver usa a com.apple.iokit.IOUSBFamily.

El nombre de módulo y versión son los mismos que muestra el comando kextstat (o kmodstat en las versiones anteriores a Mac OS X 10.2) y no son más que el valor de los build settings de Project Builder MODULE_NAME y MODULE_VERSION.

Las dependencias entre módulos del núcleo se especifican el la propiedad OSBundleLibraries de las bundle settings de Project Builder.

Línea 10,11,12: xnu/osfmk/ppc/model_dep.c: print_backtrace()

Ahora se hace un volcado del estado de cada excepción.

Obsérvese que la primera excepción se salta (skipping...) porque el sv es el mismo que el de las líneas 3 a 6.

Línea 13,14,15: xnu/osfmk/ppc/model_dep.c: dump_savearea()

Sus valores son los mismos que los de las líneas 3 y 4.

Línea 16,17,18,19,20: xnu/osfmk/ppc/model_dep.c: print_backtrace()

Ahora la función print_backtrace() imprime el valor de la variable global version, cuyo valor se fija en tiempo de compilación. Esta variable contiene caracteres '\n' con lo que ocupa las líneas 17 a 19.

Fri Sep 6 23:24:34 PDT 2002 es la fecha en que se compiló el núcleo, root:xnu/xnu-344.2.obj~2/RELEASE_PPC es el directorio donde se compiló.

Podemos saber la versión del kernel que estamos ejecutando usando el comando:

$ sysctl kern.version
kern.version = Darwin Kernel Version 5.5:
Thu May 30 14:51:26 PDT 2002; root:xnu/xnu-201.42.3.obj~1/RELEASE_PPC

Las siguientes tres llamadas no producen ninguna salida:

xnu/osfmk/ppc/misc_asm.s: Call_Debugger()
xnu/osfmk/ppc/model_dep.c: Call_DebuggerC()
xnu/osfmk/kdp/ml/ppc/kdp_machdep.c: kdp_trap()

Línea 21: xnu/osfmk/kdp/kdp_udp.c: kdp_raise_exception()

Esta línea contiene un mensaje que describe la excepción seguido del número de excepción, código y subcódigo entre paréntesis.

Esta función recibe cuatro parámetros:

exception El array xnu/osfmk/kdp/ml/ppc/kdp_machdep.c:kdp_trap_codes se usa para convertir números de excepción específicos de PowerPC en códigos de excepción genéricos de Mach, usados por el KDB (Kernel DeBuger). Los códigos de excepción de Mach están definidos en el fichero mach/exception_type.h, aunque estos no son muy útiles porque la mayoría de las excepciones de PowerPC se les asigna el código de excepción Mach EXC_BAD_ACCESS.

exception_message El código de excepción Mach, se usa para buscar el texto que describe la excepción. Para ello se usa la tabla de mensajes xnu/osfmk/kdp/kdp_udp.c: exception_message

code y subcode son parámetros que no se usan y valen 0.

Línea 22,23,24,25: xnu/osfmk/kdp/kdp_udp.c: kdp_connection_wait()

Por último se muestra la dirección MAC y dirección IP de la sesión remota para depuración, una opción que permite depurar la máquina desde otra máquina con GDB. Si la máquina remota no está disponible, como es el caso del ejemplo, el programa queda bloqueado esperando.

Descubrir la causa del error

Supongamos que uno de nuestros clientes o beta tester ha instalado nuestra kernel extension y nos informa de que se le produce un kernel panic. ¿Cómo podemos saber que error está teniendo?. Para ello debemos de pedirle que nos entregue el mensaje de panic que encontrara en el fichero panic.log del directorio /Library/logs, que comentamos antes.

¿Cómo podemos encontrar la causa del error? En primer lugar debemos empezar ejecutando la misma versión del núcleo que en la que se produjo el error. Para ello tenemos los campos kernel (línea 16) y kext (línea 7) del mensaje de panic.

Después debemos de mirar el tipo del error y en que kernel extensión se ha producido. En nuestro ejemplo se ha producido una Data Access Exception con el Program Counter valiendo 0x0E692550. Si miramos en la lista de kernel extensions cargadas (línea 7) parece que la excepción se ha producido en la kernel extension com.apple.dts.driver.PanicDriver que está cargada en la dirección 0xe691000. También podemos ver que el DAR (Data Access Register) contiene la dirección donde se ha producido el acceso no válido que en nuestro ejemplo ha sido 0xDEADBEEF (línea 4).

Después podemos usar el backtrace (línea 5) para obtener la secuencia de llamadas que llevan hasta el error. Para descifrar esta lista de direcciones de memoria es necesario crear un fichero de símbolos redireccionables para el kernel y para cada una de las kernel extensions implicadas. Los ficheros de símbolos redireccionables para las kernel extensions deben de crearse a partir de la dirección base donde se cargan las kernel extensions y debido a que kext carga las kernel extensions cada vez en una dirección de memoria distinta, no hay más remedio que generar estos ficheros para cada panic que obtengamos. No pasa lo mismo con el kernel, que siempre se carga en la misma dirección base.

A partir de Mac OS X 10.2 podemos generar los ficheros de símbolos redireccionables usando el comando kextload como se muestra abajo. La opción -s sirve para indicar el directorio donde generar el fichero de símbolos redireccionables. La opción -n hace que kextload nos pregunte por la dirección base donde está cargada la kernel extension, así como cada una de sus dependencias.

$ sudo kextload -s /tmp -n PanicDriver/build/PanicDriver.kext/
Password:
kextload: notice: extension PanicDriver/build/PanicDriver.kext/ has debug properties set
····················· ·····················
enter the hexadecimal load addresses for these modules:
com.apple.iokit.IOUSBFamily: 0xed9c000
com.apple.dts.driver.PanicDriver: 0xe696000

El comando nos genera un fichero de símbolos redireccionables para cada módulo con el nombre nombremodulo.sym

En versiones anteriores a Mac OS X 10.2 esto se hace usando el comando kmodsyms como se muestra más abajo. Obsérvese que es necesario dar la dirección donde se carga cada kernel extension así como sus dependencias. En caso de haber varias dependencias, cada dependencia se da con en un argumento separado con la opción -d. La opción -v se usa para obtener una salida verbose.

$ kmodsyms -v -k /mach_kernel \
-d /System/Library/Extensions/IOUSBFamily.kext\ /Contents/MacOS/IOUSBFamily@0xed9c000 \
-o /tmp/com.apple.dts.driver.PanicDriver.sym
PanicDriver/build/PanicDriver.kext/Contents/MacOS/PanicDriver@0xe691000

kmodsyms: Returning fake load address of 0x ed9cb10
kmodsyms: kmod name: com.apple.iokit.IOUSBFamily
kmodsyms: kmod start @ 0xedab39c
kmodsyms: kmod stop @ 0xedab408
kmodsyms: Returning fake load address of 0x e691b10
kmodsyms: kmod name: com.apple.dts.driver.PanicDriver
kmodsyms: kmod start @ 0xe692574
kmodsyms: kmod stop @ 0xe6925e0

Después, cargamos el fichero de símbolos redireccionables en GDB usando el comando add-symbol-file tal como se muestra a continuación:

% gdb /mach_kernel
GNU gdb 5.1-20020408 (Apple version gdb-228) (Sun Jul 14 10:07:24 GMT 2002)
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "powerpc-apple-macos10".
(gdb) add-symbol-file /tmp/com.apple.dts.driver.PanicDriver.sym
add symbol table from file "/tmp/com.apple.dts.driver.PanicDriver.sym" at
(y or n) y
Reading symbols from /tmp/com.apple.dts.driver.PanicDriver.sym...done.
(gdb)

Si tenemos más de un fichero de símbolos (como es típico a partir de Mac OS X 10.2), repetimos el add-symbol-file comando para cada uno.

En el caso del I/O Kit, las funciones de este módulo están escritas en C++ con lo que debemos de activar el name demanging para hacer los nombres más comprensibles. Para esto tenemos el comando set print asm-demangle on, que hace que se realice el demanging de los nombres de funciones C++ y Objective-C.

Ahora podemos poner el Program Counter apuntando a la dirección de memoria en que estaba cuando se produjo el error usando el comando x/i address. Dependiendo del tipo de excepción el PC puede contener la dirección de memoria de la instrucción que produjo la excepción o la de la siguiente instrucción.

(gdb) set print asm-demangle on
(gdb) x/i 0xe692550
0xe692550 <com_apple_dts_driver_PanicDriver::start(IOService*)+276>: stw r0,0(r9)
(gdb)

Ahora, para cada instrucción del backtrace (línea 6), deberíamos mostrar la instrucción situada inmediatamente antes de su dirección para lo cual usamos el comando x/i address-4. Este nos da el nombre de la función situada en esa dirección de memoria.

(gdb) x/i 0x0e6974a8-4
0xe6974a4 <com_apple_dts_driver_PanicDriver::start(IOService*)+104>: bl
0xeb4f5b0 <com_apple_dts_driver_PanicDriver::start(IOService*)+372>
(gdb) x/i 0x0213a88-4
0x213a84 <IOService::startCandidate(IOService*)+116>: bctrl
(gdb) x/i 0x213884-4
0x213880 <IOService::probeCandidates(OSOrderedSet*)+2096>: bctrl
(gdb) x/i 0x2141d4-4
0x2141d0 <IOService::doServiceMatch(unsigned long)+452>: bctrl
(gdb) x/i 0x214830-4
0x21482c <_IOConfigThread::main(_IOConfigThread*)+280>: bctrl
(gdb) x/i 0x204cb0-4
0x204cac <ioThreadStart+56>: bctrl
(gdb) x/i 0x204c74-4
0x204c70 <IOLibInit+184>: blr
(gdb)

Debemos tener en cuenta que todas las instrucciones que desensamblemos deben de ser instrucciones de salto que modifiquen el registro LR, en caso contrario es que hemos generado el fichero de símbolos redireccionables mal, con lo que no coincide con los del kernel que produjo el panic.

Conclusión

Usando la técnica que hemos discutido en este artículo podemos realizar un análisis post mortem de un kernel panic. Aunque la información de un panic dump podía parecer críptica a priori, después de leer este artículo esperamos que el lector considere ahora esta técnica como un mecanismo de depuración más, que Apple pone a su disposición.

Para saber más puede consultar: