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