一种memory问题导致的kernel panic的处理方法
下面是一个在kernel panic或者oops之后,能够打印更多内存信息的patch,主要用到前面介绍的die notify功能注册oops/painc回调函数。
#include <linux/mm.h> #include <linux/spinlock.h> #include <linux/slab.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/module.h> #include <linux/bitops.h> #include <linux/version.h> #include <linux/vmalloc.h> #include <linux/kasan.h> #include <linux/notifier.h> #include <linux/uaccess.h> #include <linux/kdebug.h> #include <linux/rmap.h> #include <linux/delay.h> #include <asm/sections.h> #include <linux/memblock.h> #include <linux/kallsyms.h> #include <asm-generic/kdebug.h> #include <../mm/slab.h> #include <asm/memory.h> #include <asm/esr.h> #include <asm/sysreg.h> #include <asm/system_misc.h> #define DUMP_MEMORY_SIZE 0x100 static struct pt_regs saved_regs; static void _dump_register(struct pt_regs *regs) { int i = 0; pr_err("sp=%pS(%px)\n",(void *)regs->sp, (void *)regs->sp); pr_err("pc=%pS(%px)\n",(void *)regs->pc, (void *)regs->pc); for (i = 0; i < 31; i++){ pr_err("X%d%px ", i, (void *)regs->regs[i]); } } static int check_addr_valid(unsigned long ptr) { unsigned long flags; unsigned long par; local_irq_save(flags); asm volatile("at s1e1r, %0" :: "r" (ptr)); isb(); par = read_sysreg(par_el1); local_irq_restore(flags); pr_info("PAR = %lx\n", par); if (par & SYS_PAR_EL1_F) return false; return true; } //TODO: enable CONFIG_SLUB_DEBUG CONFIG_STACKTRACE extern void print_page_info(struct page *page); extern void *get_freepointer(struct kmem_cache *s, void *object); extern void print_tracking(struct kmem_cache *s, void *object); static phys_addr_t _find_prev_slab_and_print_slub(phys_addr_t pa_addr) { void * va = __va(pa_addr); struct kmem_cache *slab_kmem_cache = NULL; struct page *page = phys_to_page(pa_addr); struct page *compound_page = compound_head(page); void *slub_info = NULL; void *s; if (pa_addr == 0 || va == NULL || compound_page == NULL) { return 0; } //TODO: EXPORT_SYMBOL(print_page_info) print_page_info(compound_page); if (!PageSlab(compound_page)) return 0; slab_kmem_cache = compound_page->slab_cache; if (!slab_kmem_cache) return 0; slub_info = nearest_obj(slab_kmem_cache, compound_page, va); pr_info("slub_info=%px, from slab %s(%x)\n", slub_info, slab_kmem_cache->memcg_params.root_cache? \ slab_kmem_cache->memcg_params.root_cache->name:slab_kmem_cache->name, slab_kmem_cache->size); if (bit_spin_trylock(PG_locked, &compound_page->flags)) { //TODO: EXPORT_SYMBOL(get_freepointer) for (s = compound_page->freelist; s != NULL; s = get_freepointer(slab_kmem_cache, s)) if (s == slub_info) break; if (s == slub_info) pr_info("slub_info %px be free.\n", slub_info); else pr_info("slub_info %px don't free.\n", slub_info); bit_spin_unlock(PG_locked, &compound_page->flags); } else pr_info("slub_info %px don't free.\n", slub_info); //TODO: EXPORT_SYMBOL(print_tracking) if (slab_kmem_cache->flags & SLAB_STORE_USER) print_tracking(slab_kmem_cache, slub_info); //prev slab info return __pa(slub_info - slab_kmem_cache->size); } static void _check_and_print_slub_info(unsigned long ptr) { struct memblock_region *region; phys_addr_t start,end; phys_addr_t prev_slub; for_each_memblock(memory, region) { start = PFN_PHYS(memblock_region_memory_base_pfn(region)); end = PFN_PHYS(memblock_region_memory_end_pfn(region)) - 1; if (__va(start) <= (void*)ptr && __va(end) >= (void*)ptr) { pr_err("ptr:%px - pa:%px\n",(void*)ptr, (void*)__pa(ptr)); prev_slub = _find_prev_slab_and_print_slub(__pa(ptr)); prev_slub = _find_prev_slab_and_print_slub(prev_slub); break; } } } static void _dump_register_data(const char *prefix, unsigned long ptr) { uint64_t pa; void *buf; if (arm_va2pa_helper((void*)ptr, &pa) == true) { buf = __va(pa); print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_ADDRESS, 16, 8, buf - (DUMP_MEMORY_SIZE>>1), DUMP_MEMORY_SIZE, true); } } static void _dump_valid_memory_for_register(struct pt_regs *regs) { int i; char buffer[64]; pr_err("\nSP register:%px memory:\n",(void*)regs->sp); if (!check_addr_valid(regs->sp)) _dump_register_data("SP ", regs->sp); pr_err("\nPC register:%px memory:\n",(void*)regs->pc); if (!check_addr_valid(regs->pc)) _dump_register_data("PC ", regs->pc); for (i = 0; i < 31; i ++) { if (!check_addr_valid(regs->regs[i])){ snprintf(buffer, sizeof(buffer), "X%d ", i); pr_err("\nX%d register:%px memory:\n", i, (void*)regs->sp); _dump_register_data((const char *)buffer, regs->regs[i]); _check_and_print_slub_info(regs->regs[i]); } else { pr_err("\nX%d register:%px memory invalid.\n", i, (void*)regs->sp); } } } static int notify_panic_handler(struct notifier_block *self, unsigned long val, void *data) { struct die_args *pargs = (struct die_args*)data; memcpy(&saved_regs, pargs->regs, sizeof(saved_regs)); if (val == DIE_OOPS) pr_err("System is die oops.\n"); else pr_err("System is die panic.\n"); _dump_register(&saved_regs); _dump_valid_memory_for_register(&saved_regs); return NOTIFY_OK; } static struct notifier_block notify_oops_panic = { .notifier_call = notify_panic_handler, }; static int __init notify_oops_panic_init(void) { atomic_notifier_chain_register(&panic_notifier_list,¬ify_oops_panic); register_die_notifier(¬ify_oops_panic); return 0; } void notify_oops_panic_exit(void) { atomic_notifier_chain_unregister(&panic_notifier_list,¬ify_oops_panic); unregister_die_notifier(¬ify_oops_panic); } static int __init setup_oops_panic_notify(char *str) { if (*str++ != '=' || !*str) return -EINVAL; if (!strcmp(str,"on") || *str == 1) oops_panic_notify = 1; return 0; } __setup("oops_panic_notify", setup_oops_panic_notify); module_init(notify_oops_panic_init); module_exit(notify_oops_panic_exit); MODUL_LICENSE("GPL v2");