diff options
author | Ben Murdoch <benm@google.com> | 2011-05-25 10:26:03 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2011-05-25 16:24:42 +0100 |
commit | e0cee9b3ed82e2391fd85d118aeaa4ea361c687d (patch) | |
tree | 31c7963cf0dfc88be29e765884e1f235076c03a4 /tools | |
parent | 1e0659c275bb392c045087af4f6b0d7565cb3d77 (diff) | |
download | v8-e0cee9b3ed82e2391fd85d118aeaa4ea361c687d.tar.gz |
Update V8 to r7079 as required by WebKit r80534.
Change-Id: I487c152e485d5a40b68997d7c0d2f1fba5da0834
Diffstat (limited to 'tools')
-rw-r--r-- | tools/disasm.py | 92 | ||||
-rwxr-xr-x | tools/grokdump.py | 840 | ||||
-rw-r--r-- | tools/gyp/v8.gyp | 2 | ||||
-rwxr-xr-x | tools/linux-tick-processor.py | 78 | ||||
-rwxr-xr-x | tools/ll_prof.py | 82 | ||||
-rw-r--r-- | tools/profile.js | 141 | ||||
-rw-r--r-- | tools/splaytree.py | 226 | ||||
-rw-r--r-- | tools/tickprocessor.js | 75 | ||||
-rw-r--r-- | tools/tickprocessor.py | 571 | ||||
-rw-r--r-- | tools/utils.py | 2 | ||||
-rw-r--r-- | tools/v8.xcodeproj/project.pbxproj | 6 | ||||
-rw-r--r-- | tools/visual_studio/x64.vsprops | 1 | ||||
-rwxr-xr-x | tools/windows-tick-processor.py | 137 |
13 files changed, 1111 insertions, 1142 deletions
diff --git a/tools/disasm.py b/tools/disasm.py new file mode 100644 index 00000000..c326382d --- /dev/null +++ b/tools/disasm.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +# +# Copyright 2011 the V8 project authors. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import os +import re +import subprocess +import tempfile + + +# Avoid using the slow (google-specific) wrapper around objdump. +OBJDUMP_BIN = "/usr/bin/objdump" +if not os.path.exists(OBJDUMP_BIN): + OBJDUMP_BIN = "objdump" + + +_COMMON_DISASM_OPTIONS = ["-M", "intel-mnemonic", "-C"] + +_DISASM_HEADER_RE = re.compile(r"[a-f0-9]+\s+<.*:$") +_DISASM_LINE_RE = re.compile(r"\s*([a-f0-9]+):\s*(\S.*)") + +# Keys must match constants in Logger::LogCodeInfo. +_ARCH_MAP = { + "ia32": "-m i386", + "x64": "-m i386 -M x86-64", + "arm": "-m arm" # Not supported by our objdump build. +} + + +def GetDisasmLines(filename, offset, size, arch, inplace): + tmp_name = None + if not inplace: + # Create a temporary file containing a copy of the code. + assert arch in _ARCH_MAP, "Unsupported architecture '%s'" % arch + arch_flags = _ARCH_MAP[arch] + tmp_name = tempfile.mktemp(".v8code") + command = "dd if=%s of=%s bs=1 count=%d skip=%d && " \ + "%s %s -D -b binary %s %s" % ( + filename, tmp_name, size, offset, + OBJDUMP_BIN, ' '.join(_COMMON_DISASM_OPTIONS), arch_flags, + tmp_name) + else: + command = "%s %s --start-address=%d --stop-address=%d -d %s " % ( + OBJDUMP_BIN, ' '.join(_COMMON_DISASM_OPTIONS), + offset, + offset + size, + filename) + process = subprocess.Popen(command, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + out, err = process.communicate() + lines = out.split("\n") + header_line = 0 + for i, line in enumerate(lines): + if _DISASM_HEADER_RE.match(line): + header_line = i + break + if tmp_name: + os.unlink(tmp_name) + split_lines = [] + for line in lines[header_line + 1:]: + match = _DISASM_LINE_RE.match(line) + if match: + line_address = int(match.group(1), 16) + split_lines.append((line_address, match.group(2))) + return split_lines diff --git a/tools/grokdump.py b/tools/grokdump.py new file mode 100755 index 00000000..de681b2b --- /dev/null +++ b/tools/grokdump.py @@ -0,0 +1,840 @@ +#!/usr/bin/env python +# +# Copyright 2011 the V8 project authors. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import ctypes +import mmap +import optparse +import os +import disasm +import sys +import types +import codecs +import re + + +USAGE="""usage: %prog [OPTION]... + +Minidump analyzer. + +Shows the processor state at the point of exception including the +stack of the active thread and the referenced objects in the V8 +heap. Code objects are disassembled and the addresses linked from the +stack (pushed return addresses) are marked with "=>". + + +Examples: + $ %prog 12345678-1234-1234-1234-123456789abcd-full.dmp +""" + +DEBUG=False + + +def DebugPrint(s): + if not DEBUG: return + print s + + +class Descriptor(object): + """Descriptor of a structure in a memory.""" + + def __init__(self, fields): + self.fields = fields + self.is_flexible = False + for _, type_or_func in fields: + if isinstance(type_or_func, types.FunctionType): + self.is_flexible = True + break + if not self.is_flexible: + self.ctype = Descriptor._GetCtype(fields) + self.size = ctypes.sizeof(self.ctype) + + def Read(self, memory, offset): + if self.is_flexible: + fields_copy = self.fields[:] + last = 0 + for name, type_or_func in fields_copy: + if isinstance(type_or_func, types.FunctionType): + partial_ctype = Descriptor._GetCtype(fields_copy[:last]) + partial_object = partial_ctype.from_buffer(memory, offset) + type = type_or_func(partial_object) + if type is not None: + fields_copy[last] = (name, type) + last += 1 + else: + last += 1 + complete_ctype = Descriptor._GetCtype(fields_copy[:last]) + else: + complete_ctype = self.ctype + return complete_ctype.from_buffer(memory, offset) + + @staticmethod + def _GetCtype(fields): + class Raw(ctypes.Structure): + _fields_ = fields + _pack_ = 1 + + def __str__(self): + return "{" + ", ".join("%s: %s" % (field, self.__getattribute__(field)) + for field, _ in Raw._fields_) + "}" + return Raw + + +# Set of structures and constants that describe the layout of minidump +# files. Based on MSDN and Google Breakpad. + +MINIDUMP_HEADER = Descriptor([ + ("signature", ctypes.c_uint32), + ("version", ctypes.c_uint32), + ("stream_count", ctypes.c_uint32), + ("stream_directories_rva", ctypes.c_uint32), + ("checksum", ctypes.c_uint32), + ("time_date_stampt", ctypes.c_uint32), + ("flags", ctypes.c_uint64) +]) + +MINIDUMP_LOCATION_DESCRIPTOR = Descriptor([ + ("data_size", ctypes.c_uint32), + ("rva", ctypes.c_uint32) +]) + +MINIDUMP_DIRECTORY = Descriptor([ + ("stream_type", ctypes.c_uint32), + ("location", MINIDUMP_LOCATION_DESCRIPTOR.ctype) +]) + +MD_EXCEPTION_MAXIMUM_PARAMETERS = 15 + +MINIDUMP_EXCEPTION = Descriptor([ + ("code", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ("record", ctypes.c_uint64), + ("address", ctypes.c_uint64), + ("parameter_count", ctypes.c_uint32), + ("unused_alignment", ctypes.c_uint32), + ("information", ctypes.c_uint64 * MD_EXCEPTION_MAXIMUM_PARAMETERS) +]) + +MINIDUMP_EXCEPTION_STREAM = Descriptor([ + ("thread_id", ctypes.c_uint32), + ("unused_alignment", ctypes.c_uint32), + ("exception", MINIDUMP_EXCEPTION.ctype), + ("thread_context", MINIDUMP_LOCATION_DESCRIPTOR.ctype) +]) + +# Stream types. +MD_UNUSED_STREAM = 0 +MD_RESERVED_STREAM_0 = 1 +MD_RESERVED_STREAM_1 = 2 +MD_THREAD_LIST_STREAM = 3 +MD_MODULE_LIST_STREAM = 4 +MD_MEMORY_LIST_STREAM = 5 +MD_EXCEPTION_STREAM = 6 +MD_SYSTEM_INFO_STREAM = 7 +MD_THREAD_EX_LIST_STREAM = 8 +MD_MEMORY_64_LIST_STREAM = 9 +MD_COMMENT_STREAM_A = 10 +MD_COMMENT_STREAM_W = 11 +MD_HANDLE_DATA_STREAM = 12 +MD_FUNCTION_TABLE_STREAM = 13 +MD_UNLOADED_MODULE_LIST_STREAM = 14 +MD_MISC_INFO_STREAM = 15 +MD_MEMORY_INFO_LIST_STREAM = 16 +MD_THREAD_INFO_LIST_STREAM = 17 +MD_HANDLE_OPERATION_LIST_STREAM = 18 + +MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE = 80 + +MINIDUMP_FLOATING_SAVE_AREA_X86 = Descriptor([ + ("control_word", ctypes.c_uint32), + ("status_word", ctypes.c_uint32), + ("tag_word", ctypes.c_uint32), + ("error_offset", ctypes.c_uint32), + ("error_selector", ctypes.c_uint32), + ("data_offset", ctypes.c_uint32), + ("data_selector", ctypes.c_uint32), + ("register_area", ctypes.c_uint8 * MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE), + ("cr0_npx_state", ctypes.c_uint32) +]) + +MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE = 512 + +# Context flags. +MD_CONTEXT_X86 = 0x00010000 +MD_CONTEXT_X86_CONTROL = (MD_CONTEXT_X86 | 0x00000001) +MD_CONTEXT_X86_INTEGER = (MD_CONTEXT_X86 | 0x00000002) +MD_CONTEXT_X86_SEGMENTS = (MD_CONTEXT_X86 | 0x00000004) +MD_CONTEXT_X86_FLOATING_POINT = (MD_CONTEXT_X86 | 0x00000008) +MD_CONTEXT_X86_DEBUG_REGISTERS = (MD_CONTEXT_X86 | 0x00000010) +MD_CONTEXT_X86_EXTENDED_REGISTERS = (MD_CONTEXT_X86 | 0x00000020) + +def EnableOnFlag(type, flag): + return lambda o: [None, type][int((o.context_flags & flag) != 0)] + +MINIDUMP_CONTEXT_X86 = Descriptor([ + ("context_flags", ctypes.c_uint32), + # MD_CONTEXT_X86_DEBUG_REGISTERS. + ("dr0", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)), + ("dr1", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)), + ("dr2", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)), + ("dr3", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)), + ("dr6", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)), + ("dr7", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_DEBUG_REGISTERS)), + # MD_CONTEXT_X86_FLOATING_POINT. + ("float_save", EnableOnFlag(MINIDUMP_FLOATING_SAVE_AREA_X86.ctype, + MD_CONTEXT_X86_FLOATING_POINT)), + # MD_CONTEXT_X86_SEGMENTS. + ("gs", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_SEGMENTS)), + ("fs", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_SEGMENTS)), + ("es", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_SEGMENTS)), + ("ds", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_SEGMENTS)), + # MD_CONTEXT_X86_INTEGER. + ("edi", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)), + ("esi", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)), + ("ebx", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)), + ("edx", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)), + ("ecx", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)), + ("eax", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_INTEGER)), + # MD_CONTEXT_X86_CONTROL. + ("ebp", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)), + ("eip", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)), + ("cs", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)), + ("eflags", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)), + ("esp", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)), + ("ss", EnableOnFlag(ctypes.c_uint32, MD_CONTEXT_X86_CONTROL)), + # MD_CONTEXT_X86_EXTENDED_REGISTERS. + ("extended_registers", + EnableOnFlag(ctypes.c_uint8 * MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE, + MD_CONTEXT_X86_EXTENDED_REGISTERS)) +]) + +MINIDUMP_MEMORY_DESCRIPTOR = Descriptor([ + ("start", ctypes.c_uint64), + ("memory", MINIDUMP_LOCATION_DESCRIPTOR.ctype) +]) + +MINIDUMP_MEMORY_DESCRIPTOR64 = Descriptor([ + ("start", ctypes.c_uint64), + ("size", ctypes.c_uint64) +]) + +MINIDUMP_MEMORY_LIST = Descriptor([ + ("range_count", ctypes.c_uint32), + ("ranges", lambda m: MINIDUMP_MEMORY_DESCRIPTOR.ctype * m.range_count) +]) + +MINIDUMP_MEMORY_LIST64 = Descriptor([ + ("range_count", ctypes.c_uint64), + ("base_rva", ctypes.c_uint64), + ("ranges", lambda m: MINIDUMP_MEMORY_DESCRIPTOR64.ctype * m.range_count) +]) + +MINIDUMP_THREAD = Descriptor([ + ("id", ctypes.c_uint32), + ("suspend_count", ctypes.c_uint32), + ("priority_class", ctypes.c_uint32), + ("priority", ctypes.c_uint32), + ("ted", ctypes.c_uint64), + ("stack", MINIDUMP_MEMORY_DESCRIPTOR.ctype), + ("context", MINIDUMP_LOCATION_DESCRIPTOR.ctype) +]) + +MINIDUMP_THREAD_LIST = Descriptor([ + ("thread_count", ctypes.c_uint32), + ("threads", lambda t: MINIDUMP_THREAD.ctype * t.thread_count) +]) + + +class MinidumpReader(object): + """Minidump (.dmp) reader.""" + + _HEADER_MAGIC = 0x504d444d + + def __init__(self, options, minidump_name): + self.minidump_name = minidump_name + self.minidump_file = open(minidump_name, "r") + self.minidump = mmap.mmap(self.minidump_file.fileno(), 0, mmap.MAP_PRIVATE) + self.header = MINIDUMP_HEADER.Read(self.minidump, 0) + if self.header.signature != MinidumpReader._HEADER_MAGIC: + print >>sys.stderr, "Warning: unsupported minidump header magic" + DebugPrint(self.header) + directories = [] + offset = self.header.stream_directories_rva + for _ in xrange(self.header.stream_count): + directories.append(MINIDUMP_DIRECTORY.Read(self.minidump, offset)) + offset += MINIDUMP_DIRECTORY.size + self.exception = None + self.exception_context = None + self.memory_list = None + self.thread_map = {} + for d in directories: + DebugPrint(d) + # TODO(vitalyr): extract system info including CPU features. + if d.stream_type == MD_EXCEPTION_STREAM: + self.exception = MINIDUMP_EXCEPTION_STREAM.Read( + self.minidump, d.location.rva) + DebugPrint(self.exception) + self.exception_context = MINIDUMP_CONTEXT_X86.Read( + self.minidump, self.exception.thread_context.rva) + DebugPrint(self.exception_context) + elif d.stream_type == MD_THREAD_LIST_STREAM: + thread_list = MINIDUMP_THREAD_LIST.Read(self.minidump, d.location.rva) + assert ctypes.sizeof(thread_list) == d.location.data_size + DebugPrint(thread_list) + for thread in thread_list.threads: + DebugPrint(thread) + self.thread_map[thread.id] = thread + elif d.stream_type == MD_MEMORY_LIST_STREAM: + print >>sys.stderr, "Warning: not a full minidump" + ml = MINIDUMP_MEMORY_LIST.Read(self.minidump, d.location.rva) + DebugPrint(ml) + for m in ml.ranges: + DebugPrint(m) + elif d.stream_type == MD_MEMORY_64_LIST_STREAM: + assert self.memory_list is None + self.memory_list = MINIDUMP_MEMORY_LIST64.Read( + self.minidump, d.location.rva) + assert ctypes.sizeof(self.memory_list) == d.location.data_size + DebugPrint(self.memory_list) + + def IsValidAddress(self, address): + return self.FindLocation(address) is not None + + def ReadU8(self, address): + location = self.FindLocation(address) + return ctypes.c_uint8.from_buffer(self.minidump, location).value + + def ReadU32(self, address): + location = self.FindLocation(address) + return ctypes.c_uint32.from_buffer(self.minidump, location).value + + def ReadBytes(self, address, size): + location = self.FindLocation(address) + return self.minidump[location:location + size] + + def FindLocation(self, address): + # TODO(vitalyr): only works for full minidumps (...64 structure variants). + offset = 0 + for r in self.memory_list.ranges: + if r.start <= address < r.start + r.size: + return self.memory_list.base_rva + offset + address - r.start + offset += r.size + return None + + def GetDisasmLines(self, address, size): + location = self.FindLocation(address) + if location is None: return [] + return disasm.GetDisasmLines(self.minidump_name, + location, + size, + "ia32", + False) + + + def Dispose(self): + self.minidump.close() + self.minidump_file.close() + + +# List of V8 instance types. Obtained by adding the code below to any .cc file. +# +# #define DUMP_TYPE(T) printf("%d: \"%s\",\n", T, #T); +# struct P { +# P() { +# printf("{\n"); +# INSTANCE_TYPE_LIST(DUMP_TYPE) +# printf("}\n"); +# } +# }; +# static P p; +INSTANCE_TYPES = { + 64: "SYMBOL_TYPE", + 68: "ASCII_SYMBOL_TYPE", + 65: "CONS_SYMBOL_TYPE", + 69: "CONS_ASCII_SYMBOL_TYPE", + 66: "EXTERNAL_SYMBOL_TYPE", + 74: "EXTERNAL_SYMBOL_WITH_ASCII_DATA_TYPE", + 70: "EXTERNAL_ASCII_SYMBOL_TYPE", + 0: "STRING_TYPE", + 4: "ASCII_STRING_TYPE", + 1: "CONS_STRING_TYPE", + 5: "CONS_ASCII_STRING_TYPE", + 2: "EXTERNAL_STRING_TYPE", + 10: "EXTERNAL_STRING_WITH_ASCII_DATA_TYPE", + 6: "EXTERNAL_ASCII_STRING_TYPE", + 6: "PRIVATE_EXTERNAL_ASCII_STRING_TYPE", + 128: "MAP_TYPE", + 129: "CODE_TYPE", + 130: "ODDBALL_TYPE", + 131: "JS_GLOBAL_PROPERTY_CELL_TYPE", + 132: "HEAP_NUMBER_TYPE", + 133: "PROXY_TYPE", + 134: "BYTE_ARRAY_TYPE", + 135: "PIXEL_ARRAY_TYPE", + 136: "EXTERNAL_BYTE_ARRAY_TYPE", + 137: "EXTERNAL_UNSIGNED_BYTE_ARRAY_TYPE", + 138: "EXTERNAL_SHORT_ARRAY_TYPE", + 139: "EXTERNAL_UNSIGNED_SHORT_ARRAY_TYPE", + 140: "EXTERNAL_INT_ARRAY_TYPE", + 141: "EXTERNAL_UNSIGNED_INT_ARRAY_TYPE", + 142: "EXTERNAL_FLOAT_ARRAY_TYPE", + 143: "FILLER_TYPE", + 144: "ACCESSOR_INFO_TYPE", + 145: "ACCESS_CHECK_INFO_TYPE", + 146: "INTERCEPTOR_INFO_TYPE", + 147: "CALL_HANDLER_INFO_TYPE", + 148: "FUNCTION_TEMPLATE_INFO_TYPE", + 149: "OBJECT_TEMPLATE_INFO_TYPE", + 150: "SIGNATURE_INFO_TYPE", + 151: "TYPE_SWITCH_INFO_TYPE", + 152: "SCRIPT_TYPE", + 153: "CODE_CACHE_TYPE", + 156: "FIXED_ARRAY_TYPE", + 157: "SHARED_FUNCTION_INFO_TYPE", + 158: "JS_MESSAGE_OBJECT_TYPE", + 159: "JS_VALUE_TYPE", + 160: "JS_OBJECT_TYPE", + 161: "JS_CONTEXT_EXTENSION_OBJECT_TYPE", + 162: "JS_GLOBAL_OBJECT_TYPE", + 163: "JS_BUILTINS_OBJECT_TYPE", + 164: "JS_GLOBAL_PROXY_TYPE", + 165: "JS_ARRAY_TYPE", + 166: "JS_REGEXP_TYPE", + 167: "JS_FUNCTION_TYPE", + 154: "DEBUG_INFO_TYPE", + 155: "BREAK_POINT_INFO_TYPE", +} + + +class Printer(object): + """Printer with indentation support.""" + + def __init__(self): + self.indent = 0 + + def Indent(self): + self.indent += 2 + + def Dedent(self): + self.indent -= 2 + + def Print(self, string): + print "%s%s" % (self._IndentString(), string) + + def PrintLines(self, lines): + indent = self._IndentString() + print "\n".join("%s%s" % (indent, line) for line in lines) + + def _IndentString(self): + return self.indent * " " + + +ADDRESS_RE = re.compile(r"0x[0-9a-fA-F]+") + + +def FormatDisasmLine(start, heap, line): + line_address = start + line[0] + stack_slot = heap.stack_map.get(line_address) + marker = " " + if stack_slot: + marker = "=>" + code = AnnotateAddresses(heap, line[1]) + return "%s%08x %08x: %s" % (marker, line_address, line[0], code) + + +def AnnotateAddresses(heap, line): + extra = [] + for m in ADDRESS_RE.finditer(line): + maybe_address = int(m.group(0), 16) + object = heap.FindObject(maybe_address) + if not object: continue + extra.append(str(object)) + if len(extra) == 0: return line + return "%s ;; %s" % (line, ", ".join(extra)) + + +class HeapObject(object): + def __init__(self, heap, map, address): + self.heap = heap + self.map = map + self.address = address + + def Is(self, cls): + return isinstance(self, cls) + + def Print(self, p): + p.Print(str(self)) + + def __str__(self): + return "HeapObject(%08x, %s)" % (self.address, + INSTANCE_TYPES[self.map.instance_type]) + + def ObjectField(self, offset): + field_value = self.heap.reader.ReadU32(self.address + offset) + return self.heap.FindObjectOrSmi(field_value) + + def SmiField(self, offset): + field_value = self.heap.reader.ReadU32(self.address + offset) + assert (field_value & 1) == 0 + return field_value / 2 + + +class Map(HeapObject): + INSTANCE_TYPE_OFFSET = 8 + + def __init__(self, heap, map, address): + HeapObject.__init__(self, heap, map, address) + self.instance_type = \ + heap.reader.ReadU8(self.address + Map.INSTANCE_TYPE_OFFSET) + + +class String(HeapObject): + LENGTH_OFFSET = 4 + + def __init__(self, heap, map, address): + HeapObject.__init__(self, heap, map, address) + self.length = self.SmiField(String.LENGTH_OFFSET) + + def GetChars(self): + return "?string?" + + def Print(self, p): + p.Print(str(self)) + + def __str__(self): + return "\"%s\"" % self.GetChars() + + +class SeqString(String): + CHARS_OFFSET = 12 + + def __init__(self, heap, map, address): + String.__init__(self, heap, map, address) + self.chars = heap.reader.ReadBytes(self.address + SeqString.CHARS_OFFSET, + self.length) + + def GetChars(self): + return self.chars + + +class ExternalString(String): + RESOURCE_OFFSET = 12 + + WEBKIT_RESOUCE_STRING_IMPL_OFFSET = 4 + WEBKIT_STRING_IMPL_CHARS_OFFSET = 8 + + def __init__(self, heap, map, address): + String.__init__(self, heap, map, address) + reader = heap.reader + self.resource = \ + reader.ReadU32(self.address + ExternalString.RESOURCE_OFFSET) + self.chars = "?external string?" + if not reader.IsValidAddress(self.resource): return + string_impl_address = self.resource + \ + ExternalString.WEBKIT_RESOUCE_STRING_IMPL_OFFSET + if not reader.IsValidAddress(string_impl_address): return + string_impl = reader.ReadU32(string_impl_address) + chars_ptr_address = string_impl + \ + ExternalString.WEBKIT_STRING_IMPL_CHARS_OFFSET + if not reader.IsValidAddress(chars_ptr_address): return + chars_ptr = reader.ReadU32(chars_ptr_address) + if not reader.IsValidAddress(chars_ptr): return + raw_chars = reader.ReadBytes(chars_ptr, 2 * self.length) + self.chars = codecs.getdecoder("utf16")(raw_chars)[0] + + def GetChars(self): + return self.chars + + +class ConsString(String): + LEFT_OFFSET = 12 + RIGHT_OFFSET = 16 + + def __init__(self, heap, map, address): + String.__init__(self, heap, map, address) + self.left = self.ObjectField(ConsString.LEFT_OFFSET) + self.right = self.ObjectField(ConsString.RIGHT_OFFSET) + + def GetChars(self): + return self.left.GetChars() + self.right.GetChars() + + +class Oddball(HeapObject): + TO_STRING_OFFSET = 4 + + def __init__(self, heap, map, address): + HeapObject.__init__(self, heap, map, address) + self.to_string = self.ObjectField(Oddball.TO_STRING_OFFSET) + + def Print(self, p): + p.Print(str(self)) + + def __str__(self): + return "<%s>" % self.to_string.GetChars() + + +class FixedArray(HeapObject): + LENGTH_OFFSET = 4 + ELEMENTS_OFFSET = 8 + + def __init__(self, heap, map, address): + HeapObject.__init__(self, heap, map, address) + self.length = self.SmiField(FixedArray.LENGTH_OFFSET) + + def Print(self, p): + p.Print("FixedArray(%08x) {" % self.address) + p.Indent() + p.Print("length: %d" % self.length) + for i in xrange(self.length): + offset = FixedArray.ELEMENTS_OFFSET + 4 * i + p.Print("[%08d] = %s" % (i, self.ObjectField(offset))) + p.Dedent() + p.Print("}") + + def __str__(self): + return "FixedArray(%08x, length=%d)" % (self.address, self.length) + + +class JSFunction(HeapObject): + CODE_ENTRY_OFFSET = 12 + SHARED_OFFSET = 20 + + def __init__(self, heap, map, address): + HeapObject.__init__(self, heap, map, address) + code_entry = \ + heap.reader.ReadU32(self.address + JSFunction.CODE_ENTRY_OFFSET) + self.code = heap.FindObject(code_entry - Code.ENTRY_OFFSET + 1) + self.shared = self.ObjectField(JSFunction.SHARED_OFFSET) + + def Print(self, p): + source = "\n".join(" %s" % line for line in self._GetSource().split("\n")) + p.Print("JSFunction(%08x) {" % self.address) + p.Indent() + p.Print("inferred name: %s" % self.shared.inferred_name) + if self.shared.script.Is(Script) and self.shared.script.name.Is(String): + p.Print("script name: %s" % self.shared.script.name) + p.Print("source:") + p.PrintLines(self._GetSource().split("\n")) + p.Print("code:") + self.code.Print(p) + if self.code != self.shared.code: + p.Print("unoptimized code:") + self.shared.code.Print(p) + p.Dedent() + p.Print("}") + + def __str__(self): + inferred_name = "" + if self.shared.Is(SharedFunctionInfo): + inferred_name = self.shared.inferred_name + return "JSFunction(%08x, %s)" % (self.address, inferred_name) + + def _GetSource(self): + source = "?source?" + start = self.shared.start_position + end = self.shared.end_position + if not self.shared.script.Is(Script): return source + script_source = self.shared.script.source + if not script_source.Is(String): return source + return script_source.GetChars()[start:end] + + +class SharedFunctionInfo(HeapObject): + CODE_OFFSET = 2 * 4 + SCRIPT_OFFSET = 7 * 4 + INFERRED_NAME_OFFSET = 9 * 4 + START_POSITION_AND_TYPE_OFFSET = 17 * 4 + END_POSITION_OFFSET = 18 * 4 + + def __init__(self, heap, map, address): + HeapObject.__init__(self, heap, map, address) + self.code = self.ObjectField(SharedFunctionInfo.CODE_OFFSET) + self.script = self.ObjectField(SharedFunctionInfo.SCRIPT_OFFSET) + self.inferred_name = \ + self.ObjectField(SharedFunctionInfo.INFERRED_NAME_OFFSET) + start_position_and_type = \ + self.SmiField(SharedFunctionInfo.START_POSITION_AND_TYPE_OFFSET) + self.start_position = start_position_and_type >> 2 + self.end_position = self.SmiField(SharedFunctionInfo.END_POSITION_OFFSET) + + +class Script(HeapObject): + SOURCE_OFFSET = 4 + NAME_OFFSET = 8 + + def __init__(self, heap, map, address): + HeapObject.__init__(self, heap, map, address) + self.source = self.ObjectField(Script.SOURCE_OFFSET) + self.name = self.ObjectField(Script.NAME_OFFSET) + + +class Code(HeapObject): + INSTRUCTION_SIZE_OFFSET = 4 + ENTRY_OFFSET = 32 + + def __init__(self, heap, map, address): + HeapObject.__init__(self, heap, map, address) + self.entry = self.address + Code.ENTRY_OFFSET + self.instruction_size = \ + heap.reader.ReadU32(self.address + Code.INSTRUCTION_SIZE_OFFSET) + + def Print(self, p): + lines = self.heap.reader.GetDisasmLines(self.entry, self.instruction_size) + p.Print("Code(%08x) {" % self.address) + p.Indent() + p.Print("instruction_size: %d" % self.instruction_size) + p.PrintLines(self._FormatLine(line) for line in lines) + p.Dedent() + p.Print("}") + + def _FormatLine(self, line): + return FormatDisasmLine(self.entry, self.heap, line) + + +class V8Heap(object): + CLASS_MAP = { + "SYMBOL_TYPE": SeqString, + "ASCII_SYMBOL_TYPE": SeqString, + "CONS_SYMBOL_TYPE": ConsString, + "CONS_ASCII_SYMBOL_TYPE": ConsString, + "EXTERNAL_SYMBOL_TYPE": ExternalString, + "EXTERNAL_SYMBOL_WITH_ASCII_DATA_TYPE": ExternalString, + "EXTERNAL_ASCII_SYMBOL_TYPE": ExternalString, + "STRING_TYPE": SeqString, + "ASCII_STRING_TYPE": SeqString, + "CONS_STRING_TYPE": ConsString, + "CONS_ASCII_STRING_TYPE": ConsString, + "EXTERNAL_STRING_TYPE": ExternalString, + "EXTERNAL_STRING_WITH_ASCII_DATA_TYPE": ExternalString, + "EXTERNAL_ASCII_STRING_TYPE": ExternalString, + + "MAP_TYPE": Map, + "ODDBALL_TYPE": Oddball, + "FIXED_ARRAY_TYPE": FixedArray, + "JS_FUNCTION_TYPE": JSFunction, + "SHARED_FUNCTION_INFO_TYPE": SharedFunctionInfo, + "SCRIPT_TYPE": Script, + "CODE_TYPE": Code + } + + def __init__(self, reader, stack_map): + self.reader = reader + self.stack_map = stack_map + self.objects = {} + + def FindObjectOrSmi(self, tagged_address): + if (tagged_address & 1) == 0: return tagged_address / 2 + return self.FindObject(tagged_address) + + def FindObject(self, tagged_address): + if tagged_address in self.objects: + return self.objects[tagged_address] + if (tagged_address & 1) != 1: return None + address = tagged_address - 1 + if not self.reader.IsValidAddress(address): return None + map_tagged_address = self.reader.ReadU32(address) + if tagged_address == map_tagged_address: + # Meta map? + meta_map = Map(self, None, address) + instance_type_name = INSTANCE_TYPES.get(meta_map.instance_type) + if instance_type_name != "MAP_TYPE": return None + meta_map.map = meta_map + object = meta_map + else: + map = self.FindObject(map_tagged_address) + if map is None: return None + instance_type_name = INSTANCE_TYPES.get(map.instance_type) + if instance_type_name is None: return None + cls = V8Heap.CLASS_MAP.get(instance_type_name, HeapObject) + object = cls(self, map, address) + self.objects[tagged_address] = object + return object + + +EIP_PROXIMITY = 64 + + +def AnalyzeMinidump(options, minidump_name): + reader = MinidumpReader(options, minidump_name) + DebugPrint("========================================") + if reader.exception is None: + print "Minidump has no exception info" + return + print "Exception info:" + exception_thread = reader.thread_map[reader.exception.thread_id] + print " thread id: %d" % exception_thread.id + print " code: %08X" % reader.exception.exception.code + print " context:" + print " eax: %08x" % reader.exception_context.eax + print " ebx: %08x" % reader.exception_context.ebx + print " ecx: %08x" % reader.exception_context.ecx + print " edx: %08x" % reader.exception_context.edx + print " edi: %08x" % reader.exception_context.edi + print " esi: %08x" % reader.exception_context.esi + print " ebp: %08x" % reader.exception_context.ebp + print " esp: %08x" % reader.exception_context.esp + print " eip: %08x" % reader.exception_context.eip + # TODO(vitalyr): decode eflags. + print " eflags: %s" % bin(reader.exception_context.eflags)[2:] + print + + stack_bottom = exception_thread.stack.start + \ + exception_thread.stack.memory.data_size + stack_map = {reader.exception_context.eip: -1} + for slot in xrange(reader.exception_context.esp, stack_bottom, 4): + maybe_address = reader.ReadU32(slot) + if not maybe_address in stack_map: + stack_map[maybe_address] = slot + heap = V8Heap(reader, stack_map) + + print "Disassembly around exception.eip:" + start = reader.exception_context.eip - EIP_PROXIMITY + lines = reader.GetDisasmLines(start, 2 * EIP_PROXIMITY) + for line in lines: + print FormatDisasmLine(start, heap, line) + print + + print "Annotated stack (from exception.esp to bottom):" + for slot in xrange(reader.exception_context.esp, stack_bottom, 4): + maybe_address = reader.ReadU32(slot) + heap_object = heap.FindObject(maybe_address) + print "%08x: %08x" % (slot, maybe_address) + if heap_object: + heap_object.Print(Printer()) + print + + reader.Dispose() + + +if __name__ == "__main__": + parser = optparse.OptionParser(USAGE) + options, args = parser.parse_args() + if len(args) != 1: + parser.print_help() + sys.exit(1) + AnalyzeMinidump(options, args[0]) diff --git a/tools/gyp/v8.gyp b/tools/gyp/v8.gyp index 15185671..6dab52d8 100644 --- a/tools/gyp/v8.gyp +++ b/tools/gyp/v8.gyp @@ -598,6 +598,8 @@ '../../src/arm/lithium-arm.h', '../../src/arm/lithium-codegen-arm.cc', '../../src/arm/lithium-codegen-arm.h', + '../../src/arm/lithium-gap-resolver-arm.cc', + '../../src/arm/lithium-gap-resolver-arm.h', '../../src/arm/macro-assembler-arm.cc', '../../src/arm/macro-assembler-arm.h', '../../src/arm/regexp-macro-assembler-arm.cc', diff --git a/tools/linux-tick-processor.py b/tools/linux-tick-processor.py deleted file mode 100755 index 67c3b955..00000000 --- a/tools/linux-tick-processor.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2008 the V8 project authors. All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# Usage: process-ticks.py <logfile> -# Where <logfile> is the log file name (eg, v8.log). - -import subprocess, re, sys, tickprocessor - -class LinuxTickProcessor(tickprocessor.TickProcessor): - - def ParseVMSymbols(self, filename, start, end): - """Extract symbols and add them to the cpp entries.""" - # Extra both dynamic and non-dynamic symbols. - command = 'nm -C -n "%s"; nm -C -n -D "%s"' % (filename, filename) - process = subprocess.Popen(command, shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - pipe = process.stdout - try: - for line in pipe: - row = re.match('^([0-9a-fA-F]{8}) . (.*)$', line) - if row: - addr = int(row.group(1), 16) - if addr < start and addr < end - start: - addr += start - self.cpp_entries.Insert(addr, tickprocessor.CodeEntry(addr, row.group(2))) - finally: - pipe.close() - - -class LinuxCmdLineProcessor(tickprocessor.CmdLineProcessor): - - def GetRequiredArgsNames(self): - return 'log_file' - - def ProcessRequiredArgs(self, args): - if len(args) != 1: - self.PrintUsageAndExit() - else: - self.log_file = args[0] - - -def Main(): - cmdline_processor = LinuxCmdLineProcessor() - cmdline_processor.ProcessArguments() - tick_processor = LinuxTickProcessor() - cmdline_processor.RunLogfileProcessing(tick_processor) - tick_processor.PrintResults() - - -if __name__ == '__main__': - Main() diff --git a/tools/ll_prof.py b/tools/ll_prof.py index 8390d4af..7f12c133 100755 --- a/tools/ll_prof.py +++ b/tools/ll_prof.py @@ -30,13 +30,13 @@ import bisect import collections import ctypes +import disasm import mmap import optparse import os import re import subprocess import sys -import tempfile import time @@ -74,27 +74,12 @@ V8_GC_FAKE_MMAP = "/tmp/__v8_gc__" JS_ORIGIN = "js" JS_SNAPSHOT_ORIGIN = "js-snapshot" -# Avoid using the slow (google-specific) wrapper around objdump. -OBJDUMP_BIN = "/usr/bin/objdump" -if not os.path.exists(OBJDUMP_BIN): - OBJDUMP_BIN = "objdump" +OBJDUMP_BIN = disasm.OBJDUMP_BIN class Code(object): """Code object.""" - _COMMON_DISASM_OPTIONS = ["-M", "intel-mnemonic", "-C"] - - _DISASM_HEADER_RE = re.compile(r"[a-f0-9]+\s+<.*:$") - _DISASM_LINE_RE = re.compile(r"\s*([a-f0-9]+):.*") - - # Keys must match constants in Logger::LogCodeInfo. - _ARCH_MAP = { - "ia32": "-m i386", - "x64": "-m i386 -M x86-64", - "arm": "-m arm" # Not supported by our objdump build. - } - _id = 0 def __init__(self, name, start_address, end_address, origin, origin_offset): @@ -150,12 +135,7 @@ class Code(object): ticks_offsets = [t[0] for t in ticks_map] ticks_counts = [t[1] for t in ticks_map] # Get a list of disassembled lines and their addresses. - lines = [] - for line in self._GetDisasmLines(code_info, options): - match = Code._DISASM_LINE_RE.match(line) - if match: - line_address = int(match.group(1), 16) - lines.append((line_address, line)) + lines = self._GetDisasmLines(code_info, options) if len(lines) == 0: return # Print annotated lines. @@ -179,9 +159,9 @@ class Code(object): total_count += count count = 100.0 * count / self.self_ticks if count >= 0.01: - print "%15.2f %s" % (count, lines[i][1]) + print "%15.2f %x: %s" % (count, lines[i][0], lines[i][1]) else: - print "%s %s" % (" " * 15, lines[i][1]) + print "%s %x: %s" % (" " * 15, lines[i][0], lines[i][1]) print assert total_count == self.self_ticks, \ "Lost ticks (%d != %d) in %s" % (total_count, self.self_ticks, self) @@ -195,39 +175,17 @@ class Code(object): self.origin) def _GetDisasmLines(self, code_info, options): - tmp_name = None if self.origin == JS_ORIGIN or self.origin == JS_SNAPSHOT_ORIGIN: - assert code_info.arch in Code._ARCH_MAP, \ - "Unsupported architecture '%s'" % arch - arch_flags = Code._ARCH_MAP[code_info.arch] - # Create a temporary file just with this code object. - tmp_name = tempfile.mktemp(".v8code") - size = self.end_address - self.start_address - command = "dd if=%s.code of=%s bs=1 count=%d skip=%d && " \ - "%s %s -D -b binary %s %s" % ( - options.log, tmp_name, size, self.origin_offset, - OBJDUMP_BIN, ' '.join(Code._COMMON_DISASM_OPTIONS), arch_flags, - tmp_name) + inplace = False + filename = options.log + ".code" else: - command = "%s %s --start-address=%d --stop-address=%d -d %s " % ( - OBJDUMP_BIN, ' '.join(Code._COMMON_DISASM_OPTIONS), - self.origin_offset, - self.origin_offset + self.end_address - self.start_address, - self.origin) - process = subprocess.Popen(command, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - out, err = process.communicate() - lines = out.split("\n") - header_line = 0 - for i, line in enumerate(lines): - if Code._DISASM_HEADER_RE.match(line): - header_line = i - break - if tmp_name: - os.unlink(tmp_name) - return lines[header_line + 1:] + inplace = True + filename = self.origin + return disasm.GetDisasmLines(filename, + self.origin_offset, + self.end_address - self.start_address, + code_info.arch, + inplace) class CodePage(object): @@ -353,7 +311,7 @@ class CodeLogReader(object): r"code-info,([^,]+),(\d+)") _CODE_CREATE_RE = re.compile( - r"code-creation,([^,]+),(0x[a-f0-9]+),(\d+),\"(.*)\"(?:,(\d+))?") + r"code-creation,([^,]+),(0x[a-f0-9]+),(\d+),\"(.*)\"(?:,(0x[a-f0-9]+),([~*])?)?(?:,(\d+))?") _CODE_MOVE_RE = re.compile( r"code-move,(0x[a-f0-9]+),(0x[a-f0-9]+)") @@ -400,12 +358,18 @@ class CodeLogReader(object): name = self.address_to_snapshot_name[start_address] origin = JS_SNAPSHOT_ORIGIN else: - name = "%s:%s" % (match.group(1), match.group(4)) + tag = match.group(1) + optimization_status = match.group(6) + func_name = match.group(4) + if optimization_status: + name = "%s:%s%s" % (tag, optimization_status, func_name) + else: + name = "%s:%s" % (tag, func_name) origin = JS_ORIGIN if self.is_snapshot: origin_offset = 0 else: - origin_offset = int(match.group(5)) + origin_offset = int(match.group(7)) code = Code(name, start_address, end_address, origin, origin_offset) conficting_code = self.code_map.Find(start_address) if conficting_code: diff --git a/tools/profile.js b/tools/profile.js index 03bee839..c9c9437e 100644 --- a/tools/profile.js +++ b/tools/profile.js @@ -38,11 +38,6 @@ function Profile() { this.bottomUpTree_ = new CallTree(); }; -/** - * Version of profiler log. - */ -Profile.VERSION = 2; - /** * Returns whether a function with the specified name must be skipped. @@ -69,6 +64,18 @@ Profile.Operation = { /** + * Enum for code state regarding its dynamic optimization. + * + * @enum {number} + */ +Profile.CodeState = { + COMPILED: 0, + OPTIMIZABLE: 1, + OPTIMIZED: 2 +}; + + +/** * Called whenever the specified operation has failed finding a function * containing the specified address. Should be overriden by subclasses. * See the Profile.Operation enum for the list of @@ -134,17 +141,30 @@ Profile.prototype.addCode = function( /** - * Creates an alias entry for a code entry. + * Registers dynamic (JIT-compiled) code entry. * - * @param {number} aliasAddr Alias address. - * @param {number} addr Code entry address. - */ -Profile.prototype.addCodeAlias = function( - aliasAddr, addr) { - var entry = this.codeMap_.findDynamicEntryByStartAddress(addr); - if (entry) { - this.codeMap_.addCode(aliasAddr, entry); + * @param {string} type Code entry type. + * @param {string} name Code entry name. + * @param {number} start Starting address. + * @param {number} size Code entry size. + * @param {number} funcAddr Shared function object address. + * @param {Profile.CodeState} state Optimization state. + */ +Profile.prototype.addFuncCode = function( + type, name, start, size, funcAddr, state) { + // As code and functions are in the same address space, + // it is safe to put them in a single code map. + var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr); + if (!func) { + func = new Profile.FunctionEntry(name); + this.codeMap_.addCode(funcAddr, func); + } else if (func.name !== name) { + // Function object has been overwritten with a new one. + func.name = name; } + var entry = new Profile.DynamicFuncCodeEntry(size, type, func, state); + this.codeMap_.addCode(start, entry); + return entry; }; @@ -183,7 +203,7 @@ Profile.prototype.deleteCode = function(start) { * @param {number} from Current code entry address. * @param {number} to New code entry address. */ -Profile.prototype.safeMoveDynamicCode = function(from, to) { +Profile.prototype.moveFunc = function(from, to) { if (this.codeMap_.findDynamicEntryByStartAddress(from)) { this.codeMap_.moveCode(from, to); } @@ -191,18 +211,6 @@ Profile.prototype.safeMoveDynamicCode = function(from, to) { /** - * Reports about deletion of a dynamic code entry. - * - * @param {number} start Starting address. - */ -Profile.prototype.safeDeleteDynamicCode = function(start) { - if (this.codeMap_.findDynamicEntryByStartAddress(start)) { - this.codeMap_.deleteCode(start); - } -}; - - -/** * Retrieves a code entry by an address. * * @param {number} addr Entry address. @@ -383,14 +391,7 @@ Profile.DynamicCodeEntry = function(size, type, name) { * Returns node name. */ Profile.DynamicCodeEntry.prototype.getName = function() { - var name = this.name; - if (name.length == 0) { - name = '<anonymous>'; - } else if (name.charAt(0) == ' ') { - // An anonymous function with location: " aaa.js:10". - name = '<anonymous>' + name; - } - return this.type + ': ' + name; + return this.type + ': ' + this.name; }; @@ -403,9 +404,73 @@ Profile.DynamicCodeEntry.prototype.getRawName = function() { Profile.DynamicCodeEntry.prototype.isJSFunction = function() { - return this.type == "Function" || - this.type == "LazyCompile" || - this.type == "Script"; + return false; +}; + + +/** + * Creates a dynamic code entry. + * + * @param {number} size Code size. + * @param {string} type Code type. + * @param {Profile.FunctionEntry} func Shared function entry. + * @param {Profile.CodeState} state Code optimization state. + * @constructor + */ +Profile.DynamicFuncCodeEntry = function(size, type, func, state) { + CodeMap.CodeEntry.call(this, size); + this.type = type; + this.func = func; + this.state = state; +}; + +Profile.DynamicFuncCodeEntry.STATE_PREFIX = ["", "~", "*"]; + +/** + * Returns node name. + */ +Profile.DynamicFuncCodeEntry.prototype.getName = function() { + var name = this.func.getName(); + return this.type + ': ' + Profile.DynamicFuncCodeEntry.STATE_PREFIX[this.state] + name; +}; + + +/** + * Returns raw node name (without type decoration). + */ +Profile.DynamicFuncCodeEntry.prototype.getRawName = function() { + return this.func.getName(); +}; + + +Profile.DynamicFuncCodeEntry.prototype.isJSFunction = function() { + return true; +}; + + +/** + * Creates a shared function object entry. + * + * @param {string} name Function name. + * @constructor + */ +Profile.FunctionEntry = function(name) { + CodeMap.CodeEntry.call(this, 0, name); +}; + + +/** + * Returns node name. + */ +Profile.FunctionEntry.prototype.getName = function() { + var name = this.name; + if (name.length == 0) { + name = '<anonymous>'; + } else if (name.charAt(0) == ' ') { + // An anonymous function with location: " aaa.js:10". + name = '<anonymous>' + name; + } + return name; }; diff --git a/tools/splaytree.py b/tools/splaytree.py deleted file mode 100644 index 8c3c4fe1..00000000 --- a/tools/splaytree.py +++ /dev/null @@ -1,226 +0,0 @@ -# Copyright 2008 the V8 project authors. All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -class Node(object): - """Nodes in the splay tree.""" - - def __init__(self, key, value): - self.key = key - self.value = value - self.left = None - self.right = None - - -class KeyNotFoundError(Exception): - """KeyNotFoundError is raised when removing a non-existing node.""" - - def __init__(self, key): - self.key = key - - -class SplayTree(object): - """The splay tree itself is just a reference to the root of the tree.""" - - def __init__(self): - """Create a new SplayTree.""" - self.root = None - - def IsEmpty(self): - """Is the SplayTree empty?""" - return not self.root - - def Insert(self, key, value): - """Insert a new node in the SplayTree.""" - # If the tree is empty, insert the new node. - if self.IsEmpty(): - self.root = Node(key, value) - return - # Splay on the key to move the last node on the search path for - # the key to the root of the tree. - self.Splay(key) - # Ignore repeated insertions with the same key. - if self.root.key == key: - return - # Insert the new node. - node = Node(key, value) - if key > self.root.key: - node.left = self.root - node.right = self.root.right - self.root.right = None - else: - node.right = self.root - node.left = self.root.left - self.root.left = None - self.root = node - - def Remove(self, key): - """Remove the node with the given key from the SplayTree.""" - # Raise exception for key that is not found if the tree is empty. - if self.IsEmpty(): - raise KeyNotFoundError(key) - # Splay on the key to move the node with the given key to the top. - self.Splay(key) - # Raise exception for key that is not found. - if self.root.key != key: - raise KeyNotFoundError(key) - removed = self.root - # Link out the root node. - if not self.root.left: - # No left child, so the new tree is just the right child. - self.root = self.root.right - else: - # Left child exists. - right = self.root.right - # Make the original left child the new root. - self.root = self.root.left - # Splay to make sure that the new root has an empty right child. - self.Splay(key) - # Insert the original right child as the right child of the new - # root. - self.root.right = right - return removed - - def Find(self, key): - """Returns the node with the given key or None if no such node exists.""" - if self.IsEmpty(): - return None - self.Splay(key) - if self.root.key == key: - return self.root - return None - - def FindMax(self): - """Returns the node with the largest key value.""" - if self.IsEmpty(): - return None - current = self.root - while current.right != None: - current = current.right - return current - - # Returns the node with the smallest key value. - def FindMin(self): - if self.IsEmpty(): - return None - current = self.root - while current.left != None: - current = current.left - return current - - def FindGreatestsLessThan(self, key): - """Returns node with greatest key less than or equal to the given key.""" - if self.IsEmpty(): - return None - # Splay on the key to move the node with the given key or the last - # node on the search path to the top of the tree. - self.Splay(key) - # Now the result is either the root node or the greatest node in - # the left subtree. - if self.root.key <= key: - return self.root - else: - tmp = self.root - self.root = self.root.left - result = self.FindMax() - self.root = tmp - return result - - def ExportValueList(self): - """Returns a list containing all the values of the nodes in the tree.""" - result = [] - nodes_to_visit = [self.root] - while len(nodes_to_visit) > 0: - node = nodes_to_visit.pop() - if not node: - continue - result.append(node.value) - nodes_to_visit.append(node.left) - nodes_to_visit.append(node.right) - return result - - def Splay(self, key): - """Perform splay operation. - - Perform the splay operation for the given key. Moves the node with - the given key to the top of the tree. If no node has the given - key, the last node on the search path is moved to the top of the - tree. - - This uses the simplified top-down splaying algorithm from: - - "Self-adjusting Binary Search Trees" by Sleator and Tarjan - - """ - if self.IsEmpty(): - return - # Create a dummy node. The use of the dummy node is a bit - # counter-intuitive: The right child of the dummy node will hold - # the L tree of the algorithm. The left child of the dummy node - # will hold the R tree of the algorithm. Using a dummy node, left - # and right will always be nodes and we avoid special cases. - dummy = left = right = Node(None, None) - current = self.root - while True: - if key < current.key: - if not current.left: - break - if key < current.left.key: - # Rotate right. - tmp = current.left - current.left = tmp.right - tmp.right = current - current = tmp - if not current.left: - break - # Link right. - right.left = current - right = current - current = current.left - elif key > current.key: - if not current.right: - break - if key > current.right.key: - # Rotate left. - tmp = current.right - current.right = tmp.left - tmp.left = current - current = tmp - if not current.right: - break - # Link left. - left.right = current - left = current - current = current.right - else: - break - # Assemble. - left.right = current.left - right.left = current.right - current.left = dummy.right - current.right = dummy.left - self.root = current diff --git a/tools/tickprocessor.js b/tools/tickprocessor.js index db2f3c9b..f105a21c 100644 --- a/tools/tickprocessor.js +++ b/tools/tickprocessor.js @@ -57,10 +57,23 @@ function readFile(fileName) { } +/** + * Parser for dynamic code optimization state. + */ +function parseState(s) { + switch (s) { + case "": return Profile.CodeState.COMPILED; + case "~": return Profile.CodeState.OPTIMIZABLE; + case "*": return Profile.CodeState.OPTIMIZED; + } + throw new Error("unknown code state: " + s); +} + + function SnapshotLogProcessor() { LogReader.call(this, { 'code-creation': { - parsers: [null, parseInt, parseInt, null], + parsers: [null, parseInt, parseInt, null, 'var-args'], processor: this.processCodeCreation }, 'code-move': { parsers: [parseInt, parseInt], processor: this.processCodeMove }, @@ -69,6 +82,7 @@ function SnapshotLogProcessor() { 'function-creation': null, 'function-move': null, 'function-delete': null, + 'sfi-move': null, 'snapshot-pos': { parsers: [parseInt, parseInt], processor: this.processSnapshotPosition }}); @@ -93,8 +107,14 @@ inherits(SnapshotLogProcessor, LogReader); SnapshotLogProcessor.prototype.processCodeCreation = function( - type, start, size, name) { - var entry = this.profile_.addCode(type, name, start, size); + type, start, size, name, maybe_func) { + if (maybe_func.length) { + var funcAddr = parseInt(maybe_func[0]); + var state = parseState(maybe_func[1]); + this.profile_.addFuncCode(type, name, start, size, funcAddr, state); + } else { + this.profile_.addCode(type, name, start, size); + } }; @@ -131,18 +151,14 @@ function TickProcessor( 'shared-library': { parsers: [null, parseInt, parseInt], processor: this.processSharedLibrary }, 'code-creation': { - parsers: [null, parseInt, parseInt, null], + parsers: [null, parseInt, parseInt, null, 'var-args'], processor: this.processCodeCreation }, 'code-move': { parsers: [parseInt, parseInt], processor: this.processCodeMove }, 'code-delete': { parsers: [parseInt], processor: this.processCodeDelete }, - 'function-creation': { parsers: [parseInt, parseInt], - processor: this.processFunctionCreation }, - 'function-move': { parsers: [parseInt, parseInt], + 'sfi-move': { parsers: [parseInt, parseInt], processor: this.processFunctionMove }, - 'function-delete': { parsers: [parseInt], - processor: this.processFunctionDelete }, 'snapshot-pos': { parsers: [parseInt, parseInt], processor: this.processSnapshotPosition }, 'tick': { parsers: [parseInt, parseInt, parseInt, parseInt, 'var-args'], @@ -155,6 +171,9 @@ function TickProcessor( processor: this.processJSProducer }, // Ignored events. 'profiler': null, + 'function-creation': null, + 'function-move': null, + 'function-delete': null, 'heap-sample-stats': null, 'heap-sample-item': null, 'heap-js-cons-item': null, @@ -285,9 +304,15 @@ TickProcessor.prototype.processSharedLibrary = function( TickProcessor.prototype.processCodeCreation = function( - type, start, size, name) { + type, start, size, name, maybe_func) { name = this.deserializedEntriesNames_[start] || name; - var entry = this.profile_.addCode(type, name, start, size); + if (maybe_func.length) { + var funcAddr = parseInt(maybe_func[0]); + var state = parseState(maybe_func[1]); + this.profile_.addFuncCode(type, name, start, size, funcAddr, state); + } else { + this.profile_.addCode(type, name, start, size); + } }; @@ -301,19 +326,8 @@ TickProcessor.prototype.processCodeDelete = function(start) { }; -TickProcessor.prototype.processFunctionCreation = function( - functionAddr, codeAddr) { - this.profile_.addCodeAlias(functionAddr, codeAddr); -}; - - TickProcessor.prototype.processFunctionMove = function(from, to) { - this.profile_.safeMoveDynamicCode(from, to); -}; - - -TickProcessor.prototype.processFunctionDelete = function(start) { - this.profile_.safeDeleteDynamicCode(start); + this.profile_.moveFunc(from, to); }; @@ -330,7 +344,7 @@ TickProcessor.prototype.includeTick = function(vmState) { }; -TickProcessor.prototype.processTick = function(pc, sp, func, vmState, stack) { +TickProcessor.prototype.processTick = function(pc, sp, tos, vmState, stack) { this.ticks_.total++; if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++; if (!this.includeTick(vmState)) { @@ -338,19 +352,14 @@ TickProcessor.prototype.processTick = function(pc, sp, func, vmState, stack) { return; } - if (func) { - var funcEntry = this.profile_.findEntry(func); + if (tos) { + var funcEntry = this.profile_.findEntry(tos); if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction()) { - func = 0; - } else { - var currEntry = this.profile_.findEntry(pc); - if (!currEntry || !currEntry.isJSFunction || currEntry.isJSFunction()) { - func = 0; - } + tos = 0; } } - this.profile_.recordTick(this.processStack(pc, func, stack)); + this.profile_.recordTick(this.processStack(pc, tos, stack)); }; diff --git a/tools/tickprocessor.py b/tools/tickprocessor.py deleted file mode 100644 index c932e3fc..00000000 --- a/tools/tickprocessor.py +++ /dev/null @@ -1,571 +0,0 @@ -# Copyright 2008 the V8 project authors. All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import csv, splaytree, sys, re -from operator import itemgetter -import getopt, os, string - -class CodeEntry(object): - - def __init__(self, start_addr, name): - self.start_addr = start_addr - self.tick_count = 0 - self.name = name - self.stacks = {} - - def Tick(self, pc, stack): - self.tick_count += 1 - if len(stack) > 0: - stack.insert(0, self.ToString()) - stack_key = tuple(stack) - self.stacks[stack_key] = self.stacks.setdefault(stack_key, 0) + 1 - - def RegionTicks(self): - return None - - def SetStartAddress(self, start_addr): - self.start_addr = start_addr - - def ToString(self): - return self.name - - def IsSharedLibraryEntry(self): - return False - - def IsICEntry(self): - return False - - def IsJSFunction(self): - return False - -class SharedLibraryEntry(CodeEntry): - - def __init__(self, start_addr, name): - CodeEntry.__init__(self, start_addr, name) - - def IsSharedLibraryEntry(self): - return True - - -class JSCodeEntry(CodeEntry): - - def __init__(self, start_addr, name, type, size, assembler): - CodeEntry.__init__(self, start_addr, name) - self.type = type - self.size = size - self.assembler = assembler - self.region_ticks = None - self.builtin_ic_re = re.compile('^(Keyed)?(Call|Load|Store)IC_') - - def Tick(self, pc, stack): - super(JSCodeEntry, self).Tick(pc, stack) - if not pc is None: - offset = pc - self.start_addr - seen = [] - narrowest = None - narrowest_width = None - for region in self.Regions(): - if region.Contains(offset): - if (not region.name in seen): - seen.append(region.name) - if narrowest is None or region.Width() < narrowest.Width(): - narrowest = region - if len(seen) == 0: - return - if self.region_ticks is None: - self.region_ticks = {} - for name in seen: - if not name in self.region_ticks: - self.region_ticks[name] = [0, 0] - self.region_ticks[name][0] += 1 - if name == narrowest.name: - self.region_ticks[name][1] += 1 - - def RegionTicks(self): - return self.region_ticks - - def Regions(self): - if self.assembler: - return self.assembler.regions - else: - return [] - - def ToString(self): - name = self.name - if name == '': - name = '<anonymous>' - elif name.startswith(' '): - name = '<anonymous>' + name - return self.type + ': ' + name - - def IsICEntry(self): - return self.type in ('CallIC', 'LoadIC', 'StoreIC') or \ - (self.type == 'Builtin' and self.builtin_ic_re.match(self.name)) - - def IsJSFunction(self): - return self.type in ('Function', 'LazyCompile', 'Script') - -class CodeRegion(object): - - def __init__(self, start_offset, name): - self.start_offset = start_offset - self.name = name - self.end_offset = None - - def Contains(self, pc): - return (self.start_offset <= pc) and (pc <= self.end_offset) - - def Width(self): - return self.end_offset - self.start_offset - - -class Assembler(object): - - def __init__(self): - # Mapping from region ids to open regions - self.pending_regions = {} - self.regions = [] - - -class FunctionEnumerator(object): - - def __init__(self): - self.known_funcs = {} - self.next_func_id = 0 - - def GetFunctionId(self, name): - if not self.known_funcs.has_key(name): - self.known_funcs[name] = self.next_func_id - self.next_func_id += 1 - return self.known_funcs[name] - - def GetKnownFunctions(self): - known_funcs_items = self.known_funcs.items(); - known_funcs_items.sort(key = itemgetter(1)) - result = [] - for func, id_not_used in known_funcs_items: - result.append(func) - return result - - -VMStates = { 'JS': 0, 'GC': 1, 'COMPILER': 2, 'OTHER': 3, 'EXTERNAL' : 4 } - - -class TickProcessor(object): - - def __init__(self): - self.log_file = '' - self.deleted_code = [] - self.vm_extent = {} - # Map from assembler ids to the pending assembler objects - self.pending_assemblers = {} - # Map from code addresses the have been allocated but not yet officially - # created to their assemblers. - self.assemblers = {} - self.js_entries = splaytree.SplayTree() - self.cpp_entries = splaytree.SplayTree() - self.total_number_of_ticks = 0 - self.number_of_library_ticks = 0 - self.unaccounted_number_of_ticks = 0 - self.excluded_number_of_ticks = 0 - self.number_of_gc_ticks = 0 - # Flag indicating whether to ignore unaccounted ticks in the report - self.ignore_unknown = False - self.func_enum = FunctionEnumerator() - self.packed_stacks = [] - - def ProcessLogfile(self, filename, included_state = None, ignore_unknown = False, separate_ic = False, call_graph_json = False): - self.log_file = filename - self.included_state = included_state - self.ignore_unknown = ignore_unknown - self.separate_ic = separate_ic - self.call_graph_json = call_graph_json - - try: - logfile = open(filename, 'rb') - except IOError: - sys.exit("Could not open logfile: " + filename) - try: - try: - logreader = csv.reader(logfile) - row_num = 1 - for row in logreader: - row_num += 1 - if row[0] == 'tick': - self.ProcessTick(int(row[1], 16), int(row[2], 16), int(row[3], 16), int(row[4]), self.PreprocessStack(row[5:])) - elif row[0] == 'code-creation': - self.ProcessCodeCreation(row[1], int(row[2], 16), int(row[3]), row[4]) - elif row[0] == 'code-move': - self.ProcessCodeMove(int(row[1], 16), int(row[2], 16)) - elif row[0] == 'code-delete': - self.ProcessCodeDelete(int(row[1], 16)) - elif row[0] == 'function-creation': - self.ProcessFunctionCreation(int(row[1], 16), int(row[2], 16)) - elif row[0] == 'function-move': - self.ProcessFunctionMove(int(row[1], 16), int(row[2], 16)) - elif row[0] == 'function-delete': - self.ProcessFunctionDelete(int(row[1], 16)) - elif row[0] == 'shared-library': - self.AddSharedLibraryEntry(row[1], int(row[2], 16), int(row[3], 16)) - self.ParseVMSymbols(row[1], int(row[2], 16), int(row[3], 16)) - elif row[0] == 'begin-code-region': - self.ProcessBeginCodeRegion(int(row[1], 16), int(row[2], 16), int(row[3], 16), row[4]) - elif row[0] == 'end-code-region': - self.ProcessEndCodeRegion(int(row[1], 16), int(row[2], 16), int(row[3], 16)) - elif row[0] == 'code-allocate': - self.ProcessCodeAllocate(int(row[1], 16), int(row[2], 16)) - except csv.Error: - print("parse error in line " + str(row_num)) - raise - finally: - logfile.close() - - def AddSharedLibraryEntry(self, filename, start, end): - # Mark the pages used by this library. - i = start - while i < end: - page = i >> 12 - self.vm_extent[page] = 1 - i += 4096 - # Add the library to the entries so that ticks for which we do not - # have symbol information is reported as belonging to the library. - self.cpp_entries.Insert(start, SharedLibraryEntry(start, filename)) - - def ParseVMSymbols(self, filename, start, end): - return - - def ProcessCodeAllocate(self, addr, assem): - if assem in self.pending_assemblers: - assembler = self.pending_assemblers.pop(assem) - self.assemblers[addr] = assembler - - def ProcessCodeCreation(self, type, addr, size, name): - if addr in self.assemblers: - assembler = self.assemblers.pop(addr) - else: - assembler = None - self.js_entries.Insert(addr, JSCodeEntry(addr, name, type, size, assembler)) - - def ProcessCodeMove(self, from_addr, to_addr): - try: - removed_node = self.js_entries.Remove(from_addr) - removed_node.value.SetStartAddress(to_addr); - self.js_entries.Insert(to_addr, removed_node.value) - except splaytree.KeyNotFoundError: - print('Code move event for unknown code: 0x%x' % from_addr) - - def ProcessCodeDelete(self, from_addr): - try: - removed_node = self.js_entries.Remove(from_addr) - self.deleted_code.append(removed_node.value) - except splaytree.KeyNotFoundError: - print('Code delete event for unknown code: 0x%x' % from_addr) - - def ProcessFunctionCreation(self, func_addr, code_addr): - js_entry_node = self.js_entries.Find(code_addr) - if js_entry_node: - js_entry = js_entry_node.value - self.js_entries.Insert(func_addr, JSCodeEntry(func_addr, js_entry.name, js_entry.type, 1, None)) - - def ProcessFunctionMove(self, from_addr, to_addr): - try: - removed_node = self.js_entries.Remove(from_addr) - removed_node.value.SetStartAddress(to_addr); - self.js_entries.Insert(to_addr, removed_node.value) - except splaytree.KeyNotFoundError: - return - - def ProcessFunctionDelete(self, from_addr): - try: - removed_node = self.js_entries.Remove(from_addr) - self.deleted_code.append(removed_node.value) - except splaytree.KeyNotFoundError: - return - - def ProcessBeginCodeRegion(self, id, assm, start, name): - if not assm in self.pending_assemblers: - self.pending_assemblers[assm] = Assembler() - assembler = self.pending_assemblers[assm] - assembler.pending_regions[id] = CodeRegion(start, name) - - def ProcessEndCodeRegion(self, id, assm, end): - assm = self.pending_assemblers[assm] - region = assm.pending_regions.pop(id) - region.end_offset = end - assm.regions.append(region) - - def IncludeTick(self, pc, sp, state): - return (self.included_state is None) or (self.included_state == state) - - def FindEntry(self, pc): - page = pc >> 12 - if page in self.vm_extent: - entry = self.cpp_entries.FindGreatestsLessThan(pc) - if entry != None: - return entry.value - else: - return entry - max = self.js_entries.FindMax() - min = self.js_entries.FindMin() - if max != None and pc < (max.key + max.value.size) and pc > min.key: - return self.js_entries.FindGreatestsLessThan(pc).value - return None - - def PreprocessStack(self, stack): - # remove all non-addresses (e.g. 'overflow') and convert to int - result = [] - for frame in stack: - if frame.startswith('0x'): - result.append(int(frame, 16)) - return result - - def ProcessStack(self, stack): - result = [] - for frame in stack: - entry = self.FindEntry(frame) - if entry != None: - result.append(entry.ToString()) - return result - - def ProcessTick(self, pc, sp, func, state, stack): - if state == VMStates['GC']: - self.number_of_gc_ticks += 1 - if not self.IncludeTick(pc, sp, state): - self.excluded_number_of_ticks += 1; - return - self.total_number_of_ticks += 1 - entry = self.FindEntry(pc) - if entry == None: - self.unaccounted_number_of_ticks += 1 - return - if entry.IsSharedLibraryEntry(): - self.number_of_library_ticks += 1 - if entry.IsICEntry() and not self.separate_ic: - if len(stack) > 0: - caller_pc = stack.pop(0) - self.total_number_of_ticks -= 1 - self.ProcessTick(caller_pc, sp, func, state, stack) - else: - self.unaccounted_number_of_ticks += 1 - else: - processed_stack = self.ProcessStack(stack) - if not entry.IsSharedLibraryEntry() and not entry.IsJSFunction(): - func_entry_node = self.js_entries.Find(func) - if func_entry_node and func_entry_node.value.IsJSFunction(): - processed_stack.insert(0, func_entry_node.value.ToString()) - entry.Tick(pc, processed_stack) - if self.call_graph_json: - self.AddToPackedStacks(pc, stack) - - def AddToPackedStacks(self, pc, stack): - full_stack = stack - full_stack.insert(0, pc) - func_names = self.ProcessStack(full_stack) - func_ids = [] - for func in func_names: - func_ids.append(self.func_enum.GetFunctionId(func)) - self.packed_stacks.append(func_ids) - - def PrintResults(self): - if not self.call_graph_json: - self.PrintStatistics() - else: - self.PrintCallGraphJSON() - - def PrintStatistics(self): - print('Statistical profiling result from %s, (%d ticks, %d unaccounted, %d excluded).' % - (self.log_file, - self.total_number_of_ticks, - self.unaccounted_number_of_ticks, - self.excluded_number_of_ticks)) - if self.total_number_of_ticks > 0: - js_entries = self.js_entries.ExportValueList() - js_entries.extend(self.deleted_code) - cpp_entries = self.cpp_entries.ExportValueList() - # Print the unknown ticks percentage if they are not ignored. - if not self.ignore_unknown and self.unaccounted_number_of_ticks > 0: - self.PrintHeader('Unknown') - self.PrintCounter(self.unaccounted_number_of_ticks) - # Print the library ticks. - self.PrintHeader('Shared libraries') - self.PrintEntries(cpp_entries, lambda e:e.IsSharedLibraryEntry()) - # Print the JavaScript ticks. - self.PrintHeader('JavaScript') - self.PrintEntries(js_entries, lambda e:not e.IsSharedLibraryEntry()) - # Print the C++ ticks. - self.PrintHeader('C++') - self.PrintEntries(cpp_entries, lambda e:not e.IsSharedLibraryEntry()) - # Print the GC ticks. - self.PrintHeader('GC') - self.PrintCounter(self.number_of_gc_ticks) - # Print call profile. - print('\n [Call profile]:') - print(' total call path') - js_entries.extend(cpp_entries) - self.PrintCallProfile(js_entries) - - def PrintHeader(self, header_title): - print('\n [%s]:' % header_title) - print(' ticks total nonlib name') - - def PrintCounter(self, ticks_count): - percentage = ticks_count * 100.0 / self.total_number_of_ticks - print(' %(ticks)5d %(total)5.1f%%' % { - 'ticks' : ticks_count, - 'total' : percentage, - }) - - def PrintEntries(self, entries, condition): - # If ignoring unaccounted ticks don't include these in percentage - # calculations - number_of_accounted_ticks = self.total_number_of_ticks - if self.ignore_unknown: - number_of_accounted_ticks -= self.unaccounted_number_of_ticks - - number_of_non_library_ticks = number_of_accounted_ticks - self.number_of_library_ticks - entries.sort(key=lambda e: (e.tick_count, e.ToString()), reverse=True) - for entry in entries: - if entry.tick_count > 0 and condition(entry): - total_percentage = entry.tick_count * 100.0 / number_of_accounted_ticks - if entry.IsSharedLibraryEntry(): - non_library_percentage = 0 - else: - non_library_percentage = entry.tick_count * 100.0 / number_of_non_library_ticks - print(' %(ticks)5d %(total)5.1f%% %(nonlib)6.1f%% %(name)s' % { - 'ticks' : entry.tick_count, - 'total' : total_percentage, - 'nonlib' : non_library_percentage, - 'name' : entry.ToString() - }) - region_ticks = entry.RegionTicks() - if not region_ticks is None: - items = region_ticks.items() - items.sort(key=lambda e: e[1][1], reverse=True) - for (name, ticks) in items: - print(' flat cum') - print(' %(flat)5.1f%% %(accum)5.1f%% %(name)s' % { - 'flat' : ticks[1] * 100.0 / entry.tick_count, - 'accum' : ticks[0] * 100.0 / entry.tick_count, - 'name': name - }) - - def PrintCallProfile(self, entries): - all_stacks = {} - total_stacks = 0 - for entry in entries: - all_stacks.update(entry.stacks) - for count in entry.stacks.itervalues(): - total_stacks += count - all_stacks_items = all_stacks.items(); - all_stacks_items.sort(key = itemgetter(1), reverse=True) - missing_percentage = (self.total_number_of_ticks - total_stacks) * 100.0 / self.total_number_of_ticks - print(' %(ticks)5d %(total)5.1f%% <no call path information>' % { - 'ticks' : self.total_number_of_ticks - total_stacks, - 'total' : missing_percentage - }) - for stack, count in all_stacks_items: - total_percentage = count * 100.0 / self.total_number_of_ticks - print(' %(ticks)5d %(total)5.1f%% %(call_path)s' % { - 'ticks' : count, - 'total' : total_percentage, - 'call_path' : stack[0] + ' <- ' + stack[1] - }) - - def PrintCallGraphJSON(self): - print('\nvar __profile_funcs = ["' + - '",\n"'.join(self.func_enum.GetKnownFunctions()) + - '"];') - print('var __profile_ticks = [') - str_packed_stacks = [] - for stack in self.packed_stacks: - str_packed_stacks.append('[' + ','.join(map(str, stack)) + ']') - print(',\n'.join(str_packed_stacks)) - print('];') - -class CmdLineProcessor(object): - - def __init__(self): - self.options = ["js", - "gc", - "compiler", - "other", - "external", - "ignore-unknown", - "separate-ic", - "call-graph-json"] - # default values - self.state = None - self.ignore_unknown = False - self.log_file = None - self.separate_ic = False - self.call_graph_json = False - - def ProcessArguments(self): - try: - opts, args = getopt.getopt(sys.argv[1:], "jgcoe", self.options) - except getopt.GetoptError: - self.PrintUsageAndExit() - for key, value in opts: - if key in ("-j", "--js"): - self.state = VMStates['JS'] - if key in ("-g", "--gc"): - self.state = VMStates['GC'] - if key in ("-c", "--compiler"): - self.state = VMStates['COMPILER'] - if key in ("-o", "--other"): - self.state = VMStates['OTHER'] - if key in ("-e", "--external"): - self.state = VMStates['EXTERNAL'] - if key in ("--ignore-unknown"): - self.ignore_unknown = True - if key in ("--separate-ic"): - self.separate_ic = True - if key in ("--call-graph-json"): - self.call_graph_json = True - self.ProcessRequiredArgs(args) - - def ProcessRequiredArgs(self, args): - return - - def GetRequiredArgsNames(self): - return - - def PrintUsageAndExit(self): - print('Usage: %(script_name)s --{%(opts)s} %(req_opts)s' % { - 'script_name': os.path.basename(sys.argv[0]), - 'opts': string.join(self.options, ','), - 'req_opts': self.GetRequiredArgsNames() - }) - sys.exit(2) - - def RunLogfileProcessing(self, tick_processor): - tick_processor.ProcessLogfile(self.log_file, self.state, self.ignore_unknown, - self.separate_ic, self.call_graph_json) - - -if __name__ == '__main__': - sys.exit('You probably want to run windows-tick-processor.py or linux-tick-processor.py.') diff --git a/tools/utils.py b/tools/utils.py index 8083091b..fb94d141 100644 --- a/tools/utils.py +++ b/tools/utils.py @@ -49,6 +49,8 @@ def GuessOS(): return 'linux' elif id == 'Darwin': return 'macos' + elif id.find('CYGWIN') >= 0: + return 'cygwin' elif id == 'Windows' or id == 'Microsoft': # On Windows Vista platform.system() can return 'Microsoft' with some # versions of Python, see http://bugs.python.org/issue1082 diff --git a/tools/v8.xcodeproj/project.pbxproj b/tools/v8.xcodeproj/project.pbxproj index 24321e52..10fbc58a 100644 --- a/tools/v8.xcodeproj/project.pbxproj +++ b/tools/v8.xcodeproj/project.pbxproj @@ -211,6 +211,7 @@ 895692A512D4ED240072C313 /* objects-printer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8946827412C26EB700C914BC /* objects-printer.cc */; }; 8956B6CF0F5D86730033B5A2 /* debug-agent.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8956B6CD0F5D86570033B5A2 /* debug-agent.cc */; }; 895FA753107FFED3006F39D4 /* constants-arm.cc in Sources */ = {isa = PBXBuildFile; fileRef = 895FA748107FFE73006F39D4 /* constants-arm.cc */; }; + 896FA1E5130F93D300042054 /* lithium-gap-resolver-arm.cc in Sources */ = {isa = PBXBuildFile; fileRef = 896FA1E3130F93D300042054 /* lithium-gap-resolver-arm.cc */; }; 896FD03A0E78D717003DFB6A /* libv8-arm.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 89F23C870E78D5B2006B2466 /* libv8-arm.a */; }; 897C77D012B68E3D000767A8 /* d8-debug.cc in Sources */ = {isa = PBXBuildFile; fileRef = 893988150F2A3686007D5254 /* d8-debug.cc */; }; 897C77D112B68E3D000767A8 /* d8-js.cc in Sources */ = {isa = PBXBuildFile; fileRef = 893988320F2A3B8B007D5254 /* d8-js.cc */; }; @@ -647,6 +648,8 @@ 895FA751107FFEAE006F39D4 /* register-allocator-arm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "register-allocator-arm.h"; path = "arm/register-allocator-arm.h"; sourceTree = "<group>"; }; 8964482B0E9C00F700E7C516 /* codegen-ia32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "codegen-ia32.h"; path = "ia32/codegen-ia32.h"; sourceTree = "<group>"; }; 896448BC0E9D530500E7C516 /* codegen-arm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "codegen-arm.h"; path = "arm/codegen-arm.h"; sourceTree = "<group>"; }; + 896FA1E3130F93D300042054 /* lithium-gap-resolver-arm.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "lithium-gap-resolver-arm.cc"; path = "arm/lithium-gap-resolver-arm.cc"; sourceTree = "<group>"; }; + 896FA1E4130F93D300042054 /* lithium-gap-resolver-arm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "lithium-gap-resolver-arm.h"; path = "arm/lithium-gap-resolver-arm.h"; sourceTree = "<group>"; }; 8970F2F00E719FB2006AE7B5 /* libv8.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libv8.a; sourceTree = BUILT_PRODUCTS_DIR; }; 897C77D912B68E3D000767A8 /* d8-arm */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "d8-arm"; sourceTree = BUILT_PRODUCTS_DIR; }; 897F767A0E71B4CC007ACF34 /* v8_shell */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = v8_shell; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1538,6 +1541,8 @@ 893E24C812B14B510083370F /* lithium-arm.h */, 893E24C912B14B520083370F /* lithium-codegen-arm.cc */, 893E24CA12B14B520083370F /* lithium-codegen-arm.h */, + 896FA1E3130F93D300042054 /* lithium-gap-resolver-arm.cc */, + 896FA1E4130F93D300042054 /* lithium-gap-resolver-arm.h */, 897FF1540E719B8F00D62E90 /* macro-assembler-arm.cc */, 897FF1550E719B8F00D62E90 /* macro-assembler-arm.h */, 89A15C700EE466D000B48DEB /* regexp-macro-assembler-arm.cc */, @@ -2290,6 +2295,7 @@ 894A59EA12D777E80000766D /* lithium.cc in Sources */, 89D7DDDC12E8DE09001E2B82 /* gdb-jit.cc in Sources */, 89D7DDDD12E8DE09001E2B82 /* inspector.cc in Sources */, + 896FA1E5130F93D300042054 /* lithium-gap-resolver-arm.cc in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/tools/visual_studio/x64.vsprops b/tools/visual_studio/x64.vsprops index 79904403..04d9c655 100644 --- a/tools/visual_studio/x64.vsprops +++ b/tools/visual_studio/x64.vsprops @@ -12,6 +12,7 @@ /> <Tool Name="VCLinkerTool" + StackReserveSize="2091752" TargetMachine="17" /> </VisualStudioPropertySheet> diff --git a/tools/windows-tick-processor.py b/tools/windows-tick-processor.py deleted file mode 100755 index ade2bf27..00000000 --- a/tools/windows-tick-processor.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2008 the V8 project authors. All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -# Usage: process-ticks.py <binary> <logfile> -# -# Where <binary> is the binary program name (eg, v8_shell.exe) and -# <logfile> is the log file name (eg, v8.log). -# -# This tick processor expects to find a map file for the binary named -# binary.map if the binary is named binary.exe. The tick processor -# only works for statically linked executables - no information about -# shared libraries is logged from v8 on Windows. - -import os, re, sys, tickprocessor - -class WindowsTickProcessor(tickprocessor.TickProcessor): - - def Unmangle(self, name): - """Performs very simple unmangling of C++ names. - - Does not handle arguments and template arguments. The mangled names have - the form: - - ?LookupInDescriptor@JSObject@internal@v8@@...arguments info... - - """ - # Name is mangled if it starts with a question mark. - is_mangled = re.match("^\?(.*)", name) - if is_mangled: - substrings = is_mangled.group(1).split('@') - try: - # The function name is terminated by two @s in a row. Find the - # substrings that are part of the function name. - index = substrings.index('') - substrings = substrings[0:index] - except ValueError: - # If we did not find two @s in a row, the mangled name is not in - # the format we expect and we give up. - return name - substrings.reverse() - function_name = "::".join(substrings) - return function_name - return name - - - def ParseMapFile(self, filename): - """Parse map file and add symbol information to the cpp entries.""" - # Locate map file. - has_dot = re.match('^([a-zA-F0-9_-]*)[\.]?.*$', filename) - if has_dot: - map_file_name = has_dot.group(1) + '.map' - try: - map_file = open(map_file_name, 'rb') - except IOError: - sys.exit("Could not open map file: " + map_file_name) - else: - sys.exit("Could not find map file for executable: " + filename) - try: - max_addr = 0 - min_addr = 2**30 - # Process map file and search for function entries. - row_regexp = re.compile(' 0001:[0-9a-fA-F]{8}\s*([_\?@$0-9a-zA-Z]*)\s*([0-9a-fA-F]{8}).*') - for line in map_file: - row = re.match(row_regexp, line) - if row: - addr = int(row.group(2), 16) - if addr > max_addr: - max_addr = addr - if addr < min_addr: - min_addr = addr - mangled_name = row.group(1) - name = self.Unmangle(mangled_name) - self.cpp_entries.Insert(addr, tickprocessor.CodeEntry(addr, name)); - i = min_addr - # Mark the pages for which there are functions in the map file. - while i < max_addr: - page = i >> 12 - self.vm_extent[page] = 1 - i += 4096 - finally: - map_file.close() - - -class WindowsCmdLineProcessor(tickprocessor.CmdLineProcessor): - - def __init__(self): - super(WindowsCmdLineProcessor, self).__init__() - self.binary_file = None - - def GetRequiredArgsNames(self): - return 'binary log_file' - - def ProcessRequiredArgs(self, args): - if len(args) != 2: - self.PrintUsageAndExit() - else: - self.binary_file = args[0] - self.log_file = args[1] - - -def Main(): - cmdline_processor = WindowsCmdLineProcessor() - cmdline_processor.ProcessArguments() - tickprocessor = WindowsTickProcessor() - tickprocessor.ParseMapFile(cmdline_processor.binary_file) - cmdline_processor.RunLogfileProcessing(tickprocessor) - tickprocessor.PrintResults() - -if __name__ == '__main__': - Main() |