一种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,&notify_oops_panic);
    register_die_notifier(&notify_oops_panic);

    return 0;
}

void notify_oops_panic_exit(void)
{
    atomic_notifier_chain_unregister(&panic_notifier_list,&notify_oops_panic);
    unregister_die_notifier(&notify_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");

 

posted @ 2021-03-31 22:23  smilingsusu  阅读(476)  评论(0编辑  收藏  举报