/*------------------------------------------------------------------------------------------*\ * * Copyright (C) 2018 AVM GmbH * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * mips-helper-functions for stackdump on smp etc. \*------------------------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** */ #define IS_KERNEL_ADDR 0x1 #define IS_MODULE_ADDR 0x2 #define IS_VMALLOC_ADDR 0x3 #define IS_STACK_ADDR 0x4 #define snprintf_add(ptxt, txtlen, args...) if(ptxt == NULL) printk(args); else { int local_add_len;\ if((local_add_len = snprintf(ptxt, txtlen, args)) > 0) { \ int tail = min((int)txtlen, local_add_len); \ (ptxt) += tail, (txtlen) -= tail; \ } \ } int memory_classifier(unsigned long addr) { if((addr >= (unsigned long)_stext && addr <= (unsigned long)_end)) { return IS_KERNEL_ADDR; } else if(is_module_text_address(addr)) { return IS_MODULE_ADDR; } else if(is_vmalloc_addr((void *)addr)) { return IS_VMALLOC_ADDR; } else if(object_is_on_stack((void *)addr)) { return IS_STACK_ADDR; } return 0; } /** * @return NULL no virtual addr */ static struct page *memory_page_classifier(unsigned long addr) { if(virt_addr_valid(addr)) { return virt_to_page((void *)addr); } return NULL; } /** */ static char *print_vmflags(char *txt, unsigned int txtlen, unsigned long vm_flags) { char *txt_start = txt; txt[0] = 0; if (vm_flags & VM_IOREMAP){snprintf_add(txt, txtlen, "ioremap ")}; if (vm_flags & VM_ALLOC) {snprintf_add(txt, txtlen, "vmalloc ")}; if (vm_flags & VM_MAP) {snprintf_add(txt, txtlen, "vmap ")}; if (vm_flags & VM_USERMAP){snprintf_add(txt, txtlen, "user ")}; return txt_start; } /** */ char *arch_print_memory_classifier(char *txt, unsigned int txtlen, unsigned long addr, int include_addr_prefix) { char sym[KSYM_SYMBOL_LEN], *modname; char txtbuf[TASK_COMM_LEN + 16]; char *txt_start = txt; unsigned long caller, size, offset, start, flags, vmflags; int type; const char *name; struct page *page; struct zone *zone; if(include_addr_prefix) { snprintf_add(txt, txtlen, "0x%08lx ", addr); } else { txt[0] = 0; } type = memory_classifier(addr); switch(type) { case IS_KERNEL_ADDR: case IS_MODULE_ADDR: #ifdef CONFIG_KALLSYMS name = kallsyms_lookup(addr, &size, &offset, &modname, sym); if(!name) { return txt_start; } snprintf_add(txt, txtlen, "%s+%#lx/%#lx", name, offset, size); if(modname) { snprintf_add(txt, txtlen, " [%s]", modname); } #endif return txt_start; case IS_VMALLOC_ADDR: if(in_nmi()) { return txt_start; } if((start = get_vmap_area(addr, &caller, &size, &vmflags))) { snprintf(txt, txtlen, "[%s: size:%lu start:%p+0x%lx alloced by:%pS]", print_vmflags(txtbuf, sizeof(txtbuf), vmflags), size, (void *)start, addr - start, (void *)caller); } return txt_start; case IS_STACK_ADDR: break; default: break; } if((start = get_taskstack_area(addr, txtbuf, sizeof(txtbuf), type == IS_STACK_ADDR ? 1 : 0))) { snprintf(txt, txtlen, "[%s: %p+0x%lx]", txtbuf, (void *)start, addr - start); return txt_start; } if(in_nmi()) { return txt_start; } page = memory_page_classifier(addr); if(!page) { return txt_start; } zone = page_zone(page); if(!spin_trylock_irqsave(&zone->lock, flags)) { return txt_start; } if (PageSlab(page)) { #if 0 /* AVM slab stuff disabled when migrating to Linux v4.9 */ if((start = get_kmemalloc_area(addr, &caller, &name, &size, &freed))) { if(caller) { snprintf(sym, sizeof(sym), " %s by:%pS", freed ? "freed" : "allocated", (void *)caller); } else { sym[0] = 0; } snprintf(txt, txtlen, "[slab: type:%s size:%lu start:0x%p+0x%lx%s]", name, size, (void *)start, addr - start, sym); } #endif } else if(PageReserved(page)) { snprintf(txt, txtlen, "[page: type:reserved]"); } else if(page_ref_count(page)) { unsigned long current_pc = avm_get_page_current_pc(page); if(current_pc) { snprintf(sym, sizeof(sym), " by:%pS", (void *)current_pc); } else { sym[0] = 0; } snprintf(txt, txtlen, "[page: type:alloc%s]", sym); } spin_unlock_irqrestore(&zone->lock, flags); return txt_start; } EXPORT_SYMBOL(print_memory_classifier); /** */ static int match_data(unsigned long data, unsigned long data_array[], unsigned int array_elements) { unsigned int i; for(i = 0; i < array_elements; i++) { if(data_array[i] == 0) { data_array[i] = data; return 0; } if(data_array[i] == data) { return 1; } } return 0; } /** */ void arch_show_stacktrace_memoryclassifier(struct pt_regs *pregs) { unsigned long data_hist[40] = {0}; char txt[KSYM_SYMBOL_LEN]; unsigned int start = 0, limit = 0; unsigned long stackdata; unsigned long *sp; if (pregs == NULL || user_mode(pregs)) return; sp = (unsigned long *)kernel_stack_pointer(pregs); for (limit = 0; limit < ARRAY_SIZE(data_hist); ++limit) { if (kstack_end(sp)) break; stackdata = *sp++; if (stackdata && match_data(stackdata, data_hist, ARRAY_SIZE(data_hist))) continue; print_memory_classifier(txt, sizeof(txt), stackdata, 0); if (!txt[0]) continue; if (start++ == 0) pr_err("Classified pointer on stack:\n"); pr_err("%08lx %s\n", stackdata, txt); } } static char *reg_name[] = {"eax","ebx","ecx","edx","esi","edi","ebp", "esp"}; /** */ void arch_show_register_memoryclassifier(struct pt_regs *regs) { unsigned long reg[8]; char txt[KSYM_SYMBOL_LEN]; unsigned int i, start = 0; if(regs == NULL) { return; } if(user_mode(regs)) { return; } reg[0] = regs->ax, reg[1] = regs->bx, reg[2] = regs->cx, reg[3] = regs->dx; reg[4] = regs->si, reg[5] = regs->di, reg[6] = regs->bp, reg[7] = regs->sp; for (i = 0; i < ARRAY_SIZE(reg); i++) { print_memory_classifier(txt, sizeof(txt), reg[i], 0); if(txt[0]) { if(start == 0) { start = 1; printk(KERN_ERR"Classified pointer on registers:\n"); } printk(KERN_ERR"%s: %08lx %s\n", reg_name[i], reg[i], txt); } } }