diff options
Diffstat (limited to 'scripts/ksymoops/ksymoops.c')
-rw-r--r-- | scripts/ksymoops/ksymoops.c | 678 |
1 files changed, 678 insertions, 0 deletions
diff --git a/scripts/ksymoops/ksymoops.c b/scripts/ksymoops/ksymoops.c new file mode 100644 index 000000000..0cd65795b --- /dev/null +++ b/scripts/ksymoops/ksymoops.c @@ -0,0 +1,678 @@ +/* + ksymoops.c. + + Read a kernel Oops file and make the best stab at converting the code to + instructions and mapping stack values to kernel symbols. + + Copyright Keith Owens <kaos@ocs.com.au>. + Released under the GNU Public Licence, Version 2. +*/ + +#define VERSION "0.6e" + +/* + + Tue Jan 5 19:26:02 EST 1999 + Version 0.6e + Added to kernel. + + Mon Jan 4 09:48:13 EST 1999 + Version 0.6d + Add ARM support. + + Thu Nov 26 16:37:46 EST 1998 + Version 0.6c + Typo in oops_code. + Add -c option. + Add -1 option. + Report if options were specified or defaulted. + + Fri Nov 6 10:38:42 EST 1998 + Version 0.6b + Remove false warnings when comparing ksyms and lsmod. + + Tue Nov 3 23:33:04 EST 1998 + Version 0.6a + Performance inprovements. + + Tue Nov 3 02:31:01 EST 1998 + Version 0.6 + Read lsmod (/proc/modules). + Ignore addresses 0-4095 when mapping address to symbol. + Discard default objects if -o specified. + Oops file must be regular. + Add "invalid operand" to Oops_print. + Move "Using_Version" copy to map.c. + Add Makefile defaults for vmlinux, ksyms, objects, System.map, lsmod. + Minor adjustment to re for ppc. + Minor adjustment to re for objdump lines with <_EIP+xxx>. + Convert from a.out to bfd, using same format as ksymoops. + Added MIPS. + PPC handling based on patches by "Ryan Nielsen" <ran@krazynet.com> + + Wed Oct 28 23:14:55 EST 1998 + Version 0.5 + No longer read vmlinux by default, it only duplicates System.map. + + Wed Oct 28 13:47:38 EST 1998 + Version 0.4 + Split into separate sources. + + Mon Oct 26 00:01:47 EST 1998 + Version 0.3c + Add alpha (arm) processing. + + Mon Oct 26 00:01:47 EST 1998 + Version 0.3b + Add sparc processing. + Handle kernel symbol versions. + + Fri Oct 23 13:11:20 EST 1998 + Version 0.3 + Add -follow to find command for people who use symlinks to modules. + Add Version_ checking. + + Thu Oct 22 22:28:30 EST 1998 + Version 0.2. + Generalise text prefix handling. + Handle messages on Code: line. + Format addresses with leading zeroes. + Minor bug fixes. + + Wed Oct 21 23:28:48 EST 1998 + Version 0.1. Rewrite from scratch in C. + + CREDITS. + Oops disassembly based on ksymoops.cc, + Copyright (C) 1995 Greg McGary <gkm@magilla.cichlid.com> + m68k code based on ksymoops.cc changes by + Andreas Schwab <schwab@issan.informatik.uni-dortmund.de> + */ + +#include "ksymoops.h" +#include <ctype.h> +#include <errno.h> +#include <malloc.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/utsname.h> + +char *prefix; +char *path_nm = "/usr/bin/nm"; /* env KSYMOOPS_NM */ +char *path_find = "/usr/bin/find"; /* env KSYMOOPS_FIND */ +char *path_objdump = "/usr/bin/objdump"; /* env KSYMOOPS_OBJDUMP */ +int debug = 0; +int errors = 0; +int warnings = 0; + +SYMBOL_SET ss_vmlinux; +SYMBOL_SET ss_ksyms_base; +SYMBOL_SET *ss_ksyms_module; +int ss_ksyms_modules; +SYMBOL_SET ss_lsmod; +SYMBOL_SET *ss_object; +int ss_objects; +SYMBOL_SET ss_system_map; + +SYMBOL_SET ss_merged; /* merged map with info from all sources */ +SYMBOL_SET ss_Version; /* Version_ numbers where available */ + +/* Regular expression stuff */ + +regex_t re_nm; +regmatch_t *re_nm_pmatch; +regex_t re_bracketed_address; +regmatch_t *re_bracketed_address_pmatch; +regex_t re_unbracketed_address; +regmatch_t *re_unbracketed_address_pmatch; + +static void usage(void) +{ + fprintf(stderr, "Version " VERSION "\n"); + fprintf(stderr, "usage: %s\n", prefix); + fprintf(stderr, + "\t\t[-v vmlinux]\tWhere to read vmlinux\n" + "\t\t[-V]\t\tNo vmlinux is available\n" + "\t\t[-o object_dir]\tDirectory containing modules\n" + "\t\t[-O]\t\tNo modules is available\n" + "\t\t[-k ksyms]\tWhere to read ksyms\n" + "\t\t[-K]\t\tNo ksyms is available\n" + "\t\t[-l lsmod]\tWhere to read lsmod\n" + "\t\t[-L]\t\tNo lsmod is available\n" + "\t\t[-m system.map]\tWhere to read System.map\n" + "\t\t[-M]\t\tNo System.map is available\n" + "\t\t[-s save.map]\tSave consolidated map\n" + "\t\t[-d]\t\tIncrease debug level by 1\n" + "\t\t[-h]\t\tPrint help text\n" + "\t\t[-c code_bytes]\tHow many bytes in each unit of code\n" + "\t\t[-1]\t\tOne shot toggle (exit after first Oops)\n" + "\t\t<Oops.file\tOops report to decode\n" + "\n" + "\t\tAll flags can occur more than once. With the exception " + "of -o\n" + "\t\tand -d which are cumulative, the last occurrence of each " + "flag is\n" + "\t\tused. Note that \"-v my.vmlinux -V\" will be taken as " + "\"No vmlinux\n" + "\t\tavailable\" but \"-V -v my.vmlinux\" will read " + "my.vmlinux. You\n" + "\t\twill be warned about such combinations.\n" + "\n" + "\t\tEach occurrence of -d increases the debug level.\n" + "\n" + "\t\tEach -o flag can refer to a directory or to a single " + "object\n" + "\t\tfile. If a directory is specified then all *.o files in " + "that\n" + "\t\tdirectory and its subdirectories are assumed to be " + "modules.\n" + "\n" + "\t\tIf any of the vmlinux, object_dir, ksyms or system.map " + "options\n" + "\t\tcontain the string *r (*m, *n, *s) then it is replaced " + "at run\n" + "\t\ttime by the current value of `uname -r` (-m, -n, -s).\n" + "\n" + "\t\tThe defaults can be changed in the Makefile, current " + "defaults\n" + "\t\tare\n\n" + "\t\t\t" +#ifdef DEF_VMLINUX + "-v " DEF_LINUX +#else + "-V" +#endif + "\n" + "\t\t\t" +#ifdef DEF_OBJECTS + "-o " DEF_OBJECTS +#else + "-O" +#endif + "\n" + "\t\t\t" +#ifdef DEF_KSYMS + "-k " DEF_KSYMS +#else + "-K" +#endif + "\n" + "\t\t\t" +#ifdef DEF_LSMOD + "-l " DEF_LSMOD +#else + "-L" +#endif + "\n" + "\t\t\t" +#ifdef DEF_MAP + "-m " DEF_MAP +#else + "-M" +#endif + "\n" + "\t\t\t-c %d\n" /* DEF_CODE_BYTES */ + "\t\t\tOops report is read from stdin\n" + "\n", + DEF_CODE_BYTES + ); +} + +/* Check if possibly conflicting options were specified */ +static void multi_opt(int specl, int specu, char type, const char *using) +{ + if (specl && specu) { + fprintf(stderr, + "Warning - you specified both -%c and -%c. Using '", + type, toupper(type)); + ++warnings; + if (using) { + fprintf(stderr, "-%c %s", type, using); + if (type == 'o') + fprintf(stderr, " ..."); + fprintf(stderr, "'\n"); + } + else + fprintf(stderr, "-%c'\n", toupper(type)); + } + else if (specl > 1 && type != 'o') { + fprintf(stderr, + "Warning - you specified -%c more than once. " + "Using '-%c %s'\n", + type, type, using); + ++warnings; + } + else if (specu > 1) { + fprintf(stderr, + "Warning - you specified -%c more than once. " + "Second and subsequent '-%c' ignored\n", + toupper(type), toupper(type)); + ++warnings; + } +} + +/* If a name contains *r (*m, *n, *s), replace with the current value of + * `uname -r` (-m, -n, -s). Actually uses uname system call rather than the + * uname command but the result is the same. + */ +static void convert_uname(char **name) +{ + char *p, *newname, *oldname, *replacement; + unsigned len; + int free_oldname = 0; + static char procname[] = "convert_uname"; + + if (!*name) + return; + + while ((p = strchr(*name, '*'))) { + struct utsname buf; + int i = uname(&buf); + if (debug) + fprintf(stderr, "DEBUG: %s %s in\n", procname, *name); + if (i) { + fprintf(stderr, + "%s: uname failed, %s will not be processed\n", + prefix, *name); + perror(prefix); + ++errors; + return; + } + switch (*(p+1)) { + case 'r': + replacement = buf.release; + break; + case 'm': + replacement = buf.machine; + break; + case 'n': + replacement = buf.nodename; + break; + case 's': + replacement = buf.sysname; + break; + default: + fprintf(stderr, + "%s: invalid replacement character '*%c' " + "in %s\n", + prefix, *(p+1), *name); + ++errors; + return; + } + len = strlen(*name)-2+strlen(replacement)+1; + if (!(newname = malloc(len))) + malloc_error(procname); + strncpy(newname, *name, (p-*name)); + strcpy(newname+(p-*name), replacement); + strcpy(newname+(p-*name)+strlen(replacement), p+2); + p = newname+(p-*name)+strlen(replacement); /* no rescan */ + oldname = *name; + *name = newname; + if (free_oldname) + free(oldname); + free_oldname = 1; + if (debug) + fprintf(stderr, "DEBUG: %s %s out\n", procname, *name); + } + return; +} + +/* Report if the option was specified or defaulted */ +static void spec_or_default(int spec, int *some_spec) { + if (spec) { + printf(" (specified)\n"); + if (some_spec) + *some_spec = 1; + } + else + printf(" (default)\n"); +} + +/* Parse the options. Verbose but what's new with getopt? */ +static void parse(int argc, + char **argv, + char **vmlinux, + char ***object, + int *objects, + char **ksyms, + char **lsmod, + char **system_map, + char **save_system_map, + char ***filename, + int *filecount, + int *spec_h, + int *code_bytes, + int *one_shot + ) +{ + int spec_v = 0, spec_V = 0; + int spec_o = 0, spec_O = 0; + int spec_k = 0, spec_K = 0; + int spec_l = 0, spec_L = 0; + int spec_m = 0, spec_M = 0; + int spec_s = 0; + int spec_c = 0; + + int c, i, some_spec = 0; + char *p; + + while ((c = getopt(argc, argv, "v:Vo:Ok:Kl:Lm:Ms:dhc:1")) != EOF) { + if (debug && c != 'd') + fprintf(stderr, "DEBUG: getopt '%c' '%s'\n", c, optarg); + switch(c) { + case 'v': + *vmlinux = optarg; + ++spec_v; + break; + case 'V': + *vmlinux = NULL; + ++spec_V; + break; + case 'o': + if (!spec_o) { + /* First -o, discard default value(s) */ + for (i = 0; i < *objects; ++i) + free((*object)[i]); + free(*object); + *object = NULL; + *objects = 0; + } + *object = realloc(*object, + ((*objects)+1)*sizeof(**object)); + if (!*object) + malloc_error("object"); + if (!(p = strdup(optarg))) + malloc_error("strdup -o"); + else { + (*object)[(*objects)++] = p; + ++spec_o; + } + break; + case 'O': + ++spec_O; + for (i = 0; i < *objects; ++i) + free((*object)[i]); + free(*object); + *object = NULL; + *objects = 0; + break; + case 'k': + *ksyms = optarg; + ++spec_k; + break; + case 'K': + *ksyms = NULL; + ++spec_K; + break; + case 'l': + *lsmod = optarg; + ++spec_l; + break; + case 'L': + *lsmod = NULL; + ++spec_L; + break; + case 'm': + *system_map = optarg; + ++spec_m; + break; + case 'M': + *system_map = NULL; + ++spec_M; + break; + case 's': + *save_system_map = optarg; + ++spec_s; + break; + case 'd': + ++debug; + break; + case 'h': + usage(); + ++*spec_h; + break; + case 'c': + ++spec_c; + errno = 0; + *code_bytes = strtoul(optarg, &p, 10); + /* Oops_code_values assumes that code_bytes is a + * multiple of 2. + */ + if (!*optarg || *p || errno || + (*code_bytes != 1 && + *code_bytes != 2 && + *code_bytes != 4 && + *code_bytes != 8)) { + fprintf(stderr, + "%s Invalid value for -c '%s'\n", + prefix, optarg); + ++errors; + if (errno) + perror(" "); + *code_bytes = DEF_CODE_BYTES; + } + break; + case '1': + *one_shot = !*one_shot; + break; + case '?': + usage(); + exit(2); + } + } + + *filecount = argc - optind; + *filename = argv + optind; + + /* Expand any requests for the current uname values */ + convert_uname(vmlinux); + if (*objects) { + for (i = 0; i < *objects; ++i) + convert_uname(*object+i); + } + convert_uname(ksyms); + convert_uname(lsmod); + convert_uname(system_map); + + /* Check for multiple options specified */ + multi_opt(spec_v, spec_V, 'v', *vmlinux); + multi_opt(spec_o, spec_O, 'o', *object ? **object : NULL); + multi_opt(spec_k, spec_K, 'k', *ksyms); + multi_opt(spec_l, spec_L, 'l', *lsmod); + multi_opt(spec_m, spec_M, 'm', *system_map); + + printf("Options used:"); + if (*vmlinux) + printf(" -v %s", *vmlinux); + else + printf(" -V"); + spec_or_default(spec_v || spec_V, &some_spec); + + printf(" "); + if (*objects) { + for (i = 0; i < *objects; ++i) + printf(" -o %s", (*object)[i]); + } + else + printf(" -O"); + spec_or_default(spec_o || spec_O, &some_spec); + + printf(" "); + if (*ksyms) + printf(" -k %s", *ksyms); + else + printf(" -K"); + spec_or_default(spec_k || spec_K, &some_spec); + + printf(" "); + if (*lsmod) + printf(" -l %s", *lsmod); + else + printf(" -L"); + spec_or_default(spec_l || spec_L, &some_spec); + + printf(" "); + if (*system_map) + printf(" -m %s", *system_map); + else + printf(" -M"); + spec_or_default(spec_m || spec_M, &some_spec); + + printf(" "); + printf(" -c %d", *code_bytes); + spec_or_default(spec_c, NULL); + + if (*one_shot) { + printf(" "); + printf(" -1"); + } + + printf("\n"); + + if (!some_spec) { + printf( +"You did not tell me where to find symbol information. I will assume\n" +"that the log matches the kernel and modules that are running right now\n" +"and I'll use the default options above for symbol resolution.\n" +"If the current kernel and/or modules do not match the log, you can get\n" +"more accurate output by telling me the kernel version and where to find\n" +"map, modules, ksyms etc. ksymoops -h explains the options.\n" + "\n"); + ++warnings; + } +} + +/* Read environment variables */ +static void read_env(const char *external, char **internal) +{ + char *p; + if ((p = getenv(external))) { + *internal = p; + if (debug) + fprintf(stderr, + "DEBUG: env override %s=%s\n", + external, *internal); + } + else { + if (debug) + fprintf(stderr, + "DEBUG: env default %s=%s\n", + external, *internal); + } +} + + +int main(int argc, char **argv) +{ + char *vmlinux = NULL; + char **object = NULL; + int objects = 0; + char *ksyms = NULL; + char *lsmod = NULL; + char *system_map = NULL; + char *save_system_map = NULL; + char **filename; + int filecount = 0; + int spec_h = 0; /* -h was specified */ + int code_bytes = DEF_CODE_BYTES; + int one_shot = 0; + int i, ret; + + prefix = *argv; + setvbuf(stdout, NULL, _IONBF, 0); + +#ifdef DEF_VMLINUX + vmlinux = DEF_LINUX; +#endif +#ifdef DEF_OBJECTS + { + char *p; + object = realloc(object, (objects+1)*sizeof(*object)); + if (!object) + malloc_error("DEF_OBJECTS"); + if (!(p = strdup(DEF_OBJECTS))) + malloc_error("DEF_OBJECTS"); + else + object[objects++] = p; + } +#endif +#ifdef DEF_KSYMS + ksyms = DEF_KSYMS; +#endif +#ifdef DEF_LSMOD + lsmod = DEF_LSMOD; +#endif +#ifdef DEF_MAP + system_map = DEF_MAP; +#endif + + parse(argc, + argv, + &vmlinux, + &object, + &objects, + &ksyms, + &lsmod, + &system_map, + &save_system_map, + &filename, + &filecount, + &spec_h, + &code_bytes, + &one_shot + ); + + if (spec_h && filecount == 0) + return(0); /* just the help text */ + + if (errors) + return(1); + + if (debug) + fprintf(stderr, "DEBUG: level %d\n", debug); + + read_env("KSYMOOPS_NM", &path_nm); + read_env("KSYMOOPS_FIND", &path_find); + read_env("KSYMOOPS_OBJDUMP", &path_objdump); + + re_compile_common(); + ss_init_common(); + + read_vmlinux(vmlinux); + read_ksyms(ksyms); + /* No point in reading modules unless ksyms shows modules loaded */ + if (ss_ksyms_modules) { + expand_objects(object, objects); + for (i = 0; i < ss_objects; ++i) + read_object(ss_object[i].source, i); + } + else if (objects) + printf("No modules in ksyms, skipping objects\n"); + /* No point in reading lsmod without ksyms */ + if (ss_ksyms_modules || ss_ksyms_base.used) + read_lsmod(lsmod); + else if (lsmod) + printf("No ksyms, skipping lsmod\n"); + read_system_map(system_map); + merge_maps(save_system_map); + + /* After all that work, it is finally time to read the Oops report */ + ret = Oops_read(filecount, filename, code_bytes, one_shot); + + if (warnings || errors) { + printf("\n"); + if (warnings) + printf("%d warning%s ", + warnings, warnings == 1 ? "" : "s"); + if (warnings && errors) + printf("and "); + if (errors) + printf("%d error%s ", errors, errors == 1 ? "" : "s"); + printf("issued. Results may not be reliable.\n"); + if (!ret) + return(1); + } + + return(ret); +} |