/*
 * linux kernel+modules oops decoder, i386 only
 *
 * requires:
 *   - gcc + gdb for decoding the Code: line
 *   - insmod-wrapper installed for module symbols
 *
 * description:
 *   run it with -h
 *
 * (c) 1997 Gerd Knorr <kraxel@cs.tu-berlin.de>
 *
 * 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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>

/* --------------------------------------------------------------------- */
/* structs                                                               */

typedef unsigned long addr_type;

struct SYMFILE {
    char fullname[256];
    char *basename;
    unsigned long start,end;
};

struct SYMBOL {
    addr_type         addr;
    char              name[40];
    char              type;
    struct SYMFILE   *file;
};

struct SYMFILE  **symfiles;
struct SYMBOL   **symbols;
int filecount, symbolcount;

/* --------------------------------------------------------------------- */
/* file and symbol handling                                              */

struct SYMBOL*
add_symbol(unsigned long addr, char type, char *name, struct SYMFILE *file)
{
    struct SYMBOL *sym;
    
    if (0 == (symbolcount % 1024))
	symbols = realloc(symbols,(symbolcount+1024)*sizeof(struct SYMBOL*));
    sym = symbols[symbolcount++] = malloc(sizeof(struct SYMBOL));

    strcpy(sym->name,name);
    sym->addr = addr;
    sym->type = type;
    sym->file = file;
    return sym;
}

int
lookup_symbol(unsigned long addr)
{
    int i;

    for (i = 0; i < symbolcount; i++) {
	if (symbols[i]->addr > addr) {
	    i--;
	    break;
	}
    }
    if (i == symbolcount)
	return -1;
    return i;
}

void
print_symbol(addr_type addr)
{
    int nr;

    nr = lookup_symbol(addr);
    printf("0x%08lx ",addr);
    if (-1 == nr) {
	printf("???");
    } else {
	printf("%c ",symbols[nr]->type);
	if (symbols[nr]->file->start)
	    printf("(%s+)",symbols[nr]->file->basename);
	printf("%s+0x%lx/0x%lx",
	       symbols[nr]->name,
	       addr-(symbols[nr]->addr),
	       (symbols[nr+1]->addr)-(symbols[nr]->addr));
    }
}

int
compare_symbols(const void *a, const void *b)
{
    addr_type aa = (*((struct SYMBOL**)a))->addr;
    addr_type bb = (*((struct SYMBOL**)b))->addr;
    if (aa == bb)
	return 0;
    return (aa < bb) ? -1 : 1;
}

struct SYMFILE*
add_symfile(char *name)
{
    struct SYMFILE *file;
    
    if (0 == (filecount % 16))
	symfiles = realloc(symfiles,(filecount+16)*sizeof(struct SYMFILE*));
    file = symfiles[filecount] = malloc(sizeof(struct SYMFILE));

    strcpy(file->fullname, name);
    if (NULL == (file->basename = strrchr(file->fullname,'/')))
	file->basename = file->fullname;
    else
	file->basename++;
    file->start = 0;
    file->end = 0;
    return file;
}

void
load_file(char *filename)
{
    FILE            *fp;
    struct SYMFILE  *file;
    char            line[128];
    char            type[2], name[40], section[20];
    int             count = 0;
    addr_type       size,base,addr;

    if (NULL == (fp = fopen(filename,"r"))) {
	printf("can't open %s: %s\n",filename,strerror(errno));
	return;
    }
    file = add_symfile(filename);
    
    while (NULL != fgets(line,127,fp)) {
	/* symbols */
	if (3 == sscanf(line,"%lx %1[BDTbdt] %39s",
			&addr,type,name)) {
	    add_symbol(addr,type[0],name,file);
	    count++;
	}
	/* module: sections */
	if (3 == sscanf(line,".%19[a-z] %lx %lx",section,&size,&base)) {
	    if (!file->start)
		file->start = base;
	    file->end = base+size;
	}
    }
    
    if (file->start) {
	printf("%4d symbols [%lx-%lx] from %s\n",
	       count,file->start,file->end,filename);
    } else {
	printf("%4d symbols from %s\n",
	       count,filename);
    }
    fclose(fp);
}

void
load_proc_modules(char *filename, char *path)
{
    FILE *fp;
    char module[256];
    char line[128],*h;

    if (NULL == (fp = fopen(filename,"r"))) {
	printf("can't open %s: %s\n",filename,strerror(errno));
	return;
    }
    
    while (NULL != fgets(line,127,fp)) {
	h = strchr(line,' ');
	if (h) {
	    *h = '\0';
	    strcpy(module,path);
	    strcat(module,"/");
	    strcat(module,line);
	    load_file(module);
	}
    }
    fclose(fp);
}

/* --------------------------------------------------------------------- */
/* decoder itself                                                        */

#define DISFILE "/tmp/decode-oops"

void
disasm(int *code, int len)
{
    FILE *fp;
    int i,oops=0;
    char line[128];

    if (NULL == (fp = fopen(DISFILE ".c","w"))) {
	printf("can't open " DISFILE ".c: %s\n",strerror(errno));
	return;
    }
    fprintf(fp,"char oops[]={");
    for (i=0; i<len; i++) fprintf(fp,"0x%x,",code[i]);
    fprintf(fp,"};\nmain(){}\n");
    fclose(fp);
    
    system("gcc -c -o " DISFILE ".o " DISFILE ".c");
    fp = popen("objdump --disassemble-all " DISFILE ".o","r");
    if (NULL == fp) {
	printf("can't run objdump: %s\n",strerror(errno));
	return;
    }
    while (NULL != fgets(line,127,fp)) {
	if (strstr(line,"<oops>"))
	    oops = 1;
	if (oops == 1)
	    printf("code:  %s",line);
    }
    printf("\n\n");
}

void
oops_decode()
{
    char           line[256], klogd[64], *linestart;
    addr_type      addr;
    int            len;
    int            code[40],codelen;
    
    while (NULL != fgets(line,255,stdin)) {
	/* handle syslog timestamp and stuff */
	if (NULL != (linestart = strstr(line,"kernel: ")))
	    linestart += 8;
	else
	    linestart = line;

	len = 0;
	if (1 == sscanf(linestart,"EIP: 0010:[<%lx>]%n",&addr,&len) && len) {
	    printf("EIP:   ");
	    print_symbol(addr);
	    printf("\n");
	} else
	if (1 == sscanf(linestart,"EIP: 0010:[%63[^]]]%n",klogd,&len) && len) {
	    /* klogd was here :-) */
	    printf("EIP:   %-40s [klogd]\n",klogd);
	}
	if (0 == strncmp(linestart,"Call Trace: ",12)) {
	    linestart += 12;
	    for (;;) {
		len = 0;
		if (1 == sscanf(linestart," [<%lx>] %n",&addr,&len) && len) {
		    printf("trace: ");
		    print_symbol(addr);
		    printf("\n");
		} else if (1 == sscanf(linestart," [%63[^]]] %n",klogd,&len)
			   && len) {
		    printf("trace: %-40s [klogd]\n",klogd);
		} else
		    break;
		linestart += len;
		if (*linestart == '\0') {
		    /* printf("DEBUG: call trace linebreak\n"); */
		    if (NULL == fgets(line,255,stdin))
			break;
		    /* handle syslog timestamp and stuff */
		    if (NULL != (linestart = strstr(line,"kernel: ")))
			linestart += 8;
		    else
			linestart = line;
		}
	    }
	}
	if (0 == strncmp(linestart,"Code: ",6)) {
	    linestart += 6;
	    codelen = 0;
	    for (;;) {
		if (1 != sscanf(linestart," %x%n",&code[codelen],&len) || !len)
		    break;
		codelen++;
		linestart += len;
	    }
	    disasm(code,codelen);
	}
    }
}

/* --------------------------------------------------------------------- */
/* main                                                                  */

char *progname;
char *loadmaps = "/lib/modules/loadmaps";

int proc_modules = 0;

void
usage(void)
{
    fprintf(stderr,
	    "This is a linux kernel oops decoder\n"
	    "usage: %s [ options ] mapfile(s) < oops\n"
	    "options:\n"
	    "  -h  print this text\n"
	    "  -m  check /proc/modules and load insmod's maps for the\n"
	    "      loaded modules from %s\n"
	    "\n"
	    "examples:\n"
	    "oops, system still running\n"
	    "        dmesg | %s -m /boot/System.map\n"
	    "oops, system still running, load map for a removed module\n"
	    "        dmesg | %s -m /boot/System.map \\\n"
	    "            /lib/modules/loadmaps/removed_module\n"
	    "decode after reboot\n"
	    "        tail -n 100 /var/log/messages |\\\n"
	    "            %s /boot/System.map /lib/modules/loadmaps.last/*\n"
	    "the hard way: written down by hand:\n"
	    "        %s /boot/System.map /lib/modules/loadmaps.last/* < log\n"
	    "Note1: The last two examples assume you have some logic in your\n"
	    "       /etc/rc* scripts which saves the existing loadmaps before\n"
	    "       loading modules and/or starting kerneld\n"
	    "Note2: Use insmod-wrapper to create the loadmaps\n",
	    progname,loadmaps,
	    progname,progname,progname,progname);
}

int
main(int argc, char **argv)
{
    int          c;
    struct stat  st;
    
    if (NULL == (progname = strrchr(argv[0],'/')))
	progname = argv[0];
    else
	progname++;

    /* parse options */
    for (;;) {
	if (-1 == (c = getopt(argc, argv, "hm")))
	    break;
	switch (c) {
	case 'm':
	    proc_modules = 1;
	    break;
	case 'h':
	default:
	    usage();
	    exit(1);
	}
    }

    if (-1 != fstat(0,&st) && S_ISCHR(st.st_mode)) {
	fprintf(stderr,
		"%s expects the oops log from stdin, try -h for help\n",
		progname);
	exit(1);
    }

    /* load symbol files */
    while (optind < argc)
	load_file(argv[optind++]);
    if (proc_modules)
	load_proc_modules("/proc/modules",loadmaps);
    printf("\n");

    /* sort symbol files */
    qsort(symbols,symbolcount,sizeof(struct SYMBOL*),compare_symbols);
    
    /* parse stdin */
    oops_decode();

    return 0;
}

