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