OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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>   | |
1944 <a href="info.html?{0}">Dump info</a>   | |
1945 <a href="modules.html?{0}">Modules</a> | |
1946 | |
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 | |
1953 | |
1954 | |
1955 <input type="search" name="val"> | |
1956 <input type="submit" name="disasm" value="Disasm"> | |
1957 | |
1958 | |
1959 | |
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>: %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> %s</td>" % self.format_address(start + size)) | |
2228 f.write("<td> %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> \n") | |
2241 f.write("base: %s" % self.reader.FormatIntPtr(module.base_of_image)) | |
2242 f.write("<br> \n") | |
2243 f.write(" end: %s" % self.reader.FormatIntPtr(module.base_of_image + | |
2244 module.size_of_image)) | |
2245 f.write("<br> \n") | |
2246 f.write(" file version: %s" % file_version) | |
2247 f.write("<br> \n") | |
2248 f.write(" product version: %s" % product_version) | |
2249 f.write("<br> \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(" 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.
| |
2269 self.reader.exception.exception.code)) | |
2270 if details == InspectionWebFormatter.CONTEXT_FULL: | |
2271 if self.reader.exception.exception.parameter_count > 0: | |
2272 f.write(" 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 </td>\n" | |
2374 if slot == highlight_address: | |
2375 f.write("<tr class=\"highlight-line\">\n") | |
2376 address_fmt = "<a name=\"highlight\"></a>%s </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 </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(": %s </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: " % address) | |
2432 if address < start_address: | |
2433 f.write(" ") | |
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("·") | |
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)
| |
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>: %s </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> ") | |
2804 f.write(datetime.datetime.fromtimestamp(mtime)) | |
2805 f.write("</td>") | |
2806 f.write("<td> ") | |
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 Loading... | |
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]) | |
OLD | NEW |