Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(42)

Side by Side Diff: tools/grokdump.py

Issue 194573005: Web page front-end for grokdump. (Closed) Base URL: https://v8.googlecode.com/svn/branches/bleeding_edge
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # 2 #
3 # Copyright 2012 the V8 project authors. All rights reserved. 3 # Copyright 2012 the V8 project authors. All rights reserved.
4 # Redistribution and use in source and binary forms, with or without 4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are 5 # modification, are permitted provided that the following conditions are
6 # met: 6 # met:
7 # 7 #
8 # * Redistributions of source code must retain the above copyright 8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer. 9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above 10 # * Redistributions in binary form must reproduce the above
(...skipping 10 matching lines...) Expand all
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 29
30 import bisect 30 import bisect
31 import cgi
31 import cmd 32 import cmd
32 import codecs 33 import codecs
33 import ctypes 34 import ctypes
34 import datetime 35 import datetime
35 import disasm 36 import disasm
36 import mmap 37 import mmap
37 import optparse 38 import optparse
38 import os 39 import os
39 import re 40 import re
40 import struct 41 import struct
41 import sys 42 import sys
43 import time
42 import types 44 import types
45 import urllib
43 import v8heapconst 46 import v8heapconst
47 import webbrowser
48 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
49 from urlparse import urlparse, parse_qs
50
51 PORT_NUMBER = 8081
52
44 53
45 USAGE="""usage: %prog [OPTIONS] [DUMP-FILE] 54 USAGE="""usage: %prog [OPTIONS] [DUMP-FILE]
46 55
47 Minidump analyzer. 56 Minidump analyzer.
48 57
49 Shows the processor state at the point of exception including the 58 Shows the processor state at the point of exception including the
50 stack of the active thread and the referenced objects in the V8 59 stack of the active thread and the referenced objects in the V8
51 heap. Code objects are disassembled and the addresses linked from the 60 heap. Code objects are disassembled and the addresses linked from the
52 stack (e.g. pushed return addresses) are marked with "=>". 61 stack (e.g. pushed return addresses) are marked with "=>".
53 62
(...skipping 640 matching lines...) Expand 10 before | Expand all | Expand 10 after
694 def FindWord(self, word, alignment=0): 703 def FindWord(self, word, alignment=0):
695 def search_inside_region(reader, start, size, location): 704 def search_inside_region(reader, start, size, location):
696 location = (location + alignment) & ~alignment 705 location = (location + alignment) & ~alignment
697 for loc in xrange(location, location + size - self.PointerSize()): 706 for loc in xrange(location, location + size - self.PointerSize()):
698 if reader._ReadWord(loc) == word: 707 if reader._ReadWord(loc) == word:
699 slot = start + (loc - location) 708 slot = start + (loc - location)
700 print "%s: %s" % (reader.FormatIntPtr(slot), 709 print "%s: %s" % (reader.FormatIntPtr(slot),
701 reader.FormatIntPtr(word)) 710 reader.FormatIntPtr(word))
702 self.ForEachMemoryRegion(search_inside_region) 711 self.ForEachMemoryRegion(search_inside_region)
703 712
713 def FindWordList(self, word):
714 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.
715 unaligned_res = [ ]
716 def search_inside_region(reader, start, size, location):
717 for loc in xrange(location, location + size - self.PointerSize()):
718 if reader._ReadWord(loc) == word:
719 slot = start + (loc - location)
720 if slot % self.PointerSize() == 0:
721 aligned_res.append(slot)
722 else:
723 unaligned_res.append(slot)
724 self.ForEachMemoryRegion(search_inside_region)
725 return (aligned_res, unaligned_res)
726
704 def FindLocation(self, address): 727 def FindLocation(self, address):
705 offset = 0 728 offset = 0
706 if self.memory_list64 is not None: 729 if self.memory_list64 is not None:
707 for r in self.memory_list64.ranges: 730 for r in self.memory_list64.ranges:
708 if r.start <= address < r.start + r.size: 731 if r.start <= address < r.start + r.size:
709 return self.memory_list64.base_rva + offset + address - r.start 732 return self.memory_list64.base_rva + offset + address - r.start
710 offset += r.size 733 offset += r.size
711 if self.memory_list is not None: 734 if self.memory_list is not None:
712 for r in self.memory_list.ranges: 735 for r in self.memory_list.ranges:
713 if r.start <= address < r.start + r.memory.data_size: 736 if r.start <= address < r.start + r.memory.data_size:
(...skipping 209 matching lines...) Expand 10 before | Expand all | Expand 10 after
923 def __str__(self): 946 def __str__(self):
924 return "HeapObject(%s, %s)" % (self.heap.reader.FormatIntPtr(self.address), 947 return "HeapObject(%s, %s)" % (self.heap.reader.FormatIntPtr(self.address),
925 INSTANCE_TYPES[self.map.instance_type]) 948 INSTANCE_TYPES[self.map.instance_type])
926 949
927 def ObjectField(self, offset): 950 def ObjectField(self, offset):
928 field_value = self.heap.reader.ReadUIntPtr(self.address + offset) 951 field_value = self.heap.reader.ReadUIntPtr(self.address + offset)
929 return self.heap.FindObjectOrSmi(field_value) 952 return self.heap.FindObjectOrSmi(field_value)
930 953
931 def SmiField(self, offset): 954 def SmiField(self, offset):
932 field_value = self.heap.reader.ReadUIntPtr(self.address + offset) 955 field_value = self.heap.reader.ReadUIntPtr(self.address + offset)
933 assert (field_value & 1) == 0 956 if (field_value & 1) == 0:
934 return field_value / 2 957 return field_value / 2
958 return None
935 959
936 960
937 class Map(HeapObject): 961 class Map(HeapObject):
938 def Decode(self, offset, size, value): 962 def Decode(self, offset, size, value):
939 return (value >> offset) & ((1 << size) - 1) 963 return (value >> offset) & ((1 << size) - 1)
940 964
941 # Instance Sizes 965 # Instance Sizes
942 def InstanceSizesOffset(self): 966 def InstanceSizesOffset(self):
943 return self.heap.PointerSize() 967 return self.heap.PointerSize()
944 968
(...skipping 396 matching lines...) Expand 10 before | Expand all | Expand 10 after
1341 return "JSFunction(%s, %s)" % \ 1365 return "JSFunction(%s, %s)" % \
1342 (self.heap.reader.FormatIntPtr(self.address), inferred_name) 1366 (self.heap.reader.FormatIntPtr(self.address), inferred_name)
1343 1367
1344 def _GetSource(self): 1368 def _GetSource(self):
1345 source = "?source?" 1369 source = "?source?"
1346 start = self.shared.start_position 1370 start = self.shared.start_position
1347 end = self.shared.end_position 1371 end = self.shared.end_position
1348 if not self.shared.script.Is(Script): return source 1372 if not self.shared.script.Is(Script): return source
1349 script_source = self.shared.script.source 1373 script_source = self.shared.script.source
1350 if not script_source.Is(String): return source 1374 if not script_source.Is(String): return source
1351 return script_source.GetChars()[start:end] 1375 if start and end:
1376 source = script_source.GetChars()[start:end]
1377 return source
1352 1378
1353 1379
1354 class SharedFunctionInfo(HeapObject): 1380 class SharedFunctionInfo(HeapObject):
1355 def CodeOffset(self): 1381 def CodeOffset(self):
1356 return 2 * self.heap.PointerSize() 1382 return 2 * self.heap.PointerSize()
1357 1383
1358 def ScriptOffset(self): 1384 def ScriptOffset(self):
1359 return 7 * self.heap.PointerSize() 1385 return 7 * self.heap.PointerSize()
1360 1386
1361 def InferredNameOffset(self): 1387 def InferredNameOffset(self):
(...skipping 13 matching lines...) Expand all
1375 if heap.PointerSize() == 8: 1401 if heap.PointerSize() == 8:
1376 start_position_and_type = \ 1402 start_position_and_type = \
1377 heap.reader.ReadU32(self.StartPositionAndTypeOffset()) 1403 heap.reader.ReadU32(self.StartPositionAndTypeOffset())
1378 self.start_position = start_position_and_type >> 2 1404 self.start_position = start_position_and_type >> 2
1379 pseudo_smi_end_position = \ 1405 pseudo_smi_end_position = \
1380 heap.reader.ReadU32(self.EndPositionOffset()) 1406 heap.reader.ReadU32(self.EndPositionOffset())
1381 self.end_position = pseudo_smi_end_position >> 2 1407 self.end_position = pseudo_smi_end_position >> 2
1382 else: 1408 else:
1383 start_position_and_type = \ 1409 start_position_and_type = \
1384 self.SmiField(self.StartPositionAndTypeOffset()) 1410 self.SmiField(self.StartPositionAndTypeOffset())
1385 self.start_position = start_position_and_type >> 2 1411 if start_position_and_type:
1412 self.start_position = start_position_and_type >> 2
1413 else:
1414 self.start_position = None
1386 self.end_position = \ 1415 self.end_position = \
1387 self.SmiField(self.EndPositionOffset()) 1416 self.SmiField(self.EndPositionOffset())
1388 1417
1389 1418
1390 class Script(HeapObject): 1419 class Script(HeapObject):
1391 def SourceOffset(self): 1420 def SourceOffset(self):
1392 return self.heap.PointerSize() 1421 return self.heap.PointerSize()
1393 1422
1394 def NameOffset(self): 1423 def NameOffset(self):
1395 return self.SourceOffset() + self.heap.PointerSize() 1424 return self.SourceOffset() + self.heap.PointerSize()
(...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after
1553 1582
1554 class KnownMap(HeapObject): 1583 class KnownMap(HeapObject):
1555 def __init__(self, heap, known_name, instance_type): 1584 def __init__(self, heap, known_name, instance_type):
1556 HeapObject.__init__(self, heap, None, None) 1585 HeapObject.__init__(self, heap, None, None)
1557 self.instance_type = instance_type 1586 self.instance_type = instance_type
1558 self.known_name = known_name 1587 self.known_name = known_name
1559 1588
1560 def __str__(self): 1589 def __str__(self):
1561 return "<%s>" % self.known_name 1590 return "<%s>" % self.known_name
1562 1591
1592 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.
1593 PAGEADDRESS_RE = re.compile(
1594 r"^P (mappage|pointerpage|datapage) (0x[0-9a-fA-F]+)$")
1595
1596 class InspectionInfo(object):
1597 def __init__(self, minidump_name, reader):
1598 self.cmt_file = minidump_name + ".comments"
1599 self.address_comments = { }
1600 self.page_address = { }
1601 print "Opening comment file '%s'" % self.cmt_file
1602 try:
1603 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.
1604 lines = f.readlines()
1605 f.close()
1606
1607 for l in lines:
1608 m = COMMENT_RE.match(l)
1609 if m:
1610 self.address_comments[int(m.group(1), 0)] = m.group(2)
1611 m = PAGEADDRESS_RE.match(l)
1612 if m:
1613 self.page_address[m.group(1)] = int(m.group(2), 0)
1614 except IOError:
1615 print "No comments file, starting a new one"
1616 self.reader = reader
1617 self.color_addresses()
1618 return
1619
1620 def get_page_address(self, page_kind):
1621 return self.page_address.get(page_kind, 0)
1622
1623 def save_page_address(self, page_kind, address):
1624 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.
1625 f.write("P %s 0x%x\n" % (page_kind, address))
1626 f.close()
1627
1628 def color_addresses(self):
1629 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.
1630
1631 # 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.
1632 exception_thread = self.reader.thread_map[self.reader.exception.thread_id]
1633 stack_top = self.reader.ExceptionSP()
1634 stack_bottom = exception_thread.stack.start + \
1635 exception_thread.stack.memory.data_size
1636 frame_pointer = self.reader.ExceptionFP()
1637 self.styles[frame_pointer] = "frame"
1638 for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()):
1639 self.styles[slot] = "stackaddress"
1640 for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()):
1641 maybe_address = self.reader.ReadUIntPtr(slot)
1642 self.styles[maybe_address] = "stackval"
1643 if slot == frame_pointer:
1644 self.styles[slot] = "frame"
1645 frame_pointer = maybe_address
1646 self.styles[self.reader.ExceptionIP()] = "pc"
1647
1648 def get_style_class(self, address):
1649 return self.styles.get(address, None)
1650
1651 def get_style_class_string(self, address):
1652 style = self.get_style_class(address)
1653 if style != None:
1654 return " class=\"%s\" " % style
1655 else:
1656 return ""
1657
1658 def set_comment(self, address, comment):
1659 self.address_comments[address] = comment
1660 f = open(self.cmt_file, "a")
Jakob Kummerow 2014/03/24 15:21:32 with
Jarin 2014/03/26 10:54:28 Done.
1661 f.write("C 0x%x %s\n" % (address, comment))
1662 f.close()
1663
1664 def get_comment(self, address):
1665 return self.address_comments.get(address, "")
1666
1563 1667
1564 class InspectionPadawan(object): 1668 class InspectionPadawan(object):
1565 """The padawan can improve annotations by sensing well-known objects.""" 1669 """The padawan can improve annotations by sensing well-known objects."""
1566 def __init__(self, reader, heap): 1670 def __init__(self, reader, heap):
1567 self.reader = reader 1671 self.reader = reader
1568 self.heap = heap 1672 self.heap = heap
1569 self.known_first_map_page = 0 1673 self.known_first_map_page = 0
1570 self.known_first_data_page = 0 1674 self.known_first_data_page = 0
1571 self.known_first_pointer_page = 0 1675 self.known_first_pointer_page = 0
1572 1676
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
1646 raise NotImplementedError 1750 raise NotImplementedError
1647 1751
1648 def PrintKnowledge(self): 1752 def PrintKnowledge(self):
1649 print " known_first_map_page = %s\n"\ 1753 print " known_first_map_page = %s\n"\
1650 " known_first_data_page = %s\n"\ 1754 " known_first_data_page = %s\n"\
1651 " known_first_pointer_page = %s" % ( 1755 " known_first_pointer_page = %s" % (
1652 self.reader.FormatIntPtr(self.known_first_map_page), 1756 self.reader.FormatIntPtr(self.known_first_map_page),
1653 self.reader.FormatIntPtr(self.known_first_data_page), 1757 self.reader.FormatIntPtr(self.known_first_data_page),
1654 self.reader.FormatIntPtr(self.known_first_pointer_page)) 1758 self.reader.FormatIntPtr(self.known_first_pointer_page))
1655 1759
1760 WEB_HEADER = """
1761 <!DOCTYPE html>
1762 <html>
1763 <head>
1764 <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
1765 <style media="screen" type="text/css">
1766
1767 .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
1768 font-family: monospace;
Jakob Kummerow 2014/03/24 15:21:32 nit: funky indentation
Jarin 2014/03/26 10:54:28 Done.
1769 }}
1770
1771 .dmptable {{
1772 border-collapse : collapse;
1773 border-spacing : 0px;
1774 }}
1775
1776 .codedump {{
1777 border-collapse : collapse;
1778 border-spacing : 0px;
1779 }}
1780
1781 .addrcomments {{
1782 border : 0px;
1783 }}
1784
1785 .register {{
1786 padding-right : 1em;
1787 }}
1788
1789 .header {{
1790 clear : both;
1791 }}
1792
1793 .header .navigation {{
1794 float : left;
1795 }}
1796
1797 .header .dumpname {{
1798 float : right;
1799 }}
1800
1801 tr.highlight-line {{
1802 background-color : yellow;
1803 }}
1804
1805 .highlight {{
1806 background-color : magenta;
1807 }}
1808
1809 tr.inexact-highlight-line {{
1810 background-color : pink;
1811 }}
1812
1813 input {{
1814 background-color: inherit;
1815 border: 1px solid LightGray;
1816 }}
1817
1818 .dumpcomments {{
1819 border : 1px solid LightGray;
1820 width : 32em;
1821 }}
1822
1823 .regions td {{
1824 padding:0 15px 0 15px;
1825 }}
1826
1827 .stackframe td {{
1828 background-color : cyan;
1829 }}
1830
1831 .pc {{
1832 }}
Jakob Kummerow 2014/03/24 15:21:32 Is this intentionally empty?
Jarin 2014/03/26 10:54:28 Removed.
1833
1834 .stackaddress {{
1835 background-color : LightGray;
1836 }}
1837
1838 .stackval {{
1839 background-color : LightCyan;
1840 }}
1841
1842 .frame {{
1843 background-color : cyan;
1844 }}
1845
1846 .commentinput {{
1847 width : 20em;
1848 }}
1849
1850 a.nodump:visited {{
1851 color : black;
1852 text-decoration : none;
1853 }}
1854
1855 a.nodump:link {{
1856 color : black;
1857 text-decoration : none;
1858 }}
1859
1860 a:visited {{
1861 color : blueviolet;
1862 }}
1863
1864 a:link {{
1865 color : blue;
1866 }}
1867
1868 .disasmcmt {{
1869 color : DarkGreen;
1870 }}
1871
1872 </style>
1873
1874 <script type="application/javascript">
1875
1876 var address_str = "address-";
1877 var address_len = address_str.length;
1878
1879 function cmt() {{
1880 var s = event.srcElement.id;
1881 var index = s.indexOf(address_str);
1882 if (index >= 0) {{
1883 send_comment(s.substring(index + address_len), event.srcElement.value);
1884 }}
1885 }}
1886
1887 function send_comment(address, comment) {{
1888 xmlhttp = new XMLHttpRequest();
1889 address = encodeURIComponent(address)
1890 comment = encodeURIComponent(comment)
1891 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.
1892 "setcomment?{0}&address=" + address +
1893 "&comment=" + comment, true);
1894 xmlhttp.send();
1895 }}
1896
1897 var dump_str = "dump-";
1898 var dump_len = dump_str.length;
1899
1900 function dump_cmt() {{
1901 var s = event.srcElement.id;
1902 var index = s.indexOf(dump_str);
1903 if (index >= 0) {{
1904 send_dump_desc(s.substring(index + dump_len), event.srcElement.value);
1905 }}
1906 }}
1907
1908 function send_dump_desc(name, desc) {{
1909 xmlhttp = new XMLHttpRequest();
1910 name = encodeURIComponent(name)
1911 desc = encodeURIComponent(desc)
1912 xmlhttp.open("GET",
1913 "setdumpdesc?dump=" + name +
1914 "&description=" + desc, true);
1915 xmlhttp.send();
1916 }}
1917
1918 function onpage(kind, address) {{
1919 xmlhttp = new XMLHttpRequest();
1920 kind = encodeURIComponent(kind)
1921 address = encodeURIComponent(address)
1922 xmlhttp.onreadystatechange = function() {{
1923 if (xmlhttp.readyState==4 && xmlhttp.status==200)
1924 location.reload(true)
Jakob Kummerow 2014/03/24 15:21:32 nit: {} around the block please
Jarin 2014/03/26 10:54:28 Done.
1925 }};
1926 xmlhttp.open("GET",
1927 "setpageaddress?{0}&kind=" + kind +
1928 "&address=" + address);
1929 xmlhttp.send();
1930 }}
1931
1932 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.
1933 }}
1934
1935 </script>
1936
1937 <title>Dump {1}</title>
1938 </head>
1939
1940 <body onload="main()">
1941 <div class="header">
1942 <form class="navigation" action="search.html">
1943 <a href="summary.html?{0}">Context info</a>&nbsp;&nbsp;&nbsp
1944 <a href="info.html?{0}">Dump info</a>&nbsp;&nbsp;&nbsp
1945 <a href="modules.html?{0}">Modules</a>&nbsp;&nbsp;&nbsp;
1946 &nbsp;
1947 <input type="search" name="val">
1948 <input type="submit" name="search" value="Search">
1949 <input type="hidden" name="dump" value="{1}">
1950 </form>
1951 <form class="navigation" action="disasm.html#highlight">
1952 &nbsp;
1953 &nbsp;
1954 &nbsp;
1955 <input type="search" name="val">
1956 <input type="submit" name="disasm" value="Disasm">
1957 &nbsp;
1958 &nbsp;
1959 &nbsp;
1960 <a href="dumps.html">Dumps...</a>
1961 </form>
1962 <!--
Jakob Kummerow 2014/03/24 15:21:32 nit: remove this if it's not needed
Jarin 2014/03/26 10:54:28 Done.
1963 <form class="dumpname" action="dump.html">
1964 Dump id:
1965 <input type="search" name="dumpid">
1966 <input type="submit" name="action" value="Go">
1967 <input type="hidden" name="dump" value="{1}">
1968 </form>
1969 -->
1970 </div>
1971 <br>
1972 <hr>
1973 """
1974
1975 WEB_FOOTER = """
1976 </body>
1977 </html>
1978 """
1979
1980 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.
1981 def __init__(self, message):
1982 Exception.__init__(self, message)
1983
1984 class InspectionWebHandler(BaseHTTPRequestHandler):
1985 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.
1986 name = qc.get("dump", [None])[0]
1987 return self.server.get_dump_formatter(name)
1988
1989 def send_success_html_headers(self):
1990 self.send_response(200)
1991 self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
1992 self.send_header("Pragma", "no-cache")
1993 self.send_header("Expires", "0")
1994 self.send_header('Content-type','text/html')
1995 self.end_headers()
1996 return
1997
1998 def do_GET(self):
1999 try:
2000 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.
2001 parsedurl = urlparse(self.path)
2002 qc = parse_qs(parsedurl.query)
2003 if parsedurl.path == "/dumps.html":
2004 self.send_success_html_headers()
2005 self.server.output_dumps(self.wfile)
2006 return
2007 if parsedurl.path == "/summary.html":
Jakob Kummerow 2014/03/24 15:21:32 s/if/elif/ like below? In turn you could omit all
2008 self.send_success_html_headers()
2009 self.fmt(qc).output_summary(self.wfile)
2010 return
2011 if parsedurl.path == "/info.html":
2012 self.send_success_html_headers()
2013 self.fmt(qc).output_info(self.wfile)
2014 return
2015 elif parsedurl.path == "/modules.html":
2016 self.send_success_html_headers()
2017 self.server.formatter.output_modules(self.wfile)
2018 return
2019 elif parsedurl.path == "/search.html":
2020 address = qc.get("val", [])
2021 if len(address) != 1:
2022 self.send_error(404, "Invalid params")
2023 return
2024 if qc.get("search", None) != None:
2025 self.send_success_html_headers()
2026 self.fmt(qc).output_search_res(self.wfile, address[0])
2027 return
2028 elif parsedurl.path == "/disasm.html":
2029 address = qc.get("val", [])
2030 exact = qc.get("exact", ["on"])
2031 if len(address) != 1:
2032 self.send_error(404, "Invalid params")
2033 return
2034 self.send_success_html_headers()
2035 self.fmt(qc).output_disasm(self.wfile, address[0], exact[0])
2036 return
2037 elif parsedurl.path == "/data.html":
2038 address = qc.get("val", [])
2039 datakind = qc.get("type", ["address"])
2040 if len(address) == 1 and len(datakind) == 1:
2041 self.send_success_html_headers()
2042 self.fmt(qc).output_data(self.wfile, address[0], datakind[0])
2043 else:
2044 self.send_error(404,'Invalid params')
2045 return
2046 elif parsedurl.path == "/setdumpdesc":
2047 name = qc.get("dump", [""])
2048 description = qc.get("description", [""])
2049
2050 if len(name) == 1 and len(description) == 1:
2051 name = name[0]
2052 description = description[0]
2053 if self.server.set_dump_desc(name, description):
2054 self.send_success_html_headers()
2055 self.wfile.write("OK")
2056 return
2057 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.
2058 return
2059
2060 elif parsedurl.path == "/setcomment":
2061 address = qc.get("address", [])
2062 comment = qc.get("comment", [""])
2063
2064 if len(address) == 1 and len(comment) == 1:
2065 address = address[0]
2066 comment = comment[0]
2067 self.fmt(qc).set_comment(address, comment)
2068 self.send_success_html_headers()
2069 self.wfile.write("OK")
2070 else:
2071 self.send_error(404,'Invalid params')
2072 return
2073
2074 elif parsedurl.path == "/setpageaddress":
2075 kind = qc.get("kind", [])
2076 address = qc.get("address", [""])
2077
2078 if len(kind) == 1 and len(address) == 1:
2079 kind = kind[0]
2080 address = address[0]
2081 self.fmt(qc).set_page_address(kind, address)
2082 self.send_success_html_headers()
2083 self.wfile.write("OK")
2084 else:
2085 self.send_error(404,'Invalid params')
2086 return
2087
2088 else:
2089 self.send_error(404,'File Not Found: %s' % self.path)
2090
2091 except IOError:
2092 self.send_error(404,'File Not Found: %s' % self.path)
2093
2094 except WebParameterError as e:
2095 self.send_error(404, 'Web parameter error: %s' % e.message)
2096
2097 HTML_REG_FORMAT = "<span class=\"register\"><b>%s</b>:&nbsp;%s</span>\n"
2098
2099 class InspectionWebFormatter(object):
2100 CONTEXT_FULL = 0
2101 CONTEXT_SHORT = 1
2102
2103 def __init__(self, options, minidump_name, server):
2104 self.dumpfilename = os.path.split(minidump_name)[1]
2105 self.encfilename = urllib.urlencode({ 'dump' : self.dumpfilename })
2106 self.reader = MinidumpReader(options, minidump_name)
2107 self.server = server
2108
2109 # Set up the heap
2110 exception_thread = self.reader.thread_map[self.reader.exception.thread_id]
2111 stack_top = self.reader.ExceptionSP()
2112 stack_bottom = exception_thread.stack.start + \
Jakob Kummerow 2014/03/24 15:21:32 nit: the style guide prefers () over trailing \, i
2113 exception_thread.stack.memory.data_size
2114 stack_map = {self.reader.ExceptionIP(): -1}
2115 for slot in xrange(stack_top, stack_bottom, self.reader.PointerSize()):
2116 maybe_address = self.reader.ReadUIntPtr(slot)
2117 if not maybe_address in stack_map:
2118 stack_map[maybe_address] = slot
2119 self.heap = V8Heap(self.reader, stack_map)
2120
2121 self.padawan = InspectionPadawan(self.reader, self.heap)
2122 self.comments = InspectionInfo(minidump_name, self.reader)
2123 self.padawan.known_first_data_page = \
2124 self.comments.get_page_address("datapage")
2125 self.padawan.known_first_map_page = \
2126 self.comments.get_page_address("mappage")
2127 self.padawan.known_first_pointer_page = \
2128 self.comments.get_page_address("pointerpage")
2129
2130 def set_comment(self, straddress, comment):
2131 try:
2132 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.
2133 self.dumpfilename, straddress, comment)
2134 address = int(straddress, 0)
2135 self.comments.set_comment(address, comment)
2136 except ValueError:
2137 print "Invalid address"
2138
2139 def set_page_address(self, kind, straddress):
2140 try:
2141 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.
2142 self.dumpfilename, kind, straddress)
2143 address = int(straddress, 0)
2144 if kind == "datapage":
2145 self.padawan.known_first_data_page = address
2146 elif kind == "mappage":
2147 self.padawan.known_first_map_page = address
2148 elif kind == "pointerpage":
2149 self.padawan.known_first_pointer_page = address
2150 self.comments.save_page_address(kind, address)
2151 except ValueError:
2152 print "Invalid address"
2153
2154 def td_from_address(self, f, address):
2155 f.write("<td %s>" % self.comments.get_style_class_string(address))
2156
2157 def format_address(self, maybeaddress, straddress = None):
2158 if maybeaddress == None:
Jakob Kummerow 2014/03/24 15:21:32 nit: the style guide wants "is None" instead of "=
2159 return "not in dump"
2160 else:
2161 if straddress == None:
2162 straddress = "0x" + self.reader.FormatIntPtr(maybeaddress)
2163 style_class = ""
2164 if not self.reader.IsValidAddress(maybeaddress):
2165 style_class = " class=\"nodump\""
2166 return "<a %s href=\"search.html?%s&val=%s&search=yes\">%s</a>" % (
2167 style_class, self.encfilename, straddress, straddress)
2168
2169 def output_header(self, f):
2170 f.write(WEB_HEADER.format(self.encfilename, cgi.escape(self.dumpfilename)))
2171
2172 def output_footer(self, f):
2173 f.write(WEB_FOOTER)
2174
2175 MAX_CONTEXT_STACK = 4096
2176
2177 def output_summary(self, f):
2178 self.output_header(f)
2179 f.write('<div class="code">')
2180 self.output_context(f, InspectionWebFormatter.CONTEXT_SHORT)
2181 self.output_disasm_pc(f)
2182
2183 # Output stack
2184 exception_thread = self.reader.thread_map[self.reader.exception.thread_id]
2185 stack_bottom = exception_thread.stack.start + \
2186 min(exception_thread.stack.memory.data_size, self.MAX_CONTEXT_STACK)
2187 stack_top = self.reader.ExceptionSP()
2188 self.output_words(f, stack_top - 16, stack_bottom, stack_top, "Stack")
2189
2190 f.write('</div>')
2191 self.output_footer(f)
2192 return
2193
2194 def output_info(self, f):
2195 self.output_header(f)
2196 f.write("<h3>Dump info</h3>\n")
2197 f.write("Description: ")
2198 self.server.output_dump_desc_field(f, self.dumpfilename)
2199 f.write("<br>\n")
2200 f.write("Filename: ")
2201 f.write("<span class=\"code\">%s</span><br>\n" % (self.dumpfilename))
2202 dt = datetime.datetime.fromtimestamp(self.reader.header.time_date_stampt)
2203 f.write("Timestamp: %s<br>\n" % dt.strftime('%Y-%m-%d %H:%M:%S'))
2204 self.output_context(f, InspectionWebFormatter.CONTEXT_FULL)
2205 self.output_address_ranges(f)
2206 self.output_footer(f)
2207 return
2208
2209 def output_address_ranges(self, f):
2210 regions = { }
2211 def print_region(reader, start, size, location):
2212 regions[start] = size
2213 self.reader.ForEachMemoryRegion(print_region)
2214 f.write("<h3>Available memory regions</h3>\n")
2215 f.write('<div class="code">')
2216 f.write("<table class=\"regions\">\n")
2217 f.write("<thead><tr>")
2218 f.write("<th>Start address</th>")
2219 f.write("<th>End address</th>")
2220 f.write("<th>Number of bytes</th>")
2221 f.write("</tr></thead>")
2222 f.write("</thead>\n")
2223 for start in sorted(regions):
2224 size = regions[start]
2225 f.write("<tr>")
2226 f.write("<td>%s</td>" % self.format_address(start))
2227 f.write("<td>&nbsp;%s</td>" % self.format_address(start + size))
2228 f.write("<td>&nbsp;%d</td>" % size)
2229 f.write("</tr>\n")
2230 f.write("</table>\n")
2231 f.write('</div>')
2232 return
2233
2234 def output_module_details(self, f, module):
2235 f.write("<b>%s</b>" % GetModuleName(self.reader, module))
2236 file_version = GetVersionString(module.version_info.dwFileVersionMS,
2237 module.version_info.dwFileVersionLS)
2238 product_version = GetVersionString(module.version_info.dwProductVersionMS,
2239 module.version_info.dwProductVersionLS)
2240 f.write("<br>&nbsp;&nbsp;\n")
2241 f.write("base: %s" % self.reader.FormatIntPtr(module.base_of_image))
2242 f.write("<br>&nbsp;&nbsp;\n")
2243 f.write(" end: %s" % self.reader.FormatIntPtr(module.base_of_image +
2244 module.size_of_image))
2245 f.write("<br>&nbsp;&nbsp;\n")
2246 f.write(" file version: %s" % file_version)
2247 f.write("<br>&nbsp;&nbsp;\n")
2248 f.write(" product version: %s" % product_version)
2249 f.write("<br>&nbsp;&nbsp;\n")
2250 time_date_stamp = datetime.datetime.fromtimestamp(module.time_date_stamp)
2251 f.write(" timestamp: %s" % time_date_stamp)
2252 f.write("<br>\n");
2253
2254 def output_modules(self, f):
2255 self.output_header(f)
2256 f.write('<div class="code">')
2257 for module in self.reader.module_list.modules:
2258 self.output_module_details(f, module)
2259 f.write("</div>")
2260 self.output_footer(f)
2261 return
2262
2263 def output_context(self, f, details):
2264 exception_thread = self.reader.thread_map[self.reader.exception.thread_id]
2265 f.write("<h3>Exception context</h3>")
2266 f.write('<div class="code">\n')
2267 f.write("Thread id: %d" % exception_thread.id)
2268 f.write("&nbsp;&nbsp; Exception code: %08X\n" % (
Jakob Kummerow 2014/03/24 15:21:32 nit: I'd break as: f.write("&nbsp;&nbsp; Excep
Jarin 2014/03/26 10:54:28 Done.
2269 self.reader.exception.exception.code))
2270 if details == InspectionWebFormatter.CONTEXT_FULL:
2271 if self.reader.exception.exception.parameter_count > 0:
2272 f.write("&nbsp;&nbsp; Exception parameters: \n")
2273 for i in xrange(0, self.reader.exception.exception.parameter_count):
2274 f.write("%08x" % self.reader.exception.exception.information[i])
2275 f.write("<br><br>\n")
2276
2277 for r in CONTEXT_FOR_ARCH[self.reader.arch]:
2278 f.write(HTML_REG_FORMAT % (
2279 r, self.format_address(self.reader.Register(r))))
2280 # TODO(vitalyr): decode eflags.
2281 if self.reader.arch == MD_CPU_ARCHITECTURE_ARM:
2282 f.write("<b>cpsr</b>: %s" % bin(self.reader.exception_context.cpsr)[2:])
2283 else:
2284 f.write("<b>eflags</b>: %s" % (
2285 bin(self.reader.exception_context.eflags)[2:]))
2286 f.write('</div>\n')
2287 return
2288
2289 def align_down(self, a, size):
2290 alignment_correction = a % size
2291 return a - alignment_correction
2292
2293 def align_up(self, a, size):
2294 alignment_correction = (size - 1) - (
2295 (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.
2296 return a + alignment_correction
2297
2298
2299 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.
2300 heap_object = self.padawan.SenseObject(address)
2301 return cgi.escape(str(heap_object or ""))
2302
2303
2304 def output_data(self, f, straddress, datakind):
2305 try:
2306 self.output_header(f)
2307 address = int(straddress, 0)
2308 if not self.reader.IsValidAddress(address):
2309 f.write("<h3>Address 0x%x not found in the dump.</h3>" % address)
2310 return
2311 region = self.reader.FindRegion(address)
2312 if datakind == "address":
2313 self.output_words(f, region[0], region[0] + region[1], address, "Dump")
2314 elif datakind == "ascii":
2315 self.output_ascii(f, region[0], region[0] + region[1], address)
2316 self.output_footer(f)
2317
2318 except ValueError:
2319 f.write("<h3>Unrecognized address format \"%s\".</h3>" % straddress)
2320 return
2321
2322
2323 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.
2324 highlight_address, desc):
2325 region = self.reader.FindRegion(highlight_address)
2326 if region == None:
2327 f.write("<h3>Address 0x%x not found in the dump.</h3>\n" % (
2328 highlight_address))
2329 return
2330 size = self.heap.PointerSize()
2331 start_address = self.align_down(start_address, size)
2332 low = self.align_down(region[0], size)
2333 high = self.align_up(region[0] + region[1], size)
2334 if start_address < low:
2335 start_address = low
2336 end_address = self.align_up(end_address, size)
2337 if end_address > high:
2338 end_address = high
2339
2340 expand = ""
2341 if start_address != low or end_address != high:
2342 expand = "(<a href=\"data.html?%s&val=0x%x#highlight\">more...</a>)" % (
2343 self.encfilename, highlight_address)
2344
2345 f.write(("<h3>%s 0x%x - 0x%x, "
2346 "highlighting <a href=\"#highlight\">0x%x</a> %s</h3>\n") % (
2347 desc, start_address, end_address, highlight_address, expand))
2348 f.write('<div class="code">')
2349 f.write("<table class=\"codedump\""
2350 "cellspacing=\"0\" cellpadding=\"0\">\n")
2351
2352 for slot in xrange(start_address,
2353 end_address,
Jakob Kummerow 2014/03/24 15:21:32 nit: fits on one line?
Jarin 2014/03/26 10:54:28 Done.
2354 size):
2355 heap_object = ""
2356 maybe_address = None
2357 end_region = region[0] + region[1]
2358 if slot < region[0] or slot + size > end_region:
2359 straddress = "0x"
2360 for i in xrange(end_region, slot + size):
2361 straddress += "??"
2362 for i in reversed(
2363 xrange(max(slot, region[0]), min(slot + size, end_region))):
2364 straddress += "%02x" % self.reader.ReadU8(i)
2365 for i in xrange(slot, region[0]):
2366 straddress += "??"
2367 else:
2368 maybe_address = self.reader.ReadUIntPtr(slot)
2369 straddress = self.format_address(maybe_address)
2370 if maybe_address:
2371 heap_object = self.format_object(maybe_address)
2372
2373 address_fmt = "%s&nbsp;</td>\n"
2374 if slot == highlight_address:
2375 f.write("<tr class=\"highlight-line\">\n")
2376 address_fmt = "<a name=\"highlight\"></a>%s&nbsp;</td>\n"
2377 if slot <= highlight_address and highlight_address < slot + size:
2378 f.write("<tr class=\"inexact-highlight-line\">\n")
2379 address_fmt = "<a name=\"highlight\"></a>%s&nbsp;</td>\n"
2380 else:
2381 f.write("<tr>\n")
2382
2383 f.write(" <td>")
2384 self.output_comment_box(f, "da-", slot)
2385 f.write(" </td>\n")
2386 self.td_from_address(f, slot)
2387 f.write(address_fmt % self.format_address(slot))
2388 self.td_from_address(f, maybe_address)
2389 f.write(":&nbsp; %s &nbsp;</td>\n" % straddress)
2390 f.write(" <td>")
2391 if maybe_address != None:
2392 self.output_comment_box(
2393 f, "sv-" + self.reader.FormatIntPtr(slot), maybe_address)
2394 f.write(" </td>\n")
2395 f.write(" <td>%s</td>\n" % (heap_object or ''))
2396 f.write("</tr>\n")
2397 f.write("</table>\n")
2398 f.write("</div>")
2399 return
2400
2401 def output_ascii(self, f, start_address, end_address, highlight_address):
2402 region = self.reader.FindRegion(highlight_address)
2403 if region == None:
2404 f.write("<h3>Address %x not found in the dump.</h3>" %
2405 highlight_address)
2406 return
2407 if start_address < region[0]:
2408 start_address = region[0]
2409 if end_address > region[0] + region[1]:
2410 end_address = region[0] + region[1]
2411
2412 expand = ""
2413 if start_address != region[0] or end_address != region[0] + region[1]:
2414 expand = ("(<a href=\"data.html?%s&val=0x%x&type=ascii#highlight\">"
2415 "more...</a>)") % (
2416 self.encfilename, highlight_address)
2417
2418 f.write("<h3>ASCII dump 0x%x - 0x%x, highlighting 0x%x %s</h3>" % (
2419 start_address, end_address, highlight_address, expand))
2420
2421 line_width = 64
2422
2423 f.write('<div class="code">')
2424
2425 start = self.align_down(start_address, line_width)
2426
2427 for address in xrange(start, end_address):
2428 if address % 64 == 0:
2429 if address != start:
2430 f.write("<br>")
2431 f.write("0x%08x:&nbsp;" % address)
2432 if address < start_address:
2433 f.write("&nbsp;")
2434 else:
2435 if address == highlight_address:
2436 f.write("<span class=\"highlight\">")
2437 code = self.reader.ReadU8(address)
2438 if code < 127 and code >= 32:
2439 f.write("&#")
2440 f.write(str(code))
2441 else:
2442 f.write("&middot")
Jakob Kummerow 2014/03/24 15:21:32 Doesn't this need s/&middot/&middot;/ ?
Jarin 2014/03/26 10:54:28 Done (the char code above needs ';', too)
2443 if address == highlight_address:
2444 f.write("</span>")
2445 f.write("</div>")
2446 return
2447
2448 def output_disasm(self, f, straddress, strexact):
2449 try:
2450 self.output_header(f)
2451 address = int(straddress, 0)
2452 if not self.reader.IsValidAddress(address):
2453 f.write("<h3>Address 0x%x not found in the dump.</h3>" % address)
2454 return
2455 region = self.reader.FindRegion(address)
2456 self.output_disasm_range(
2457 f, region[0], region[0] + region[1], address, strexact == "on")
2458 self.output_footer(f)
2459 except ValueError:
2460 f.write("<h3>Unrecognized address format \"%s\".</h3>" % straddress)
2461 return
2462
2463 def output_disasm_range(
2464 self, f, start_address, end_address, highlight_address, exact):
2465 region = self.reader.FindRegion(highlight_address)
2466 if start_address < region[0]:
2467 start_address = region[0]
2468 if end_address > region[0] + region[1]:
2469 end_address = region[0] + region[1]
2470 count = end_address - start_address
2471 lines = self.reader.GetDisasmLines(start_address, count)
2472 found = False
2473 if exact:
2474 for line in lines:
2475 if line[0] + start_address == highlight_address:
2476 found = True
2477 break
2478 if not found:
2479 start_address = highlight_address
2480 count = end_address - start_address
2481 lines = self.reader.GetDisasmLines(highlight_address, count)
2482 expand = ""
2483 if start_address != region[0] or end_address != region[0] + region[1]:
2484 exactness = ""
2485 if exact and not found and end_address == region[0] + region[1]:
2486 exactness = "&exact=off"
2487 expand = ("(<a href=\"disasm.html?%s%s"
2488 "&val=0x%x#highlight\">more...</a>)") % (
2489 self.encfilename, exactness, highlight_address)
2490
2491 f.write("<h3>Disassembling 0x%x - 0x%x, highlighting 0x%x %s</h3>" % (
2492 start_address, end_address, highlight_address, expand))
2493 f.write('<div class="code">')
2494 f.write("<table class=\"codedump\" cellspacing=\"0\" cellpadding=\"0\">\n");
2495 for i in xrange(0, len(lines)):
2496 line = lines[i]
2497 next_address = count
2498 if i + 1 < len(lines):
2499 next_line = lines[i + 1]
2500 next_address = next_line[0]
2501 self.format_disasm_line(
2502 f, start_address, line, next_address, highlight_address)
2503 f.write("</table>\n")
2504 f.write("</div>")
2505 return
2506
2507 def annotate_disasm_addresses(self, line):
2508 extra = []
2509 for m in ADDRESS_RE.finditer(line):
2510 maybe_address = int(m.group(0), 16)
2511 formatted_address = self.format_address(maybe_address, m.group(0))
2512 line = line.replace(m.group(0), formatted_address)
2513 object = self.padawan.SenseObject(maybe_address)
2514 if not object: continue
2515 extra.append(cgi.escape(str(object)))
2516 if len(extra) == 0: return line
2517 return "%s <span class=\"disasmcmt\">;; %s</span>" % (
2518 line, ", ".join(extra))
2519
2520
2521 def format_disasm_line(
2522 self, f, start, line, next_address, highlight_address):
2523 line_address = start + line[0]
2524 address_fmt = " <td>%s</td>\n"
2525 if line_address == highlight_address:
2526 f.write("<tr class=\"highlight-line\">\n")
2527 address_fmt = " <td><a name=\"highlight\">%s</a></td>\n"
2528 elif (line_address < highlight_address and
2529 highlight_address < next_address + start):
2530 f.write("<tr class=\"inexact-highlight-line\">\n")
2531 address_fmt = " <td><a name=\"highlight\">%s</a></td>\n"
2532 else:
2533 f.write("<tr>\n")
2534 num_bytes = next_address - line[0]
2535 stack_slot = self.heap.stack_map.get(line_address)
2536 marker = ""
2537 if stack_slot:
2538 marker = "=>"
2539 op_offset = 3 * num_bytes - 1
2540
2541 code = line[1]
2542 # Compute the actual call target which the disassembler is too stupid
2543 # to figure out (it adds the call offset to the disassembly offset rather
2544 # than the absolute instruction address).
2545 if self.heap.reader.arch == MD_CPU_ARCHITECTURE_X86:
2546 if code.startswith("e8"):
2547 words = code.split()
2548 if len(words) > 6 and words[5] == "call":
2549 offset = int(words[4] + words[3] + words[2] + words[1], 16)
2550 target = (line_address + offset + 5) & 0xFFFFFFFF
2551 code = code.replace(words[6], "0x%08x" % target)
2552 # TODO(jkummerow): port this hack to ARM and x64.
2553
2554 opcodes = code[:op_offset]
2555 code = self.annotate_disasm_addresses(code[op_offset:])
2556 f.write(" <td>")
2557 self.output_comment_box(f, "codel-", line_address)
2558 f.write("</td>\n")
2559 f.write(address_fmt % marker)
2560 self.td_from_address(f, line_address)
2561 f.write("%s (+0x%x)</td>\n" % (
2562 self.format_address(line_address), line[0]))
2563 f.write(" <td>:&nbsp;%s&nbsp;</td>\n" % opcodes)
2564 f.write(" <td>%s</td>\n" % code)
2565 f.write("</tr>\n")
2566
2567 def output_comment_box(self, f, prefix, address):
2568 f.write(("<input type=\"text\" class=\"commentinput\""
2569 "id=\"%s-address-0x%s\" onchange=\"cmt()\" value=\"%s\">\n") % (
2570 prefix,
2571 self.reader.FormatIntPtr(address),
2572 cgi.escape(self.comments.get_comment(address)) or ""))
2573
2574 MAX_FOUND_RESULTS = 100
2575
2576 def output_find_results(self, f, results):
2577 f.write("Addresses")
2578 toomany = len(results) > self.MAX_FOUND_RESULTS
2579 if toomany:
2580 f.write("(found %i results, displaying only first %i)" % (
2581 len(results), self.MAX_FOUND_RESULTS))
2582 f.write(": \n")
2583 results = sorted(results)
2584 results = results[:min(len(results), self.MAX_FOUND_RESULTS)]
2585 for address in results:
2586 f.write("<span %s>%s</span>\n" % (
2587 self.comments.get_style_class_string(address), \
2588 self.format_address(address)))
2589 if toomany:
2590 f.write("...\n")
2591
2592
Jakob Kummerow 2014/03/24 15:21:32 nit: just one empty line
Jarin 2014/03/26 10:54:28 Done.
2593 def output_page_info(self, f, page_kind, page_address, my_page_address):
2594 if my_page_address == page_address and page_address != 0:
2595 f.write("Marked first %s page.\n" % page_kind)
2596 else:
2597 f.write("<span id=\"%spage\" style=\"display:none\">" % page_kind)
2598 f.write("Marked first %s page." % page_kind)
2599 f.write("</span>\n")
2600 f.write("<button onclick=\"onpage('%spage', '0x%x')\">" % (
2601 page_kind, my_page_address))
2602 f.write("Mark as first %s page</button>\n" % page_kind)
2603 return
2604
2605 def output_search_res(self, f, straddress):
2606 try:
2607 self.output_header(f)
2608 f.write("<h3>Search results for %s</h3>" % straddress)
2609
2610 address = int(straddress, 0)
2611
2612 f.write("Comment: ")
2613 self.output_comment_box(f, "search-", address)
2614 f.write("<br>\n")
2615
2616 page_address = address & ~self.heap.PageAlignmentMask()
2617
2618 f.write("Page info: \n")
2619 self.output_page_info(f, "data", self.padawan.known_first_data_page, \
2620 page_address)
2621 self.output_page_info(f, "map", self.padawan.known_first_map_page, \
2622 page_address)
2623 self.output_page_info(f, "pointer", \
2624 self.padawan.known_first_pointer_page, \
2625 page_address)
2626
2627 if not self.reader.IsValidAddress(address):
2628 f.write("<h3>The contents at address %s not found in the dump.</h3>" % \
2629 straddress)
2630 else:
2631 # Print as words
2632 self.output_words(f, address - 8, address + 32, address, "Dump")
2633
2634 # Print as ASCII
2635 f.write("<hr>\n")
2636 self.output_ascii(f, address, address + 256, address)
2637
2638 # Print as code
2639 f.write("<hr>\n")
2640 self.output_disasm_range(f, address - 16, address + 16, address, True)
2641
2642 aligned_res, unaligned_res = self.reader.FindWordList(address)
2643
2644 if len(aligned_res) > 0:
2645 f.write("<h3>Occurrences of 0x%x at aligned addresses</h3>\n" %
2646 address)
2647 self.output_find_results(f, aligned_res)
2648
2649 if len(unaligned_res) > 0:
2650 f.write("<h3>Occurrences of 0x%x at unaligned addresses</h3>\n" % \
2651 address)
2652 self.output_find_results(f, unaligned_res)
2653
2654 if len(aligned_res) + len(unaligned_res) == 0:
2655 f.write("<h3>No occurences of 0x%x found in the dump</h3>\n" % address)
2656
2657 self.output_footer(f)
2658
2659 except ValueError:
2660 f.write("<h3>Unrecognized address format \"%s\".</h3>" % straddress)
2661 return
2662
2663 def output_disasm_pc(self, f):
2664 address = self.reader.ExceptionIP()
2665 if not self.reader.IsValidAddress(address):
2666 return
2667 self.output_disasm_range(f, address - 16, address + 16, address, True)
2668
2669
2670 WEB_DUMPS_HEADER = """
2671 <!DOCTYPE html>
2672 <html>
2673 <head>
2674 <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
2675 <style media="screen" type="text/css">
2676
2677 .dumplist {
2678 border-collapse : collapse;
2679 border-spacing : 0px;
2680 font-family: monospace;
2681 }
2682
2683 .dumpcomments {
2684 border : 1px solid LightGray;
2685 width : 32em;
2686 }
2687
2688 </style>
2689
2690 <script type="application/javascript">
2691
2692 var dump_str = "dump-";
2693 var dump_len = dump_str.length;
2694
2695 function dump_cmt() {
2696 var s = event.srcElement.id;
2697 var index = s.indexOf(dump_str);
2698 if (index >= 0) {
2699 send_dump_desc(s.substring(index + dump_len), event.srcElement.value);
2700 }
2701 }
2702
2703 function send_dump_desc(name, desc) {
2704 xmlhttp = new XMLHttpRequest();
2705 name = encodeURIComponent(name)
2706 desc = encodeURIComponent(desc)
2707 xmlhttp.open("GET",
2708 "setdumpdesc?dump=" + name +
2709 "&description=" + desc, true);
2710 xmlhttp.send();
2711 }
2712
2713 </script>
2714
2715 <title>Dump list</title>
2716 </head>
2717
2718 <body>
2719 """
2720
2721 WEB_DUMPS_FOOTER = """
2722 </body>
2723 </html>
2724 """
2725
2726 DUMP_FILE_RE = re.compile(r"[-_0-9a-zA-Z][-\._0-9a-zA-Z]*\.dmp$")
2727
2728
2729 class InspectionWebServer(HTTPServer):
2730 def __init__(self, port_number, options, minidump_name):
2731 HTTPServer.__init__(self, ('', port_number), InspectionWebHandler)
2732 splitpath = os.path.split(minidump_name)
2733 self.dumppath = splitpath[0]
2734 self.dumpfilename = splitpath[1]
2735 self.formatter = InspectionWebFormatter(options, minidump_name, self)
2736 self.formatters = { self.dumpfilename : self.formatter }
2737 print "Initialized at path '%s'" % self.dumppath
2738
2739 def output_dump_desc_field(self, f, name):
2740 try:
2741 descfile = open(os.path.join(self.dumppath, name + ".desc"), "r")
2742 desc = descfile.readline()
2743 descfile.close()
2744 except IOError:
2745 desc = ""
2746 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.
2747 "id=\"dump-%s\" onchange=\"dump_cmt()\" value=\"%s\">\n") % (
2748 cgi.escape(name),
2749 desc))
2750
2751 def set_dump_desc(self, name, description):
2752 if not DUMP_FILE_RE.match(name):
2753 return False
2754 fname = os.path.join(self.dumppath, name)
2755 if not os.path.isfile(fname):
2756 return False
2757 fname = fname + ".desc"
2758 descfile = open(fname, "w")
2759 descfile.write(description)
2760 descfile.close()
2761 return True
2762
2763 def get_dump_formatter(self, name):
2764 if name == None:
2765 # Get the default formatter
2766 return self.formatter
2767 else:
2768 if not DUMP_FILE_RE.match(name):
2769 raise WebParameterError("Invalid name '%s'" % name)
2770 fmt = self.formatters.get(name, None)
2771 if fmt == None:
2772 try:
2773 fmt = InspectionWebFormatter(
2774 options, os.path.join(self.dumppath, name), self)
2775 self.formatters[name] = fmt
2776 except IOError:
2777 raise WebParameterError("Could not open dump '%s'" % name)
2778 return fmt
2779
2780 def output_dumps(self, f):
2781 f.write(WEB_DUMPS_HEADER)
2782 f.write("<h3>List of available dumps</h3>")
2783 f.write("<table class=\"dumplist\">\n")
2784 f.write("<thead><tr>")
2785 f.write("<th>Name</th>")
2786 f.write("<th>File time</th>")
2787 f.write("<th>Comment</th>")
2788 f.write("</tr></thead>")
2789 dumps_by_time = { }
2790 for fname in os.listdir(self.dumppath):
2791 if DUMP_FILE_RE.match(fname):
2792 mtime = os.stat(os.path.join(self.dumppath, fname)).st_mtime
2793 fnames = dumps_by_time.get(mtime, [])
2794 fnames.append(fname)
2795 dumps_by_time[mtime] = fnames
2796
2797 for mtime in sorted(dumps_by_time, reverse=True):
2798 fnames = dumps_by_time[mtime]
2799 for fname in fnames:
2800 f.write("<tr>\n")
2801 f.write("<td><a href=\"summary.html?%s\">%s</td>\n" % (
2802 (urllib.urlencode({ 'dump' : fname }), fname)))
2803 f.write("<td>&nbsp;&nbsp;&nbsp;")
2804 f.write(datetime.datetime.fromtimestamp(mtime))
2805 f.write("</td>")
2806 f.write("<td>&nbsp;&nbsp;&nbsp;")
2807 self.output_dump_desc_field(f, fname)
2808 f.write("</td>")
2809 f.write("</tr>\n")
2810 f.write("</table>\n")
2811 f.write(WEB_DUMPS_FOOTER)
2812 return
1656 2813
1657 class InspectionShell(cmd.Cmd): 2814 class InspectionShell(cmd.Cmd):
1658 def __init__(self, reader, heap): 2815 def __init__(self, reader, heap):
1659 cmd.Cmd.__init__(self) 2816 cmd.Cmd.__init__(self)
1660 self.reader = reader 2817 self.reader = reader
1661 self.heap = heap 2818 self.heap = heap
1662 self.padawan = InspectionPadawan(reader, heap) 2819 self.padawan = InspectionPadawan(reader, heap)
1663 self.prompt = "(grok) " 2820 self.prompt = "(grok) "
1664 2821
1665 def do_da(self, address): 2822 def do_da(self, address):
(...skipping 323 matching lines...) Expand 10 before | Expand all | Expand 10 after
1989 heap_object.Print(Printer()) 3146 heap_object.Print(Printer())
1990 print 3147 print
1991 3148
1992 reader.Dispose() 3149 reader.Dispose()
1993 3150
1994 3151
1995 if __name__ == "__main__": 3152 if __name__ == "__main__":
1996 parser = optparse.OptionParser(USAGE) 3153 parser = optparse.OptionParser(USAGE)
1997 parser.add_option("-s", "--shell", dest="shell", action="store_true", 3154 parser.add_option("-s", "--shell", dest="shell", action="store_true",
1998 help="start an interactive inspector shell") 3155 help="start an interactive inspector shell")
3156 parser.add_option("-w", "--web", dest="web", action="store_true",
3157 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)
1999 parser.add_option("-c", "--command", dest="command", default="", 3158 parser.add_option("-c", "--command", dest="command", default="",
2000 help="run an interactive inspector shell command and exit") 3159 help="run an interactive inspector shell command and exit")
2001 parser.add_option("-f", "--full", dest="full", action="store_true", 3160 parser.add_option("-f", "--full", dest="full", action="store_true",
2002 help="dump all information contained in the minidump") 3161 help="dump all information contained in the minidump")
2003 parser.add_option("--symdir", dest="symdir", default=".", 3162 parser.add_option("--symdir", dest="symdir", default=".",
2004 help="directory containing *.pdb.sym file with symbols") 3163 help="directory containing *.pdb.sym file with symbols")
2005 parser.add_option("--objdump", 3164 parser.add_option("--objdump",
2006 default="/usr/bin/objdump", 3165 default="/usr/bin/objdump",
2007 help="objdump tool to use [default: %default]") 3166 help="objdump tool to use [default: %default]")
2008 options, args = parser.parse_args() 3167 options, args = parser.parse_args()
2009 if os.path.exists(options.objdump): 3168 if os.path.exists(options.objdump):
2010 disasm.OBJDUMP_BIN = options.objdump 3169 disasm.OBJDUMP_BIN = options.objdump
2011 OBJDUMP_BIN = options.objdump 3170 OBJDUMP_BIN = options.objdump
2012 else: 3171 else:
2013 print "Cannot find %s, falling back to default objdump" % options.objdump 3172 print "Cannot find %s, falling back to default objdump" % options.objdump
2014 if len(args) != 1: 3173 if len(args) != 1:
2015 parser.print_help() 3174 parser.print_help()
2016 sys.exit(1) 3175 sys.exit(1)
2017 AnalyzeMinidump(options, args[0]) 3176 if options.web:
3177 try:
3178 server = InspectionWebServer(PORT_NUMBER, options, args[0])
3179 print 'Started httpserver on port ' , PORT_NUMBER
3180 webbrowser.open('http://localhost:8081/summary.html')
3181 server.serve_forever()
3182 except KeyboardInterrupt:
3183 print '^C received, shutting down the web server'
3184 server.socket.close()
3185 else:
3186 AnalyzeMinidump(options, args[0])
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698