Index: tools/grokdump.py |
diff --git a/tools/grokdump.py b/tools/grokdump.py |
index a5a2ae08a879b11447352a262e02222da4e7933b..43a5c7cb413ddffd6f24aabe1e9f0000aca0fb9a 100755 |
--- a/tools/grokdump.py |
+++ b/tools/grokdump.py |
@@ -28,6 +28,7 @@ |
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
import bisect |
+import cgi |
import cmd |
import codecs |
import ctypes |
@@ -39,8 +40,16 @@ import os |
import re |
import struct |
import sys |
+import time |
import types |
+import urllib |
import v8heapconst |
+import webbrowser |
+from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer |
+from urlparse import urlparse, parse_qs |
+ |
+PORT_NUMBER = 8081 |
+ |
USAGE="""usage: %prog [OPTIONS] [DUMP-FILE] |
@@ -701,6 +710,20 @@ class MinidumpReader(object): |
reader.FormatIntPtr(word)) |
self.ForEachMemoryRegion(search_inside_region) |
+ def FindWordList(self, word): |
+ aligned_res = [ ] |
Jakob Kummerow
2014/03/24 15:21:32
nit: the style guide doesn't like spaces inside em
Jarin
2014/03/26 10:54:28
Done.
|
+ 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 +953,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 +1372,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 +1408,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()) |
@@ -1560,6 +1589,81 @@ class KnownMap(HeapObject): |
def __str__(self): |
return "<%s>" % self.known_name |
+COMMENT_RE = re.compile(r"^C (0x[0-9a-fA-F]+) (.*)$") |
Jakob Kummerow
2014/03/24 15:21:32
style nit: two empty lines between top-level thing
Jarin
2014/03/26 10:54:28
Done.
|
+PAGEADDRESS_RE = re.compile( |
+ r"^P (mappage|pointerpage|datapage) (0x[0-9a-fA-F]+)$") |
+ |
+class InspectionInfo(object): |
+ def __init__(self, minidump_name, reader): |
+ self.cmt_file = minidump_name + ".comments" |
+ self.address_comments = { } |
+ self.page_address = { } |
+ print "Opening comment file '%s'" % self.cmt_file |
+ try: |
+ f = open(self.cmt_file, "r") |
Jakob Kummerow
2014/03/24 15:21:32
The pythonic way to express open/close sequences i
Jarin
2014/03/26 10:54:28
Done.
|
+ 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) |
+ except IOError: |
+ print "No comments file, starting a new one" |
+ self.reader = reader |
+ 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): |
+ f = open(self.cmt_file, "a") |
Jakob Kummerow
2014/03/24 15:21:32
again, prefer "with"
Jarin
2014/03/26 10:54:28
Done.
|
+ f.write("P %s 0x%x\n" % (page_kind, address)) |
+ f.close() |
+ |
+ def color_addresses(self): |
+ self.styles = { } |
Jakob Kummerow
2014/03/24 15:21:32
nit: I have a weak preference to initialize all me
Jarin
2014/03/26 10:54:28
Done.
|
+ |
+ # Color all stack addresses |
Jakob Kummerow
2014/03/24 15:21:32
nit: trailing full stop please
Jarin
2014/03/26 10:54:28
Done.
|
+ 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 |
+ f = open(self.cmt_file, "a") |
Jakob Kummerow
2014/03/24 15:21:32
with
Jarin
2014/03/26 10:54:28
Done.
|
+ 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.""" |
@@ -1653,6 +1757,1059 @@ 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=ISO-8859-1" http-equiv="content-type"> |
+<style media="screen" type="text/css"> |
+ |
+.code {{ |
Jakob Kummerow
2014/03/24 15:21:32
If you used %s-placeholders ("foo %s baz" % bar),
Jarin
2014/03/26 10:54:28
However, with %s you cannot reuse the same argumen
|
+ font-family: monospace; |
Jakob Kummerow
2014/03/24 15:21:32
nit: funky indentation
Jarin
2014/03/26 10:54:28
Done.
|
+}} |
+ |
+.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; |
+}} |
+ |
+.pc {{ |
+}} |
Jakob Kummerow
2014/03/24 15:21:32
Is this intentionally empty?
Jarin
2014/03/26 10:54:28
Removed.
|
+ |
+.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; |
+}} |
+ |
+.disasmcmt {{ |
+ color : DarkGreen; |
+}} |
+ |
+</style> |
+ |
+<script type="application/javascript"> |
+ |
+var address_str = "address-"; |
+var address_len = address_str.length; |
+ |
+function cmt() {{ |
+ 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", |
Jakob Kummerow
2014/03/24 15:21:32
I think using GET to perform actions is considered
Jarin
2014/03/26 10:54:28
Yeah, I am leaving as is.
|
+ "setcomment?{0}&address=" + address + |
+ "&comment=" + comment, true); |
+ xmlhttp.send(); |
+}} |
+ |
+var dump_str = "dump-"; |
+var dump_len = dump_str.length; |
+ |
+function dump_cmt() {{ |
+ 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) |
Jakob Kummerow
2014/03/24 15:21:32
nit: {} around the block please
Jarin
2014/03/26 10:54:28
Done.
|
+ }}; |
+ xmlhttp.open("GET", |
+ "setpageaddress?{0}&kind=" + kind + |
+ "&address=" + address); |
+ xmlhttp.send(); |
+}} |
+ |
+function main() {{ |
Jakob Kummerow
2014/03/24 15:21:32
looks like we don't need this urgently.
Jarin
2014/03/26 10:54:28
Removed.
|
+}} |
+ |
+</script> |
+ |
+<title>Dump {1}</title> |
+</head> |
+ |
+<body onload="main()"> |
+ <div class="header"> |
+ <form class="navigation" action="search.html"> |
+ <a href="summary.html?{0}">Context info</a>   |
+ <a href="info.html?{0}">Dump info</a>   |
+ <a href="modules.html?{0}">Modules</a> |
+ |
+ <input type="search" name="val"> |
+ <input type="submit" name="search" value="Search"> |
+ <input type="hidden" name="dump" value="{1}"> |
+ </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> |
+ <!-- |
Jakob Kummerow
2014/03/24 15:21:32
nit: remove this if it's not needed
Jarin
2014/03/26 10:54:28
Done.
|
+ <form class="dumpname" action="dump.html"> |
+ Dump id: |
+ <input type="search" name="dumpid"> |
+ <input type="submit" name="action" value="Go"> |
+ <input type="hidden" name="dump" value="{1}"> |
+ </form> |
+ --> |
+ </div> |
+ <br> |
+ <hr> |
+""" |
+ |
+WEB_FOOTER = """ |
+</body> |
+</html> |
+""" |
+ |
+class WebParameterError(Exception): |
Jakob Kummerow
2014/03/24 15:21:32
again, two empty lines before and after top-level
Jarin
2014/03/26 10:54:28
Done.
|
+ def __init__(self, message): |
+ Exception.__init__(self, message) |
+ |
+class InspectionWebHandler(BaseHTTPRequestHandler): |
+ def fmt(self, qc): |
Jakob Kummerow
2014/03/24 15:21:32
naming nit: abbreviations are generally frowned up
Jarin
2014/03/26 10:54:28
Done.
|
+ name = qc.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: |
+ print "GET!" |
Jakob Kummerow
2014/03/24 15:21:32
Looks like a debugging aid. Is this still needed?
Jarin
2014/03/26 10:54:28
Removed.
|
+ parsedurl = urlparse(self.path) |
+ qc = parse_qs(parsedurl.query) |
+ if parsedurl.path == "/dumps.html": |
+ self.send_success_html_headers() |
+ self.server.output_dumps(self.wfile) |
+ return |
+ if parsedurl.path == "/summary.html": |
Jakob Kummerow
2014/03/24 15:21:32
s/if/elif/ like below? In turn you could omit all
|
+ self.send_success_html_headers() |
+ self.fmt(qc).output_summary(self.wfile) |
+ return |
+ if parsedurl.path == "/info.html": |
+ self.send_success_html_headers() |
+ self.fmt(qc).output_info(self.wfile) |
+ return |
+ elif parsedurl.path == "/modules.html": |
+ self.send_success_html_headers() |
+ self.server.formatter.output_modules(self.wfile) |
+ return |
+ elif parsedurl.path == "/search.html": |
+ address = qc.get("val", []) |
+ if len(address) != 1: |
+ self.send_error(404, "Invalid params") |
+ return |
+ if qc.get("search", None) != None: |
+ self.send_success_html_headers() |
+ self.fmt(qc).output_search_res(self.wfile, address[0]) |
+ return |
+ elif parsedurl.path == "/disasm.html": |
+ address = qc.get("val", []) |
+ exact = qc.get("exact", ["on"]) |
+ if len(address) != 1: |
+ self.send_error(404, "Invalid params") |
+ return |
+ self.send_success_html_headers() |
+ self.fmt(qc).output_disasm(self.wfile, address[0], exact[0]) |
+ return |
+ elif parsedurl.path == "/data.html": |
+ address = qc.get("val", []) |
+ datakind = qc.get("type", ["address"]) |
+ if len(address) == 1 and len(datakind) == 1: |
+ self.send_success_html_headers() |
+ self.fmt(qc).output_data(self.wfile, address[0], datakind[0]) |
+ else: |
+ self.send_error(404,'Invalid params') |
+ return |
+ elif parsedurl.path == "/setdumpdesc": |
+ name = qc.get("dump", [""]) |
+ description = qc.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') |
Jakob Kummerow
2014/03/24 15:21:32
again, I'd prefer if+else over if+return
Jarin
2014/03/26 10:54:28
Done.
|
+ return |
+ |
+ elif parsedurl.path == "/setcomment": |
+ address = qc.get("address", []) |
+ comment = qc.get("comment", [""]) |
+ |
+ if len(address) == 1 and len(comment) == 1: |
+ address = address[0] |
+ comment = comment[0] |
+ self.fmt(qc).set_comment(address, comment) |
+ self.send_success_html_headers() |
+ self.wfile.write("OK") |
+ else: |
+ self.send_error(404,'Invalid params') |
+ return |
+ |
+ elif parsedurl.path == "/setpageaddress": |
+ kind = qc.get("kind", []) |
+ address = qc.get("address", [""]) |
+ |
+ if len(kind) == 1 and len(address) == 1: |
+ kind = kind[0] |
+ address = address[0] |
+ self.fmt(qc).set_page_address(kind, address) |
+ self.send_success_html_headers() |
+ self.wfile.write("OK") |
+ else: |
+ self.send_error(404,'Invalid params') |
+ return |
+ |
+ 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, options, minidump_name, server): |
+ self.dumpfilename = os.path.split(minidump_name)[1] |
+ self.encfilename = urllib.urlencode({ 'dump' : self.dumpfilename }) |
+ self.reader = MinidumpReader(options, minidump_name) |
+ self.server = 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 + \ |
Jakob Kummerow
2014/03/24 15:21:32
nit: the style guide prefers () over trailing \, i
|
+ 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: |
+ print "Adding comment to dump '%s': %s -> '%s'" % ( |
Jakob Kummerow
2014/03/24 15:21:32
Still needed? If you don't want to delete it, cons
Jarin
2014/03/26 10:54:28
Removed.
|
+ self.dumpfilename, straddress, comment) |
+ address = int(straddress, 0) |
+ self.comments.set_comment(address, comment) |
+ except ValueError: |
+ print "Invalid address" |
+ |
+ def set_page_address(self, kind, straddress): |
+ try: |
+ print "Adding page annotation to dump '%s': %s -> %s" % ( |
Jakob Kummerow
2014/03/24 15:21:32
still needed?
Jarin
2014/03/26 10:54:28
Removed.
|
+ self.dumpfilename, kind, straddress) |
+ 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 == None: |
Jakob Kummerow
2014/03/24 15:21:32
nit: the style guide wants "is None" instead of "=
|
+ return "not in dump" |
+ else: |
+ if straddress == 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&search=yes\">%s</a>" % ( |
+ style_class, self.encfilename, straddress, straddress) |
+ |
+ def output_header(self, f): |
+ f.write(WEB_HEADER.format(self.encfilename, 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>") |
+ f.write("</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" % ( |
Jakob Kummerow
2014/03/24 15:21:32
nit: I'd break as:
f.write(" Excep
Jarin
2014/03/26 10:54:28
Done.
|
+ 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) |
Jakob Kummerow
2014/03/24 15:21:32
nit: fits on previous line?
Jarin
2014/03/26 10:54:28
Done.
|
+ return a + alignment_correction |
+ |
+ |
+ def format_object(self, address): |
Jakob Kummerow
2014/03/24 15:21:32
nit: one empty line is enough between non-toplevel
Jarin
2014/03/26 10:54:28
Done.
|
+ 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, \ |
Jakob Kummerow
2014/03/24 15:21:32
nit: trailing \ is not necessary
Jarin
2014/03/26 10:54:28
Done.
|
+ highlight_address, desc): |
+ region = self.reader.FindRegion(highlight_address) |
+ if region == 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\"" |
+ "cellspacing=\"0\" cellpadding=\"0\">\n") |
+ |
+ for slot in xrange(start_address, |
+ end_address, |
Jakob Kummerow
2014/03/24 15:21:32
nit: fits on one line?
Jarin
2014/03/26 10:54:28
Done.
|
+ 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 name=\"highlight\"></a>%s </td>\n" |
+ if slot <= highlight_address and highlight_address < slot + size: |
+ f.write("<tr class=\"inexact-highlight-line\">\n") |
+ address_fmt = "<a name=\"highlight\"></a>%s </td>\n" |
+ else: |
+ f.write("<tr>\n") |
+ |
+ f.write(" <td>") |
+ self.output_comment_box(f, "da-", slot) |
+ f.write(" </td>\n") |
+ self.td_from_address(f, slot) |
+ f.write(address_fmt % self.format_address(slot)) |
+ 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 == 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]: |
+ expand = ("(<a href=\"data.html?%s&val=0x%x&type=ascii#highlight\">" |
+ "more...</a>)") % ( |
+ self.encfilename, highlight_address) |
+ |
+ 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)) |
+ else: |
+ f.write("·") |
Jakob Kummerow
2014/03/24 15:21:32
Doesn't this need s/·/·/ ?
Jarin
2014/03/26 10:54:28
Done (the char code above needs ';', too)
|
+ 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\" cellspacing=\"0\" cellpadding=\"0\">\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 = self.padawan.SenseObject(maybe_address) |
+ if not object: continue |
+ extra.append(cgi.escape(str(object))) |
+ if len(extra) == 0: return line |
+ return "%s <span class=\"disasmcmt\">;; %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 name=\"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 name=\"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) |
+ 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=\"cmt()\" value=\"%s\">\n") % ( |
+ 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") |
+ |
+ |
Jakob Kummerow
2014/03/24 15:21:32
nit: just one empty line
Jarin
2014/03/26 10:54:28
Done.
|
+ 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=ISO-8859-1" 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_cmt() { |
+ 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(HTTPServer): |
+ def __init__(self, port_number, options, minidump_name): |
+ HTTPServer.__init__(self, ('', port_number), InspectionWebHandler) |
+ splitpath = os.path.split(minidump_name) |
+ self.dumppath = splitpath[0] |
+ self.dumpfilename = splitpath[1] |
+ self.formatter = InspectionWebFormatter(options, minidump_name, self) |
+ self.formatters = { self.dumpfilename : self.formatter } |
+ print "Initialized at path '%s'" % self.dumppath |
+ |
+ 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\" " |
Jakob Kummerow
2014/03/24 15:21:32
nit: the indentation here is particularly inconsis
Jarin
2014/03/26 10:54:28
Done.
|
+ "id=\"dump-%s\" onchange=\"dump_cmt()\" 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 == None: |
+ # Get the default formatter |
+ return self.formatter |
+ else: |
+ if not DUMP_FILE_RE.match(name): |
+ raise WebParameterError("Invalid name '%s'" % name) |
+ fmt = self.formatters.get(name, None) |
+ if fmt == None: |
+ try: |
+ fmt = InspectionWebFormatter( |
+ options, os.path.join(self.dumppath, name), self) |
+ self.formatters[name] = fmt |
+ except IOError: |
+ raise WebParameterError("Could not open dump '%s'" % name) |
+ return fmt |
+ |
+ 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</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 +3153,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 page") |
Jakob Kummerow
2014/03/24 15:21:32
nit: s/page/server on localhost:8081/ (is it worth
Jarin
2014/03/26 10:54:28
Done (the port not configurable yet)
|
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 +3173,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:8081/summary.html') |
+ server.serve_forever() |
+ except KeyboardInterrupt: |
+ print '^C received, shutting down the web server' |
+ server.socket.close() |
+ else: |
+ AnalyzeMinidump(options, args[0]) |