[patch] new oops decoder with module support

Gerd Knorr (kraxel@goldbach.isdn.cs.tu-berlin.de)
Fri, 23 Jan 1998 23:11:55 +0100 (MET)


Hi !

This is my oops decoder with support for loadable modules. There is a
wrapper script for insmod to dump the module symbols to a file (using
insmod's -m switch) and a new, improved oops decoder (much like ksymoops
with module support, but written in plain C).

Gerd

------------------------------------------------------------------------
diff -urN kernel/2.1.80/scripts/insmod-wrapper linux/scripts/insmod-wrapper
--- kernel/2.1.80/scripts/insmod-wrapper Thu Jan 1 01:00:00 1970
+++ linux/scripts/insmod-wrapper Fri Jan 23 22:59:54 1998
@@ -0,0 +1,67 @@
+#!/bin/sh
+# insmod wrapper for loadmap trickery
+# usage:
+# 1) rename insmod to insmod-real
+# 2) copy or symlink this script to insmod
+#
+# then the script will dump all module loadmaps to $MAPDIR
+#
+#######################################################################
+
+# destination directory
+MAPDIR=/lib/modules/loadmaps
+
+# the real thing
+INSMOD=/sbin/insmod-real
+
+#######################################################################
+OPTS=""
+ARGS=""
+MAP=""
+DEST=""
+
+CALLED="$0 $*"
+
+set -- `getopt fkmpsxXvo: $*` || exit 1
+while [ "$1" != "" ]; do
+ case $1 in
+ -f | -k | -p | -x | -X | -v)
+ # pass throuth
+ OPTS="$OPTS $1"; shift ;;
+ -o)
+ # pass throuth with arg, note name
+ OPTS="$OPTS $1 $2"; DEST="$2"; shift; shift;;
+ -m)
+ # note this, no file but stdout for loadmap
+ OPTS="$OPTS $1"; MAP="nofile"; shift ;;
+ -s)
+ # skip - no syslog becauce we want dump to a file
+ shift;;
+ --)
+ # grab module name for loadmap filename
+ MOD=$2; shift; shift;;
+ *=*)
+ # some module arg
+ ARGS="$ARGS $1"; shift;;
+ *)
+ # Huh?
+ echo "insmod-wrapper: what the heck is '$1' ???"
+ echo "command line: $CALLED"
+ exit 1;;
+ esac
+done
+
+if [ "$MAP" = "nofile" ]; then
+ # -m on command line -- dump loadmap to stdout (to make it
+ # work like people expect from docs)
+ $INSMOD $OPTS $MOD $ARGS
+else
+ # dump it to our directory
+ [ "$DEST" = "" ] && DEST="`basename $MOD .o`"
+ DESTFILE="$MAPDIR/$DEST"
+ echo "orig : $CALLED" > $DESTFILE
+ echo "calling : $INSMOD $OPTS -m $MOD $ARGS" >> $DESTFILE
+ echo "timestamp: `date`" >> $DESTFILE
+ echo "--" >> $DESTFILE
+ $INSMOD $OPTS -m $MOD $ARGS >> $DESTFILE
+fi
diff -urN kernel/2.1.80/scripts/module-oops.c linux/scripts/module-oops.c
--- kernel/2.1.80/scripts/module-oops.c Thu Jan 1 01:00:00 1970
+++ linux/scripts/module-oops.c Fri Jan 23 22:59:54 1998
@@ -0,0 +1,385 @@
+/*
+ * linux kernel+modules oops decoder, i386 only
+ *
+ * requires:
+ * - gcc + objdump 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;
+}