/*
    oops.c.

    Oops processing for ksymoop.

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

 */

#include "ksymoops.h"
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <memory.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* Error detected by bfd */
static void Oops_bfd_perror(const char *msg)
{
    static char const procname[] = "Oops_bfd_perror";
    bfd_error_type err = bfd_get_error();
    ERROR("%s %s", msg, bfd_errmsg(err));
}

/* Open the ksymoops binary so we have an input bfd to copy data from. */
static void Oops_open_input_bfd(char **me, bfd **ibfd, const OPTIONS *options)
{
    char **matches, **match;
    static char const procname[] = "Oops_open_input_bfd";

    *me = find_fullpath(prefix);

    if (!*ibfd && !(*ibfd = bfd_openr(*me, NULL))) {
	Oops_bfd_perror(*me);
	FATAL("giving up");
    }

    if (!bfd_check_format_matches(*ibfd, bfd_object, &matches)) {
	Oops_bfd_perror(*me);
	if (bfd_get_error() == bfd_error_file_ambiguously_recognized) {
	    printf("%s Matching formats:", procname);
	    match = matches;
	    while (*match)
		printf(" %s", *match++);
	    printf("\n");
	    free(matches);
	}
	FATAL("giving up");
    }
}

/* If target or architecture is not already set, default to the same as
 * ksymoops.
 */
static void Oops_set_default_ta(const char *me, const bfd *ibfd,
				OPTIONS *options)
{
    static char const procname[] = "Oops_set_default_ta";
    const char *bt;
    const bfd_arch_info_type *bai;
    int t = 0, a = 0;

    if (!options->target) {
	bt = bfd_get_target(ibfd);	/* Bah, undocumented bfd function */
	if (!bt) {
	    Oops_bfd_perror(me);
	    FATAL("cannot get bfd_target for %s:", me);
	}
	options->target = bt;
	t = 1;
    }

    if (!options->architecture) {
	/* bfd_get_arch_info should really take (const bfd *) */
	bai = bfd_get_arch_info((bfd *)ibfd);
	if (!bai) {
	    Oops_bfd_perror(me);
	    FATAL("cannot get bfd_arch_info for %s:", me);
	}
	options->architecture = bai->printable_name;
	a = 1;
    }
    if (t || a) {
	printf("Using defaults from ksymoops");
	if (t)
	    printf(" -t %s", options->target);
	if (a)
	    printf(" -a %s", options->architecture);
	printf("\n");
    }
}

/* Write the code values to a file using bfd. */
static int Oops_write_bfd_data(const bfd *ibfd, bfd *obfd,
			       const char *code, int size)
{
    asection *isec, *osec;
    asymbol *osym;

    /* bfd_get_section_by_name should really take (const bfd *,) */
    if (!(isec = bfd_get_section_by_name((bfd *)ibfd, ".text"))) {
	Oops_bfd_perror("get_section");
	return(0);
    }
    if (!bfd_set_start_address(obfd, 0)) {
	Oops_bfd_perror("set_start_address");
	return(0);
    }
    if (!(osec = bfd_make_section(obfd, ".text"))) {
	Oops_bfd_perror("make_section");
	return(0);
    }
    if (!bfd_set_section_flags(obfd, osec, 
	bfd_get_section_flags(ibfd, isec))) {
	Oops_bfd_perror("set_section_flags");
	return(0);
    }
    if (!bfd_set_section_alignment(obfd, osec,
	bfd_get_section_alignment(ibfd, isec))) {
	Oops_bfd_perror("set_section_alignment");
	return(0);
    }
    osec->output_section = osec;
    if (!(osym = bfd_make_empty_symbol(obfd))) {
	Oops_bfd_perror("make_empty_symbol");
	return(0);
    }
    osym->name = "_XXX";
    osym->section = osec;
    osym->flags = BSF_GLOBAL;
    osym->value = 0;
    if (!bfd_set_symtab(obfd, &osym, 1)) {
	Oops_bfd_perror("set_symtab");
	return(0);
    }
    if (!bfd_set_section_size(obfd, osec, size)) {
	Oops_bfd_perror("set_section_size");
	return(0);
    }
    if (!bfd_set_section_vma(obfd, osec, 0)) {
	Oops_bfd_perror("set_section_vma");
	return(0);
    }
    if (!bfd_set_section_contents(obfd, osec, (PTR) code, 0, size)) {
	Oops_bfd_perror("set_section_contents");
	return(0);
    }
    if (!bfd_close(obfd)) {
	Oops_bfd_perror("close(obfd)");
	return(0);
    }
    return 1;
}

/* Write the Oops code to a temporary file with suitable header and trailer. */
static char *Oops_code_to_file(const char *code, int size, const bfd *ibfd,
			       OPTIONS *options)
{
    char *file;
    bfd *obfd;
    const bfd_arch_info_type *bai;

    file = tmpnam(NULL);

    /* Set the target on the output file */
    if (!(obfd = bfd_openw(file, options->target))) {
	Oops_bfd_perror(file);
	if (bfd_get_error() == bfd_error_wrong_format)
	    printf("Sorry, looks like your binutils cannot "
		   "handle target %s\n", options->target);
	return(NULL);
    }
    bfd_set_format(obfd, bfd_object);

    /* Set the architecture on the output file */
    if (!(bai = bfd_scan_arch(options->architecture))) {
	Oops_bfd_perror("scan_arch for specified architecture");
	if (bfd_get_error() == bfd_error_wrong_format)
	    printf("Sorry, looks like your binutils cannot "
		"handle the specified architecture\n");
	return(NULL);
    }
    bfd_set_arch_info(obfd, bai);
    options->address_bits = bfd_arch_bits_per_address(obfd);

    if (!Oops_write_bfd_data(ibfd, obfd, code, size))
	return(NULL);
    return(file);
}

/* Run objdump against the binary Oops code */
static FILE *Oops_objdump(const char *file, const OPTIONS *options)
{
    char *cmd;
    FILE *f;
    static char const objdump_options[] = "-dhf ";
    static char const procname[] = "Oops_objdump";

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


/* Forward references. */
static const char *Oops_arch_to_eip(const OPTIONS *options);
static void Oops_decode_one_add(SYMBOL_SET *ss, const addr_t address,
				const char *line1, const OPTIONS *options);
static addr_t Oops_truncate_address(addr_t address, const OPTIONS *options);

/* Process one code line from objdump, ignore everything else */
static void Oops_decode_one(SYMBOL_SET *ss, const char *line, addr_t eip,
			    int adjust, const OPTIONS *options)
{
    int i;
    addr_t address, eip_relative;
    char *line2, *map, **string = NULL;
    static regex_t     re_Oops_objdump;
    static regmatch_t *re_Oops_objdump_pmatch;
    static char const procname[] = "Oops_decode_one";

    /* objdump output.  Optional whitespace, hex digits, optional
     * ' <_XXX+offset>', ':'.  The '+offset' after _XXX is also optional.
     * Older binutils output 'xxxxxxxx <_XXX+offset>:', newer versions do
     * '00000000 <_XXX>:' first followed by '      xx:' lines.
     *
     * Just to complicate things even more, objdump recognises jmp, call,
     * etc., converts the code to something like this :-
     * "   f: e8 32 34 00 00  call   3446 <_XXX+0x3446>"
     * Recognise this and append the eip adjusted address, followed by the
     * map_address text for that address.
     *
     * With any luck, objdump will take care of all such references which
     * makes this routine architecture insensitive.  No need to test for
     * i386 jmp, call or m68k swl etc.
     */
    RE_COMPILE(&re_Oops_objdump,
	    "^ *"
	    "([0-9a-fA-F]+)"				/* 1 */
	    "( <_XXX[^>]*>)?"				/* 2 */
	    ":"
	    "("						/* 3 */
	    ".* +<_XXX\\+0?x?([0-9a-fA-F]+)> *$"	/* 4 */
	    ")?"
	    ".*"
	    ,
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_objdump_pmatch);

    i = regexec(&re_Oops_objdump, line, re_Oops_objdump.re_nsub+1,
	re_Oops_objdump_pmatch, 0);
    DEBUG(4, "regexec %d", i);
    if (i != 0)
	return;

    re_strings(&re_Oops_objdump, line, re_Oops_objdump_pmatch, &string);
    address = hexstring(string[1]);
    if (errno) {
	ERROR("Invalid hex value in objdump line, treated as zero - '%s'\n"
	    "  objdump line '%s'",
	    string[1], line);
	perror(prefix);
	address = 0;
    }
    address = Oops_truncate_address(address + eip + adjust, options);
    if (string[4]) {
	/* EIP relative data to be adjusted. */
	eip_relative = hexstring(string[4]);
	if (errno) {
	    ERROR("Invalid hex value in objdump line, treated as zero - '%s'\n"
	          "objdump line '%s'", string[4], line);
	    perror(prefix);
	    eip_relative = 0;
	}
	eip_relative = Oops_truncate_address(eip_relative + eip + adjust,
		options);
	map = map_address(&ss_merged, eip_relative, options);
	/* new text is original line, eip_relative in hex, map text */
	i = strlen(line)+1+2*sizeof(eip_relative)+1+strlen(map)+1;
	line2 = malloc(i);
	if (!line2)
	    malloc_error(procname);
	snprintf(line2, i, "%s %s %s",
	    line, format_address(eip_relative, options), map);
	Oops_decode_one_add(ss, address, line2, options);
	free(line2);
    }
    else
	Oops_decode_one_add(ss, address, line, options);  /* as is */
    re_strings_free(&re_Oops_objdump, &string);
}

/* Maximum number of code bytes to process.  It needs to be a multiple of 2 for
 * endianess swapping (-e).  Sparc and alpha dump 36 bytes so use 64.
 */
#define CODE_SIZE 64

/* Extract the hex values from the Code: line and convert to binary.
 * Strange as it seems, this is actually architecture independent, nothing in
 * this proc cares who created the Code: line.
 */
static int Oops_code_values(const unsigned char* code_text, unsigned char *code,
			    int *adjust, char ***string, int string_max,
			    const OPTIONS *options)
{
    int byte = 0, byte_prev, len;
    U16 u16;
    U32 u32;
    U64 u64, value;
    const char *p;
    static regex_t     re_Oops_code_value;
    static regmatch_t *re_Oops_code_value_pmatch;
    static const char procname[] = "Oops_code_values";

    /* Given by re_Oops_code: code_text is a message (e.g. "general protection")
     * or one or more hex fields separated by spaces.  Some architectures
     * bracket the current instruction with '<' and '>', others use '(' and ')'.
     * The first character is nonblank.
     */
    if (!isxdigit(*code_text)) {
	WARNING("Code looks like message, not hex digits.  "
	    "No disassembly attempted.");
	return(0);
    }
    memset(code, '\0', CODE_SIZE);
    p = code_text;
    *adjust = 0;    /* EIP points to code byte 0 */

    /* Code values.  Hex values separated by white space.  On some systems, the
     * current instruction is bracketed in '<' and '>' or '(' and ')'.
     */
    RE_COMPILE(&re_Oops_code_value,
	    "^"
	    "([<(]?)"			/* 1 */
	    "([0-9a-fA-F]+)"		/* 2 */
	    "[)>]?"
	    " *"
	    ,
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_code_value_pmatch);

    re_string_check(re_Oops_code_value.re_nsub+1, string_max, procname);
    while (regexec(&re_Oops_code_value, p, re_Oops_code_value.re_nsub+1,
	    re_Oops_code_value_pmatch, 0) == 0) {
	re_strings(&re_Oops_code_value, p,
	    re_Oops_code_value_pmatch, string);
	if (byte >= CODE_SIZE)
	    break;
	value = hexstring((*string)[2]);
	if (errno) {
	    ERROR("Invalid hex value in code_value line, treated as zero - "
	          "'%s'\ncode_value line '%s'", (*string)[2], code_text);
	    perror(prefix);
	    value = 0;
	}
	if ((*string)[1] && *((*string)[1]))
	    *adjust = -byte;    /* this byte is EIP */
	/* On some architectures Code: is a stream of bytes, on some it is a
	 * stream of shorts, on some it is a stream of ints.  Consistent we're
	 * not!
	 */
	len = strlen((*string)[2]);
	byte_prev = byte;
	if (byte+len/2 > CODE_SIZE) {
	    WARNING("extra values in Code line, ignored - '%s'", (*string)[2]);
	    break;
	}
	switch (len) {
	    case 2:
		code[byte++] = value;
		break;
	    case 4:
		u16 = value;
		memcpy(code+byte, &u16, sizeof(u16));
		byte += sizeof(u16);
		break;
	    case 8:
		u32 = value;
		memcpy(code+byte, &u32, sizeof(u32));
		byte += sizeof(u32);
		break;
	    case 16:
		u64 = value;
		memcpy(code+byte, &u64, sizeof(u64));
		byte += sizeof(u64);
		break;
	    default:
		ERROR("invalid value 0x%s in Code line, must be 2, 4, 8 or 16 "
		      "digits, value ignored", (*string)[2]);
		break;
	}
	if (options->endianess && len != 2) {
	    /* Reverse the endianess of the bytes just stored */
	    char c;
	    len /= 2;
	    while (len) {
		c = code[byte_prev];
		code[byte_prev] = code[byte_prev+len-1];
		code[byte_prev+len-1] = c;
		++byte_prev;
		len -= 2;
	    }
	}
	p += re_Oops_code_value_pmatch[0].rm_eo;
    }

    if (*p)
	WARNING("garbage '%s' at end of code line ignored", p);

    return(1);
}

/* Add an objdump line to the symbol table.  Just to keep the non-ix86 users
 * happy, replace XXX with an target specific string.
 */
static void Oops_decode_one_add(SYMBOL_SET *ss, const addr_t address,
				const char *line1, const OPTIONS *options)
{
    int i;
    const char *p1;
    char *line2, *p2, *p3;
    static char const procname[] = "Oops_decode_one_add";

    i = strlen(line1)+1;
    line2 = malloc(i);
    if (!line2)
	malloc_error(procname);
    memset(line2, '\0', i);
    p1 = line1;
    p2 = line2;
    while (1) {
	if ((p3 = strstr(p1, "_XXX"))) {
	    memcpy(p2, p1, p3-p1+1);
	    p2 += p3 - p1 + 1;
	    p1 = p3 + 4;
	    strcpy(p2, Oops_arch_to_eip(options));
	    while (*p2)
		++p2;
	}
	else {
	    strcpy(p2, p1);
	    break;
	}
    }
    if (strlen(line2) > strlen(line1))
	ERROR("line2 overrun, should never happen line1='%s'\nline2='%s'",
	    line1, line2);
    add_symbol_n(ss, address, 'C', 1, line2);
    free(line2);
}

/* Set the eip from the EIP line */
static void Oops_set_eip(const char *value, addr_t *eip, SYMBOL_SET *ss,
			 const char *me, const bfd *ibfd,
			 OPTIONS *options)
{
    static const char procname[] = "Oops_set_eip";
    char eip_name[10], *p;

    /* Ensure target is set so EIP text is correct */
    Oops_set_default_ta(me, ibfd, options);

    *eip = hexstring(value);
    if (errno) {
	ERROR("Invalid hex value in %s line, ignored - '%s'",
	    Oops_arch_to_eip(options), value);
	perror(prefix);
	*eip = 0;
    }
    snprintf(eip_name, sizeof(eip_name), ">>%-3s;",
	 Oops_arch_to_eip(options));
    while ((p = strstr(eip_name, " ;")))
        strncpy(p, "; ", 2);
    *eip = Oops_truncate_address(*eip, options);
    add_symbol_n(ss, *eip, 'E', 1, eip_name);
}

/******************************************************************************/
/*                     Start architecture sensitive code                      */
/******************************************************************************/

/* All address arithmetic is done in 64 bit mode, truncate to the result to
 * a valid range for the target.
 */
static addr_t Oops_truncate_address(addr_t address, const OPTIONS *options)
{
    if (options->address_bits == 32) {
	U32 u32 = address;
	if (strstr(options->target, "i370"))
	    u32 &= 0x7fffffff;  /* Really 31 bit addressing on i370 */
	return(u32);
    }
    return(address);	/* 0, 64, anything else */
}

/* Return a string to represent the eip, depending on the target. */
static const char *Oops_arch_to_eip(const OPTIONS *options)
{
    if (!options->target)
	return("???");
    if (strstr(options->target, "i386"))
	return("EIP");
    if (strstr(options->target, "alpha") ||
	strstr(options->target, "arm")   ||
	strstr(options->target, "m68k")  ||
	strstr(options->target, "mips"))
	    return("PC");
    if (strstr(options->target, "sparc")) {
	if (strstr(options->target, "64"))
	    return("TPC");
	else
	    return("PC");
    }
    if (strstr(options->target, "powerpc"))
	return("NIP");
    if (strstr(options->target, "i370"))
	return("PSW");
    return("???");
}

/* Look for the EIP: line, returns start of the relevant hex value */
static char *Oops_eip(const char *line, char ***string, int string_max,
		      const bfd *ibfd, OPTIONS *options)
{
    int i;
    static regex_t     re_Oops_eip_sparc;
    static regmatch_t *re_Oops_eip_sparc_pmatch;
    static regex_t     re_Oops_eip_sparc64;
    static regmatch_t *re_Oops_eip_sparc64_pmatch;
    static regex_t     re_Oops_eip_ppc;
    static regmatch_t *re_Oops_eip_ppc_pmatch;
    static regex_t     re_Oops_eip_mips;
    static regmatch_t *re_Oops_eip_mips_pmatch;
    static regex_t     re_Oops_eip_other;
    static regmatch_t *re_Oops_eip_other_pmatch;
    static const char procname[] = "Oops_eip";

    /* Oops 'EIP:' line for sparc, actually PSR followed by PC */
    RE_COMPILE(&re_Oops_eip_sparc,
	    "^PSR: [0-9a-fA-F]+ PC: " UNBRACKETED_ADDRESS,
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_eip_sparc_pmatch);

    i = regexec(&re_Oops_eip_sparc, line, re_Oops_eip_sparc.re_nsub+1,
	re_Oops_eip_sparc_pmatch, 0);
    DEBUG(4, "regexec sparc %d", i);
    if (i == 0) {
	re_string_check(re_Oops_eip_sparc.re_nsub+1, string_max,
	    procname);
	re_strings(&re_Oops_eip_sparc, line, re_Oops_eip_sparc_pmatch,
	    string);
	return((*string)[re_Oops_eip_sparc.re_nsub]);
    }

    /* Oops 'EIP:' line for sparc64, actually TSTATE followed by TPC */
    RE_COMPILE(&re_Oops_eip_sparc64,
	    "^TSTATE: [0-9a-fA-F]{16} TPC: " UNBRACKETED_ADDRESS,
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_eip_sparc64_pmatch);

    re_string_check(re_Oops_eip_sparc64.re_nsub+1, string_max, procname);
    i = regexec(&re_Oops_eip_sparc64, line, re_Oops_eip_sparc64.re_nsub+1,
	re_Oops_eip_sparc64_pmatch, 0);
    DEBUG(4, "regexec sparc64 %d", i);
    if (i == 0) {
	/* Special case for sparc64.  If the output target is defaulting to the
	 * same format as ksymoops then the default is wrong, kernel is 64 bit,
	 * ksymoops is 32 bit.  When we see an EIP from sparc64, set the correct
	 * default.
	 */
	if (!options->target && !options->architecture &&
	    strcmp(bfd_get_target(ibfd), "elf32-sparc")) {
	    options->target = "elf64-sparc";
	    options->architecture = "sparc:v9a";
	}
	re_strings(&re_Oops_eip_sparc64, line, re_Oops_eip_sparc64_pmatch,
	    string);
	return((*string)[re_Oops_eip_sparc64.re_nsub]);
    }

    /* Oops 'EIP:' line for PPC, all over the place */
    RE_COMPILE(&re_Oops_eip_ppc,
	    "("
	      "kernel pc "
	      "|trap at PC: "
	      "|bad area pc "
	      "|NIP: "
	    ")"
	    UNBRACKETED_ADDRESS,
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_eip_ppc_pmatch);

    i = regexec(&re_Oops_eip_ppc, line, re_Oops_eip_ppc.re_nsub+1,
	re_Oops_eip_ppc_pmatch, 0);
    DEBUG(4, "regexec ppc %d", i);
    if (i == 0) {
	re_string_check(re_Oops_eip_ppc.re_nsub+1, string_max,
	    procname);
	re_strings(&re_Oops_eip_ppc, line, re_Oops_eip_ppc_pmatch,
	    string);
	return((*string)[re_Oops_eip_ppc.re_nsub]);
    }

    /* Oops 'EIP:' line for mips, epc, optional white space, ':',
     * optional white space, unbracketed address.
     */
    RE_COMPILE(&re_Oops_eip_mips,
	    "^epc *:+ *"
	    UNBRACKETED_ADDRESS,
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_eip_mips_pmatch);

    i = regexec(&re_Oops_eip_mips, line, re_Oops_eip_mips.re_nsub+1,
	re_Oops_eip_mips_pmatch, 0);
    DEBUG(4, "regexec mips %d", i);
    if (i == 0) {
	re_string_check(re_Oops_eip_mips.re_nsub+1, string_max,
	    procname);
	re_strings(&re_Oops_eip_mips, line, re_Oops_eip_mips_pmatch,
	    string);
	return((*string)[re_Oops_eip_mips.re_nsub]);
    }

    /* Oops 'EIP:' line for other architectures */
    RE_COMPILE(&re_Oops_eip_other,
		"^("
    /* i386 */	  "EIP: +.*"
    /* m68k */	  "|PC *= *"
    /* arm */	  "|pc *: *"
		")"
		BRACKETED_ADDRESS
		,
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_eip_other_pmatch);

    i = regexec(&re_Oops_eip_other, line, re_Oops_eip_other.re_nsub+1,
	re_Oops_eip_other_pmatch, 0);
    DEBUG(4, "regexec other %d", i);
    if (i == 0) {
	re_string_check(re_Oops_eip_other.re_nsub+1, string_max,
	    procname);
	re_strings(&re_Oops_eip_other, line, re_Oops_eip_other_pmatch,
	    string);
	return((*string)[re_Oops_eip_other.re_nsub]);
    }
    return(NULL);
}

/* Look for the alpha spinlock stuck line, returns TRUE with string[1]
 * containing the PC, string[2] containing the previous PC.
 */
static int Oops_eip_alpha_spinlock(const char *line, char ***string,
				   int string_max, OPTIONS *options)
{
    int i;
    static regex_t     re_Oops_eip_alpha_spinlock;
    static regmatch_t *re_Oops_eip_alpha_spinlock_pmatch;
    static const char procname[] = "Oops_eip_alpha_spinlock";

    RE_COMPILE(&re_Oops_eip_alpha_spinlock,
	    "^spinlock stuck at " UNBRACKETED_ADDRESS
	    ".*owner.*at " UNBRACKETED_ADDRESS,
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_eip_alpha_spinlock_pmatch);

    i = regexec(&re_Oops_eip_alpha_spinlock, line,
	re_Oops_eip_alpha_spinlock.re_nsub+1,
	re_Oops_eip_alpha_spinlock_pmatch, 0);
    DEBUG(4, "regexec %d", i);
    if (i == 0) {
	re_string_check(re_Oops_eip_alpha_spinlock.re_nsub+1, string_max,
	    procname);
	re_strings(&re_Oops_eip_alpha_spinlock, line,
	    re_Oops_eip_alpha_spinlock_pmatch, string);
	return(1);
    }
    return(0);
}

/* Look for the mips ra line, returns start of the relevant hex value */
static char *Oops_mips_ra(const char *line, char ***string, int string_max)
{
    int i;
    static regex_t     re_Oops_mips_ra;
    static regmatch_t *re_Oops_mips_ra_pmatch;
    static const char procname[] = "Oops_mips_ra";

    /* Oops 'ra:' line for mips, ra, optional white space, one or
     * more '=', optional white space, unbracketed address.
     */
    RE_COMPILE(&re_Oops_mips_ra,
	    "ra *=+ *"
	    UNBRACKETED_ADDRESS,
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_mips_ra_pmatch);

    i = regexec(&re_Oops_mips_ra, line, re_Oops_mips_ra.re_nsub+1,
	re_Oops_mips_ra_pmatch, 0);
    DEBUG(4, "regexec %d", i);
    if (i == 0) {
	re_string_check(re_Oops_mips_ra.re_nsub+1, string_max, procname);
	re_strings(&re_Oops_mips_ra, line, re_Oops_mips_ra_pmatch,
	    string);
	return((*string)[re_Oops_mips_ra.re_nsub]);
    }
    return(NULL);
}

/* Set the mips ra from the ra line */
static void Oops_set_mips_ra(const char *value, SYMBOL_SET *ss)
{
    static const char procname[] = "Oops_set_mips_ra";
    addr_t ra;
    ra = hexstring(value);
    if (errno) {
	ERROR(" Invalid hex value in ra line, ignored - '%s'", value);
	perror(prefix);
	ra = 0;
    }
    add_symbol_n(ss, ra, 'R', 1, ">>RA; ");
}

/* Look for the sparc o7/i7 registers line, returns start of the relevant hex
 * value.
 */
static char *Oops_sparc_oi7(const char *line, char ***string, int string_max)
{
    int i;
    static regex_t     re_Oops_sparc_oi7;
    static regmatch_t *re_Oops_sparc_oi7_pmatch;
    static const char procname[] = "Oops_sparc_oi7";

    RE_COMPILE(&re_Oops_sparc_oi7,
	    "^[io][04]: [0-9a-fA-F iosp:]+ ([io]7|ret_pc): "
	    UNBRACKETED_ADDRESS,
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_sparc_oi7_pmatch);

    re_string_check(re_Oops_sparc_oi7.re_nsub+1, string_max, procname);
    i = regexec(&re_Oops_sparc_oi7, line, re_Oops_sparc_oi7.re_nsub+1,
	re_Oops_sparc_oi7_pmatch, 0);
    DEBUG(4, "regexec %d", i);
    if (i == 0) {
	re_strings(&re_Oops_sparc_oi7, line, re_Oops_sparc_oi7_pmatch,
	    string);
	return((*string)[re_Oops_sparc_oi7.re_nsub]);
    }
    return(NULL);
}

/* Set the sparc o7/i7 from the oi7 line */
static void Oops_set_sparc_oi7(const char *value, char ***string,
			       SYMBOL_SET *ss)
{
    static const char procname[] = "Oops_set_sparc_oi7";
    addr_t oi7;
    int o7 = 1;
    if ((*string)[1] && !strcmp((*string)[1], "i7"))
	o7 = 0;
    oi7 = hexstring(value);
    if (errno) {
	ERROR("Invalid hex value in oi7 line, ignored - '%s'", value);
	perror(prefix);
	oi7 = 0;
    }
    add_symbol_n(ss, oi7, 'O', 1, o7 ? ">>O7; " : ">>I7; ");
}

/* Look for the sparc register dump lines end */
static int Oops_sparc_regdump(const char *line, char ***string)
{
    int i;
    static regex_t     re_Oops_sparc_regdump;
    static regmatch_t *re_Oops_sparc_regdump_pmatch;
    static const char procname[] = "Oops_sparc_regdump";

    RE_COMPILE(&re_Oops_sparc_regdump,
	    "^("
		"i[04]: "
		"|Instruction DUMP: "
		"|Caller\\["
	    ")",
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_sparc_regdump_pmatch);

    i = regexec(&re_Oops_sparc_regdump, line, re_Oops_sparc_regdump.re_nsub+1,
	re_Oops_sparc_regdump_pmatch, 0);
    DEBUG(4, "regexec %d", i);
    if (i == 0)
	return 1;
    return 0;
}

/* Look for the Trace multilines :(.  Returns start of addresses. */
static const char *Oops_trace(const char *line)
{
    int i;
    const char *start = NULL;
    static int trace_line = 0;
    static regex_t     re_Oops_trace;
    static regmatch_t *re_Oops_trace_pmatch;
    static const char procname[] = "Oops_trace";

    /* Oops 'Trace' lines. */
    RE_COMPILE(&re_Oops_trace,
	    "^("						     /* 1 */
		    "(Call Trace: )"				     /* 2 */
    /* alpha */     "|(Trace: )"				     /* 3 */
    /* various */   "|(" BRACKETED_ADDRESS ")"			     /* 4,5 */
    /* ppc */	    "|(Call backtrace:)"			     /* 6 */
    /* ppc */	    "|" UNBRACKETED_ADDRESS			     /* 7 */
    /* arm */	    "|Function entered at (" BRACKETED_ADDRESS ")"   /* 8,9 */
    /* sparc64 */   "|Caller\\[" UNBRACKETED_ADDRESS "\\]"	     /* 10 */
    /* i386 */	    "|(" REVBRACKETED_ADDRESS ")"		     /* 11,12 */
	    ")",
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_trace_pmatch);

    i = regexec(&re_Oops_trace, line, re_Oops_trace.re_nsub+1,
	re_Oops_trace_pmatch, 0);
    DEBUG(4, "regexec %d", i);
    if (i == 0) {
#undef MATCHED
#define MATCHED(n) (re_Oops_trace_pmatch[n].rm_so != -1)
	if (MATCHED(2) || MATCHED(3)) {
	    /* Most start lines */
	    trace_line = 1;
	    start = line + re_Oops_trace_pmatch[0].rm_eo;
	}
	else if (MATCHED(6)) {
	    /* ppc start line, not a bracketed address, just an address */
	    trace_line = 2;     /* ppc */
	    start = line + re_Oops_trace_pmatch[0].rm_eo;
	}
	else if (MATCHED(8)) {
	    /* arm only line, two bracketed addresses on each line */
	    trace_line = 3;
	    start = line + re_Oops_trace_pmatch[8].rm_so;
	}
	else if (MATCHED(10)) {
	    /* sparc64 only line, one unbracketed address */
	    trace_line = 3;
	    start = line + re_Oops_trace_pmatch[10].rm_so;
	}
	else if (MATCHED(11)) {
	    /* Reversed addresses for spinloops :( */
	    trace_line = 1;
	    start = line + re_Oops_trace_pmatch[11].rm_so;
	}
	else if (trace_line == 1 && MATCHED(4))
	    /* Most continuation lines */
	    start = line + re_Oops_trace_pmatch[4].rm_so;
	else if (trace_line == 2 && MATCHED(7))
	    /* ppc continuation line */
	    start = line + re_Oops_trace_pmatch[7].rm_so;
	else
	    trace_line = 0;
    }
    else
	trace_line = 0;
    if (trace_line) {
	if (trace_line == 3)
	    trace_line = 0;	/* no continuation lines */
	return(start);
    }
    return(NULL);
}

/* Process a trace call line, extract addresses */
static void Oops_trace_line(const char *line, const char *p, SYMBOL_SET *ss)
{
    char **string = NULL;
    regex_t *pregex;
    regmatch_t *pregmatch;
    static const char procname[] = "Oops_trace_line";

    /* ppc does not bracket its addresses */
    if (isxdigit(*p)) {
	pregex = &re_unbracketed_address;
	pregmatch = re_unbracketed_address_pmatch;
    }
    else if (strncmp(p, "<[", 2) == 0) {
	/* I hate special cases! */
	pregex = &re_revbracketed_address;
	pregmatch = re_revbracketed_address_pmatch;
    }
    else {
	pregex = &re_bracketed_address;
	pregmatch = re_bracketed_address_pmatch;
    }

    /* Loop over (un|rev)?bracketed addresses */
    while (1) {
	if (regexec(pregex, p, pregex->re_nsub+1, pregmatch, 0) == 0) {
	    re_strings(pregex, p, pregmatch, &string);
	    add_symbol(ss, string[1], 'T', 1, "Trace;");
	    p += pregmatch[0].rm_eo;
	}
	else if (strncmp(p, "from ", 5) == 0)
	    p += 5;     /* arm does "address from address" */
	else
	    break;
    }

    if (*p && !strcmp(p, "..."))
	WARNING("garbage '%s' at end of trace line ignored", p);
    re_strings_free(pregex, &string);
}

/* Do pattern matching to decide if the line should be printed.  When reading a
 * syslog containing multiple Oops, you need the intermediate data (registers,
 * tss etc.) to go with the decoded text.  Sets text to the start of the useful
 * text, after any prefix.  Note that any leading white space is treated as part
 * of the prefix, later routines do not see any indentation.
 *
 * Note: If a line is not printed, it will not be scanned for any other text.
 */

#ifdef TIME_RE
/* time the re code */
#include <sys/time.h>
struct timeval before, after;
long long diff[30];
int count[30];
#define START gettimeofday(&before, NULL);
#define DIFF(n) gettimeofday(&after, NULL); \
	++count[n]; \
	diff[n] += (after.tv_sec - before.tv_sec)*1000001 + (after.tv_usec - before.tv_usec); \
	before = after;
#else
#define START
#define DIFF(n)
#endif

static int Oops_print(const char *line, const char **text, char ***string,
		      int string_max)
{
    int i, print = 0;
    static int stack_line = 0, trace_line = 0;
    static regex_t     re_Oops_prefix;
    static regmatch_t *re_Oops_prefix_pmatch;
    static regex_t     re_Oops_print_s;
    static regmatch_t *re_Oops_print_s_pmatch;
    static regex_t     re_Oops_print_a;
    static regmatch_t *re_Oops_print_a_pmatch;
    static const char procname[] = "Oops_print";

    START

    *text = line;

    /* Lines to be ignored.  For some reason the "amuse the user" print in
     * some die_if_kernel routines causes regexec to run very slowly.
     */

    if (strstr(*text, "\\|/ ____ \\|/")  ||
	strstr(*text, "\"@'/ ,. \\`@\"") ||
	strstr(*text, "\"@'/ .. \\`@\"") ||
	strstr(*text, "/_| \\__/ |_\\")  ||
	strstr(*text, "   \\__U_/"))
	return(1);  /* print but avoid regexec */

    DIFF(1);

    /* Trial and error with regex leads me to the following conclusions.
     *
     * regex does not like a lot of sub patterns that start with "^".  So split
     * the patterns into two groups, one set must appear at the start of the
     * line, the other set can appear anywhere.
     *
     * Lots of bracketed subexpressions really slows regex down.  Only use 
     * brackets where absolutely necessary.  Examples are where you want to
     * extract matching substrings such as stack, call trace etc.  Or where
     * you have suboptions within a string, like the sparc die_if_kernel text.
     * Otherwise omit brackets, except that every re should have at least one
     * bracketed expression, that is how RE_COMPILE tells if the expression
     * has already been compiled.
     */

    /* Prefixes to be ignored.  These prefixes were originally done as
     * ^((pattern1)|(pattern2)|(pattern3))+  but that caused pathological
     * behaviour in regexec when faced with lines like
     *                     : "=r" (__cu_len), "=r" (__cu_from), "=r" (__cu_to)
     * Would you believe 11 seconds just to resolve that one line :(.  So just
     * look for one prefix at a time and loop until all prefixes are stripped.
     */
    RE_COMPILE(&re_Oops_prefix,
	    "^("			/* start of line */
	    " +"			/* leading spaces */
	    "|[^ ]{3} [ 0-9][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2} "
	      "[^ ]+ kernel: +"		/* syslogd */
	    "|<[0-9]+>"			/* kmsg */
	    ")",
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_prefix_pmatch);

    do {
	i = regexec(&re_Oops_prefix, *text, re_Oops_prefix.re_nsub+1,
	    re_Oops_prefix_pmatch, 0);
	DEBUG(4, "regexec prefix %d", i);
	if (i == 0)
	    *text += re_Oops_prefix_pmatch[0].rm_eo;  /* step over prefix */
    } while (i == 0);

    DIFF(2);

    /* Lots of possibilities.  Expand as required for all architectures. */

    /* These patterns must appear at the start of the line, after stripping
     * the prefix above.
     *
     * The order below is required to handle multiline outupt.
     * string 2 is defined if the text is 'Stack from '.
     * string 3 is defined if the text is 'Stack: '.
     * string 4 is defined if the text might be a stack continuation.
     * string 5 is defined if the text is 'Call Trace: '.
     * string 6 is defined if the text might be a trace continuation.
     * string 7 is the address part of the BRACKETED_ADDRESS.
     *
     * string 8 is defined if the text contains a version number.  No Oops
     * report contains this as of 2.1.125 but IMHO it should be added.  If
     * anybody wants to print a VERSION_nnnn line in their Oops, this code
     * is ready.
     *
     * string 9 is defined if the text is 'Trace: ' (alpha).
     * string 10 is defined if the text is 'Call backtrace:' (ppc).
     * string 11 is defined if the text is 'bh:' (i386).  Stack addresses are
     * on the next line.  In our typical inconsistent manner, the bh: stack
     * addresses are different from every other address, they are <[...]>
     * instead of [<...>] so define yet another re, string 12.  Sigh.
     *
     * Some strings indicate stack data (S), some indicate trace data (T).
     */
    RE_COMPILE(&re_Oops_print_s,
    /* arch type */				/* Required order */
		"^("					/*  1 */
    /* i386 */	    "(Stack: )"				/*  2  S */
    /* m68k */	    "|(Stack from )"			/*  3  S */
    /* various */   "|([0-9a-fA-F]{4,})"		/*  4  S */
    /* various */   "|(Call Trace: )"			/*  5  T */
    /* various */   "|(" BRACKETED_ADDRESS ")"		/* 6,7 T */
    /* various */   "|(Version_[0-9]+)"			/*  8 */
    /* alpha */	    "|(Trace: )"			/*  9  T */
    /* ppc */	    "|(Call backtrace:)"		/* 10  T */
    /* i386 */	    "|(bh:)"				/* 11  T */
    /* i386 */	    "|" REVBRACKETED_ADDRESS		/* 12  T */

    /* order does not matter from here on */
    
    /* various */   "|Process .*stackpage="
    /* various */   "|Code *: "
    /* various */   "|Kernel panic"
    /* various */   "|In swapper task"

    /* i386 2.0 */  "|Corrupted stack page"
    /* i386 */	    "|invalid operand: "
    /* i386 */	    "|Oops: "
    /* i386 */	    "|Cpu: +[0-9]"
    /* i386 */	    "|current->tss"
    /* i386 */	    "|\\*pde +="
    /* i386 */	    "|EIP: "
    /* i386 */	    "|EFLAGS: "
    /* i386 */	    "|eax: "
    /* i386 */	    "|esi: "
    /* i386 */	    "|ds: "
    /* i386 */	    "|CR0: "
    /* i386 */	    "|wait_on_"
    /* i386 */	    "|irq: "

    /* m68k */	    "|pc[:=]"
    /* m68k */	    "|68060 access"
    /* m68k */	    "|Exception at "
    /* m68k */	    "|d[04]: "
    /* m68k */	    "|Frame format="
    /* m68k */	    "|wb [0-9] stat"
    /* m68k */	    "|push data: "
    /* m68k */	    "|baddr="
    /* any other m68K lines to print? */

    /* alpha */	    "|Arithmetic fault"
    /* alpha */	    "|Instruction fault"
    /* alpha */	    "|Bad unaligned kernel"
    /* alpha */	    "|Forwarding unaligned exception"
    /* alpha */	    "|: unhandled unaligned exception"
    /* alpha */	    "|pc *="
    /* alpha */	    "|r[0-9]+ *="
    /* alpha */	    "|gp *="
    /* alpha */	    "|spinlock stuck"
    /* any other alpha lines to print? */

    /* sparc */	    "|tsk->"
    /* sparc */	    "|PSR: "
    /* sparc */	    "|[goli]0: "
    /* sparc */	    "|Instruction DUMP: "
    /* any other sparc lines to print? */

    /* sparc64 */   "|TSTATE: "
    /* sparc64 */   "|[goli]4: "
    /* sparc64 */   "|Caller\\["
    /* sparc64 */   "|CPU\\["
    /* any other sparc64 lines to print? */

    /* ppc */	    "|MSR: "
    /* ppc */	    "|TASK = "
    /* ppc */	    "|last math "
    /* ppc */	    "|GPR[0-9]+: "
    /* any other ppc lines to print? */

    /* mips */	    "|\\$[0-9 ]+:"
    /* mips */	    "|epc "
    /* mips */	    "|Status:"
    /* mips */	    "|Cause :"
    /* any other mips lines to print? */

    /* arm */	    "|Backtrace:"
    /* arm */	    "|Function entered at"
    /* arm */	    "|\\*pgd ="
    /* arm */	    "|Internal error"
    /* arm */	    "|pc :"
    /* arm */	    "|sp :"
    /* arm */	    "|r[0-9][0-9 ]:"
    /* arm */	    "|Flags:"
    /* arm */	    "|Control:"
    /* any other arm lines to print? */

		")",
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_print_s_pmatch);

    i = regexec(&re_Oops_print_s, *text, re_Oops_print_s.re_nsub+1,
	re_Oops_print_s_pmatch, 0);
    DEBUG(4, "regexec start %d", i);
    DIFF(3);
    print = 0;
#undef MATCHED
#define MATCHED(n) (re_Oops_print_s_pmatch[n].rm_so != -1)
    DIFF(4);
    /* Handle multiline messages, messy.  Only treat strings that look like
     * addresses as useful when they immediately follow a definite stack or
     * trace header or a previously valid stack or trace line.
     */
    /* Stack lines */
    if (!MATCHED(2) && !MATCHED(3) && !MATCHED(4))
	stack_line = 0;
    else if (MATCHED(2) || MATCHED(3))
	stack_line = 1;
    else if (stack_line && !MATCHED(4))
	stack_line = 0;
    /* Trace lines */
    if (!MATCHED(5) && !MATCHED(6) && !MATCHED(9) && !MATCHED(10) &&
	!MATCHED(11) && !MATCHED(12))
	trace_line = 0;
    else if (MATCHED(5) || MATCHED(9) || MATCHED(10) || MATCHED(11))
	trace_line = 1;
    else if (trace_line && !MATCHED(6) && !MATCHED(12))
	trace_line = 0;
    if (i == 0) {
	print = 1;
	if (MATCHED(4) && !stack_line) {
	    print = 0;
	    DEBUG(4, "ambiguous stack line ignored");
	}
	if ((MATCHED(6) || MATCHED(12)) && !trace_line) {
	    print = 0;
	    DEBUG(4, "ambiguous trace line ignored");
	}
    }
    if (print && MATCHED(4)) {
	/* Ambiguity with 3Com messages, a line like 3c503.c:v1.10 9/23/93
	 * starts with 4 or more hex digits but is not an error message, instead
	 * it is a filename.  Look for '.c:' before the first space and ignore
	 * if found.  Also ad1848, look for '/' before first space.  Do not look
	 * for ':' on its own, arm does "address: stack values" :(.
	 * 
	 * Ambiguity of ambiguities, saith the preacher; all is ambiguity.
	 */
	const char *p1, *p2;
	p2 = strstr(*text, " ");
	if (p2) {
	    if ((p1 = strstr(*text, ".c:")) && p1 < p2)
		print = 0;  /* ignore ambiguous match */
	    else if ((p1 = strstr(*text, "/")) && p1 < p2)
		print = 0;  /* ignore ambiguous match */
	}
	if (!print)
	    DEBUG(4, "ambiguous start ignored");
    }
    /* delay splitting into strings until we really them */
    if (print && MATCHED(8)) {
	re_string_check(re_Oops_print_s.re_nsub+1, string_max,
	    procname);
	re_strings(&re_Oops_print_s, *text,
	    re_Oops_print_s_pmatch,
	    string);
	add_Version((*string)[8]+8, "Oops");
    }
    DIFF(5);

    /* These patterns can appear anywhere in the line, after stripping
     * the prefix above.
     */
    RE_COMPILE(&re_Oops_print_a,
   /* arch type */

    /* various */   "Unable to handle kernel"
    /* various */   "|Aiee"      /* anywhere in text is a bad sign (TM) */
    /* various */   "|die_if_kernel"  /* ditto */

    /* alpha */     "|\\([0-9]\\): Oops "
    /* alpha */     "|: memory violation"
    /* alpha */     "|: Exception at"
    /* alpha */     "|: Arithmetic fault"
    /* alpha */     "|: Instruction fault"
    /* alpha */     "|: arithmetic trap"
    /* alpha */     "|: unaligned trap"

    /* sparc      die_if_kernel has no fixed text, identify by (pid): text.
     *            Somebody has been playful with the texts.
     *
     *            Alas adding this next pattern increases run time by 15% on
     *            its own!  It would be considerably faster if sparc had
     *            consistent error texts.
     */
    /* sparc */     "|\\([0-9]+\\): "
			"("
			    "Whee"
			    "|Oops"
			    "|Kernel"
			    "|.*Penguin"
			    "|BOGUS"
			")"

    /* ppc */       "|kernel pc "
    /* ppc */       "|trap at PC: "
    /* ppc */       "|bad area pc "
    /* ppc */       "|NIP: "

    /* mips */      "| ra *="

		,
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_print_a_pmatch);

    if (!print) {
	i = regexec(&re_Oops_print_a, *text, re_Oops_print_a.re_nsub+1,
	    re_Oops_print_a_pmatch, 0);
	DEBUG(4, "regexec anywhere %d", i);
	if (i == 0)
	    print = 1;
    }
    DIFF(6);

    return(print);
}

/* Look for the Code: line.  Returns start of the code bytes. */
static const char *Oops_code(const char *line, char ***string, int string_max)
{
    int i;
    char *p;
    static regex_t     re_Oops_code;
    static regmatch_t *re_Oops_code_pmatch;
    static const char procname[] = "Oops_code";

    /* Oops 'Code: ' hopefully followed by at least one hex code.  sparc
     * brackets the PC in '<' and '>'.  arm brackets the PC in '(' and ')'.
     */
    RE_COMPILE(&re_Oops_code,
		"^("				/*  1 */
    /* sparc */   "(Instruction DUMP)"		/*  2 */
    /* various */ "|(Code *)"			/*  3 */
		")"
		": +"
		"("				/*  4 */
		  "(general protection.*)"
		  "|(<[0-9]+>)"
		  "|(([<(]?[0-9a-fA-F]+[>)]? +)+[<(]?[0-9a-fA-F]+[>)]?)"
		")"
		"(.*)$"			/* trailing garbage */
		,
	REG_NEWLINE|REG_EXTENDED|REG_ICASE,
	&re_Oops_code_pmatch);

    i = regexec(&re_Oops_code, line, re_Oops_code.re_nsub+1,
	re_Oops_code_pmatch, 0);
    DEBUG(4, "regexec %d", i);
    if (i == 0) {
	re_string_check(re_Oops_code.re_nsub+1, string_max, procname);
	re_strings(&re_Oops_code, line, re_Oops_code_pmatch,
	    string);
	if ((*string)[re_Oops_code.re_nsub] &&
	    *((*string)[re_Oops_code.re_nsub])) {
	    /* Performance fix to Code: re means that trailing space is no
	     * longer part of the Code: text.  No point in complaining about
	     * trailing space.
	     */
	    for (p = (*string)[re_Oops_code.re_nsub]; isspace(*p); ++p)
		/* skip white space */ ;
	    if (*p) {
		WARNING("trailing garbage ignored on Code: line\n"
		    "  Text: '%s'\n"
		    "  Garbage: '%s'",
		    line, (*string)[re_Oops_code.re_nsub]);
	    }
	}
	return((*string)[4]);
    }
    return(NULL);
}

/******************************************************************************/
/*                      End architecture sensitive code                       */
/******************************************************************************/

/* Decode the Oops Code: via objdump. */
static void Oops_decode(const unsigned char* code_text, addr_t eip,
			SYMBOL_SET *ss, char ***string, int string_max,
			const bfd *ibfd, OPTIONS *options)
{
    FILE *f;
    char *file, *line = NULL, code[CODE_SIZE];
    int size = 0, adjust, lines = 0;
    static char const procname[] = "Oops_decode";

    DEBUG(1, "");
    /* text to binary */
    if (!Oops_code_values(code_text, code, &adjust, string, string_max,
			  options))
	return;
    /* binary to same format as ksymoops */
    if (!(file = Oops_code_to_file(code, CODE_SIZE, ibfd, options)))
	return;
    /* objdump the pseudo object */
    if (!(f = Oops_objdump(file, options)))   
	return;
    while (fgets_local(&line, &size, f, procname)) {
	DEBUG(2, "%s", line);
	++lines;
	Oops_decode_one(ss, line, eip, adjust, options);
    }
    pclose_local(f, procname);  /* opened in Oops_objdump */
    free(line);
    if (!lines)
	ERROR("no objdump lines read for %s", file);
    else if (unlink(file)) {
	ERROR("could not unlink %s", file);
	perror(prefix);
    }
}

/* Reached the end of an Oops report, format the extracted data. */
static void Oops_format(const SYMBOL_SET *ss_format, OPTIONS *options)
{
    int i;
    SYMBOL *s;
    addr_t eip = 0;
    char *eye_catcher;
    static const char procname[] = "Oops_format";

    DEBUG(1, "");

    compare_Version();  /* Oops might have a version one day */
    printf("\n");
    for (s = ss_format->symbol, i = 0; i < ss_format->used; ++i, ++s) {
	/* For type C data, print Code:, address, map, "name" (actually
	 * the text of an objdump line).  For other types print name,
	 * address, map.
	 */
	if (s->type == 'C') {
	    eye_catcher = "";
	    if (eip && i < ss_format->used-1 && (s+1)->type == 'C' &&
		eip >= s->address && eip < (s+1)->address)
		eye_catcher = "   <=====";
	    if (options->short_lines)
		printf("Code;  %s %s%s\n%s%s\n",
		    format_address(s->address, options),
		    map_address(&ss_merged, s->address, options), eye_catcher,
		    s->name, eye_catcher);
	    else
		printf("Code;  %s %-30s %s%s\n",
		    format_address(s->address, options),
		    map_address(&ss_merged, s->address, options),
		    s->name, eye_catcher);
	}
	else {
	    printf("%s %s %s",
		s->name,
		format_address(s->address, options),
		map_address(&ss_merged, s->address, options));
	    if (s->type == 'E') {
		eip = s->address;
		if (eip)
		    printf("   <=====");
	    }
	    printf("\n");
	}
    }
    printf("\n");
}

/* Select next Oops input file */
static FILE *Oops_next_file(OPTIONS *options)
{
    static FILE *f = NULL;
    static int first_file = 1;
    static const char procname[] = "Oops_next_file";

    if (first_file) {
	f = stdin;
	first_file = 0;
    }
    while (options->filecount) {
	if (f)
	    fclose_local(f, procname);
	f = NULL;
	if (regular_file(*(options->filename), procname))
	    f = fopen_local(*(options->filename), "r", procname);
	if (f)
	    DEBUG(1, "reading Oops report from %s", *(options->filename));
	++options->filename;
	--options->filecount;
	if (f)
	    return(f);
    }
    return(f);
}

/* Read the Oops report */
#define MAX_STRINGS 300 /* Maximum strings in any Oops re */
int Oops_read(OPTIONS *options)
{
    char *line = NULL, **string = NULL, *me;
    const char *start, *text;
    int i, size = 0, lineno = 0, lastprint = 0, print;
    addr_t eip = 0;
    int sparc_regdump = 0;
    FILE *f;
    SYMBOL_SET ss_format;
    bfd *ibfd = NULL;
    static const char procname[] = "Oops_read";

    ss_init(&ss_format, "Oops log data");
    Oops_open_input_bfd(&me, &ibfd, options);

    if (!options->filecount && isatty(0))
	printf("Reading Oops report from the terminal\n");

    string = malloc(MAX_STRINGS*sizeof(*string));
    if (!string)
	malloc_error(procname);
    memset(string, '\0', MAX_STRINGS*sizeof(*string));

    do {
	if (!(f = Oops_next_file(options)))
	    continue;
	while (fgets_local(&line, &size, f, procname)) {
	    DEBUG(3, "%s", line);
	    ++lineno;
	    print = Oops_print(line, &text, &string, MAX_STRINGS);
	    if (print && Oops_sparc_regdump(text, &string)) {
		sparc_regdump = 1;
	    } else {
		if (options->target &&
		    strstr(options->target, "sparc") &&
		    sparc_regdump &&
		    ss_format.used) {
		    Oops_format(&ss_format, options);
		    ss_free(&ss_format);
		}
		sparc_regdump = 0;
	    }
	    if (print) {
		if (options->one_shot && lastprint == 0)
		    read_symbol_sources(options);
		puts(line);
		lastprint = lineno;
		if ((start = Oops_eip(text, &string, MAX_STRINGS, ibfd,
				      options)))
		    Oops_set_eip(start, &eip, &ss_format, me, ibfd, options);
		if (Oops_eip_alpha_spinlock(text, &string, MAX_STRINGS,
					    options)) {
		    /* Set the eip and previous user from the alpha spinlock
		     * stuck line.  YASC, this line contains both the eip and
		     * the equivalent of a trace entry.
		     */
		    Oops_set_eip(string[1], &eip, &ss_format, me, ibfd, options);
		    Oops_trace_line(line, string[2], &ss_format);
		}
		if ((start = Oops_mips_ra(text, &string, MAX_STRINGS)))
		    Oops_set_mips_ra(start, &ss_format);
		if ((start = Oops_sparc_oi7(text, &string, MAX_STRINGS)))
		    Oops_set_sparc_oi7(start, &string, &ss_format);
		if ((start = Oops_trace(text)))
		    Oops_trace_line(text, start, &ss_format);
		if ((start = Oops_code(text, &string, MAX_STRINGS))) {
		    /* In case no EIP line was seen */
		    Oops_set_default_ta(me, ibfd, options);
		    Oops_decode(start, eip, &ss_format, &string, MAX_STRINGS,
				ibfd, options);
		    Oops_format(&ss_format, options);
		    ss_free(&ss_format);
		    if (options->one_shot)
			return(0);
		}
	    }
	    /* More than 5 (arbitrary) lines which were not printed
	     * and there is some saved data, assume we missed the
	     * Code: line.
	     */
	    if (ss_format.used && lineno > lastprint+5) {
		WARNING("Code line not seen, dumping what data is available");
		Oops_format(&ss_format, options);
		ss_free(&ss_format);
		if (options->one_shot)
		    return(0);
	    }
	}
	if (ss_format.used) {
	    if (options->target &&
		strstr(options->target, "sparc") == 0 &&
		sparc_regdump)
		;	/* sparc regdump comes *after* code, ignore :( */
	    else
		WARNING("Code line not seen, dumping what data is available");
	    Oops_format(&ss_format, options);
	    ss_free(&ss_format);
	    if (options->one_shot)
		return(0);
	}
    } while (options->filecount != 0);

#ifdef TIME_RE
    for (i = 0; i < sizeof(diff)/sizeof(diff[0]); ++i)
	printf("diff %d %d %lld %lld\n", i, count[i], diff[i], count[i] ? diff[i]/count[i] : 0);
#endif

    if (ibfd && !bfd_close(ibfd)) {
	Oops_bfd_perror("close(ibfd)");
	return(0);
    }

    for (i = 0; i < sizeof(string); ++i) {
	free(string[i]);
	string[i] = NULL;
    }
    free(line);
    if (options->one_shot)
	return(3);  /* one shot mode, end of input, no data */
    return(0);
}
