From 2c83eb550eeab3c6d38b8ccf2998b438e4b5c052 Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Wed, 24 Oct 2018 13:30:16 +0000 Subject: [PATCH] [vm] Integrate dart binary with Crashpad on Windows. dart binary would instantiate CrashpadClient if DART_CRASHPAD_HANDLER and DART_CRASHPAD_CRASHES_DIR environment variables are set. - DART_CRASHPAD_HANDLER should contain the path to the crashpad_handler binary that would handle the crash and write minidump; - DART_CRASHPAD_CRASHES_DIR should contain the path to the crashpad database which would be used to store minidumps. Rewrite --copy-crash-dumps support on windows to use Crashpad integration instead of editing Windows registry. Embedding crashpad required to roll a new zlib version because Crashpad depends on the zlib. This version of zlib is buildable with its own BUILD.gn so our custom BUILD.gn is removed. Change-Id: I048aad16b234e1d750f0a24782b04e3b6e19703d Reviewed-on: https://dart-review.googlesource.com/c/81007 Commit-Queue: Vyacheslav Egorov Reviewed-by: Zach Anderson Reviewed-by: Martin Kustermann --- DEPS | 25 +++- build/config/BUILDCONFIG.gn | 3 + build/config/compiler/BUILD.gn | 7 ++ build/config/win/BUILD.gn | 3 - build/toolchain/win/BUILD.gn | 37 ++++-- runtime/bin/BUILD.gn | 23 +++- runtime/bin/filter.cc | 10 ++ runtime/bin/main.cc | 49 ++++++++ runtime/bin/zlib/BUILD.gn | 54 -------- runtime/runtime_args.gni | 9 +- third_party/.gitignore | 1 + tools/gn.py | 18 ++- tools/minidump.py | 190 +++++++++++++++++++++++++++++ tools/testing/dart/test_suite.dart | 4 + tools/utils.py | 132 +++++--------------- 15 files changed, 380 insertions(+), 185 deletions(-) delete mode 100644 runtime/bin/zlib/BUILD.gn create mode 100644 tools/minidump.py diff --git a/DEPS b/DEPS index d3af193b7ebda..d02659c568457 100644 --- a/DEPS +++ b/DEPS @@ -144,7 +144,10 @@ vars = { "web_socket_channel_tag": "1.0.9", "WebCore_rev": "fb11e887f77919450e497344da570d780e078bc8", "yaml_tag": "2.1.15", - "zlib_rev": "c3d0a6190f2f8c924a05ab6cc97b8f975bddd33f", + "zlib_rev": "c44fb7248079cc3d5563b14b3f758aee60d6b415", + "crashpad_rev": "bf327d8ceb6a669607b0dbab5a83a275d03f99ed", + "minichromium_rev": "8d641e30a8b12088649606b912c2bc4947419ccc", + "googletest_rev": "f854f1d27488996dc8a6db3c9453f80b02585e12", } deps = { @@ -376,11 +379,23 @@ deps = { "@" + Var("web_socket_channel_tag"), Var("dart_root") + "/third_party/pkg/yaml": Var("dart_git") + "yaml.git" + "@" + Var("yaml_tag"), - Var("dart_root") + "/third_party/cygwin": { - "url": Var("chromium_git") + "/chromium/deps/cygwin.git" + "@" + +} + +deps_os = { + "win": { + Var("dart_root") + "/third_party/cygwin": + Var("chromium_git") + "/chromium/deps/cygwin.git" + "@" + "c89e446b273697fadf3a10ff1007a97c0b7de6df", - "condition": "checkout_win", - }, + Var("dart_root") + "/third_party/crashpad/crashpad": + Var("chromium_git") + "/crashpad/crashpad.git" + "@" + + Var("crashpad_rev"), + Var("dart_root") + "/third_party/mini_chromium/mini_chromium": + Var("chromium_git") + "/chromium/mini_chromium" + "@" + + Var("minichromium_rev"), + Var("dart_root") + "/third_party/googletest": + Var("fuchsia_git") + "/third_party/googletest" + "@" + + Var("googletest_rev"), + } } # TODO(iposva): Move the necessary tools so that hooks can be run diff --git a/build/config/BUILDCONFIG.gn b/build/config/BUILDCONFIG.gn index 03349440b381a..109e3fc4b5aa8 100644 --- a/build/config/BUILDCONFIG.gn +++ b/build/config/BUILDCONFIG.gn @@ -269,6 +269,9 @@ if (is_debug) { } _native_compiler_configs += [ _default_optimization_config ] +# zlib's BUILD.gn expects to have this config among default configs. +_native_compiler_configs += [ "//build/config/compiler:default_optimization" ] + # Symbol setup. _default_symbols_config = "//build/config/compiler:symbols" _native_compiler_configs += [ _default_symbols_config ] diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn index 4e9d66b15858f..80255219e6c12 100644 --- a/build/config/compiler/BUILD.gn +++ b/build/config/compiler/BUILD.gn @@ -748,6 +748,13 @@ config("no_optimize") { } } +# These are two named configs that zlib's BUILD.gn expects to exist. +config("default_optimization") { +} + +config("optimize_speed") { +} + # Symbols ---------------------------------------------------------------------- config("symbols") { diff --git a/build/config/win/BUILD.gn b/build/config/win/BUILD.gn index 458479e259382..daf26ed6f67a5 100644 --- a/build/config/win/BUILD.gn +++ b/build/config/win/BUILD.gn @@ -73,9 +73,6 @@ config("common_linker_setup") { # exceeds maximum allowable size (80000000) # which started happening more regularly after VS2013 Update 4. "/maxilksize:2147483647", - - # Force the creation of a .lib file for all executable() targets. - "/EXPORT:main", ] # ASLR makes debugging with windbg difficult because Chrome.exe and diff --git a/build/toolchain/win/BUILD.gn b/build/toolchain/win/BUILD.gn index b8d222acc6a55..0784d0326b7de 100644 --- a/build/toolchain/win/BUILD.gn +++ b/build/toolchain/win/BUILD.gn @@ -22,13 +22,13 @@ tool_wrapper_path = rebase_path("tool_wrapper.py", root_build_dir) # Setup the Visual Studio state. toolchain_data = exec_script("setup_toolchain.py", - [ - visual_studio_path, - windows_sdk_path, - visual_studio_runtime_dirs, - current_cpu, - ], - "scope") + [ + visual_studio_path, + windows_sdk_path, + visual_studio_runtime_dirs, + current_cpu, + ], + "scope") if (vc_bin_dir == "") { vc_bin_dir = toolchain_data.vc_bin_dir @@ -164,6 +164,26 @@ template("msvc_toolchain") { rspfile_content = "{{libs}} {{solibs}} {{inputs_newline}} {{ldflags}}" } + tool("solink_module") { + dllname = "{{output_dir}}/{{target_output_name}}{{output_extension}}" # e.g. foo.dll + pdbname = "${dllname}.pdb" + rspfile = "${dllname}.rsp" + + command = "$python_path $tool_wrapper_path link-wrapper $env False link.exe /nologo /DLL /OUT:$dllname /PDB:$pdbname @$rspfile" + default_output_extension = ".dll" + default_output_dir = "{{root_out_dir}}" + description = "LINK_MODULE(DLL) {{output}}" + outputs = [ + dllname, + pdbname, + ] + runtime_outputs = outputs + + # The use of inputs_newline is to work around a fixed per-line buffer + # size in the linker. + rspfile_content = "{{libs}} {{solibs}} {{inputs_newline}} {{ldflags}}" + } + tool("link") { binary_output = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" @@ -194,8 +214,7 @@ template("msvc_toolchain") { } tool("copy") { - command = - "$python_path $tool_wrapper_path recursive-mirror {{source}} {{output}}" + command = "$python_path $tool_wrapper_path recursive-mirror {{source}} {{output}}" description = "COPY {{source}} {{output}}" } diff --git a/runtime/bin/BUILD.gn b/runtime/bin/BUILD.gn index 6a819ccc1e292..a2bdfc3d6bf48 100644 --- a/runtime/bin/BUILD.gn +++ b/runtime/bin/BUILD.gn @@ -675,6 +675,24 @@ template("dart_executable") { deps += [ "//third_party/tcmalloc" ] } + include_dirs = [ + "..", + "//third_party", + ] + + if (dart_use_crashpad) { + assert(is_win, "dart_use_crashpad is only supported on Windows") + deps += [ + "//third_party/crashpad/crashpad/client", + "//third_party/mini_chromium/mini_chromium/base", + + # This binary is used to handle crashes of the dart binary. + "//third_party/crashpad/crashpad/handler:crashpad_handler", + ] + include_dirs += [ "//third_party/crashpad" ] + defines += [ "DART_USE_CRASHPAD" ] + } + sources = [ "dart_embedder_api_impl.cc", "error_exit.cc", @@ -690,11 +708,6 @@ template("dart_executable") { "vmservice_impl.h", ] + extra_sources - include_dirs = [ - "..", - "//third_party", - ] - if (is_win) { ldflags = [ "/EXPORT:Dart_True" ] } else { diff --git a/runtime/bin/filter.cc b/runtime/bin/filter.cc index 5059d7f737ac6..292b97833ea07 100644 --- a/runtime/bin/filter.cc +++ b/runtime/bin/filter.cc @@ -293,6 +293,16 @@ ZLibDeflateFilter::~ZLibDeflateFilter() { bool ZLibDeflateFilter::Init() { int window_bits = window_bits_; + if ((raw_ || gzip_) && (window_bits == 8)) { + // zlib deflater does not work with windows size of 8 bits. Old versions + // of zlib would silently upgrade window size to 9 bits, newer versions + // return Z_STREAM_ERROR if window size is 8 bits but the stream header + // is suppressed. To maintain the old behavior upgrade window size here. + // This is safe because you can inflate a stream deflated with zlib + // using 9-bits with 8-bits window. + // For more details see https://crbug.com/691074. + window_bits = 9; + } if (raw_) { window_bits = -window_bits; } else if (gzip_) { diff --git a/runtime/bin/main.cc b/runtime/bin/main.cc index 052fa2fad5453..5473185869d1b 100644 --- a/runtime/bin/main.cc +++ b/runtime/bin/main.cc @@ -11,6 +11,11 @@ #include "include/dart_embedder_api.h" #include "include/dart_tools_api.h" +#if defined(DART_USE_CRASHPAD) +#include "crashpad/client/crashpad_client.h" +#include "crashpad/client/crashpad_info.h" +#endif + #include "bin/builtin.h" #include "bin/console.h" #include "bin/dartutils.h" @@ -947,6 +952,45 @@ Dart_Handle GetVMServiceAssetsArchiveCallback() { static Dart_GetVMServiceAssetsArchive GetVMServiceAssetsArchiveCallback = NULL; #endif // !defined(NO_OBSERVATORY) +#if defined(DART_USE_CRASHPAD) +#if !defined(HOST_OS_WINDOWS) +#error "Currently we only support Crashpad on Windows" +#endif + +static void ConfigureCrashpadClient(crashpad::CrashpadClient* client) { + // DART_CRASHPAD_HANDLER and DART_CRASHPAD_CRASHES_DIR are set by the + // testing framework. + wchar_t* handler = _wgetenv(L"DART_CRASHPAD_HANDLER"); + wchar_t* crashes_dir = _wgetenv(L"DART_CRASHPAD_CRASHES_DIR"); + if (handler == nullptr || crashes_dir == nullptr) { + return; + } + + // Crashpad uses STL so we use it here too even though in general we + // avoid it. + const base::FilePath handler_path{std::wstring(handler)}; + const base::FilePath crashes_dir_path{std::wstring(crashes_dir)}; + const std::string url(""); + std::map annotations; + char* test_name = getenv("DART_TEST_NAME"); + if (test_name != nullptr) { + annotations["dart_test_name"] = test_name; + } + + std::vector arguments; + if (!client->StartHandler(handler_path, crashes_dir_path, crashes_dir_path, + url, annotations, arguments, + /*restartable=*/true, + /*asynchronous_start=*/false)) { + Log::PrintErr("Failed to start the crash handler!\n"); + Platform::Exit(kErrorExitCode); + } + crashpad::CrashpadInfo::GetCrashpadInfo() + ->set_gather_indirectly_referenced_memory(crashpad::TriState::kEnabled, + /*limit=*/500 * MB); +} +#endif // DART_USE_CRASHPAD + void main(int argc, char** argv) { char* script_name; const int EXTRA_VM_ARGUMENTS = 10; @@ -1010,6 +1054,11 @@ void main(int argc, char** argv) { } DartUtils::SetEnvironment(Options::environment()); +#if defined(DART_USE_CRASHPAD) + crashpad::CrashpadClient crashpad_client; + ConfigureCrashpadClient(&crashpad_client); +#endif + Loader::InitOnce(); #if defined(DART_LINK_APP_SNAPSHOT) diff --git a/runtime/bin/zlib/BUILD.gn b/runtime/bin/zlib/BUILD.gn deleted file mode 100644 index 8950ae18a2f58..0000000000000 --- a/runtime/bin/zlib/BUILD.gn +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) 2013 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -config("zlib_config") { - include_dirs = [ "//third_party/zlib" ] -} - -static_library("zlib") { - if (!is_win) { - # Don't stomp on "libzlib" on other platforms. - output_name = "chrome_zlib" - } - - zlib_path = "//third_party/zlib" - sources = [ - "$zlib_path/adler32.c", - "$zlib_path/compress.c", - "$zlib_path/crc32.c", - "$zlib_path/crc32.h", - "$zlib_path/deflate.c", - "$zlib_path/deflate.h", - "$zlib_path/gzclose.c", - "$zlib_path/gzguts.h", - "$zlib_path/gzlib.c", - "$zlib_path/gzread.c", - "$zlib_path/gzwrite.c", - "$zlib_path/infback.c", - "$zlib_path/inffast.c", - "$zlib_path/inffast.h", - "$zlib_path/inffixed.h", - "$zlib_path/inflate.c", - "$zlib_path/inflate.h", - "$zlib_path/inftrees.c", - "$zlib_path/inftrees.h", - "$zlib_path/mozzconf.h", - "$zlib_path/trees.c", - "$zlib_path/trees.h", - "$zlib_path/uncompr.c", - "$zlib_path/zconf.h", - "$zlib_path/zlib.h", - "$zlib_path/zutil.c", - "$zlib_path/zutil.h", - ] - - configs -= [ "//build/config/compiler:chromium_code" ] - configs += [ "//build/config/compiler:no_chromium_code" ] - - all_dependent_configs = [ ":zlib_config" ] - - if (is_clang) { - cflags = [ "-Wno-shift-negative-value" ] - } -} diff --git a/runtime/runtime_args.gni b/runtime/runtime_args.gni index 16fff6445d7cf..234ac23a41bd8 100644 --- a/runtime/runtime_args.gni +++ b/runtime/runtime_args.gni @@ -42,16 +42,17 @@ declare_args() { # verified at the operating system level. dart_use_fallback_root_certificates = false - # The BUILD.gn file that we pull from chromium as part of zlib has a - # dependence on //base, which we don't pull in. In a standalone build of the - # VM, we set this to //runtime/bin/zlib where we have a BUILD.gn file without - # a dependence on //base. + # TODO(vegorov): Can we eliminate this? dart_zlib_path = "//third_party/zlib" # Whether to link the standalone VM against tcmalloc. The standalone build of # the VM enables this only for Linux builds. dart_use_tcmalloc = false + # Whether to link Crashpad library for crash handling. Only supported on + # Windows for now. + dart_use_crashpad = false + # Controls the kind of core snapshot linked into the standalone VM. Using a # core-jit snapshot breaks the ability to change various flags that affect # code generation. diff --git a/third_party/.gitignore b/third_party/.gitignore index 569ad8d971e3d..9faa3c65d607f 100644 --- a/third_party/.gitignore +++ b/third_party/.gitignore @@ -13,3 +13,4 @@ !clang.tar.gz.sha1 !unittest.tar.gz.sha1 !update.sh +!/googletest diff --git a/tools/gn.py b/tools/gn.py index c2546a1714dad..50740efed5ea7 100755 --- a/tools/gn.py +++ b/tools/gn.py @@ -24,6 +24,7 @@ DART_USE_TSAN = "DART_USE_TSAN" # Use instead of --tsan DART_USE_TOOLCHAIN = "DART_USE_TOOLCHAIN" # Use instread of --toolchain-prefix DART_USE_SYSROOT = "DART_USE_SYSROOT" # Use instead of --target-sysroot +DART_USE_CRASHPAD = "DART_USE_CRASHPAD" # Use instead of --use-crashpad # use instead of --platform-sdk DART_MAKE_PLATFORM_SDK = "DART_MAKE_PLATFORM_SDK" @@ -160,7 +161,6 @@ def UseSysroot(args, gn_args): # Otherwise use the sysroot. return True - def ToGnArgs(args, mode, arch, target_os): gn_args = {} @@ -174,6 +174,13 @@ def ToGnArgs(args, mode, arch, target_os): gn_args['target_cpu'] = TargetCpuForArch(arch, target_os) gn_args['dart_target_arch'] = DartTargetCpuForArch(arch) + # Configure Crashpad library if it is used. + gn_args['dart_use_crashpad'] = (args.use_crashpad or + DART_USE_CRASHPAD in os.environ) + if gn_args['dart_use_crashpad']: + # Tell Crashpad's BUILD files which checkout layout to use. + gn_args['crashpad_dependencies'] = 'dart' + if arch != HostCpuForArch(arch): # Training an app-jit snapshot under a simulator is slow. Use script # snapshots instead. @@ -188,8 +195,6 @@ def ToGnArgs(args, mode, arch, target_os): gn_args['dart_platform_bytecode'] = args.bytecode - gn_args['dart_zlib_path'] = "//runtime/bin/zlib" - # Use tcmalloc only when targeting Linux and when not using ASAN. gn_args['dart_use_tcmalloc'] = ((gn_args['target_os'] == 'linux') and not UseSanitizer(args)) @@ -321,6 +326,9 @@ def ProcessOptions(args): print ("Cross-compilation to %s is not supported for architecture %s." % (os_name, arch)) return False + if HOST_OS != 'win' and args.use_crashpad: + print "Crashpad is only supported on Windows" + return False return True @@ -460,6 +468,10 @@ def parse_args(args): help='Number of simultaneous GN invocations', dest='workers', default=multiprocessing.cpu_count()) + other_group.add_argument('--use-crashpad', + default=False, + dest='use_crashpad', + action='store_true') options = parser.parse_args(args) if not ProcessOptions(options): diff --git a/tools/minidump.py b/tools/minidump.py new file mode 100644 index 0000000000000..2f382e418b5f2 --- /dev/null +++ b/tools/minidump.py @@ -0,0 +1,190 @@ +# Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file +# for details. All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +# This file contains a set of utilities for parsing minidumps. + +import ctypes +import mmap +import os +import sys + + +class Enum(object): + def __init__(self, type, name2value): + self.name2value = name2value + self.value2name = {v: k for k, v in name2value.iteritems()} + self.type = type + + def from_raw(self, v): + if v not in self.value2name: + return 'Unknown(' + str(v) + ')' + return self.value2name[v] + + def to_raw(self, v): + return self.name2value[v] + + +class Descriptor(object): + """A handy wrapper over ctypes.Structure""" + + def __init__(self, fields): + self.fields = fields + self.ctype = Descriptor._GetCtype(fields) + self.size = ctypes.sizeof(self.ctype) + + def Read(self, address): + return self.ctype.from_address(address) + + @staticmethod + def _GetCtype(fields): + raw_fields = [] + wrappers = {} + for field in fields: + (name, type) = field + if isinstance(type, Enum): + raw_fields.append(('_raw_' + name, type.type)) + wrappers[name] = type + else: + raw_fields.append(field) + + class Raw(ctypes.Structure): + _fields_ = raw_fields + _pack_ = 1 + + def __getattribute__(self, name): + if name in wrappers: + return wrappers[name].from_raw(getattr(self, '_raw_' + name)) + else: + return ctypes.Structure.__getattribute__(self, name) + + def __repr__(self): + return '{' + ', '.join('%s: %s' % (field, self.__getattribute__(field)) + for field, _ in fields) + '}' + + return Raw + + +# Structures below are based on the information in the MSDN pages and +# Breakpad/Crashpad sources. + + +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_STREAM_TYPE = { + '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, +} + + +MINIDUMP_DIRECTORY = Descriptor([ + ('stream_type', Enum(ctypes.c_uint32, MINIDUMP_STREAM_TYPE)), + ('location', MINIDUMP_LOCATION_DESCRIPTOR.ctype) +]) + + +MINIDUMP_MISC_INFO_2 = Descriptor([ + ('SizeOfInfo', ctypes.c_uint32), + ('Flags1', ctypes.c_uint32), + ('ProcessId', ctypes.c_uint32), + ('ProcessCreateTime', ctypes.c_uint32), + ('ProcessUserTime', ctypes.c_uint32), + ('ProcessKernelTime', ctypes.c_uint32), + ('ProcessorMaxMhz', ctypes.c_uint32), + ('ProcessorCurrentMhz', ctypes.c_uint32), + ('ProcessorMhzLimit', ctypes.c_uint32), + ('ProcessorMaxIdleState', ctypes.c_uint32), + ('ProcessorCurrentIdleState', ctypes.c_uint32), +]); + + +MINIDUMP_MISC1_PROCESS_ID = 0x00000001 + + +# A helper to get a raw address of the memory mapped buffer returned by +# mmap. +def BufferToAddress(buf): + obj = ctypes.py_object(buf) + address = ctypes.c_void_p() + length = ctypes.c_ssize_t() + ctypes.pythonapi.PyObject_AsReadBuffer( + obj, ctypes.byref(address), ctypes.byref(length)) + return address.value + + +class MinidumpFile(object): + """Class for reading minidump files.""" + _HEADER_MAGIC = 0x504d444d + + def __init__(self, minidump_name): + self.minidump_name = minidump_name + self.minidump_file = open(minidump_name, 'r') + self.minidump = mmap.mmap(self.minidump_file.fileno(), 0, access=mmap.ACCESS_READ) + self.minidump_address = BufferToAddress(self.minidump) + self.header = self.Read(MINIDUMP_HEADER, 0) + if self.header.signature != MinidumpFile._HEADER_MAGIC: + raise Exception('Unsupported minidump header magic') + self.directories = [] + offset = self.header.stream_directories_rva + for _ in xrange(self.header.stream_count): + self.directories.append(self.Read(MINIDUMP_DIRECTORY, offset)) + offset += MINIDUMP_DIRECTORY.size + + def GetProcessId(self): + for dir in self.directories: + if dir.stream_type == 'MD_MISC_INFO_STREAM': + info = self.Read(MINIDUMP_MISC_INFO_2, dir.location.rva) + if info.Flags1 & MINIDUMP_MISC1_PROCESS_ID != 0: + return info.ProcessId + return -1 + + def Read(self, what, offset): + return what.Read(self.minidump_address + offset) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.minidump.close() + self.minidump_file.close() + + +# Returns process id of the crashed process recorded in the given minidump. +def GetProcessIdFromDump(path): + try: + with MinidumpFile(path) as f: + return int(f.GetProcessId()) + except: + return -1 diff --git a/tools/testing/dart/test_suite.dart b/tools/testing/dart/test_suite.dart index b1a99bb6578c1..96533ad07b5b5 100644 --- a/tools/testing/dart/test_suite.dart +++ b/tools/testing/dart/test_suite.dart @@ -128,6 +128,10 @@ abstract class TestSuite { _environmentOverrides = { 'DART_CONFIGURATION': configuration.configurationDirectory, }; + if (configuration.copyCoreDumps && Platform.isWindows) { + _environmentOverrides['DART_CRASHPAD_HANDLER'] = + new Path(buildDir + '/crashpad_handler.exe').toNativePath(); + } } Map get environmentOverrides => _environmentOverrides; diff --git a/tools/utils.py b/tools/utils.py index 648dbd4f66622..a435328d70b22 100644 --- a/tools/utils.py +++ b/tools/utils.py @@ -36,6 +36,11 @@ def GetBotUtils(): return imp.load_source('bot_utils', os.path.join(DART_DIR, 'tools', 'bots', 'bot_utils.py')) +def GetMinidumpUtils(): + '''Dynamically load the tools/minidump.py python module.''' + return imp.load_source('minidump', os.path.join(DART_DIR, 'tools', 'minidump.py')) + + class Version(object): def __init__(self, channel, major, minor, patch, prerelease, prerelease_patch): @@ -762,90 +767,22 @@ def __exit__(self, *args): class WindowsCoreDumpEnabler(object): - """Configure Windows Error Reporting to store crash dumps. - - The documentation can be found here: - https://msdn.microsoft.com/en-us/library/windows/desktop/bb787181.aspx + """This enabler assumes that Dart binary was built with Crashpad support. + In this case DART_CRASHPAD_CRASHES_DIR environment variable allows to + specify the location of Crashpad crashes database. Actual minidumps will + be written into reports subfolder of the database. """ - - WINDOWS_COREDUMP_FOLDER = r'crashes' - - WER_NAME = r'SOFTWARE\Microsoft\Windows\Windows Error Reporting' - WER_LOCALDUMPS_NAME = r'%s\LocalDumps' % WER_NAME - IMGEXEC_NAME = (r'SOFTWARE\Microsoft\Windows NT\CurrentVersion' - r'\Image File Execution Options\WerFault.exe') + CRASHPAD_DB_FOLDER = os.path.join(DART_DIR, r'crashes') + DUMPS_FOLDER = os.path.join(CRASHPAD_DB_FOLDER, r'reports') def __init__(self): - # Depending on whether we're in cygwin or not we use a different import. - try: - import winreg - except ImportError: - import _winreg as winreg - self.winreg = winreg + pass def __enter__(self): - # We want 32 and 64 bit coredumps to land in the same coredump directory. - for sam in [self.winreg.KEY_WOW64_64KEY, self.winreg.KEY_WOW64_32KEY]: - # In case WerFault.exe was prevented from executing, we fix it here. - # TODO(kustermann): Remove this once https://crbug.com/691971 is fixed. - self._prune_existing_key( - self.winreg.HKEY_LOCAL_MACHINE, self.IMGEXEC_NAME, sam) - - # Create (or open) the WER keys. - with self.winreg.CreateKeyEx( - self.winreg.HKEY_LOCAL_MACHINE, self.WER_NAME, 0, - self.winreg.KEY_ALL_ACCESS | sam) as wer: - with self.winreg.CreateKeyEx( - self.winreg.HKEY_LOCAL_MACHINE, self.WER_LOCALDUMPS_NAME, 0, - self.winreg.KEY_ALL_ACCESS | sam) as wer_localdumps: - # Prevent any modal UI dialog & disable normal windows error reporting - # TODO(kustermann): Remove this once https://crbug.com/691971 is fixed - self.winreg.SetValueEx(wer, "DontShowUI", 0, self.winreg.REG_DWORD, 1) - self.winreg.SetValueEx(wer, "Disabled", 0, self.winreg.REG_DWORD, 1) - - coredump_folder = os.path.join( - os.getcwd(), WindowsCoreDumpEnabler.WINDOWS_COREDUMP_FOLDER) - - # Create the directory which will contain the dumps - if not os.path.exists(coredump_folder): - os.mkdir(coredump_folder) - - # Do full dumps (not just mini dumps), keep max 100 dumps and specify - # folder. - self.winreg.SetValueEx( - wer_localdumps, "DumpType", 0, self.winreg.REG_DWORD, 2) - self.winreg.SetValueEx( - wer_localdumps, "DumpCount", 0, self.winreg.REG_DWORD, 200) - self.winreg.SetValueEx( - wer_localdumps, "DumpFolder", 0, self.winreg.REG_EXPAND_SZ, - coredump_folder) + os.environ['DART_CRASHPAD_CRASHES_DIR'] = WindowsCoreDumpEnabler.CRASHPAD_DB_FOLDER def __exit__(self, *_): - # We remove the local dumps settings after running the tests. - for sam in [self.winreg.KEY_WOW64_64KEY, self.winreg.KEY_WOW64_32KEY]: - with self.winreg.CreateKeyEx( - self.winreg.HKEY_LOCAL_MACHINE, self.WER_LOCALDUMPS_NAME, 0, - self.winreg.KEY_ALL_ACCESS | sam) as wer_localdumps: - self.winreg.DeleteValue(wer_localdumps, 'DumpType') - self.winreg.DeleteValue(wer_localdumps, 'DumpCount') - self.winreg.DeleteValue(wer_localdumps, 'DumpFolder') - - def _prune_existing_key(self, key, subkey, wowbit): - handle = None - - # If the open fails, the key doesn't exist and it's fine. - try: - handle = self.winreg.OpenKey( - key, subkey, 0, self.winreg.KEY_READ | wowbit) - except OSError: - pass - - # If the key exists then we delete it. If the deletion does not work, we - # let the exception through. - if handle: - handle.Close() - self.winreg.DeleteKeyEx(key, subkey, wowbit, 0) - + del os.environ['DART_CRASHPAD_CRASHES_DIR'] class BaseCoreDumpArchiver(object): """This class reads coredumps file written by UnexpectedCrashDumpArchiver @@ -1012,8 +949,9 @@ def __init__(self, output_directory): class WindowsCoreDumpArchiver(BaseCoreDumpArchiver): def __init__(self, output_directory): - super(WindowsCoreDumpArchiver, self).__init__(os.path.join( - os.getcwd(), WindowsCoreDumpEnabler.WINDOWS_COREDUMP_FOLDER), output_directory) + super(WindowsCoreDumpArchiver, self).__init__( + WindowsCoreDumpEnabler.DUMPS_FOLDER, output_directory) + self._dumps_by_pid = None def _cleanup(self): found = super(WindowsCoreDumpArchiver, self)._cleanup() @@ -1023,35 +961,25 @@ def _cleanup(self): return found def _find_coredump_file(self, crash): - pattern = os.path.join(self._search_dir, '*.%s.*' % crash.pid) - for core_filename in glob.glob(pattern): - return core_filename + if self._dumps_by_pid is None: + # If this function is invoked the first time then look through the directory + # that contains crashes for all dump files and collect pid -> filename + # mapping. + self._dumps_by_pid = {} + minidump = GetMinidumpUtils() + pattern = os.path.join(self._search_dir, '*.dmp') + for core_filename in glob.glob(pattern): + pid = minidump.GetProcessIdFromDump(core_filename) + if pid != -1: + self._dumps_by_pid[str(pid)] = core_filename + if crash.pid in self._dumps_by_pid: + return self._dumps_by_pid[crash.pid] def _report_missing_crashes(self, missing, throw=True): # Let's only print the debugging information and not throw. We'll do more # validation for werfault.exe and throw afterwards. super(WindowsCoreDumpArchiver, self)._report_missing_crashes(missing, throw=False) - # Let's check again for the image execution options for werfault. Maybe - # puppet came a long during testing and reverted our change. - try: - import winreg - except ImportError: - import _winreg as winreg - for wowbit in [winreg.KEY_WOW64_64KEY, winreg.KEY_WOW64_32KEY]: - try: - with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, - WindowsCoreDumpEnabler.IMGEXEC_NAME, - 0, - winreg.KEY_READ | wowbit) as handle: - raise Exception( - "Found werfault.exe was disabled. Probably by puppet. Too bad! " - "(For more information see https://crbug.com/691971)") - except OSError: - # If the open did not work the werfault.exe execution setting is as it - # should be. - pass - if throw: missing_as_string = ', '.join([str(c) for c in missing]) raise Exception('Missing crash dumps for: %s' % missing_as_string)