/**
* @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);
}
}