¿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:
- 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)
- xnu/osfmk/ppc/lowmem_vectors.s:
L_exception_entry()
- xnu/osfmk/ppc/hw_exception.s:
thandler()
- xnu/osfmk/ppc/trap.c: trap()
- 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:
|