#!/usr/bin/ruby

#/* LICENSE:
#  =========================================================================
#    CMPack'04 Source Code Release for OPEN-R SDK 1.1.5-r2 for ERS7
#    Copyright (C) 2004 Multirobot Lab [Project Head: Manuela Veloso]
#    School of Computer Science, Carnegie Mellon University
#    All rights reserved.
#  ========================================================================= */
# portions Copyright (C) 2002 Sony corporation

######################################################################
# emonParser: Shows information about the CPU exception.
#
#    Permission to use, copy, modify, and redistribute this software
#    for non-commercial use is hereby granted.
#
#    This software is provided "as is" without warranty of any kind,
#    either expressed or implied, including but not limited to the
#    implied warranties of fitness for a particular purpose.
#
# Installation:
#
#    Before running this program, add /usr/local/OPEN_R_SDK/bin
#    directory to your PATH.
#
# Usage:
#
#    emonLogParser EMON_LOG
#
#       Shows the name of the object that caused the CPU exception.
#
#         EMON_LOG: The path of the file named EMON.LOG created when
#                   the CPU exception ocurred.
#
#    emonLogParser EMON_LOG NOSNAP_ELF
#
#       Shows the symbol corresponding to the value of epc.  If
#       epc==badvaddr, the value of ra is used instead.
#
#         EMON_LOG: The path of the file named EMON.LOG created when
#                   the CPU exception ocurred.
#
#         NOSNAP_ELF: The path of the file with the suffix
#                     ".nosnap.elf" created when the object that
#                     caused the CPU exception was built.
#
#    emonLogParser EMON_LOG NOSNAP_ELF ADDR
#
#       Shows the symbol corresponding to ADDR.
#
#         EMON_LOG: The path of the file named EMON.LOG created when
#                   the CPU exception ocurred.
#
#         NOSNAP_ELF: The path of the file with the suffix
#                     ".nosnap.elf" created when the object that
#                     caused the CPU exception was built.
#
#         ADDR: The target address.  e.g. 0x1234ab00
#

$SHexRe = "([0-9a-fA-F]+)";
$LHexRe = "0x#{$SHexRe}";
$WordSize = 4; # word size in bytes

######################################################################
# subroutines
#
def usage()
  print $stderr,"usage: emonParser EMON_LOG [NOSNAP_ELF_FILE [ADDR]]\n"
end

def readHex(str,re)
  if(str !~ Regexp.new(re + $LHexRe))
    print "str='#{str}'\n"
    print "re='#{re}'\n"
    raise "INTERNAL ERROR: readHex failed: '#{re}'\n"
  end
  $1
end

def run(command)
  ans = `#{command}`
end

def find_function_name(addr,static_offset,nosnap_file)
  name = nil

  # Calculate the static address of the target
  staticAddr = addr + static_offset;
  printf("finding name for static addr:\t%8x\n",staticAddr);
  
  # Find related function name
  command = "mipsel-linux-nm -C #{nosnap_file} | sort";
  syms = IO.popen(command)
  
  prev=nil;
  while (line=syms.gets())
    if (line =~ /^([0-9a-f]+)/)
      current = "0x#{$1}".hex
      if(staticAddr < current)
        name = prev
        break
      end
    end
    prev = line
  end
  syms.gets(nil)
  syms.close()

  name
end

######################################################################
# main
#
if (ARGV.size < 1 || ARGV.size > 3)
  usage();
  exit(1);
end

emonLogFilename, nosnapFile, target = ARGV;

emonLogFile = File.open(emonLogFilename,"r")
emonLogArr = []
while(line=emonLogFile.gets())
  emonLogArr.push(line)
end
emonLog = emonLogArr.join('')
emonLogFile.close()

# Read in context number of offending object
context = readHex(emonLog,"\n context: ")

print "context:\t#{context}\n";

# Find name of offending object
objName = nil
ela_idx=0
while(ela_idx < emonLogArr.size)
  line = emonLogArr[ela_idx]
  case line
  when Regexp.new("^(\\S+)\s*0x#{context} #{$LHexRe} #{$LHexRe} ")
    objName = $1
    break
  end
  ela_idx+=1;
end
if(objName.nil?)
  raise "ERROR: can't find the context: #{context}\n"
end
print "object:\t\t#{objName}\n";

# Print out general reason for crash
ela_idx=0
while(ela_idx < emonLogArr.size)
  line = emonLogArr[ela_idx]
  case line
  when /exception code/
    print line;
    break
  end
  ela_idx+=1
end

if(nosnapFile.nil?)
  exit(0)
end

# Read in virtual address involved, if any
badvaddr= readHex(emonLog, "badvaddr: \\$8: ");
print "badvaddr:\t#{badvaddr}\n";

# Read in program counter involved
epc = readHex(emonLog, "epc:\\$14: ");
print "epc:\t\t#{epc}\n";

ra = readHex(emonLog, "ra:r31: ");
print "ra:\t\t#{ra}\n";

if(!target.nil?)
  target.sub!(/^0x/,"")
  print "target address:\t#{target} (command line argument)\n"
elsif (epc == badvaddr)
  target = ra;
  print "target address:\t#{target} (ra)\n"
else
  target = epc;
  print "target address:\t#{target} (epc)\n";
end
target = target.hex

# Read in pointer to global variable section
gpReg = readHex(emonLog, "gp:r28: ");
print "gp:\t\t#{gpReg}\n";

# Grab address of global variable section in elf file
gpGrep = run("mipsel-linux-readelf -s #{nosnapFile} | grep '_gp$'");
if(gpGrep !~ Regexp.new("^\\s*\\S+:\\s*#{$SHexRe}\\s+.*_gp"))
  raise "INTERNAL ERROR: unexpected $gpGrep: #{gpGrep}\n";
end
gpSym = $1;
print "_gp:\t\t#{gpSym}\n";

static_offset =  -gpReg.hex + gpSym.hex;

# Calculate the static address of the target
staticAddr = target + static_offset;
printf("static addr:\t%08x\n",staticAddr);

# Find related function name
printf "symbol:\t\t%s",find_function_name(target,static_offset,nosnapFile)

### Grab stack data

stackFrame = []
stackFrameOffset = nil

state = 0
emonLogArr.each {|log_line|
  case state
  when 0
    case log_line
    when /\[stack dump\]/
      state = 1
    end
  when 1
    case log_line
    when Regexp.new($SHexRe + ":\\s*" + (($SHexRe + "\\s*")*4))
      add = $1.hex
      if(stackFrameOffset.nil?)
        stackFrameOffset = add
      end
      stackFrame[(add - stackFrameOffset)/$WordSize + 0] = $2.hex
      stackFrame[(add - stackFrameOffset)/$WordSize + 1] = $3.hex
      stackFrame[(add - stackFrameOffset)/$WordSize + 2] = $4.hex
      stackFrame[(add - stackFrameOffset)/$WordSize + 3] = $5.hex
    end
  end
}

#stackFrame.each_index {|idx|
#  word = stackFrame[idx]
#  printf "%08x: %08x\n",stackFrameOffset+idx*$WordSize,stackFrame[idx]
#}

objdumpfile = IO.popen("mipsel-linux-objdump -S -C #{nosnapFile}")
objdump = objdumpfile.gets(nil)
objdumpfile.close()
objdump = objdump.split('\n')

spReg = readHex(emonLog,"sp:r29: ").hex
printf "spReg:\t\t%08x\n",spReg

raReg = readHex(emonLog,"ra:r31: ").hex
printf "raReg:\t\t%08x\n",raReg

at_top_of_stack = false
stack_level = 0
cur_epc = target
cur_stack_offset = (spReg - stackFrameOffset)/$WordSize
while(!at_top_of_stack)
  printf "stack level %2d: %s\n",stack_level,find_function_name(cur_epc,static_offset,nosnapFile)

  # find top of function
  top_of_func_idx = nil
  top_of_func_addr = nil
  od_idx = 0
  cur_epc_re = Regexp.new("^\s+"+sprintf("%x",cur_epc + static_offset))
  print "finding top of function\n"
  while(od_idx < objdump.size)
    case objdump[od_idx]
    when Regexp.new("^"+$SHexRe+"\s+")
      top_of_func_addr = $1.hex
      top_of_func_idx = od_idx
    when cur_epc_re
      break
    end
    od_idx += 1
  end
  printf "  found at: %08x (idx %d)\n",top_of_func_addr,top_of_func_idx

  # find return address by finding load of ra
  print "finding return address\n"
  od_idx = top_of_func_idx+1
  ra_offset = nil
  while(od_idx < objdump.size)
    case objdump[od_idx]
    when /\s+lw\s+ra,([-0-9]+)\(sp\)/
      # look for stack offset of return address
      ra_offset = $1.to_i
      break
    when Regexp.new("^"+$SHexRe+"\s+")
      # stop if hit next function
      break
    end
    od_idx += 1
  end
  ra = nil
  if(ra_offset.nil?)
    if(stack_level == 0)
      ra = raReg
    else
      raise "Couldn't find return address\n"
    end
  else
    printf "ra_offset:\t%d (0x%08x)\n",ra_offset,ra_offset
    ra = stackFrame[cur_stack_offset + ra_offset/$WordSize]
  end
  printf "ra:\t%08x (%x)\n",ra,ra+static_offset

  # find change to stack offset
  od_idx = top_of_func_idx+1
  stack_add = nil
  while(od_idx < objdump.size)
    case objdump[od_idx]
    when /\s+addiu\s+sp,sp,([0-9]+)/
      stack_add = $1.to_i
      break
    when Regexp.new("^"+$SHexRe+"\s+")
      # stop if hit next function
      break
    end
    od_idx += 1
  end
  if(stack_add.nil?)
    printf "Warning:Couldn't find change to stack offset, assuming 0\n"
    stack_add = 0
  else
    printf "stack_add:\t%d (0x%08x)\n",stack_add,stack_add
  end

  cur_epc = ra
  cur_stack_offset += stack_add/$WordSize

  stack_level += 1

  at_top_of_stack = false;
end
