/*
    object.c.

    object handling routines for ksymoops.  Read modules, vmlinux, etc.

    Copyright 1999 Keith Owens <kaos@ocs.com.au>.
    Released under the GNU Public Licence, Version 2.

 */

#include "ksymoops.h"
#include <malloc.h>
#include <string.h>
#include <sys/stat.h>

/* Extract all symbols definitions from an object using nm */
static void read_nm_symbols(SYMBOL_SET *ss, const char *file)
{
    FILE *f;
    char *cmd, *line = NULL, **string = NULL;
    int i, size = 0;
    static char const procname[] = "read_nm_symbols";

    if (!regular_file(file, procname))
	return;

    cmd = malloc(strlen(path_nm)+strlen(file)+2);
    if (!cmd)
	malloc_error("nm command");
    strcpy(cmd, path_nm);
    strcat(cmd, " ");
    strcat(cmd, file);
    DEBUG(2, "command '%s'", cmd);
    if (!(f = popen_local(cmd, procname)))
	return;
    free(cmd);

    while (fgets_local(&line, &size, f, procname)) {
	i = regexec(&re_nm, line, re_nm.re_nsub+1, re_nm_pmatch, 0);
	DEBUG(4, "regexec %d", i);
	if (i == 0) {
	    re_strings(&re_nm, line, re_nm_pmatch, &string);
	    add_symbol(ss, string[1], *string[2], 1, string[3]);
	}
    }

    pclose_local(f, procname);
    re_strings_free(&re_nm, &string);
    free(line);
    DEBUG(2, "%s used %d out of %d entries", ss->source, ss->used, ss->alloc);
}

/* Read the symbols from vmlinux */
void read_vmlinux(const char *vmlinux)
{
    static char procname[] = "read_vmlinux";
    if (!vmlinux)
	return;
    ss_init(&ss_vmlinux, "vmlinux");
    read_nm_symbols(&ss_vmlinux, vmlinux);
    if (ss_vmlinux.used) {
	ss_sort_na(&ss_vmlinux);
	extract_Version(&ss_vmlinux);
    }
    else
	WARNING("no kernel symbols in vmlinux, is %s a valid vmlinux file?",
	    vmlinux);
}


/* Read the symbols from one object (module) */
void read_object(const char *object, int i)
{
    static char procname[] = "read_object";
    ss_init(ss_object[i], object);
    read_nm_symbols(ss_object[i], object);
    if ((ss_object[i])->used) {
	ss_sort_na(ss_object[i]);
	extract_Version(ss_object[i]);
    }
    else
	WARNING("no symbols in %s", object);
}

/* Add a new entry to the list of objects */
static SYMBOL_SET *add_ss_object(const char *file)
{
    int i;
    /* Avoid reading any object twice. */
    for (i = 0; i < ss_objects; ++i) {
	if (strcmp((ss_object[i])->source, file) == 0)
	    return(ss_object[i]);
    }
    ++ss_objects;
    ss_object = realloc(ss_object, ss_objects*sizeof(*ss_object));
    if (!ss_object)
	malloc_error("realloc ss_object");
    if (!(ss_object[ss_objects-1] = malloc(sizeof(*ss_object[0]))))
	malloc_error("alloc ss_object[n]");
    ss_init(ss_object[ss_objects-1], file);
    return(ss_object[ss_objects-1]);
}

/* Run a directory and its subdirectories, looking for *.o files */
static void find_objects(const char *dir)
{
    FILE *f;
    char *cmd, *line = NULL;
    int size = 0, files = 0;
    static char const procname[] = "find_objects";
    static char const options[] = " -follow -name '*.o' -print";

    cmd = malloc(strlen(path_find)+1+strlen(dir)+strlen(options)+1);
    if (!cmd)
	malloc_error("find command");
    strcpy(cmd, path_find);
    strcat(cmd, " ");
    strcat(cmd, dir);
    strcat(cmd, options);
    DEBUG(2, "command '%s'", cmd);
    if (!(f = popen_local(cmd, procname)))
	return;
    free(cmd);

    while (fgets_local(&line, &size, f, procname)) {
	DEBUG(2, "%s", line);
	add_ss_object(line);
	++files;
    }

    pclose_local(f, procname);
    if (!files)
	WARNING("no *.o files in %s.  Is %s a valid module directory?",
	    dir, dir);
}

/* Take the user supplied list of objects which can include directories.
 * Expand directories into any *.o files.  The results are stored in
 * ss_object, leaving the user supplied options untouched.  If a ksyms
 * entry contains the name of its object file, use that by preference.
 */
void expand_objects(const OPTIONS *options)
{
    struct stat statbuf;
    int i, do_the_lot = 0;
    SYMBOL_SET *ss;
    const char *file;
    static char const procname[] = "expand_objects";

    for (i = 0; i < ss_ksyms_modules; ++i) {
	ss = ss_ksyms_module[i];
	if (!ss->object) {
	    do_the_lot = 1;
	    continue;
	}
	file = ss->object;
	DEBUG(1, "using %s for %s", file, ss->source);
	if (stat(ss->object, &statbuf)) {
	    ERROR("cannot stat(%s) for %s", file, ss->source);
	    perror(prefix);
	    do_the_lot = 1;
	    continue;
	}
	if (statbuf.st_mtime != ss->mtime)
	    WARNING("object %s for module %s has changed since load",
		file, ss->source);
	ss->related = add_ss_object(file);
    }

    if (!do_the_lot) {
	DEBUG(1, "all ksyms modules map to specific object files");
	return;
    }
    DEBUG(1, "missing modutils assists, doing it the hard way");

    for (i = 0; i < options->objects; ++i) {
	file = options->object[i];
	DEBUG_S(2, "checking %s - ", file);
	if (!stat(file, &statbuf) && S_ISDIR(statbuf.st_mode)) {
	    DEBUG_E(2, "directory, expanding");
	    find_objects(file);
	}
	else {
	    DEBUG_E(2, "not directory");
	    add_ss_object(file);
	}
    }
}

/* Map a symbol type to a section code. 0 - text, 1 - data, 2 - read only data,
 * 3 - C (cannot relocate), 4 - the rest.
 */
static int section(char type)
{
    switch (type) {
    case 'T':
    case 't':
	return 0;
    case 'D':
    case 'd':
	return 1;
    case 'R':
    case 'r':
	return 2;
    case 'C':
	return 3;
    default:
	return 4;
    }
}

/* Given ksyms module data which has a related object, create a copy of the
 * object data, adjusting the offsets to match where the module was loaded.
 */
SYMBOL_SET *adjust_object_offsets(SYMBOL_SET *ss)
{
    int i;
    addr_t adjust[] = {0, 0, 0, 0, 0};
    char type;
    SYMBOL *sk, *so;
    SYMBOL_SET *ssc;
    static char const procname[] = "adjust_object_offsets";

    ssc = ss_copy(ss->related);

    /* For common symbols, calculate the adjustment */
    for (i = 0; i < ss->used; ++i) {
	sk = ss->symbol+i;
	/* modutils assists have type G, the symbol contains the section name */
	if (sk->type == 'G') {
	    if (strstr(sk->name, "_S.text_"))
	        type = 'T';
	    else if (strstr(sk->name, "_S.rodata_"))
	        type = 'R';
	    else if (strstr(sk->name, "_S.data_"))
	        type = 'D';
	    else
		type = ' ';
	    /* Assumption: all sections start at relative offset 0 */
	    adjust[section(type)] = sk->address;
	}
	else if ((so = find_symbol_name(ssc, sk->name, NULL))) {
	    if (!adjust[section(so->type)])
	        adjust[section(so->type)] = sk->address - so->address;
	}
    }
    for (i = 0; i < ssc->used; ++i) {
	so = ssc->symbol+i;
	/* Type C does not relocate well, silently ignore */
	if (so->type != 'C' && adjust[section(so->type)])
	    so->address += adjust[section(so->type)];
	else
	    so->keep = 0;  /* do not merge into final map */
    }
    /* Copy modutils assist symbols to object symbol table */
    for (i = 0; i < ss->used; ++i) {
	sk = ss->symbol+i;
	if (sk->type == 'G' || sk->type == 'g')
	    add_symbol_n(ssc, sk->address, sk->type, 1, sk->name);
    }
    ss->related = ssc;  /* map using adjusted copy */
    DEBUG(2, "%s %llx %llx %llx", ss->source, adjust[0], adjust[1], adjust[2]);
    return(ssc);
}
