Comprensión profunda del proceso de kernel panic

En el proceso de desarrollo de nuestro proyecto, muchas veces habrá una situación en la que el sistema del teléfono móvil falla y se reinicia por alguna razón (el reinicio se divide en reinicio de Android y reinicio del kernel, y solo discutimos la situación del reinicio del kernel, es decir , kernel panic), crash restart Básicamente, es el problema más grave del sistema. Hay recurrencias y probabilidades estables. La dificultad de resolver los problemas también es muy diferente. Después de que ocurre un problema, generalmente obtenemos información de registro del kernel como esta (la El siguiente registro solo se usa para llamar a BUG( ) como ejemplo, habrá algunas diferencias en la información del registro de fallas causadas por otras anomalías):

[    2.052157] <2>-(2)[1:swapper/0]------------[ cut here ]------------
[    2.052163] <2>-(2)[1:swapper/0]Kernel BUG at c04289dc [verbose debug info unavailable]
[    2.052169] <2>-(2)[1:swapper/0]Internal error: Oops - BUG: 0 [#1] PREEMPT SMP ARM
[    2.052178] <2>-(2)[1:swapper/0]disable aee kernel api[    3.052192] <2>-(2)[1:swapper/0]Non-crashing CPUs did not react to IPI
[    3.052204] <2>-(2)[1:swapper/0]CPU: 2 PID: 1 Comm: swapper/0 Tainted: G        W      3.18.35+ #3
[    3.052211] <2>-(2)[1:swapper/0]task: df060000 ti: df04a000 task.ti: df04a000
[    3.052227] <2>-(2)[1:swapper/0]PC is at ltr553_i2c_probe+0x94/0x9c
[    3.052233] <2>-(2)[1:swapper/0]LR is at 0x0
[    3.052242] <2>-(2)[1:swapper/0]pc : [<c04289dc>]    lr : [<00000000>]    psr: a0000113
[    3.052242] <2>sp : df04bd30  ip : 00000000  fp : df04bd4c
[    3.052249] <2>-(2)[1:swapper/0]r10: 00000003  r9 : de348fc0  r8 : c0428948
[    3.052255] <2>-(2)[1:swapper/0]r7 : dea1bc00  r6 : dea1bc04  r5 : dea1bc20  r4 : c0b53358
[    3.052262] <2>-(2)[1:swapper/0]r3 : c115ef4c  r2 : 00000000  r1 : 00000000  r0 : de366a00
[    4.354655] <2>-(2)[1:swapper/0] oops_end, 1, 11
[    4.354740] <2>-(2)[1:swapper/0]Kernel panic - not syncing: Fatal exception

Esta es la información importante relevante que emite el kernel de Linux antes de que falle, incluidos los punteros de PC, las pilas de llamadas, etc., que son pistas muy importantes para la depuración. Por ejemplo, podemos usar la herramienta GUN tools (add2Line) combinada con el kernel tabla de mapeo de símbolos vmlinux para ubicar el código actual El número de línea específico del código donde se encuentra el puntero de la PC (ubicar la línea del código de error no significa que se haya encontrado la causa raíz del problema y que se haya solucionado la excepción, esto depende de la complejidad de la excepción). Una comprensión profunda del significado y el mecanismo de esta información clave del registro de impresión es muy útil para que podamos localizar y analizar tales problemas de fallas (existen limitaciones en el análisis de problemas causados ​​por el uso de la memoria y la inestabilidad del hardware), lo cual es lo que necesitamos profundizar La intención original de aprender el proceso de excepción del núcleo.

Aquí tenemos que resolver algunas preguntas:

  • ¿Cómo surgió la información del registro de claves que quedó antes del bloqueo, cuál es el uso y cuál es el significado específico?
  • ¿Cómo usar estas pistas restantes para averiguar en qué archivo y línea se encuentra el código problemático?
  • ¿Cuál es el proceso general desde una excepción fatal hasta un bloqueo en el kernel y cómo analizar problemas como un bloqueo?

Por esta razón, este artículo toma el disparador activo BUG() más común como ejemplo para analizar las preguntas anteriores y analizar todo el proceso de kernel panic.

¿Qué es ERROR()?

Las personas que tienen experiencia en la depuración de controladores deben saber esto. El ERROR aquí no es lo mismo que el "defecto de software" en el que generalmente pensamos. El ERROR () mencionado aquí en realidad se usa en el kernel de Linux para interceptar programas del kernel que superan las expectativas. El comportamiento es un mecanismo para que el software informe excepciones de forma activa. Hay una pregunta aquí, ¿cuándo se usará? En términos generales, hay dos situaciones en las que se usa. Una es durante el proceso de desarrollo de software. Si se encuentra una falla fatal en la lógica del código, puede llamar a BUG() para hacer que el núcleo muera (similar a afirmar). Esto es conveniente para localizar el problema y corregirlo.Lógica de ejecución de código;Otra situación es que necesitamos usarlo cuando el sistema entra en kernel panic debido a alguna razón especial (generalmente se necesita ramdump para la depuración).

BUG() y BUG_ON(1) son esencialmente lo mismo. El último es solo una simple encapsulación basada en el primero. La esencia de BUG() es incorporar una instrucción indefinida: 0xe7f001f2, activando ARM para iniciar una excepción de instrucción indefinida (PS : ARM tiene 10 tipos de excepciones, puede revisar el capítulo del modelo de excepción ARM para obtener más detalles).

<kernel-3.18/arch/arm/include/asm/bug.h>
#define BUG_INSTR_VALUE 0xe7f001f2
#define BUG_INSTR(__value) __inst_arm(__value)
#define BUG() _BUG(__FILE__, __LINE__, BUG_INSTR_VALUE)
#define _BUG(file, line, value) __BUG(file, line, value)
#define __BUG(__file, __line, __value)    \
do {        \
 asm volatile(BUG_INSTR(__value) "\n");   \
 unreachable();      \
} while (0)

Análisis de procesos BUG()

El diagrama de flujo general de BUG() para reiniciar el sistema:

Llamar a BUG() enviará una instrucción indefinida a la CPU, lo que activará ARM para iniciar una excepción de instrucción indefinida y luego ingresará al proceso de manejo de excepciones del kernel para pasar por Oops, die(), __die() y otros procesos para generar pistas clave para depuración y análisis, y finalmente Ingrese panic() para finalizar el proceso de renacimiento. Este es el flujo básico de todo el proceso. Veamos primero qué hace específicamente die().

proceso morir()

código fuente:

void die(const char *str, struct pt_regs *regs, int err)
{
 enum bug_trap_type bug_type = BUG_TRAP_TYPE_NONE;
 unsigned long flags = oops_begin();
 int sig = SIGSEGV;
 
 if (!user_mode(regs))
  bug_type = report_bug(regs->ARM_pc, regs);
 if (bug_type != BUG_TRAP_TYPE_NONE)
  str = "Oops - BUG";
 if (__die(str, err, regs))
  sig = 0;
 oops_end(flags, regs, sig);
}

El proceso general es más o menos el siguiente:

En términos generales, el proceso de análisis de código combinado con el registro del kernel brindará una comprensión más profunda. Si se trata de una excepción causada por BUG()/BUG_ON(1), entonces puede ver el siguiente registro icónico cuando vaya a report_bug:

enum bug_trap_type report_bug(unsigned long bugaddr, struct pt_regs *regs) 
{
...
 if (!is_valid_bugaddr(bugaddr))
  return BUG_TRAP_TYPE_NONE;
...
 printk(KERN_DEFAULT "------------[ cut here ]------------\n");
 if (file)
  pr_crit("kernel BUG at %s:%u!\n", file, line);
 else
  pr_crit("Kernel BUG at %p [verbose debug info unavailable]\n",
   (void *)bugaddr);
 
===>
[    2.052157] <2>-(2)[1:swapper/0]------------[ cut here ]------------
[    2.052163] <2>-(2)[1:swapper/0]Kernel BUG at c04289dc [verbose debug info unavailable]
[    2.052169] <2>-(2)[1:swapper/0]Internal error: Oops - BUG: 0 [#1] PREEMPT SMP ARM

Por lo tanto, si ve esta información "[cortar aquí]" en el registro, puede inferir que el sistema se reinició debido a una falla fatal en el software y llamó activamente a BUG(), y puede intentar localizar, analizar y reparar el excepción basada en la información relevante. Aquí debe tenerse en cuenta que hay otra situación __WARN() que también imprimirá el registro icónico de "[ cortar aquí ]", pero el kernel no colgará, lo que es fácil de causar confusión :

#define __WARN()    warn_slowpath_null(__FILE__, __LINE__)
#define __WARN_printf(arg...) warn_slowpath_fmt(__FILE__, __LINE__, arg)
void warn_slowpath_fmt(const char *file, int line, const char *fmt, ...)
{
...
 warn_slowpath_common(file, line, __builtin_return_address(0),
        TAINT_WARN, &args);
static void warn_slowpath_common(const char *file, int line, void *caller,
     unsigned taint, struct slowpath_args *args)
{
...
 pr_warn("------------[ cut here ]------------\n");
 pr_warn("WARNING: CPU: %d PID: %d at %s:%d %pS()\n",
  raw_smp_processor_id(), current->pid, file, line, caller);
 
===>
[    1.106219] <2>-(2)[1:swapper/0]------------[ cut here ]------------
[    1.107018] <2>-(2)[1:swapper/0]WARNING: CPU: 2 PID: 1 at /home/android/work/prj/n-6580/kernel-3.18/kernel/irq/manage.c:454 __enable_irq+0x50/0x8c()

Entonces, de hecho, es fácil distinguir las dos situaciones por la apariencia.Si es BUG()/BUG_ON(1), el núcleo definitivamente colgará y se reiniciará, y __WARN() solo descargará_stack() sin bloquearse. el código fuente y la información de registro También es fácil distinguir las dos situaciones. Si es BUG()/BUG_ON(1), debe haber una salida de registro similar a la siguiente, solo busque la palabra clave: "Error interno: Vaya ".

[    2.052163] <2>-(2)[1:swapper/0]Kernel BUG at c04289dc [verbose debug info unavailable]
[    2.052169] <2>-(2)[1:swapper/0]Internal error: Oops - BUG: 0 [#1] PREEMPT SMP ARM

 Información a través del tren: ruta de aprendizaje de la tecnología del código fuente del kernel de Linux + video tutorial del código fuente del kernel

Aprendizaje a través del entrenamiento: código fuente del kernel de Linux, ajuste de memoria, sistema de archivos, gestión de procesos, controlador de dispositivo/pila de protocolo de red

Análisis del proceso __die()

La salida de información de registro de lo anterior no es suficiente para ubicar la ubicación del código problemático específico, incluido el puntero de PC más crítico, la pila de llamadas y otras pistas que son cruciales para la depuración están en __die() en la salida.

diagrama de flujo:

Imprima la información del registro icónico:

static int __die(const char *str, int err, struct pt_regs *regs)
{
...
 printk(KERN_EMERG "Internal error: %s: %x [#%d]" S_PREEMPT S_SMP
        S_ISA "\n", str, err, ++die_counter);
===>
[    2.052169] <2>-(2)[1:swapper/0]Internal error: Oops - BUG: 0 [#1] PREEMPT SMP ARM

El registro muestra que la cadena anormal es Oops - BUG, ​​el código de error es 0 y el número de contadores de dados: 1

El significado original de Oops es un término del argot para "oops", lo que significa que el núcleo tiene un accidente y no sabe cómo solucionarlo.

notificar_die() notificará a los módulos que están interesados ​​en Oops para ejecutar devoluciones de llamada relacionadas.Por ejemplo, el módulo de motor de excepción aee de mtk está registrado en la cadena de notificación die_chain.

int notrace notify_die(enum die_val val, const char *str,
        struct pt_regs *regs, long err, int trap, int sig)
{
 struct die_args args = {
  .regs = regs,
  .str = str,
  .err = err,
  .trapnr = trap,
  .signr = sig,
 };
 return atomic_notifier_call_chain(&die_chain, val, &args);
}

El motor de excepción aee de mtk se registrará en la cadena de notificación die_chain cuando se inicialice el kernel, y podemos ver que la cadena de notificación de pánico está realmente registrada.

int __init aee_ipanic_init(void)
{
 spin_lock_init(&ipanic_lock);
 mrdump_init();
 atomic_notifier_chain_register(&panic_notifier_list, &panic_blk);
 register_die_notifier(&die_blk);
 register_ipanic_ops(&ipanic_oops_ops);
 ipanic_log_temp_init();
 ipanic_msdc_init();
 LOGI("ipanic: startup, partition assgined %s\n", AEE_IPANIC_PLABEL);
 return 0;
}

La información clave útil para nuestra depuración y seguimiento está impresa en __show_regs():

void __show_regs(struct pt_regs *regs)
{
 unsigned long flags;
 char buf[64];
 
 show_regs_print_info(KERN_DEFAULT);
 print_symbol("PC is at %s\n", instruction_pointer(regs));
 print_symbol("LR is at %s\n", regs->ARM_lr);
 printk("pc : [<%08lx>]    lr : [<%08lx>]    psr: %08lx\n"
        "sp : %08lx  ip : %08lx  fp : %08lx\n",
  regs->ARM_pc, regs->ARM_lr, regs->ARM_cpsr,
  regs->ARM_sp, regs->ARM_ip, regs->ARM_fp);
 printk("r10: %08lx  r9 : %08lx  r8 : %08lx\n",
  regs->ARM_r10, regs->ARM_r9,
  regs->ARM_r8);
 printk("r7 : %08lx  r6 : %08lx  r5 : %08lx  r4 : %08lx\n",
  regs->ARM_r7, regs->ARM_r6,
  regs->ARM_r5, regs->ARM_r4);
 printk("r3 : %08lx  r2 : %08lx  r1 : %08lx  r0 : %08lx\n",
  regs->ARM_r3, regs->ARM_r2,
  regs->ARM_r1, regs->ARM_r0);
 flags = regs->ARM_cpsr;
 buf[0] = flags & PSR_N_BIT ? 'N' : 'n';
 buf[1] = flags & PSR_Z_BIT ? 'Z' : 'z';
 buf[2] = flags & PSR_C_BIT ? 'C' : 'c';
 buf[3] = flags & PSR_V_BIT ? 'V' : 'v';
 buf[4] = '\0';
 printk("Flags: %s  IRQs o%s  FIQs o%s  Mode %s  ISA %s  Segment %s\n",
  buf, interrupts_enabled(regs) ? "n" : "ff",
  fast_interrupts_enabled(regs) ? "n" : "ff",
  processor_modes[processor_mode(regs)],
  isa_modes[isa_mode(regs)],
  get_fs() == get_ds() ? "kernel" : "user");
 show_extra_register_data(regs, 128);
}
void dump_stack_print_info(const char *log_lvl)
{
 printk("%sCPU: %d PID: %d Comm: %.20s %s %s %.*s\n",
        log_lvl, raw_smp_processor_id(), current->pid, current->comm,
        print_tainted(), init_utsname()->release,
        (int)strcspn(init_utsname()->version, " "),
        init_utsname()->version);
...

Esto imprime información importante, como dónde se detuvo la PC, información de registro relacionada, si ocurrió una excepción de usuario o kernel, la CPU donde ocurrió la excepción y el pid del proceso.

El siguiente dump_mem() se usa para volcar la información de la memoria del hilo actual:

dump_mem(KERN_EMERG, "Stack: ", regs->ARM_sp,
 THREAD_SIZE + (unsigned long)task_stack_page(tsk));

Use dump_backtrace(regs, tsk) para imprimir la información de pila de llamadas más intuitiva para la depuración:

[    3.056363] <2>-(2)[1:swapper/0]Backtrace: -(2)[1:swapper/0]
[ 3.056386] <2>-(2)[1:swapper/0][<c010badc>] (dump_backtrace) from [<c010bc7c>] (show_stack+0x18/0x1c) [ 3.056393] <2>-(2)[1:swapper/0] r6:c103d790-(2)[1:swapper/0] r5:ffffffff-(2)[1:swapper/0] r4:00000000-(2)[1:swapper/0] r3:00000000-(2)[1:swapper/0] [ 3.056426] <2>-(2)[1:swapper/0][<c010bc64>] (show_stack) from [<c0a91e64>] (dump_stack+0x90/0xa4) [ 3.056439] <2>-(2)[1:swapper/0][<c0a91dd4>] (dump_stack) from [<c072d264>] (ipan6503] <2>-(2)[1:swapper/0][<c013e6bc>] (notifier_call_chain) from [<c013eb84>] (atomic_notifier_call_chain+0x3c/0x50) [ 3.056509] <2>-(2)[1:swapper/0] r10:c10efec4-(2)[1:swapper/0] r9:df060000-(2)[1:swapper/0] r8:df04a020-(2)[1:swapper/0] r7:c0caaaf0-(2)[1:swapper/0] r6:c10f0c88-(2)[1:swapper/0] r5:00000001 [ 3.056539] <2>-(2)[1:swapper/0] r4:df04bb74-(2)[1:swapper/0] [ 3.056554] <2>-(2)[1:swapper/0][<c013eb48>] (atomic_notifier_call_chain) from [<c013f244>] (notify_die+0x44/0x4c) [ 3.056560] <2>-(2)[1:swapper/0] r6:df04bce8-(2)[1:swapper/0] r5:00000000-(2)[1:swapper/0] r4:00000001-(2)[1:swapper/0] [ 3.056585] <2>-(2)[1:swapper/0][<c013f200>] (notify_die) from [<c010bd94>] (die+0x114/0x41c) [ 3.056590] <2>-(2)[1:swapper/0] r4:c102826c-(2)[1:swapper/0] [ 3.056607] <2>-(2)[1:swapper/0][<c010bc80>] (die) from [<c010c0c0>] (arm_notify_die+0x24/0x5c) [ 3.056612] <2>-(2)[1:swapper/0] r10:df04a000-(2)[1:swapper/0] r9:00000000-(2)[1:swapper/0] r8:df04bce8-(2)[1:swapper/0] r7:e7f001f2-(2)[1:swapper/0] r6:df04a000-(2)[1:swapper/0] r5:c04289dc [ 3.056642] <2>-(2)[1:swapper/0] r4:00000004-(2)[1:swapper/0] [ 3.056658] <2>-(2)[1:swapper/0][<c010c09c>] (arm_notify_die) from [<c01001cc>] (do_undefinstr+0x1a4/0x1ec) [ 3.056670] <2>-(2)[1:swapper/0][<c0100028>] (do_undefinstr) from [<c010c98c>] (__und_svc_finish+0x0/0x34) [ 3.056676] <2>-(2)[1:swapper/0]Exception stack(0xdf04bce8 to 0xdf04bd30) [ 3.056687] <2>-(2)[1:swapper/0]bce0: de366a00 00000000 00000000 c115ef4c c0b53358 dea1bc20 [ 3.056698] <2>-(2)[1:swapper/0]bd00: dea1bc04 dea1bc00 c0428948 de348fc0 00000003 df04bd4c 00000000 df04bd30 [ 3.056706] <2>-(2)[1:swapper/0]bd20: 00000000 c04289dc a0000113 ffffffff [ 3.056711] <2>-(2)[1:swapper/0] r9:c010c98c-(2)[1:swapper/0] r8:e7100000-(2)[1:swapper/0] r7:00000000-(2)[1:swapper/0] r6:c010cd98-(2)[1:swapper/0] r5:00000000-(2)[1:swapper/0] r4:c04289e0 [ 3.056750] <2>-(2)[1:swapper/0][<c0428948>] (ltr553_i2c_probe) from [<c07d88d8>] (i2c_device_probe+0xd0/0x12c) [ 3.056756] <2>-(2)[1:swapper/0] r5:dea1bc20-(2)[1:swapper/0] r4:c0b53358-(2)[1:swapper/0] [ 3.056778] <2>-(2)[1:swapper/0][<c07d8808>] (i2c_device_probe) from [<c03c298c>] (driver_probe_device+0x160/0x43c)

A través de la información de la pila de llamadas anterior combinada con GUN Tools (add2Line), básicamente puede ubicar la ubicación del código específico donde ocurrió la excepción.

Finalmente, el puntero de la PC y las primeras 4 instrucciones se imprimirán a través de dump_instr(KERN_EMERG, regs):

static void dump_instr(const char *lvl, struct pt_regs *regs)
{
...
 for (i = -4; i < 1 + !!thumb; i++) {
  unsigned int val, bad;
  if (thumb)
   bad = __get_user(val, &((u16 *)addr)[i]);
  else
   bad = __get_user(val, &((u32 *)addr)[i]);
  if (!bad)
   p += sprintf(p, i == 0 ? "(%0*x) " : "%0*x ", width, val);
  else {
   p += sprintf(p, "bad PC value");
   break;
  }
 }
 printk("%sCode: %s\n", lvl, str);
..
===>
[    3.226706] <2>-(2)[1:swapper/0][<c0a8ae74>] (/0]Code: e89da830 e30e3f4c e34c3115 e5830000 (e7f001f2)

¿Has visto este e7f001f2, te parece familiar? ¡Esta es la instrucción indefinida incrustada en BUG()!

En este punto, se ha emitido la mayor parte de la información clave. Puede usar la herramienta add2Line para ubicar el número de línea específico del código muerto y ver aproximadamente lo que sucedió. Si es una excepción causada por BUG(), entonces puede considere analizarlo y repararlo Anormal, porque BUG() es una excepción de informe activo, en términos generales, la dificultad de depuración es mucho más fácil que otros métodos de informe pasivo.

Por ejemplo:

Conozca la dirección donde la PC murió del registro anterior y use la herramienta add2Line combinada con la tabla de mapeo de símbolos del kernel vmlinux para ubicar el número de línea del archivo donde se encuentra el código específico:

arm-linux-androideabi-addr2line -e out/target/product/$project/obj/KERNEL_OBJ/vmlinux -f -C c04289dc
ltr553_i2c_probe
/aosp/kernel-3.18/drivers/misc/mediatek/alsps/ltr553/ltr553.c:3278

Después de ubicar el número de línea de código específico, puede analizar más a fondo el registro de código para averiguar la causa del problema y corregir la excepción (en términos generales, la excepción causada por BUG() es más fácil de resolver y la dificultad de otras situaciones es muy diferente..). Entonces, ¿qué hará el núcleo a continuación? Una vez que se haya emitido la información importante, irá directamente al proceso de pánico del kernel.

proceso de pánico

El significado original de pánico es "pánico", aquí significa que el núcleo tiene un error fatal y no puede continuar ejecutándose.

diagrama de flujo:

Finalmente, adjunte el diagrama de tiempo general:

 

Supongo que te gusta

Origin blog.csdn.net/youzhangjing_/article/details/131399999
Recomendado
Clasificación