/** * @file daemon/liblegacy/opd_kernel.c * Dealing with the kernel and kernel module samples * * @remark Copyright 2002 OProfile authors * @remark Read the file COPYING * * @author John Levon * @author Philippe Elie */ #include "opd_kernel.h" #include "opd_proc.h" #include "opd_image.h" #include "opd_mapping.h" #include "opd_printf.h" #include "opd_24_stats.h" #include "oprofiled.h" #include "op_fileio.h" #include "op_config_24.h" #include "op_libiberty.h" #include "p_module.h" #include <string.h> #include <stdlib.h> #include <errno.h> /* kernel module */ struct opd_module { char * name; struct opd_image * image; unsigned long start; unsigned long end; struct list_head module_list; }; static struct opd_image * kernel_image; /* kernel and module support */ static unsigned long kernel_start; static unsigned long kernel_end; static struct list_head opd_modules = { &opd_modules, &opd_modules }; static unsigned int nr_modules=0; void opd_init_kernel_image(void) { /* for no vmlinux */ if (!vmlinux) vmlinux = "no-vmlinux"; kernel_image = opd_get_kernel_image(vmlinux, NULL, 0, 0); kernel_image->ref_count++; } void opd_parse_kernel_range(char const * arg) { sscanf(arg, "%lx,%lx", &kernel_start, &kernel_end); verbprintf(vmisc, "OPD_PARSE_KERNEL_RANGE: kernel_start = %lx, kernel_end = %lx\n", kernel_start, kernel_end); if (!kernel_start && !kernel_end) { fprintf(stderr, "Warning: mis-parsed kernel range: %lx-%lx\n", kernel_start, kernel_end); fprintf(stderr, "kernel profiles will be wrong.\n"); } } /** * opd_create_module - allocate and initialise a module description * @param name module name * @param start start address * @param end end address */ static struct opd_module * opd_create_module(char * name, unsigned long start, unsigned long end) { struct opd_module * module = xmalloc(sizeof(struct opd_module)); module->name = xstrdup(name); module->image = NULL; module->start = start; module->end = end; list_add(&module->module_list, &opd_modules); return module; } /** * opd_find_module_by_name - find a module by name, ccreating a new once if * search fail * @param name module name */ static struct opd_module * opd_find_module_by_name(char * name) { struct list_head * pos; struct opd_module * module; list_for_each(pos, &opd_modules) { module = list_entry(pos, struct opd_module, module_list); if (!strcmp(name, module->name)) return module; } return opd_create_module(name, 0, 0); } void opd_clear_module_info(void) { struct list_head * pos; struct list_head * pos2; struct opd_module * module; verbprintf(vmodule, "Removing module list\n"); list_for_each_safe(pos, pos2, &opd_modules) { module = list_entry(pos, struct opd_module, module_list); free(module->name); free(module); } list_init(&opd_modules); opd_clear_kernel_mapping(); } /** * opd_get_module_info - parse mapping information for kernel modules * * Parse the file /proc/ksyms to read in mapping information for * all kernel modules. The modutils package adds special symbols * to this file which allows determination of the module image * and mapping addresses of the form : * * __insmod_modulename_Oobjectfile_Mmtime_Vversion * __insmod_modulename_Ssectionname_Llength * * Currently the image file "objectfile" is stored, and details of * ".text" sections. * * There is no query_module API that allow to get directly the pathname * of a module so we need to parse all the /proc/ksyms. */ static void opd_get_module_info(void) { char * line; char * cp, * cp2, * cp3; FILE * fp; struct opd_module * mod; char * modname; char * filename; nr_modules=0; fp = op_try_open_file("/proc/ksyms", "r"); if (!fp) { printf("oprofiled: /proc/ksyms not readable, can't process module samples.\n"); return; } verbprintf(vmodule, "Read module info.\n"); while (1) { line = op_get_line(fp); if (!line) break; if (!strcmp("", line)) { free(line); continue; } if (strlen(line) < 9) { printf("oprofiled: corrupt /proc/ksyms line \"%s\"\n", line); break; } if (strncmp("__insmod_", line + 9, 9)) { free(line); continue; } cp = line + 18; cp2 = cp; while ((*cp2) && !!strncmp("_S", cp2+1, 2) && !!strncmp("_O", cp2+1, 2)) cp2++; if (!*cp2) { printf("oprofiled: corrupt /proc/ksyms line \"%s\"\n", line); break; } cp2++; modname = xmalloc((size_t)((cp2-cp) + 1)); strncpy(modname, cp, (size_t)((cp2-cp))); modname[cp2-cp] = '\0'; mod = opd_find_module_by_name(modname); free(modname); switch (*(++cp2)) { case 'O': /* get filename */ cp2++; cp3 = cp2; while ((*cp3) && !!strncmp("_M", cp3+1, 2)) cp3++; if (!*cp3) { free(line); continue; } cp3++; filename = xmalloc((size_t)(cp3 - cp2 + 1)); strncpy(filename, cp2, (size_t)(cp3 - cp2)); filename[cp3-cp2] = '\0'; mod->image = opd_get_kernel_image(filename, NULL, 0, 0); mod->image->ref_count++; free(filename); break; case 'S': /* get extent of .text section */ cp2++; if (strncmp(".text_L", cp2, 7)) { free(line); continue; } cp2 += 7; sscanf(line, "%lx", &mod->start); sscanf(cp2, "%lu", &mod->end); mod->end += mod->start; break; } free(line); } if (line) free(line); op_close_file(fp); } /** * opd_drop_module_sample - drop a module sample efficiently * @param eip eip of sample * * This function is called to recover from failing to put a samples even * after re-reading /proc/ksyms. It's either a rogue sample, or from a module * that didn't create symbols (like in some initrd setups). So we check with * query_module() if we can place it in a symbol-less module, and if so create * a negative entry for it, to quickly ignore future samples. * * Problem uncovered by Bob Montgomery <bobm@fc.hp.com> * */ static void opd_drop_module_sample(unsigned long eip) { char * module_names; char * name; size_t size = 1024; size_t ret; uint nr_mods; uint mod = 0; opd_24_stats[OPD_LOST_MODULE]++; module_names = xmalloc(size); while (query_module(NULL, QM_MODULES, module_names, size, &ret)) { if (errno != ENOSPC) { verbprintf(vmodule, "query_module failed: %s\n", strerror(errno)); return; } size = ret; module_names = xrealloc(module_names, size); } nr_mods = ret; name = module_names; while (mod < nr_mods) { struct module_info info; if (!query_module(name, QM_INFO, &info, sizeof(info), &ret)) { if (eip >= info.addr && eip < info.addr + info.size) { verbprintf(vmodule, "Sample from unprofilable module %s\n", name); opd_create_module(name, info.addr, info.addr + info.size); break; } } mod++; name += strlen(name) + 1; } if (module_names) free(module_names); } /** * opd_find_module_by_eip - find a module by its eip * @param eip EIP value * * find in the modules container the module which * contain this eip return %NULL if not found. * caller must check than the module image is valid */ static struct opd_module * opd_find_module_by_eip(unsigned long eip) { struct list_head * pos; struct opd_module * module; list_for_each(pos, &opd_modules) { module = list_entry(pos, struct opd_module, module_list); if (module->start <= eip && module->end > eip) return module; } return NULL; } /** * opd_handle_module_sample - process a module sample * @param eip EIP value * @param counter counter number * * Process a sample in module address space. The sample eip * is matched against module information. If the search was * successful, the sample is output to the relevant file. * * Note that for modules and the kernel, the offset will be * wrong in the file, as it is not a file offset, but the offset * from the text section. This is fixed up in pp. * * If the sample could not be located in a module, it is treated * as a kernel sample. */ static void opd_handle_module_sample(unsigned long eip, u32 counter) { struct opd_module * module; module = opd_find_module_by_eip(eip); if (!module) { /* not found in known modules, re-read our info and retry */ opd_clear_module_info(); opd_get_module_info(); module = opd_find_module_by_eip(eip); } if (module) { if (module->image != NULL) { opd_24_stats[OPD_MODULE]++; opd_put_image_sample(module->image, eip - module->start, counter); } else { opd_24_stats[OPD_LOST_MODULE]++; verbprintf(vmodule, "No image for sampled module %s\n", module->name); } } else { opd_drop_module_sample(eip); } } void opd_handle_kernel_sample(unsigned long eip, u32 counter) { if (no_vmlinux || eip < kernel_end) { opd_24_stats[OPD_KERNEL]++; opd_put_image_sample(kernel_image, eip - kernel_start, counter); return; } /* in a module */ opd_handle_module_sample(eip, counter); } int opd_eip_is_kernel(unsigned long eip) { #ifdef __i386 #define KERNEL_OFFSET 0xC0000000 /* * kernel_start == 0 when using --no-vmlinux. * This is wrong, wrong, wrong, wrong, but we don't have much * choice. It obviously breaks for IA64. */ if (!kernel_start) return eip >= KERNEL_OFFSET; #endif return eip >= kernel_start; } void opd_add_kernel_map(struct opd_proc * proc, unsigned long eip) { struct opd_module * module; struct opd_image * image; char const * app_name; app_name = proc->name; if (!app_name) { verbprintf(vmisc, "un-named proc for tid %d\n", proc->tid); return; } if (eip < kernel_end) { image = opd_get_kernel_image(vmlinux, app_name, proc->tid, proc->tgid); if (!image) { verbprintf(vmisc, "Can't create image for %s %s\n", vmlinux, app_name); return; } opd_add_mapping(proc, image, kernel_start, 0, kernel_end); return; } module = opd_find_module_by_eip(eip); if (!module) { /* not found in known modules, re-read our info and retry */ opd_clear_module_info(); opd_get_module_info(); module = opd_find_module_by_eip(eip); } if (module) { /* module->name is only the module name not the full path */ char const * module_name = 0; if (module->image) module_name = module->image->name; if (!module_name) { verbprintf(vmodule, "unable to get path name for module %s\n", module->name); module_name = module->name; } image = opd_get_kernel_image(module_name, app_name, proc->tid, proc->tgid); if (!image) { verbprintf(vmodule, "Can't create image for %s %s\n", module->name, app_name); return; } opd_add_mapping(proc, image, module->start, 0, module->end); } else { opd_drop_module_sample(eip); } }