/* Copyright (C) 2007-2011 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
*/

/*
 * Contains implementation of a class DumpFile of routines that implements
 * access to a log file.
 */

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "regex/regex.h"

#include "ndk-stack-parser.h"

/* Enumerates states of the crash parser.
 */
typedef enum NDK_CRASH_PARSER_STATE {
  /* Parser expects the beginning of the crash dump. */
  EXPECTS_CRASH_DUMP,
  /* Parser expects the build fingerprint, or process and thread information. */
  EXPECTS_BUILD_FINGREPRINT_OR_PID,
  /* Parser expects the process and thread information. */
  EXPECTS_PID,
  /* Parser expects the signal information, or the first crash frame. */
  EXPECTS_SIGNAL_OR_FRAME,
  /* Parser expects a crash frame. */
  EXPECTS_FRAME,
} NDK_CRASH_PARSER_STATE;

/* Crash parser descriptor.
 */
struct NdkCrashParser {
  /* Handle to the stream where to print the output. */
  FILE*                 out_handle;

  /* Path to the root folder where symbols are stored. */
  char*                 sym_root;

  /* Current state of the parser. */
  NDK_CRASH_PARSER_STATE state;

  /* Compiled regular expressions */
  regex_t     re_pid_header;
  regex_t     re_sig_header;
  regex_t     re_frame_header;
};

/* Crash dumps begin with a string containing this substring. */
static const char _crash_dump_header[] =
  "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***";

/* Build fingerprint contains this substring. */
static const char _build_fingerprint_header[] = "Build fingerprint:";

/* Regular expression for the process ID information line. */
static const char _pid_header[] = "pid: [0-9]+, tid: [0-9]+.*";

/* Regular expression for the signal information line. */
static const char _sig_header[] = "signal*[ \t][0-9]+";

/* Regular expression for the frame information line. */
static const char _frame_header[] = "\\#[0-9]+[ |\t]+[pc|eip]+:*[ |\t]+([0-9a-f]{8})*";

#ifndef min
#define min(a,b) (((a) < (b)) ? a : b)
#endif

/* Parses a line representing a crash frame.
 * This routine will try to obtain source file / line information for the
 * frame's address, and print that information to the specified output handle.
 * Param:
 *  parser - NdkCrashParser descriptor, created and initialized with a call to
 *    NdkCrashParser routine.
 *  frame - Line containing crash frame.
 * Return:
 *  0 If source file information has been found and printed, or -1 if that
 *  information was not available.
 */
static int ParseFrame(NdkCrashParser* parser, const char* frame);

/* Matches a string against a regular expression.
 * Param:
 *  line - String to matches against the regular expression.
 *  regex - Regular expression to match the string against.
 *  match - Upon successful match contains information about the part of the
 *    string that matches the regular expression.
 * Return:
 *  Boolean: 1 if a match has been found, or 0 if match has not been found in
 *  the string.
 */
static int MatchRegex(const char* line, const regex_t* regex, regmatch_t* match);

/* Returns pointer to the next separator (a space, or a tab) in the string. */
static const char* next_separator(const char* str);

/* Returns pointer to the next token (a character other than space, or a tab)
 * in the string.
 */
static const char* next_token(const char* str);

/* Gets next token from the string.
 * param:
 *  str - String where to get the next token from. Note that if string begins
 *    with a separator, this routine will return first token after that
 *    separator. If string begins with a token, this routine will return next
 *    token after that.
 *  token - Upon success contains a copy of the next token in the string.
 *  size - Size of the 'token' buffer.
 * Return:
 *  Beginning of the returned token in the string.
 */
static const char* get_next_token(const char* str, char* token, size_t size);

/* Return pointer to first word "pc", "eip", or "ip" in string "frame"
 * param:
 *  frame - a line from dump
 * Return:
 *  The first occurrence of "pc", "eip", or "ip"
 */
static const char* find_pc(const char *frame);

NdkCrashParser*
CreateNdkCrashParser(FILE* out_handle, const char* sym_root)
{
  NdkCrashParser* parser;

  parser = (NdkCrashParser*)calloc(sizeof(*parser), 1);
  if (parser == NULL)
      return NULL;

  parser->out_handle = out_handle;
  parser->state      = EXPECTS_CRASH_DUMP;

  parser->sym_root = strdup(sym_root);
  if (!parser->sym_root)
      goto BAD_INIT;

  if (regcomp(&parser->re_pid_header, _pid_header, REG_EXTENDED | REG_NEWLINE) ||
      regcomp(&parser->re_sig_header, _sig_header, REG_EXTENDED | REG_NEWLINE) ||
      regcomp(&parser->re_frame_header, _frame_header, REG_EXTENDED | REG_NEWLINE))
      goto BAD_INIT;

  return parser;

BAD_INIT:
  DestroyNdkCrashParser(parser);
  return NULL;
}

void
DestroyNdkCrashParser(NdkCrashParser* parser)
{
  if (parser != NULL) {
    /* Release compiled regular expressions */
    regfree(&parser->re_frame_header);
    regfree(&parser->re_sig_header);
    regfree(&parser->re_pid_header);
    /* Release symbol path */
    free(parser->sym_root);
    /* Release parser itself */
    free(parser);
  }
}

int
ParseLine(NdkCrashParser* parser, const char* line)
{
  regmatch_t match;
  int found = 0;

  if (line == NULL || *line == '\0') {
    // Nothing to parse.
    return 1;
  }

  // Lets see if this is the beginning of a crash dump.
  if (strstr(line, _crash_dump_header) != NULL) {
    if (parser->state != EXPECTS_CRASH_DUMP) {
      // Printing another crash dump was in progress. Mark the end of it.
      fprintf(parser->out_handle, "Crash dump is completed\n\n");
    }

    // New crash dump begins.
    fprintf(parser->out_handle, "********** Crash dump: **********\n");
    parser->state = EXPECTS_BUILD_FINGREPRINT_OR_PID;

    return 0;
  }

  switch (parser->state) {
    case EXPECTS_BUILD_FINGREPRINT_OR_PID:
      if (strstr(line, _build_fingerprint_header) != NULL) {
        fprintf(parser->out_handle, "%s\n",
                strstr(line, _build_fingerprint_header));
        parser->state = EXPECTS_PID;
        found = 1;
      }
      // Let it fall through to the EXPECTS_PID, in case the dump doesn't
      // contain build fingerprint.
    case EXPECTS_PID:
      if (MatchRegex(line, &parser->re_pid_header, &match)) {
        fprintf(parser->out_handle, "%s\n", line + match.rm_so);
        parser->state = EXPECTS_SIGNAL_OR_FRAME;
        return 0;
      } else {
        return !found;
      }

    case EXPECTS_SIGNAL_OR_FRAME:
      if (MatchRegex(line, &parser->re_sig_header, &match)) {
        fprintf(parser->out_handle, "%s\n", line + match.rm_so);
        parser->state = EXPECTS_FRAME;
        found = 1;
      }
      // Let it fall through to the EXPECTS_FRAME, in case the dump doesn't
      // contain signal fingerprint.
    case EXPECTS_FRAME:
      if (!MatchRegex(line, &parser->re_frame_header, &match))
        return !found;
      // Regex generated by x86_64-w64-mingw32 compiler erroneously match
      // frame line with #[0-9]+ in "stack:" section even when the line has
      //  no word "pc", "eip", or "ip" in it.
      //
      //   stack:
      //      I/DEBUG   ( 1151):     #00  5f09db68  401f01c4  /system/lib/libc.so
      //
      // To workaround, let's double check if pc is found!
      //
      if (!(find_pc(line)))
        return !found;

      parser->state = EXPECTS_FRAME;
      return ParseFrame(parser, line + match.rm_so);

    default:
      return 1;
  }
}

static int
MatchRegex(const char* line, const regex_t* regex, regmatch_t* match)
{
  int err = regexec(regex, line, 1, match, 0x00400/*REG_TRACE*/);
#if 0
  char rerr[4096];
  if (err) {
    regerror(err, regex, rerr, sizeof(rerr));
    fprintf(stderr, "regexec(%s, %s) has failed: %s\n", line, regex, rerr);
  }
#endif

  return err == 0;
}

static const char*
next_separator(const char* str)
{
  return str + strcspn(str, " \t");
}

static const char*
next_token(const char* str)
{
  str = next_separator(str);
  return str + strspn(str, " \t");
}

static const char*
get_next_token(const char* str, char* token, size_t size)
{
  const char* start = next_token(str);
  const char* end = next_separator(start);
  if (start != end) {
    const size_t to_copy = min((size_t)(end - start), (size - 1));
    memcpy(token, start, to_copy);
    token[to_copy] = '\0';
    return start;
  } else {
    return NULL;
  }
}

static const char *
find_pc(const char *frame)
{
  const char *pcstrs[] = { "pc", "eip", "ip" };
  int i;
  for (i=0; i<sizeof(pcstrs)/sizeof(pcstrs[0]); i++) {
    const char *p = strstr(frame, pcstrs[i]);
    // check it's a word, not part of filename or something
    if (p && p!=frame) {
      char l = p[-1];
      char r = p[strlen(pcstrs[i])];
      if ((l==' ' || l=='\t') && (r==' ' || r=='\t'))
        return p;
    }
  }
  return NULL;
}

int
ParseFrame(NdkCrashParser* parser, const char* frame)
{
  uint64_t address;
  const char* wrk;
  char* eptr;
  char pc_address[17];
  char module_path[2048];
  char* module_name;
  char sym_file[2048];
  const int ac = 5;
  char *av[ac];
  FILE *f;

  fprintf(parser->out_handle, "Stack frame %s", frame);

  // Advance to the instruction pointer token.
  if ((wrk=find_pc(frame)) == NULL) {
    fprintf(parser->out_handle,
            "Parser is unable to locate instruction pointer token.\n");
    return -1;
  }

  // Next token after the instruction pointer token is its address.
  wrk = get_next_token(wrk, pc_address, sizeof(pc_address));
  // PC address is a hex value. Get it.
  eptr = pc_address + strlen(pc_address);
  address = strtoul(pc_address, &eptr, 16);

  // Next token is module path.
  get_next_token(wrk, module_path, sizeof(module_path));

  // Extract basename of module, we should not care about its path
  // on the device.
  module_name = strrchr(module_path,'/');
  if (module_name == NULL)
      module_name = module_path;
  else {
      module_name += 1;
      if (*module_name == '\0') {
          /* Trailing slash in the module path, this should not happen */
          /* Back-off with the full module-path */
          module_name = module_path;
      }
  }

  // Build path to the symbol file.
  snprintf(sym_file, sizeof(sym_file), "%s/%s", parser->sym_root, module_name);

  if ((f=fopen(sym_file, "r")) == NULL) {
    if (errno == ENOENT) {
        printf("\n");
    } else {
        printf(": Unable to open symbol file %s. Error (%d): %s\n",
                sym_file, errno, strerror(errno));
    }
    return -1;
  }

  // call addr2line if sym_file exist
  extern int addr2line_main (int argc, char **argv);

  av[0] = "ndk-stack";
  av[1] = "-fpC";  // f:function, p:pretty-print, C:demangle
  av[2] = "-e";    // e:exe-filename
  av[3] = sym_file;
  av[4] = pc_address;
  (void)address;

  printf(": Routine ");
  return addr2line_main(ac, av);
}