| Index: tools/grokdump.py
 | 
| diff --git a/tools/grokdump.py b/tools/grokdump.py
 | 
| index a5a2ae08a879b11447352a262e02222da4e7933b..8178b2f0cfef2c032208002d2ebd74eb48c587d5 100755
 | 
| --- a/tools/grokdump.py
 | 
| +++ b/tools/grokdump.py
 | 
| @@ -27,7 +27,9 @@
 | 
|  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | 
|  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
|  
 | 
| +import BaseHTTPServer
 | 
|  import bisect
 | 
| +import cgi
 | 
|  import cmd
 | 
|  import codecs
 | 
|  import ctypes
 | 
| @@ -37,10 +39,15 @@ import mmap
 | 
|  import optparse
 | 
|  import os
 | 
|  import re
 | 
| -import struct
 | 
|  import sys
 | 
|  import types
 | 
| +import urllib
 | 
| +import urlparse
 | 
|  import v8heapconst
 | 
| +import webbrowser
 | 
| +
 | 
| +PORT_NUMBER = 8081
 | 
| +
 | 
|  
 | 
|  USAGE="""usage: %prog [OPTIONS] [DUMP-FILE]
 | 
|  
 | 
| @@ -701,6 +708,20 @@ class MinidumpReader(object):
 | 
|                              reader.FormatIntPtr(word))
 | 
|      self.ForEachMemoryRegion(search_inside_region)
 | 
|  
 | 
| +  def FindWordList(self, word):
 | 
| +    aligned_res = []
 | 
| +    unaligned_res = []
 | 
| +    def search_inside_region(reader, start, size, location):
 | 
| +      for loc in xrange(location, location + size - self.PointerSize()):
 | 
| +        if reader._ReadWord(loc) == word:
 | 
| +          slot = start + (loc - location)
 | 
| +          if slot % self.PointerSize() == 0:
 | 
| +            aligned_res.append(slot)
 | 
| +          else:
 | 
| +            unaligned_res.append(slot)
 | 
| +    self.ForEachMemoryRegion(search_inside_region)
 | 
| +    return (aligned_res, unaligned_res)
 | 
| +
 | 
|    def FindLocation(self, address):
 | 
|      offset = 0
 | 
|      if self.memory_list64 is not None:
 | 
| @@ -930,8 +951,9 @@ class HeapObject(object):
 | 
|  
 | 
|    def SmiField(self, offset):
 | 
|      field_value = self.heap.reader.ReadUIntPtr(self.address + offset)
 | 
| -    assert (field_value & 1) == 0
 | 
| -    return field_value / 2
 | 
| +    if (field_value & 1) == 0:
 | 
| +      return field_value / 2
 | 
| +    return None
 | 
|  
 | 
|  
 | 
|  class Map(HeapObject):
 | 
| @@ -1348,7 +1370,9 @@ class JSFunction(HeapObject):
 | 
|      if not self.shared.script.Is(Script): return source
 | 
|      script_source = self.shared.script.source
 | 
|      if not script_source.Is(String): return source
 | 
| -    return script_source.GetChars()[start:end]
 | 
| +    if start and end:
 | 
| +      source = script_source.GetChars()[start:end]
 | 
| +    return source
 | 
|  
 | 
|  
 | 
|  class SharedFunctionInfo(HeapObject):
 | 
| @@ -1382,7 +1406,10 @@ class SharedFunctionInfo(HeapObject):
 | 
|      else:
 | 
|        start_position_and_type = \
 | 
|            self.SmiField(self.StartPositionAndTypeOffset())
 | 
| -      self.start_position = start_position_and_type >> 2
 | 
| +      if start_position_and_type:
 | 
| +        self.start_position = start_position_and_type >> 2
 | 
| +      else:
 | 
| +        self.start_position = None
 | 
|        self.end_position = \
 | 
|            self.SmiField(self.EndPositionOffset())
 | 
|  
 | 
| @@ -1561,6 +1588,79 @@ class KnownMap(HeapObject):
 | 
|      return "<%s>" % self.known_name
 | 
|  
 | 
|  
 | 
| +COMMENT_RE = re.compile(r"^C (0x[0-9a-fA-F]+) (.*)$")
 | 
| +PAGEADDRESS_RE = re.compile(
 | 
| +    r"^P (mappage|pointerpage|datapage) (0x[0-9a-fA-F]+)$")
 | 
| +
 | 
| +
 | 
| +class InspectionInfo(object):
 | 
| +  def __init__(self, minidump_name, reader):
 | 
| +    self.comment_file = minidump_name + ".comments"
 | 
| +    self.address_comments = {}
 | 
| +    self.page_address = {}
 | 
| +    if os.path.exists(self.comment_file):
 | 
| +      with open(self.comment_file, "r") as f:
 | 
| +        lines = f.readlines()
 | 
| +        f.close()
 | 
| +
 | 
| +        for l in lines:
 | 
| +          m = COMMENT_RE.match(l)
 | 
| +          if m:
 | 
| +            self.address_comments[int(m.group(1), 0)] = m.group(2)
 | 
| +          m = PAGEADDRESS_RE.match(l)
 | 
| +          if m:
 | 
| +            self.page_address[m.group(1)] = int(m.group(2), 0)
 | 
| +    self.reader = reader
 | 
| +    self.styles = {}
 | 
| +    self.color_addresses()
 | 
| +    return
 | 
| +
 | 
| +  def get_page_address(self, page_kind):
 | 
| +    return self.page_address.get(page_kind, 0)
 | 
| +
 | 
| +  def save_page_address(self, page_kind, address):
 | 
| +    with open(self.comment_file, "a") as f:
 | 
| +      f.write("P %s 0x%x\n" % (page_kind, address))
 | 
| +      f.close()
 | 
| +
 | 
| +  def color_addresses(self):
 | 
| +    # Color all stack addresses.
 | 
| +    exception_thread = self.reader.thread_map[self.reader.exception.thread_id]
 | 
| +    stack_top = self.reader.ExceptionSP()
 | 
| +    stack_bottom = exception_thread.stack.start + \
 | 
| +        exception_thread.stack.memory.data_size
 | 
| +    frame_pointer = self.reader.ExceptionFP()
 | 
| +    self.styles[frame_pointer] = "frame"
 | 
| +    for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()):
 | 
| +      self.styles[slot] = "stackaddress"
 | 
| +    for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()):
 | 
| +      maybe_address = self.reader.ReadUIntPtr(slot)
 | 
| +      self.styles[maybe_address] = "stackval"
 | 
| +      if slot == frame_pointer:
 | 
| +        self.styles[slot] = "frame"
 | 
| +        frame_pointer = maybe_address
 | 
| +    self.styles[self.reader.ExceptionIP()] = "pc"
 | 
| +
 | 
| +  def get_style_class(self, address):
 | 
| +    return self.styles.get(address, None)
 | 
| +
 | 
| +  def get_style_class_string(self, address):
 | 
| +    style = self.get_style_class(address)
 | 
| +    if style != None:
 | 
| +      return " class=\"%s\" " % style
 | 
| +    else:
 | 
| +      return ""
 | 
| +
 | 
| +  def set_comment(self, address, comment):
 | 
| +    self.address_comments[address] = comment
 | 
| +    with open(self.comment_file, "a") as f:
 | 
| +      f.write("C 0x%x %s\n" % (address, comment))
 | 
| +      f.close()
 | 
| +
 | 
| +  def get_comment(self, address):
 | 
| +    return self.address_comments.get(address, "")
 | 
| +
 | 
| +
 | 
|  class InspectionPadawan(object):
 | 
|    """The padawan can improve annotations by sensing well-known objects."""
 | 
|    def __init__(self, reader, heap):
 | 
| @@ -1653,6 +1753,1033 @@ class InspectionPadawan(object):
 | 
|            self.reader.FormatIntPtr(self.known_first_data_page),
 | 
|            self.reader.FormatIntPtr(self.known_first_pointer_page))
 | 
|  
 | 
| +WEB_HEADER = """
 | 
| +<!DOCTYPE html>
 | 
| +<html>
 | 
| +<head>
 | 
| +<meta content="text/html; charset=utf-8" http-equiv="content-type">
 | 
| +<style media="screen" type="text/css">
 | 
| +
 | 
| +.code {
 | 
| +  font-family: monospace;
 | 
| +}
 | 
| +
 | 
| +.dmptable {
 | 
| +  border-collapse : collapse;
 | 
| +  border-spacing : 0px;
 | 
| +}
 | 
| +
 | 
| +.codedump {
 | 
| +  border-collapse : collapse;
 | 
| +  border-spacing : 0px;
 | 
| +}
 | 
| +
 | 
| +.addrcomments {
 | 
| +  border : 0px;
 | 
| +}
 | 
| +
 | 
| +.register {
 | 
| +  padding-right : 1em;
 | 
| +}
 | 
| +
 | 
| +.header {
 | 
| +  clear : both;
 | 
| +}
 | 
| +
 | 
| +.header .navigation {
 | 
| +  float : left;
 | 
| +}
 | 
| +
 | 
| +.header .dumpname {
 | 
| +  float : right;
 | 
| +}
 | 
| +
 | 
| +tr.highlight-line {
 | 
| +  background-color : yellow;
 | 
| +}
 | 
| +
 | 
| +.highlight {
 | 
| +  background-color : magenta;
 | 
| +}
 | 
| +
 | 
| +tr.inexact-highlight-line {
 | 
| +  background-color : pink;
 | 
| +}
 | 
| +
 | 
| +input {
 | 
| +  background-color: inherit;
 | 
| +  border: 1px solid LightGray;
 | 
| +}
 | 
| +
 | 
| +.dumpcomments {
 | 
| +  border : 1px solid LightGray;
 | 
| +  width : 32em;
 | 
| +}
 | 
| +
 | 
| +.regions td {
 | 
| +  padding:0 15px 0 15px;
 | 
| +}
 | 
| +
 | 
| +.stackframe td {
 | 
| +  background-color : cyan;
 | 
| +}
 | 
| +
 | 
| +.stackaddress {
 | 
| +  background-color : LightGray;
 | 
| +}
 | 
| +
 | 
| +.stackval {
 | 
| +  background-color : LightCyan;
 | 
| +}
 | 
| +
 | 
| +.frame {
 | 
| +  background-color : cyan;
 | 
| +}
 | 
| +
 | 
| +.commentinput {
 | 
| +  width : 20em;
 | 
| +}
 | 
| +
 | 
| +a.nodump:visited {
 | 
| +  color : black;
 | 
| +  text-decoration : none;
 | 
| +}
 | 
| +
 | 
| +a.nodump:link {
 | 
| +  color : black;
 | 
| +  text-decoration : none;
 | 
| +}
 | 
| +
 | 
| +a:visited {
 | 
| +  color : blueviolet;
 | 
| +}
 | 
| +
 | 
| +a:link {
 | 
| +  color : blue;
 | 
| +}
 | 
| +
 | 
| +.disasmcomment {
 | 
| +  color : DarkGreen;
 | 
| +}
 | 
| +
 | 
| +</style>
 | 
| +
 | 
| +<script type="application/javascript">
 | 
| +
 | 
| +var address_str = "address-";
 | 
| +var address_len = address_str.length;
 | 
| +
 | 
| +function comment() {
 | 
| +  var s = event.srcElement.id;
 | 
| +  var index = s.indexOf(address_str);
 | 
| +  if (index >= 0) {
 | 
| +    send_comment(s.substring(index + address_len), event.srcElement.value);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +function send_comment(address, comment) {
 | 
| +  xmlhttp = new XMLHttpRequest();
 | 
| +  address = encodeURIComponent(address)
 | 
| +  comment = encodeURIComponent(comment)
 | 
| +  xmlhttp.open("GET",
 | 
| +      "setcomment?%(query_dump)s&address=" + address +
 | 
| +      "&comment=" + comment, true);
 | 
| +  xmlhttp.send();
 | 
| +}
 | 
| +
 | 
| +var dump_str = "dump-";
 | 
| +var dump_len = dump_str.length;
 | 
| +
 | 
| +function dump_comment() {
 | 
| +  var s = event.srcElement.id;
 | 
| +  var index = s.indexOf(dump_str);
 | 
| +  if (index >= 0) {
 | 
| +    send_dump_desc(s.substring(index + dump_len), event.srcElement.value);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +function send_dump_desc(name, desc) {
 | 
| +  xmlhttp = new XMLHttpRequest();
 | 
| +  name = encodeURIComponent(name)
 | 
| +  desc = encodeURIComponent(desc)
 | 
| +  xmlhttp.open("GET",
 | 
| +      "setdumpdesc?dump=" + name +
 | 
| +      "&description=" + desc, true);
 | 
| +  xmlhttp.send();
 | 
| +}
 | 
| +
 | 
| +function onpage(kind, address) {
 | 
| +  xmlhttp = new XMLHttpRequest();
 | 
| +  kind = encodeURIComponent(kind)
 | 
| +  address = encodeURIComponent(address)
 | 
| +  xmlhttp.onreadystatechange = function() {
 | 
| +    if (xmlhttp.readyState==4 && xmlhttp.status==200) {
 | 
| +      location.reload(true)
 | 
| +    }
 | 
| +  };
 | 
| +  xmlhttp.open("GET",
 | 
| +      "setpageaddress?%(query_dump)s&kind=" + kind +
 | 
| +      "&address=" + address);
 | 
| +  xmlhttp.send();
 | 
| +}
 | 
| +
 | 
| +</script>
 | 
| +
 | 
| +<title>Dump %(dump_name)s</title>
 | 
| +</head>
 | 
| +
 | 
| +<body>
 | 
| +  <div class="header">
 | 
| +    <form class="navigation" action="search.html">
 | 
| +      <a href="summary.html?%(query_dump)s">Context info</a>   
 | 
| +      <a href="info.html?%(query_dump)s">Dump info</a>   
 | 
| +      <a href="modules.html?%(query_dump)s">Modules</a>   
 | 
| +       
 | 
| +      <input type="search" name="val">
 | 
| +      <input type="submit" name="search" value="Search">
 | 
| +      <input type="hidden" name="dump" value="%(dump_name)s">
 | 
| +    </form>
 | 
| +    <form class="navigation" action="disasm.html#highlight">
 | 
| +       
 | 
| +       
 | 
| +       
 | 
| +      <input type="search" name="val">
 | 
| +      <input type="submit" name="disasm" value="Disasm">
 | 
| +       
 | 
| +       
 | 
| +       
 | 
| +      <a href="dumps.html">Dumps...</a>
 | 
| +    </form>
 | 
| +  </div>
 | 
| +  <br>
 | 
| +  <hr>
 | 
| +"""
 | 
| +
 | 
| +
 | 
| +WEB_FOOTER = """
 | 
| +</body>
 | 
| +</html>
 | 
| +"""
 | 
| +
 | 
| +
 | 
| +class WebParameterError(Exception):
 | 
| +  def __init__(self, message):
 | 
| +    Exception.__init__(self, message)
 | 
| +
 | 
| +
 | 
| +class InspectionWebHandler(BaseHTTPServer.BaseHTTPRequestHandler):
 | 
| +  def formatter(self, query_components):
 | 
| +    name = query_components.get("dump", [None])[0]
 | 
| +    return self.server.get_dump_formatter(name)
 | 
| +
 | 
| +  def send_success_html_headers(self):
 | 
| +    self.send_response(200)
 | 
| +    self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
 | 
| +    self.send_header("Pragma", "no-cache")
 | 
| +    self.send_header("Expires", "0")
 | 
| +    self.send_header('Content-type','text/html')
 | 
| +    self.end_headers()
 | 
| +    return
 | 
| +
 | 
| +  def do_GET(self):
 | 
| +    try:
 | 
| +      parsedurl = urlparse.urlparse(self.path)
 | 
| +      query_components = urlparse.parse_qs(parsedurl.query)
 | 
| +      if parsedurl.path == "/dumps.html":
 | 
| +        self.send_success_html_headers()
 | 
| +        self.server.output_dumps(self.wfile)
 | 
| +      elif parsedurl.path == "/summary.html":
 | 
| +        self.send_success_html_headers()
 | 
| +        self.formatter(query_components).output_summary(self.wfile)
 | 
| +      elif parsedurl.path == "/info.html":
 | 
| +        self.send_success_html_headers()
 | 
| +        self.formatter(query_components).output_info(self.wfile)
 | 
| +      elif parsedurl.path == "/modules.html":
 | 
| +        self.send_success_html_headers()
 | 
| +        self.formatter(query_components).output_modules(self.wfile)
 | 
| +      elif parsedurl.path == "/search.html":
 | 
| +        address = query_components.get("val", [])
 | 
| +        if len(address) != 1:
 | 
| +          self.send_error(404, "Invalid params")
 | 
| +          return
 | 
| +        self.send_success_html_headers()
 | 
| +        self.formatter(query_components).output_search_res(
 | 
| +            self.wfile, address[0])
 | 
| +      elif parsedurl.path == "/disasm.html":
 | 
| +        address = query_components.get("val", [])
 | 
| +        exact = query_components.get("exact", ["on"])
 | 
| +        if len(address) != 1:
 | 
| +          self.send_error(404, "Invalid params")
 | 
| +          return
 | 
| +        self.send_success_html_headers()
 | 
| +        self.formatter(query_components).output_disasm(
 | 
| +            self.wfile, address[0], exact[0])
 | 
| +      elif parsedurl.path == "/data.html":
 | 
| +        address = query_components.get("val", [])
 | 
| +        datakind = query_components.get("type", ["address"])
 | 
| +        if len(address) == 1 and len(datakind) == 1:
 | 
| +          self.send_success_html_headers()
 | 
| +          self.formatter(query_components).output_data(
 | 
| +              self.wfile, address[0], datakind[0])
 | 
| +        else:
 | 
| +          self.send_error(404,'Invalid params')
 | 
| +      elif parsedurl.path == "/setdumpdesc":
 | 
| +        name = query_components.get("dump", [""])
 | 
| +        description = query_components.get("description", [""])
 | 
| +        if len(name) == 1 and len(description) == 1:
 | 
| +          name = name[0]
 | 
| +          description = description[0]
 | 
| +          if self.server.set_dump_desc(name, description):
 | 
| +            self.send_success_html_headers()
 | 
| +            self.wfile.write("OK")
 | 
| +            return
 | 
| +        self.send_error(404,'Invalid params')
 | 
| +      elif parsedurl.path == "/setcomment":
 | 
| +        address = query_components.get("address", [])
 | 
| +        comment = query_components.get("comment", [""])
 | 
| +        if len(address) == 1 and len(comment) == 1:
 | 
| +          address = address[0]
 | 
| +          comment = comment[0]
 | 
| +          self.formatter(query_components).set_comment(address, comment)
 | 
| +          self.send_success_html_headers()
 | 
| +          self.wfile.write("OK")
 | 
| +        else:
 | 
| +          self.send_error(404,'Invalid params')
 | 
| +      elif parsedurl.path == "/setpageaddress":
 | 
| +        kind = query_components.get("kind", [])
 | 
| +        address = query_components.get("address", [""])
 | 
| +        if len(kind) == 1 and len(address) == 1:
 | 
| +          kind = kind[0]
 | 
| +          address = address[0]
 | 
| +          self.formatter(query_components).set_page_address(kind, address)
 | 
| +          self.send_success_html_headers()
 | 
| +          self.wfile.write("OK")
 | 
| +        else:
 | 
| +          self.send_error(404,'Invalid params')
 | 
| +      else:
 | 
| +        self.send_error(404,'File Not Found: %s' % self.path)
 | 
| +
 | 
| +    except IOError:
 | 
| +      self.send_error(404,'File Not Found: %s' % self.path)
 | 
| +
 | 
| +    except WebParameterError as e:
 | 
| +      self.send_error(404, 'Web parameter error: %s' % e.message)
 | 
| +
 | 
| +
 | 
| +HTML_REG_FORMAT = "<span class=\"register\"><b>%s</b>: %s</span>\n"
 | 
| +
 | 
| +
 | 
| +class InspectionWebFormatter(object):
 | 
| +  CONTEXT_FULL = 0
 | 
| +  CONTEXT_SHORT = 1
 | 
| +
 | 
| +  def __init__(self, switches, minidump_name, http_server):
 | 
| +    self.dumpfilename = os.path.split(minidump_name)[1]
 | 
| +    self.encfilename = urllib.urlencode({ 'dump' : self.dumpfilename })
 | 
| +    self.reader = MinidumpReader(switches, minidump_name)
 | 
| +    self.server = http_server
 | 
| +
 | 
| +    # Set up the heap
 | 
| +    exception_thread = self.reader.thread_map[self.reader.exception.thread_id]
 | 
| +    stack_top = self.reader.ExceptionSP()
 | 
| +    stack_bottom = exception_thread.stack.start + \
 | 
| +        exception_thread.stack.memory.data_size
 | 
| +    stack_map = {self.reader.ExceptionIP(): -1}
 | 
| +    for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()):
 | 
| +      maybe_address = self.reader.ReadUIntPtr(slot)
 | 
| +      if not maybe_address in stack_map:
 | 
| +        stack_map[maybe_address] = slot
 | 
| +    self.heap = V8Heap(self.reader, stack_map)
 | 
| +
 | 
| +    self.padawan = InspectionPadawan(self.reader, self.heap)
 | 
| +    self.comments = InspectionInfo(minidump_name, self.reader)
 | 
| +    self.padawan.known_first_data_page = (
 | 
| +        self.comments.get_page_address("datapage"))
 | 
| +    self.padawan.known_first_map_page = (
 | 
| +        self.comments.get_page_address("mappage"))
 | 
| +    self.padawan.known_first_pointer_page = (
 | 
| +        self.comments.get_page_address("pointerpage"))
 | 
| +
 | 
| +  def set_comment(self, straddress, comment):
 | 
| +    try:
 | 
| +      address = int(straddress, 0)
 | 
| +      self.comments.set_comment(address, comment)
 | 
| +    except ValueError:
 | 
| +      print "Invalid address"
 | 
| +
 | 
| +  def set_page_address(self, kind, straddress):
 | 
| +    try:
 | 
| +      address = int(straddress, 0)
 | 
| +      if kind == "datapage":
 | 
| +        self.padawan.known_first_data_page = address
 | 
| +      elif kind == "mappage":
 | 
| +        self.padawan.known_first_map_page = address
 | 
| +      elif kind == "pointerpage":
 | 
| +        self.padawan.known_first_pointer_page = address
 | 
| +      self.comments.save_page_address(kind, address)
 | 
| +    except ValueError:
 | 
| +      print "Invalid address"
 | 
| +
 | 
| +  def td_from_address(self, f, address):
 | 
| +    f.write("<td %s>" % self.comments.get_style_class_string(address))
 | 
| +
 | 
| +  def format_address(self, maybeaddress, straddress = None):
 | 
| +    if maybeaddress is None:
 | 
| +      return "not in dump"
 | 
| +    else:
 | 
| +      if straddress is None:
 | 
| +        straddress = "0x" + self.reader.FormatIntPtr(maybeaddress)
 | 
| +      style_class = ""
 | 
| +      if not self.reader.IsValidAddress(maybeaddress):
 | 
| +        style_class = " class=\"nodump\""
 | 
| +      return ("<a %s href=\"search.html?%s&val=%s\">%s</a>" %
 | 
| +              (style_class, self.encfilename, straddress, straddress))
 | 
| +
 | 
| +  def output_header(self, f):
 | 
| +    f.write(WEB_HEADER %
 | 
| +        { "query_dump" : self.encfilename,
 | 
| +          "dump_name"  : cgi.escape(self.dumpfilename) })
 | 
| +
 | 
| +  def output_footer(self, f):
 | 
| +    f.write(WEB_FOOTER)
 | 
| +
 | 
| +  MAX_CONTEXT_STACK = 4096
 | 
| +
 | 
| +  def output_summary(self, f):
 | 
| +    self.output_header(f)
 | 
| +    f.write('<div class="code">')
 | 
| +    self.output_context(f, InspectionWebFormatter.CONTEXT_SHORT)
 | 
| +    self.output_disasm_pc(f)
 | 
| +
 | 
| +    # Output stack
 | 
| +    exception_thread = self.reader.thread_map[self.reader.exception.thread_id]
 | 
| +    stack_bottom = exception_thread.stack.start + \
 | 
| +        min(exception_thread.stack.memory.data_size, self.MAX_CONTEXT_STACK)
 | 
| +    stack_top = self.reader.ExceptionSP()
 | 
| +    self.output_words(f, stack_top - 16, stack_bottom, stack_top, "Stack")
 | 
| +
 | 
| +    f.write('</div>')
 | 
| +    self.output_footer(f)
 | 
| +    return
 | 
| +
 | 
| +  def output_info(self, f):
 | 
| +    self.output_header(f)
 | 
| +    f.write("<h3>Dump info</h3>\n")
 | 
| +    f.write("Description: ")
 | 
| +    self.server.output_dump_desc_field(f, self.dumpfilename)
 | 
| +    f.write("<br>\n")
 | 
| +    f.write("Filename: ")
 | 
| +    f.write("<span class=\"code\">%s</span><br>\n" % (self.dumpfilename))
 | 
| +    dt = datetime.datetime.fromtimestamp(self.reader.header.time_date_stampt)
 | 
| +    f.write("Timestamp: %s<br>\n" % dt.strftime('%Y-%m-%d %H:%M:%S'))
 | 
| +    self.output_context(f, InspectionWebFormatter.CONTEXT_FULL)
 | 
| +    self.output_address_ranges(f)
 | 
| +    self.output_footer(f)
 | 
| +    return
 | 
| +
 | 
| +  def output_address_ranges(self, f):
 | 
| +    regions = {}
 | 
| +    def print_region(_reader, start, size, _location):
 | 
| +      regions[start] = size
 | 
| +    self.reader.ForEachMemoryRegion(print_region)
 | 
| +    f.write("<h3>Available memory regions</h3>\n")
 | 
| +    f.write('<div class="code">')
 | 
| +    f.write("<table class=\"regions\">\n")
 | 
| +    f.write("<thead><tr>")
 | 
| +    f.write("<th>Start address</th>")
 | 
| +    f.write("<th>End address</th>")
 | 
| +    f.write("<th>Number of bytes</th>")
 | 
| +    f.write("</tr></thead>\n")
 | 
| +    for start in sorted(regions):
 | 
| +      size = regions[start]
 | 
| +      f.write("<tr>")
 | 
| +      f.write("<td>%s</td>" % self.format_address(start))
 | 
| +      f.write("<td> %s</td>" % self.format_address(start + size))
 | 
| +      f.write("<td> %d</td>" % size)
 | 
| +      f.write("</tr>\n")
 | 
| +    f.write("</table>\n")
 | 
| +    f.write('</div>')
 | 
| +    return
 | 
| +
 | 
| +  def output_module_details(self, f, module):
 | 
| +    f.write("<b>%s</b>" % GetModuleName(self.reader, module))
 | 
| +    file_version = GetVersionString(module.version_info.dwFileVersionMS,
 | 
| +                                    module.version_info.dwFileVersionLS)
 | 
| +    product_version = GetVersionString(module.version_info.dwProductVersionMS,
 | 
| +                                       module.version_info.dwProductVersionLS)
 | 
| +    f.write("<br>  \n")
 | 
| +    f.write("base: %s" % self.reader.FormatIntPtr(module.base_of_image))
 | 
| +    f.write("<br>  \n")
 | 
| +    f.write("  end: %s" % self.reader.FormatIntPtr(module.base_of_image +
 | 
| +                                            module.size_of_image))
 | 
| +    f.write("<br>  \n")
 | 
| +    f.write("  file version: %s" % file_version)
 | 
| +    f.write("<br>  \n")
 | 
| +    f.write("  product version: %s" % product_version)
 | 
| +    f.write("<br>  \n")
 | 
| +    time_date_stamp = datetime.datetime.fromtimestamp(module.time_date_stamp)
 | 
| +    f.write("  timestamp: %s" % time_date_stamp)
 | 
| +    f.write("<br>\n");
 | 
| +
 | 
| +  def output_modules(self, f):
 | 
| +    self.output_header(f)
 | 
| +    f.write('<div class="code">')
 | 
| +    for module in self.reader.module_list.modules:
 | 
| +      self.output_module_details(f, module)
 | 
| +    f.write("</div>")
 | 
| +    self.output_footer(f)
 | 
| +    return
 | 
| +
 | 
| +  def output_context(self, f, details):
 | 
| +    exception_thread = self.reader.thread_map[self.reader.exception.thread_id]
 | 
| +    f.write("<h3>Exception context</h3>")
 | 
| +    f.write('<div class="code">\n')
 | 
| +    f.write("Thread id: %d" % exception_thread.id)
 | 
| +    f.write("   Exception code: %08X\n" %
 | 
| +            self.reader.exception.exception.code)
 | 
| +    if details == InspectionWebFormatter.CONTEXT_FULL:
 | 
| +      if self.reader.exception.exception.parameter_count > 0:
 | 
| +        f.write("   Exception parameters: \n")
 | 
| +        for i in xrange(0, self.reader.exception.exception.parameter_count):
 | 
| +          f.write("%08x" % self.reader.exception.exception.information[i])
 | 
| +        f.write("<br><br>\n")
 | 
| +
 | 
| +    for r in CONTEXT_FOR_ARCH[self.reader.arch]:
 | 
| +      f.write(HTML_REG_FORMAT %
 | 
| +              (r, self.format_address(self.reader.Register(r))))
 | 
| +    # TODO(vitalyr): decode eflags.
 | 
| +    if self.reader.arch == MD_CPU_ARCHITECTURE_ARM:
 | 
| +      f.write("<b>cpsr</b>: %s" % bin(self.reader.exception_context.cpsr)[2:])
 | 
| +    else:
 | 
| +      f.write("<b>eflags</b>: %s" %
 | 
| +              bin(self.reader.exception_context.eflags)[2:])
 | 
| +    f.write('</div>\n')
 | 
| +    return
 | 
| +
 | 
| +  def align_down(self, a, size):
 | 
| +    alignment_correction = a % size
 | 
| +    return a - alignment_correction
 | 
| +
 | 
| +  def align_up(self, a, size):
 | 
| +    alignment_correction = (size - 1) - ((a + size - 1) % size)
 | 
| +    return a + alignment_correction
 | 
| +
 | 
| +  def format_object(self, address):
 | 
| +    heap_object = self.padawan.SenseObject(address)
 | 
| +    return cgi.escape(str(heap_object or ""))
 | 
| +
 | 
| +  def output_data(self, f, straddress, datakind):
 | 
| +    try:
 | 
| +      self.output_header(f)
 | 
| +      address = int(straddress, 0)
 | 
| +      if not self.reader.IsValidAddress(address):
 | 
| +        f.write("<h3>Address 0x%x not found in the dump.</h3>" % address)
 | 
| +        return
 | 
| +      region = self.reader.FindRegion(address)
 | 
| +      if datakind == "address":
 | 
| +        self.output_words(f, region[0], region[0] + region[1], address, "Dump")
 | 
| +      elif datakind == "ascii":
 | 
| +        self.output_ascii(f, region[0], region[0] + region[1], address)
 | 
| +      self.output_footer(f)
 | 
| +
 | 
| +    except ValueError:
 | 
| +      f.write("<h3>Unrecognized address format \"%s\".</h3>" % straddress)
 | 
| +    return
 | 
| +
 | 
| +  def output_words(self, f, start_address, end_address,
 | 
| +                   highlight_address, desc):
 | 
| +    region = self.reader.FindRegion(highlight_address)
 | 
| +    if region is None:
 | 
| +      f.write("<h3>Address 0x%x not found in the dump.</h3>\n" %
 | 
| +              (highlight_address))
 | 
| +      return
 | 
| +    size = self.heap.PointerSize()
 | 
| +    start_address = self.align_down(start_address, size)
 | 
| +    low = self.align_down(region[0], size)
 | 
| +    high = self.align_up(region[0] + region[1], size)
 | 
| +    if start_address < low:
 | 
| +      start_address = low
 | 
| +    end_address = self.align_up(end_address, size)
 | 
| +    if end_address > high:
 | 
| +      end_address = high
 | 
| +
 | 
| +    expand = ""
 | 
| +    if start_address != low or end_address != high:
 | 
| +      expand = ("(<a href=\"data.html?%s&val=0x%x#highlight\">"
 | 
| +                " more..."
 | 
| +                " </a>)" %
 | 
| +                (self.encfilename, highlight_address))
 | 
| +
 | 
| +    f.write("<h3>%s 0x%x - 0x%x, "
 | 
| +            "highlighting <a href=\"#highlight\">0x%x</a> %s</h3>\n" %
 | 
| +            (desc, start_address, end_address, highlight_address, expand))
 | 
| +    f.write('<div class="code">')
 | 
| +    f.write("<table class=\"codedump\">\n")
 | 
| +
 | 
| +    for slot in xrange(start_address, end_address, size):
 | 
| +      heap_object = ""
 | 
| +      maybe_address = None
 | 
| +      end_region = region[0] + region[1]
 | 
| +      if slot < region[0] or slot + size > end_region:
 | 
| +        straddress = "0x"
 | 
| +        for i in xrange(end_region, slot + size):
 | 
| +          straddress += "??"
 | 
| +        for i in reversed(
 | 
| +            xrange(max(slot, region[0]), min(slot + size, end_region))):
 | 
| +          straddress += "%02x" % self.reader.ReadU8(i)
 | 
| +        for i in xrange(slot, region[0]):
 | 
| +          straddress += "??"
 | 
| +      else:
 | 
| +        maybe_address = self.reader.ReadUIntPtr(slot)
 | 
| +        straddress = self.format_address(maybe_address)
 | 
| +        if maybe_address:
 | 
| +          heap_object = self.format_object(maybe_address)
 | 
| +
 | 
| +      address_fmt = "%s </td>\n"
 | 
| +      if slot == highlight_address:
 | 
| +        f.write("<tr class=\"highlight-line\">\n")
 | 
| +        address_fmt = "<a id=\"highlight\"></a>%s </td>\n"
 | 
| +      elif slot < highlight_address and highlight_address < slot + size:
 | 
| +        f.write("<tr class=\"inexact-highlight-line\">\n")
 | 
| +        address_fmt = "<a id=\"highlight\"></a>%s </td>\n"
 | 
| +      else:
 | 
| +        f.write("<tr>\n")
 | 
| +
 | 
| +      f.write("  <td>")
 | 
| +      self.output_comment_box(f, "da-", slot)
 | 
| +      f.write("</td>\n")
 | 
| +      f.write("  ")
 | 
| +      self.td_from_address(f, slot)
 | 
| +      f.write(address_fmt % self.format_address(slot))
 | 
| +      f.write("  ")
 | 
| +      self.td_from_address(f, maybe_address)
 | 
| +      f.write(":  %s  </td>\n" % straddress)
 | 
| +      f.write("  <td>")
 | 
| +      if maybe_address != None:
 | 
| +        self.output_comment_box(
 | 
| +            f, "sv-" + self.reader.FormatIntPtr(slot), maybe_address)
 | 
| +      f.write("  </td>\n")
 | 
| +      f.write("  <td>%s</td>\n" % (heap_object or ''))
 | 
| +      f.write("</tr>\n")
 | 
| +    f.write("</table>\n")
 | 
| +    f.write("</div>")
 | 
| +    return
 | 
| +
 | 
| +  def output_ascii(self, f, start_address, end_address, highlight_address):
 | 
| +    region = self.reader.FindRegion(highlight_address)
 | 
| +    if region is None:
 | 
| +      f.write("<h3>Address %x not found in the dump.</h3>" %
 | 
| +          highlight_address)
 | 
| +      return
 | 
| +    if start_address < region[0]:
 | 
| +      start_address = region[0]
 | 
| +    if end_address > region[0] + region[1]:
 | 
| +      end_address = region[0] + region[1]
 | 
| +
 | 
| +    expand = ""
 | 
| +    if start_address != region[0] or end_address != region[0] + region[1]:
 | 
| +      link = ("data.html?%s&val=0x%x&type=ascii#highlight" %
 | 
| +              (self.encfilename, highlight_address))
 | 
| +      expand = "(<a href=\"%s\">more...</a>)" % link
 | 
| +
 | 
| +    f.write("<h3>ASCII dump 0x%x - 0x%x, highlighting 0x%x %s</h3>" %
 | 
| +            (start_address, end_address, highlight_address, expand))
 | 
| +
 | 
| +    line_width = 64
 | 
| +
 | 
| +    f.write('<div class="code">')
 | 
| +
 | 
| +    start = self.align_down(start_address, line_width)
 | 
| +
 | 
| +    for address in xrange(start, end_address):
 | 
| +      if address % 64 == 0:
 | 
| +        if address != start:
 | 
| +          f.write("<br>")
 | 
| +        f.write("0x%08x: " % address)
 | 
| +      if address < start_address:
 | 
| +        f.write(" ")
 | 
| +      else:
 | 
| +        if address == highlight_address:
 | 
| +          f.write("<span class=\"highlight\">")
 | 
| +        code = self.reader.ReadU8(address)
 | 
| +        if code < 127 and code >= 32:
 | 
| +          f.write("&#")
 | 
| +          f.write(str(code))
 | 
| +          f.write(";")
 | 
| +        else:
 | 
| +          f.write("·")
 | 
| +        if address == highlight_address:
 | 
| +          f.write("</span>")
 | 
| +    f.write("</div>")
 | 
| +    return
 | 
| +
 | 
| +  def output_disasm(self, f, straddress, strexact):
 | 
| +    try:
 | 
| +      self.output_header(f)
 | 
| +      address = int(straddress, 0)
 | 
| +      if not self.reader.IsValidAddress(address):
 | 
| +        f.write("<h3>Address 0x%x not found in the dump.</h3>" % address)
 | 
| +        return
 | 
| +      region = self.reader.FindRegion(address)
 | 
| +      self.output_disasm_range(
 | 
| +          f, region[0], region[0] + region[1], address, strexact == "on")
 | 
| +      self.output_footer(f)
 | 
| +    except ValueError:
 | 
| +      f.write("<h3>Unrecognized address format \"%s\".</h3>" % straddress)
 | 
| +    return
 | 
| +
 | 
| +  def output_disasm_range(
 | 
| +      self, f, start_address, end_address, highlight_address, exact):
 | 
| +    region = self.reader.FindRegion(highlight_address)
 | 
| +    if start_address < region[0]:
 | 
| +      start_address = region[0]
 | 
| +    if end_address > region[0] + region[1]:
 | 
| +      end_address = region[0] + region[1]
 | 
| +    count = end_address - start_address
 | 
| +    lines = self.reader.GetDisasmLines(start_address, count)
 | 
| +    found = False
 | 
| +    if exact:
 | 
| +      for line in lines:
 | 
| +        if line[0] + start_address == highlight_address:
 | 
| +          found = True
 | 
| +          break
 | 
| +      if not found:
 | 
| +        start_address = highlight_address
 | 
| +        count = end_address - start_address
 | 
| +        lines = self.reader.GetDisasmLines(highlight_address, count)
 | 
| +    expand = ""
 | 
| +    if start_address != region[0] or end_address != region[0] + region[1]:
 | 
| +      exactness = ""
 | 
| +      if exact and not found and end_address == region[0] + region[1]:
 | 
| +        exactness = "&exact=off"
 | 
| +      expand = ("(<a href=\"disasm.html?%s%s"
 | 
| +                "&val=0x%x#highlight\">more...</a>)" %
 | 
| +                (self.encfilename, exactness, highlight_address))
 | 
| +
 | 
| +    f.write("<h3>Disassembling 0x%x - 0x%x, highlighting 0x%x %s</h3>" %
 | 
| +            (start_address, end_address, highlight_address, expand))
 | 
| +    f.write('<div class="code">')
 | 
| +    f.write("<table class=\"codedump\">\n");
 | 
| +    for i in xrange(0, len(lines)):
 | 
| +      line = lines[i]
 | 
| +      next_address = count
 | 
| +      if i + 1 < len(lines):
 | 
| +        next_line = lines[i + 1]
 | 
| +        next_address = next_line[0]
 | 
| +      self.format_disasm_line(
 | 
| +          f, start_address, line, next_address, highlight_address)
 | 
| +    f.write("</table>\n")
 | 
| +    f.write("</div>")
 | 
| +    return
 | 
| +
 | 
| +  def annotate_disasm_addresses(self, line):
 | 
| +    extra = []
 | 
| +    for m in ADDRESS_RE.finditer(line):
 | 
| +      maybe_address = int(m.group(0), 16)
 | 
| +      formatted_address = self.format_address(maybe_address, m.group(0))
 | 
| +      line = line.replace(m.group(0), formatted_address)
 | 
| +      object_info = self.padawan.SenseObject(maybe_address)
 | 
| +      if not object_info:
 | 
| +        continue
 | 
| +      extra.append(cgi.escape(str(object_info)))
 | 
| +    if len(extra) == 0:
 | 
| +      return line
 | 
| +    return ("%s <span class=\"disasmcomment\">;; %s</span>" %
 | 
| +            (line, ", ".join(extra)))
 | 
| +
 | 
| +  def format_disasm_line(
 | 
| +      self, f, start, line, next_address, highlight_address):
 | 
| +    line_address = start + line[0]
 | 
| +    address_fmt = "  <td>%s</td>\n"
 | 
| +    if line_address == highlight_address:
 | 
| +      f.write("<tr class=\"highlight-line\">\n")
 | 
| +      address_fmt = "  <td><a id=\"highlight\">%s</a></td>\n"
 | 
| +    elif (line_address < highlight_address and
 | 
| +          highlight_address < next_address + start):
 | 
| +      f.write("<tr class=\"inexact-highlight-line\">\n")
 | 
| +      address_fmt = "  <td><a id=\"highlight\">%s</a></td>\n"
 | 
| +    else:
 | 
| +      f.write("<tr>\n")
 | 
| +    num_bytes = next_address - line[0]
 | 
| +    stack_slot = self.heap.stack_map.get(line_address)
 | 
| +    marker = ""
 | 
| +    if stack_slot:
 | 
| +      marker = "=>"
 | 
| +    op_offset = 3 * num_bytes - 1
 | 
| +
 | 
| +    code = line[1]
 | 
| +    # Compute the actual call target which the disassembler is too stupid
 | 
| +    # to figure out (it adds the call offset to the disassembly offset rather
 | 
| +    # than the absolute instruction address).
 | 
| +    if self.heap.reader.arch == MD_CPU_ARCHITECTURE_X86:
 | 
| +      if code.startswith("e8"):
 | 
| +        words = code.split()
 | 
| +        if len(words) > 6 and words[5] == "call":
 | 
| +          offset = int(words[4] + words[3] + words[2] + words[1], 16)
 | 
| +          target = (line_address + offset + 5) & 0xFFFFFFFF
 | 
| +          code = code.replace(words[6], "0x%08x" % target)
 | 
| +    # TODO(jkummerow): port this hack to ARM and x64.
 | 
| +
 | 
| +    opcodes = code[:op_offset]
 | 
| +    code = self.annotate_disasm_addresses(code[op_offset:])
 | 
| +    f.write("  <td>")
 | 
| +    self.output_comment_box(f, "codel-", line_address)
 | 
| +    f.write("</td>\n")
 | 
| +    f.write(address_fmt % marker)
 | 
| +    f.write("  ")
 | 
| +    self.td_from_address(f, line_address)
 | 
| +    f.write("%s (+0x%x)</td>\n" %
 | 
| +            (self.format_address(line_address), line[0]))
 | 
| +    f.write("  <td>: %s </td>\n" % opcodes)
 | 
| +    f.write("  <td>%s</td>\n" % code)
 | 
| +    f.write("</tr>\n")
 | 
| +
 | 
| +  def output_comment_box(self, f, prefix, address):
 | 
| +    f.write("<input type=\"text\" class=\"commentinput\" "
 | 
| +            "id=\"%s-address-0x%s\" onchange=\"comment()\" value=\"%s\">" %
 | 
| +            (prefix,
 | 
| +             self.reader.FormatIntPtr(address),
 | 
| +             cgi.escape(self.comments.get_comment(address)) or ""))
 | 
| +
 | 
| +  MAX_FOUND_RESULTS = 100
 | 
| +
 | 
| +  def output_find_results(self, f, results):
 | 
| +    f.write("Addresses")
 | 
| +    toomany = len(results) > self.MAX_FOUND_RESULTS
 | 
| +    if toomany:
 | 
| +      f.write("(found %i results, displaying only first %i)" %
 | 
| +              (len(results), self.MAX_FOUND_RESULTS))
 | 
| +    f.write(": \n")
 | 
| +    results = sorted(results)
 | 
| +    results = results[:min(len(results), self.MAX_FOUND_RESULTS)]
 | 
| +    for address in results:
 | 
| +      f.write("<span %s>%s</span>\n" %
 | 
| +              (self.comments.get_style_class_string(address),
 | 
| +               self.format_address(address)))
 | 
| +    if toomany:
 | 
| +      f.write("...\n")
 | 
| +
 | 
| +
 | 
| +  def output_page_info(self, f, page_kind, page_address, my_page_address):
 | 
| +    if my_page_address == page_address and page_address != 0:
 | 
| +      f.write("Marked first %s page.\n" % page_kind)
 | 
| +    else:
 | 
| +      f.write("<span id=\"%spage\" style=\"display:none\">" % page_kind)
 | 
| +      f.write("Marked first %s page." % page_kind)
 | 
| +      f.write("</span>\n")
 | 
| +      f.write("<button onclick=\"onpage('%spage', '0x%x')\">" %
 | 
| +              (page_kind, my_page_address))
 | 
| +      f.write("Mark as first %s page</button>\n" % page_kind)
 | 
| +    return
 | 
| +
 | 
| +  def output_search_res(self, f, straddress):
 | 
| +    try:
 | 
| +      self.output_header(f)
 | 
| +      f.write("<h3>Search results for %s</h3>" % straddress)
 | 
| +
 | 
| +      address = int(straddress, 0)
 | 
| +
 | 
| +      f.write("Comment: ")
 | 
| +      self.output_comment_box(f, "search-", address)
 | 
| +      f.write("<br>\n")
 | 
| +
 | 
| +      page_address = address & ~self.heap.PageAlignmentMask()
 | 
| +
 | 
| +      f.write("Page info: \n")
 | 
| +      self.output_page_info(f, "data", self.padawan.known_first_data_page, \
 | 
| +                            page_address)
 | 
| +      self.output_page_info(f, "map", self.padawan.known_first_map_page, \
 | 
| +                            page_address)
 | 
| +      self.output_page_info(f, "pointer", \
 | 
| +                            self.padawan.known_first_pointer_page, \
 | 
| +                            page_address)
 | 
| +
 | 
| +      if not self.reader.IsValidAddress(address):
 | 
| +        f.write("<h3>The contents at address %s not found in the dump.</h3>" % \
 | 
| +                straddress)
 | 
| +      else:
 | 
| +        # Print as words
 | 
| +        self.output_words(f, address - 8, address + 32, address, "Dump")
 | 
| +
 | 
| +        # Print as ASCII
 | 
| +        f.write("<hr>\n")
 | 
| +        self.output_ascii(f, address, address + 256, address)
 | 
| +
 | 
| +        # Print as code
 | 
| +        f.write("<hr>\n")
 | 
| +        self.output_disasm_range(f, address - 16, address + 16, address, True)
 | 
| +
 | 
| +      aligned_res, unaligned_res = self.reader.FindWordList(address)
 | 
| +
 | 
| +      if len(aligned_res) > 0:
 | 
| +        f.write("<h3>Occurrences of 0x%x at aligned addresses</h3>\n" %
 | 
| +                address)
 | 
| +        self.output_find_results(f, aligned_res)
 | 
| +
 | 
| +      if len(unaligned_res) > 0:
 | 
| +        f.write("<h3>Occurrences of 0x%x at unaligned addresses</h3>\n" % \
 | 
| +                address)
 | 
| +        self.output_find_results(f, unaligned_res)
 | 
| +
 | 
| +      if len(aligned_res) + len(unaligned_res) == 0:
 | 
| +        f.write("<h3>No occurences of 0x%x found in the dump</h3>\n" % address)
 | 
| +
 | 
| +      self.output_footer(f)
 | 
| +
 | 
| +    except ValueError:
 | 
| +      f.write("<h3>Unrecognized address format \"%s\".</h3>" % straddress)
 | 
| +    return
 | 
| +
 | 
| +  def output_disasm_pc(self, f):
 | 
| +    address = self.reader.ExceptionIP()
 | 
| +    if not self.reader.IsValidAddress(address):
 | 
| +      return
 | 
| +    self.output_disasm_range(f, address - 16, address + 16, address, True)
 | 
| +
 | 
| +
 | 
| +WEB_DUMPS_HEADER = """
 | 
| +<!DOCTYPE html>
 | 
| +<html>
 | 
| +<head>
 | 
| +<meta content="text/html; charset=utf-8" http-equiv="content-type">
 | 
| +<style media="screen" type="text/css">
 | 
| +
 | 
| +.dumplist {
 | 
| +  border-collapse : collapse;
 | 
| +  border-spacing : 0px;
 | 
| +  font-family: monospace;
 | 
| +}
 | 
| +
 | 
| +.dumpcomments {
 | 
| +  border : 1px solid LightGray;
 | 
| +  width : 32em;
 | 
| +}
 | 
| +
 | 
| +</style>
 | 
| +
 | 
| +<script type="application/javascript">
 | 
| +
 | 
| +var dump_str = "dump-";
 | 
| +var dump_len = dump_str.length;
 | 
| +
 | 
| +function dump_comment() {
 | 
| +  var s = event.srcElement.id;
 | 
| +  var index = s.indexOf(dump_str);
 | 
| +  if (index >= 0) {
 | 
| +    send_dump_desc(s.substring(index + dump_len), event.srcElement.value);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +function send_dump_desc(name, desc) {
 | 
| +  xmlhttp = new XMLHttpRequest();
 | 
| +  name = encodeURIComponent(name)
 | 
| +  desc = encodeURIComponent(desc)
 | 
| +  xmlhttp.open("GET",
 | 
| +      "setdumpdesc?dump=" + name +
 | 
| +      "&description=" + desc, true);
 | 
| +  xmlhttp.send();
 | 
| +}
 | 
| +
 | 
| +</script>
 | 
| +
 | 
| +<title>Dump list</title>
 | 
| +</head>
 | 
| +
 | 
| +<body>
 | 
| +"""
 | 
| +
 | 
| +WEB_DUMPS_FOOTER = """
 | 
| +</body>
 | 
| +</html>
 | 
| +"""
 | 
| +
 | 
| +DUMP_FILE_RE = re.compile(r"[-_0-9a-zA-Z][-\._0-9a-zA-Z]*\.dmp$")
 | 
| +
 | 
| +
 | 
| +class InspectionWebServer(BaseHTTPServer.HTTPServer):
 | 
| +  def __init__(self, port_number, switches, minidump_name):
 | 
| +    BaseHTTPServer.HTTPServer.__init__(
 | 
| +        self, ('', port_number), InspectionWebHandler)
 | 
| +    splitpath = os.path.split(minidump_name)
 | 
| +    self.dumppath = splitpath[0]
 | 
| +    self.dumpfilename = splitpath[1]
 | 
| +    self.default_formatter = InspectionWebFormatter(
 | 
| +        switches, minidump_name, self)
 | 
| +    self.formatters = { self.dumpfilename : self.default_formatter }
 | 
| +    self.switches = switches
 | 
| +
 | 
| +  def output_dump_desc_field(self, f, name):
 | 
| +    try:
 | 
| +      descfile = open(os.path.join(self.dumppath, name + ".desc"), "r")
 | 
| +      desc = descfile.readline()
 | 
| +      descfile.close()
 | 
| +    except IOError:
 | 
| +      desc = ""
 | 
| +    f.write("<input type=\"text\" class=\"dumpcomments\" "
 | 
| +            "id=\"dump-%s\" onchange=\"dump_comment()\" value=\"%s\">\n" %
 | 
| +            (cgi.escape(name), desc))
 | 
| +
 | 
| +  def set_dump_desc(self, name, description):
 | 
| +    if not DUMP_FILE_RE.match(name):
 | 
| +      return False
 | 
| +    fname = os.path.join(self.dumppath, name)
 | 
| +    if not os.path.isfile(fname):
 | 
| +      return False
 | 
| +    fname = fname + ".desc"
 | 
| +    descfile = open(fname, "w")
 | 
| +    descfile.write(description)
 | 
| +    descfile.close()
 | 
| +    return True
 | 
| +
 | 
| +  def get_dump_formatter(self, name):
 | 
| +    if name is None:
 | 
| +      return self.default_formatter
 | 
| +    else:
 | 
| +      if not DUMP_FILE_RE.match(name):
 | 
| +        raise WebParameterError("Invalid name '%s'" % name)
 | 
| +      formatter = self.formatters.get(name, None)
 | 
| +      if formatter is None:
 | 
| +        try:
 | 
| +          formatter = InspectionWebFormatter(
 | 
| +              self.switches, os.path.join(self.dumppath, name), self)
 | 
| +          self.formatters[name] = formatter
 | 
| +        except IOError:
 | 
| +          raise WebParameterError("Could not open dump '%s'" % name)
 | 
| +      return formatter
 | 
| +
 | 
| +  def output_dumps(self, f):
 | 
| +    f.write(WEB_DUMPS_HEADER)
 | 
| +    f.write("<h3>List of available dumps</h3>")
 | 
| +    f.write("<table class=\"dumplist\">\n")
 | 
| +    f.write("<thead><tr>")
 | 
| +    f.write("<th>Name</th>")
 | 
| +    f.write("<th>File time</th>")
 | 
| +    f.write("<th>Comment</th>")
 | 
| +    f.write("</tr></thead>")
 | 
| +    dumps_by_time = {}
 | 
| +    for fname in os.listdir(self.dumppath):
 | 
| +      if DUMP_FILE_RE.match(fname):
 | 
| +        mtime = os.stat(os.path.join(self.dumppath, fname)).st_mtime
 | 
| +        fnames = dumps_by_time.get(mtime, [])
 | 
| +        fnames.append(fname)
 | 
| +        dumps_by_time[mtime] = fnames
 | 
| +
 | 
| +    for mtime in sorted(dumps_by_time, reverse=True):
 | 
| +      fnames = dumps_by_time[mtime]
 | 
| +      for fname in fnames:
 | 
| +        f.write("<tr>\n")
 | 
| +        f.write("<td><a href=\"summary.html?%s\">%s</a></td>\n" % (
 | 
| +            (urllib.urlencode({ 'dump' : fname }), fname)))
 | 
| +        f.write("<td>   ")
 | 
| +        f.write(datetime.datetime.fromtimestamp(mtime))
 | 
| +        f.write("</td>")
 | 
| +        f.write("<td>   ")
 | 
| +        self.output_dump_desc_field(f, fname)
 | 
| +        f.write("</td>")
 | 
| +        f.write("</tr>\n")
 | 
| +    f.write("</table>\n")
 | 
| +    f.write(WEB_DUMPS_FOOTER)
 | 
| +    return
 | 
|  
 | 
|  class InspectionShell(cmd.Cmd):
 | 
|    def __init__(self, reader, heap):
 | 
| @@ -1996,6 +3123,8 @@ if __name__ == "__main__":
 | 
|    parser = optparse.OptionParser(USAGE)
 | 
|    parser.add_option("-s", "--shell", dest="shell", action="store_true",
 | 
|                      help="start an interactive inspector shell")
 | 
| +  parser.add_option("-w", "--web", dest="web", action="store_true",
 | 
| +                    help="start a web server on localhost:%i" % PORT_NUMBER)
 | 
|    parser.add_option("-c", "--command", dest="command", default="",
 | 
|                      help="run an interactive inspector shell command and exit")
 | 
|    parser.add_option("-f", "--full", dest="full", action="store_true",
 | 
| @@ -2014,4 +3143,14 @@ if __name__ == "__main__":
 | 
|    if len(args) != 1:
 | 
|      parser.print_help()
 | 
|      sys.exit(1)
 | 
| -  AnalyzeMinidump(options, args[0])
 | 
| +  if options.web:
 | 
| +    try:
 | 
| +      server = InspectionWebServer(PORT_NUMBER, options, args[0])
 | 
| +      print 'Started httpserver on port ' , PORT_NUMBER
 | 
| +      webbrowser.open('http://localhost:%i/summary.html' % PORT_NUMBER)
 | 
| +      server.serve_forever()
 | 
| +    except KeyboardInterrupt:
 | 
| +      print '^C received, shutting down the web server'
 | 
| +      server.socket.close()
 | 
| +  else:
 | 
| +    AnalyzeMinidump(options, args[0])
 | 
| 
 |