diff --git a/.gitmodules b/.gitmodules index cbda14d4d4f6..c114e1489d86 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "vendor/apm"] path = vendor/apm url = https://github.com/atom/apm.git +[submodule "vendor/breakpad"] + path = vendor/breakpad + url = https://github.com/atom/chromium-breakpad.git diff --git a/app/atom_main.cc b/app/atom_main.cc index 5795418cbf57..c673fdab67de 100644 --- a/app/atom_main.cc +++ b/app/atom_main.cc @@ -17,6 +17,7 @@ #include "app/atom_main_delegate.h" #include "base/environment.h" +#include "common/crash_reporter/win/crash_service_main.h" #include "content/public/app/startup_helper_win.h" #include "sandbox/win/src/sandbox_types.h" #else // defined(OS_WIN) @@ -34,16 +35,20 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { int argc = 0; wchar_t** wargv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - // Attach to the parent console if we've got one so that stdio works - AttachConsole(ATTACH_PARENT_PROCESS); - - FILE* dontcare; - freopen_s(&dontcare, "CON", "w", stdout); - freopen_s(&dontcare, "CON", "w", stderr); - freopen_s(&dontcare, "CON", "r", stdin); - scoped_ptr env(base::Environment::Create()); - std::string node_indicator; + + // Make output work in console if we are not in cygiwn. + std::string os; + if (env->GetVar("OS", &os) && os != "cygwin") { + AttachConsole(ATTACH_PARENT_PROCESS); + + FILE* dontcare; + freopen_s(&dontcare, "CON", "w", stdout); + freopen_s(&dontcare, "CON", "w", stderr); + freopen_s(&dontcare, "CON", "r", stdin); + } + + std::string node_indicator, crash_service_indicator; if (env->GetVar("ATOM_SHELL_INTERNAL_RUN_AS_NODE", &node_indicator) && node_indicator == "1") { // Convert argv to to UTF8 @@ -81,6 +86,10 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { } // Now that conversion is done, we can finally start. return node::Start(argc, argv); + } else if (env->GetVar("ATOM_SHELL_INTERNAL_CRASH_SERVICE", + &crash_service_indicator) && + crash_service_indicator == "1") { + return crash_service::Main(cmd); } sandbox::SandboxInterfaceInfo sandbox_info = {0}; diff --git a/app/atom_main_delegate_mac.mm b/app/atom_main_delegate_mac.mm index e2073df5caff..e304582ee9ca 100644 --- a/app/atom_main_delegate_mac.mm +++ b/app/atom_main_delegate_mac.mm @@ -34,7 +34,7 @@ base::FilePath AtomMainDelegate::GetResourcesPakFilePath() { void AtomMainDelegate::OverrideFrameworkBundlePath() { base::mac::SetOverrideFrameworkBundlePath( - GetFrameworksPath().Append("Atom.framework")); + GetFrameworksPath().Append("Atom Framework.framework")); } void AtomMainDelegate::OverrideChildProcessPath() { diff --git a/atom.gyp b/atom.gyp index 5f3efe70eea4..501183054c4a 100644 --- a/atom.gyp +++ b/atom.gyp @@ -2,6 +2,7 @@ 'variables': { 'project_name': 'atom', 'product_name': 'Atom', + 'framework_name': 'Atom Framework', 'app_sources': [ 'app/atom_main.cc', 'app/atom_main.h', @@ -14,7 +15,6 @@ 'browser/api/lib/atom-delegate.coffee', 'browser/api/lib/auto-updater.coffee', 'browser/api/lib/browser-window.coffee', - 'browser/api/lib/crash-reporter.coffee', 'browser/api/lib/dialog.coffee', 'browser/api/lib/ipc.coffee', 'browser/api/lib/menu.coffee', @@ -26,6 +26,7 @@ 'browser/atom/rpc-server.coffee', 'common/api/lib/callbacks-registry.coffee', 'common/api/lib/clipboard.coffee', + 'common/api/lib/crash-reporter.coffee', 'common/api/lib/id-weak-map.coffee', 'common/api/lib/shell.coffee', 'renderer/api/lib/ipc.coffee', @@ -41,8 +42,6 @@ 'browser/api/atom_api_auto_updater.h', 'browser/api/atom_api_browser_ipc.cc', 'browser/api/atom_api_browser_ipc.h', - 'browser/api/atom_api_crash_reporter.h', - 'browser/api/atom_api_crash_reporter.cc', 'browser/api/atom_api_dialog.cc', 'browser/api/atom_api_dialog.h', 'browser/api/atom_api_event.cc', @@ -87,9 +86,6 @@ 'browser/browser_mac.mm', 'browser/browser_win.cc', 'browser/browser_observer.h', - 'browser/crash_reporter.h', - 'browser/crash_reporter_mac.mm', - 'browser/crash_reporter_win.cc', 'browser/native_window.cc', 'browser/native_window.h', 'browser/native_window_mac.h', @@ -132,6 +128,8 @@ 'common/api/api_messages.h', 'common/api/atom_api_clipboard.cc', 'common/api/atom_api_clipboard.h', + 'common/api/atom_api_crash_reporter.cc', + 'common/api/atom_api_crash_reporter.h', 'common/api/atom_api_id_weak_map.cc', 'common/api/atom_api_id_weak_map.h', 'common/api/atom_api_shell.cc', @@ -143,6 +141,16 @@ 'common/api/atom_extensions.h', 'common/api/object_life_monitor.cc', 'common/api/object_life_monitor.h', + 'common/crash_reporter/crash_reporter.cc', + 'common/crash_reporter/crash_reporter.h', + 'common/crash_reporter/crash_reporter_mac.h', + 'common/crash_reporter/crash_reporter_mac.mm', + 'common/crash_reporter/crash_reporter_win.cc', + 'common/crash_reporter/crash_reporter_win.h', + 'common/crash_reporter/win/crash_service.cc', + 'common/crash_reporter/win/crash_service.h', + 'common/crash_reporter/win/crash_service_main.cc', + 'common/crash_reporter/win/crash_service_main.h', 'common/draggable_region.cc', 'common/draggable_region.h', 'common/node_bindings.cc', @@ -187,9 +195,6 @@ '-change', '@loader_path/../Frameworks/Sparkle.framework/Versions/A/Sparkle', '@rpath/Sparkle.framework/Versions/A/Sparkle', - '-change', - '@executable_path/../Frameworks/Quincy.framework/Versions/A/Quincy', - '@rpath/Quincy.framework/Versions/A/Quincy', '${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}' ], 'atom_source_root': '_dump_symbols ], 'conditions': [ ['OS=="mac"', { 'targets': [ { 'target_name': '<(project_name)_framework', - 'product_name': '<(product_name)', + 'product_name': '<(framework_name)', 'type': 'shared_library', 'dependencies': [ '<(project_name)_lib', @@ -409,7 +476,6 @@ 'libraries': [ '$(SDKROOT)/System/Library/Frameworks/Carbon.framework', 'frameworks/Sparkle.framework', - 'frameworks/Quincy.framework', ], }, 'mac_bundle': 1, @@ -421,7 +487,7 @@ 'LIBRARY_SEARCH_PATHS': [ '<(libchromiumcontent_library_dir)', ], - 'LD_DYLIB_INSTALL_NAME': '@rpath/<(product_name).framework/<(product_name)', + 'LD_DYLIB_INSTALL_NAME': '@rpath/<(framework_name).framework/<(framework_name)', 'LD_RUNPATH_SEARCH_PATHS': [ '@loader_path/Libraries', ], @@ -431,12 +497,19 @@ }, 'copies': [ { - 'destination': '<(PRODUCT_DIR)/<(product_name).framework/Versions/A/Libraries', + 'destination': '<(PRODUCT_DIR)/<(framework_name).framework/Versions/A/Libraries', 'files': [ '<(libchromiumcontent_library_dir)/ffmpegsumo.so', '<(libchromiumcontent_library_dir)/libchromiumcontent.dylib', ], }, + { + 'destination': '<(PRODUCT_DIR)/<(framework_name).framework/Versions/A/Resources', + 'files': [ + '<(PRODUCT_DIR)/Inspector', + '<(PRODUCT_DIR)/crash_report_sender.app', + ], + }, ], 'postbuilds': [ { @@ -449,7 +522,7 @@ 'postbuild_name': 'Add symlinks for framework subdirectories', 'action': [ 'tools/mac/create-framework-subdir-symlinks.sh', - '<(product_name)', + '<(framework_name)', 'Libraries', 'Frameworks', ], diff --git a/browser/api/atom_api_crash_reporter.cc b/browser/api/atom_api_crash_reporter.cc deleted file mode 100644 index 1c5d85de28ef..000000000000 --- a/browser/api/atom_api_crash_reporter.cc +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "browser/api/atom_api_crash_reporter.h" - -#include "browser/crash_reporter.h" -#include "common/v8_conversions.h" -#include "vendor/node/src/node.h" -#include "vendor/node/src/node_internals.h" - -namespace atom { - -namespace api { - -// static -v8::Handle CrashReporter::SetCompanyName(const v8::Arguments &args) { - crash_reporter::CrashReporter::SetCompanyName(FromV8Value(args[0])); - return v8::Undefined(); -} - -// static -v8::Handle CrashReporter::SetSubmissionURL( - const v8::Arguments &args) { - crash_reporter::CrashReporter::SetSubmissionURL(FromV8Value(args[0])); - return v8::Undefined(); -} - -// static -v8::Handle CrashReporter::SetAutoSubmit(const v8::Arguments &args) { - crash_reporter::CrashReporter::SetAutoSubmit(FromV8Value(args[0])); - return v8::Undefined(); -} - -// static -void CrashReporter::Initialize(v8::Handle target) { - node::SetMethod(target, "setCompanyName", SetCompanyName); - node::SetMethod(target, "setSubmissionUrl", SetSubmissionURL); - node::SetMethod(target, "setAutoSubmit", SetAutoSubmit); -} - -} // namespace api - -} // namespace atom - -NODE_MODULE(atom_browser_crash_reporter, atom::api::CrashReporter::Initialize) diff --git a/browser/api/lib/crash-reporter.coffee b/browser/api/lib/crash-reporter.coffee deleted file mode 100644 index 90f582730312..000000000000 --- a/browser/api/lib/crash-reporter.coffee +++ /dev/null @@ -1 +0,0 @@ -module.exports = process.atomBinding 'crash_reporter' diff --git a/browser/atom_browser_main_parts_mac.mm b/browser/atom_browser_main_parts_mac.mm index cf2b0c7a4c15..4b2c7ab99372 100644 --- a/browser/atom_browser_main_parts_mac.mm +++ b/browser/atom_browser_main_parts_mac.mm @@ -20,7 +20,9 @@ void AtomBrowserMainParts::PreMainMessageLoopStart() { [NSApp setDelegate:delegate]; base::FilePath frameworkPath = brightray::MainApplicationBundlePath() - .Append("Contents").Append("Frameworks").Append("Atom.framework"); + .Append("Contents") + .Append("Frameworks") + .Append("Atom Framework.framework"); NSBundle* frameworkBundle = [NSBundle bundleWithPath:base::mac::FilePathToNSString(frameworkPath)]; NSNib* mainNib = [[NSNib alloc] initWithNibNamed:@"MainMenu" diff --git a/browser/crash_reporter.h b/browser/crash_reporter.h deleted file mode 100644 index 14b6c7ad7814..000000000000 --- a/browser/crash_reporter.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef ATOM_BROWSER_CRASH_REPORTER_H_ -#define ATOM_BROWSER_CRASH_REPORTER_H_ - -#include - -#include "base/basictypes.h" - -namespace crash_reporter { - -class CrashReporter { - public: - static void SetCompanyName(const std::string& name); - static void SetSubmissionURL(const std::string& url); - static void SetAutoSubmit(bool yes); - - private: - DISALLOW_IMPLICIT_CONSTRUCTORS(CrashReporter); -}; - -} // namespace crash_reporter - -#endif // ATOM_BROWSER_CRASH_REPORTER_H_ diff --git a/browser/crash_reporter_mac.mm b/browser/crash_reporter_mac.mm deleted file mode 100644 index 1fda81ae51f1..000000000000 --- a/browser/crash_reporter_mac.mm +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "browser/crash_reporter.h" - -#import - -#include "base/strings/sys_string_conversions.h" - -namespace crash_reporter { - -// static -void CrashReporter::SetCompanyName(const std::string& name) { - BWQuincyManager *manager = [BWQuincyManager sharedQuincyManager]; - [manager setCompanyName:base::SysUTF8ToNSString(name)]; -} - -// static -void CrashReporter::SetSubmissionURL(const std::string& url) { - BWQuincyManager *manager = [BWQuincyManager sharedQuincyManager]; - [manager setSubmissionURL:base::SysUTF8ToNSString(url)]; -} - -// static -void CrashReporter::SetAutoSubmit(bool yes) { - BWQuincyManager *manager = [BWQuincyManager sharedQuincyManager]; - [manager setAutoSubmitCrashReport:yes]; -} - -} // namespace crash_reporter diff --git a/browser/crash_reporter_win.cc b/browser/crash_reporter_win.cc deleted file mode 100644 index ca3c7e6f6b50..000000000000 --- a/browser/crash_reporter_win.cc +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "browser/crash_reporter.h" - -namespace crash_reporter { - -// static -void CrashReporter::SetCompanyName(const std::string& name) { -} - -// static -void CrashReporter::SetSubmissionURL(const std::string& url) { -} - -// static -void CrashReporter::SetAutoSubmit(bool yes) { -} - -} // namespace crash_reporter - diff --git a/common.gypi b/common.gypi index c367ccf645e9..d153c79d3e32 100644 --- a/common.gypi +++ b/common.gypi @@ -59,7 +59,17 @@ }, }, 'xcode_settings': { - 'GCC_TREAT_WARNINGS_AS_ERRORS': 'NO' + 'GCC_TREAT_WARNINGS_AS_ERRORS': 'NO', + 'WARNING_CFLAGS': [ + '-Wno-parentheses-equality', + '-Wno-unused-function', + '-Wno-sometimes-uninitialized', + '-Wno-pointer-sign', + '-Wno-string-plus-int', + '-Wno-unused-variable', + '-Wno-deprecated-declarations', + '-Wno-return-type', + ], }, }], ['_target_name=="libuv"', { @@ -72,6 +82,15 @@ }], # OS=="win" ], }], + ['_target_name.startswith("breakpad") or _target_name in ["crash_report_sender", "dump_syms"]', { + 'xcode_settings': { + 'WARNING_CFLAGS': [ + '-Wno-deprecated-declarations', + '-Wno-unused-private-field', + '-Wno-unused-function', + ], + }, + }], ], 'msvs_cygwin_shell': 0, # Strangely setting it to 1 would make building under cygwin fail. 'msvs_disabled_warnings': [ @@ -107,6 +126,9 @@ ], }, }, + 'xcode_settings': { + 'DEBUG_INFORMATION_FORMAT': 'dwarf-with-dsym', + }, }, 'conditions': [ # Settings to compile with clang under OS X. @@ -164,5 +186,16 @@ ], }, }], # msvs_express==1 + # The breakdpad on Windows assumes Debug_x64 and Release_x64 configurations. + ['OS=="win"', { + 'target_defaults': { + 'configurations': { + 'Debug_x64': { + }, + 'Release_x64': { + }, + }, + }, + }], # OS=="win" ], } diff --git a/common/api/atom_api_crash_reporter.cc b/common/api/atom_api_crash_reporter.cc new file mode 100644 index 000000000000..26b1c2938f13 --- /dev/null +++ b/common/api/atom_api_crash_reporter.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "common/api/atom_api_crash_reporter.h" + +#include "common/crash_reporter/crash_reporter.h" +#include "common/v8_conversions.h" +#include "vendor/node/src/node.h" +#include "vendor/node/src/node_internals.h" + +namespace atom { + +namespace api { + +// static +v8::Handle CrashReporter::Start(const v8::Arguments& args) { + std::string product_name, company_name, submit_url; + bool auto_submit, skip_system; + std::map dict; + if (!FromV8Arguments(args, &product_name, &company_name, &submit_url, + &auto_submit, &skip_system, &dict)) + return node::ThrowTypeError("Bad argument"); + + crash_reporter::CrashReporter::GetInstance()->Start( + product_name, company_name, submit_url, auto_submit, skip_system, dict); + + return v8::Undefined(); +} + +// static +void CrashReporter::Initialize(v8::Handle target) { + node::SetMethod(target, "start", Start); +} + +} // namespace api + +} // namespace atom + +NODE_MODULE(atom_common_crash_reporter, atom::api::CrashReporter::Initialize) diff --git a/browser/api/atom_api_crash_reporter.h b/common/api/atom_api_crash_reporter.h similarity index 54% rename from browser/api/atom_api_crash_reporter.h rename to common/api/atom_api_crash_reporter.h index 7b42a0de5261..f31b8b1e9471 100644 --- a/browser/api/atom_api_crash_reporter.h +++ b/common/api/atom_api_crash_reporter.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef ATOM_BROWSER_API_ATOM_API_CRASH_REPORTER_H_ -#define ATOM_BROWSER_API_ATOM_API_CRASH_REPORTER_H_ +#ifndef ATOM_COMMON_API_ATOM_API_CRASH_REPORTER_H_ +#define ATOM_COMMON_API_ATOM_API_CRASH_REPORTER_H_ #include "base/basictypes.h" #include "v8/include/v8.h" @@ -17,9 +17,7 @@ class CrashReporter { static void Initialize(v8::Handle target); private: - static v8::Handle SetCompanyName(const v8::Arguments &args); - static v8::Handle SetSubmissionURL(const v8::Arguments &args); - static v8::Handle SetAutoSubmit(const v8::Arguments &args); + static v8::Handle Start(const v8::Arguments& args); DISALLOW_IMPLICIT_CONSTRUCTORS(CrashReporter); }; @@ -28,4 +26,4 @@ class CrashReporter { } // namespace atom -#endif // ATOM_BROWSER_API_ATOM_API_CRASH_REPORTER_H_ +#endif // ATOM_COMMON_API_ATOM_API_CRASH_REPORTER_H_ diff --git a/common/api/atom_bindings.cc b/common/api/atom_bindings.cc index 3216232a676e..d86698df9642 100644 --- a/common/api/atom_bindings.cc +++ b/common/api/atom_bindings.cc @@ -4,7 +4,6 @@ #include "common/api/atom_bindings.h" -#include "base/debug/debugger.h" #include "base/logging.h" #include "common/atom_version.h" #include "common/v8_conversions.h" @@ -18,6 +17,8 @@ static int kMaxCallStackSize = 200; // Same with WebKit. static uv_async_t dummy_uv_handle; +struct DummyClass { bool crash; }; + void UvNoOp(uv_async_t* handle, int status) { } @@ -110,7 +111,7 @@ v8::Handle AtomBindings::Binding(const v8::Arguments& args) { // static v8::Handle AtomBindings::Crash(const v8::Arguments& args) { - base::debug::BreakDebugger(); + static_cast(NULL)->crash = true; return v8::Undefined(); } diff --git a/common/api/atom_extensions.h b/common/api/atom_extensions.h index edf2cfd1e60e..c373d49a3f98 100644 --- a/common/api/atom_extensions.h +++ b/common/api/atom_extensions.h @@ -11,7 +11,6 @@ NODE_EXT_LIST_START // Module names start with `atom_browser_` can only be used by browser process. NODE_EXT_LIST_ITEM(atom_browser_app) NODE_EXT_LIST_ITEM(atom_browser_auto_updater) -NODE_EXT_LIST_ITEM(atom_browser_crash_reporter) NODE_EXT_LIST_ITEM(atom_browser_dialog) NODE_EXT_LIST_ITEM(atom_browser_ipc) NODE_EXT_LIST_ITEM(atom_browser_menu) @@ -26,6 +25,7 @@ NODE_EXT_LIST_ITEM(atom_renderer_ipc) // Module names start with `atom_common_` can be used by both browser and // renderer processes. NODE_EXT_LIST_ITEM(atom_common_clipboard) +NODE_EXT_LIST_ITEM(atom_common_crash_reporter) NODE_EXT_LIST_ITEM(atom_common_id_weak_map) NODE_EXT_LIST_ITEM(atom_common_shell) NODE_EXT_LIST_ITEM(atom_common_v8_util) diff --git a/common/api/lib/crash-reporter.coffee b/common/api/lib/crash-reporter.coffee new file mode 100644 index 000000000000..89d48f2f3f6c --- /dev/null +++ b/common/api/lib/crash-reporter.coffee @@ -0,0 +1,38 @@ +{spawn} = require 'child_process' +binding = process.atomBinding 'crash_reporter' + +class CrashReporter + start: (options={}) -> + {productName, companyName, submitUrl, autoSubmit, ignoreSystemCrashHandler, extra} = options + + productName ?= 'Atom-Shell' + companyName ?= 'GitHub, Inc' + submitUrl ?= 'http://54.249.141.25:1127/post' + autoSubmit ?= true + ignoreSystemCrashHandler ?= false + extra ?= {} + + extra._productName ?= productName + extra._companyName ?= companyName + extra._version ?= + if process.__atom_type is 'browser' + require('app').getVersion() + else + require('remote').require('app').getVersion() + + start = -> binding.start productName, companyName, submitUrl, autoSubmit, ignoreSystemCrashHandler, extra + + if process.platform is 'darwin' + start() + else + args = [ + "--reporter-url=#{submitUrl}" + "--application-name=#{productName}" + "--v=1" + ] + env = ATOM_SHELL_INTERNAL_CRASH_SERVICE: 1 + + spawn process.execPath, args, {env, detached: true} + start() + +module.exports = new CrashReporter diff --git a/common/crash_reporter/crash_reporter.cc b/common/crash_reporter/crash_reporter.cc new file mode 100644 index 000000000000..39d66956bd8f --- /dev/null +++ b/common/crash_reporter/crash_reporter.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "common/crash_reporter/crash_reporter.h" + +#include "base/command_line.h" +#include "browser/browser.h" +#include "common/atom_version.h" +#include "content/public/common/content_switches.h" + +namespace crash_reporter { + +CrashReporter::CrashReporter() { + const CommandLine& command = *CommandLine::ForCurrentProcess(); + is_browser_ = command.GetSwitchValueASCII(switches::kProcessType).empty(); +} + +CrashReporter::~CrashReporter() { +} + +void CrashReporter::Start(const std::string& product_name, + const std::string& company_name, + const std::string& submit_url, + bool auto_submit, + bool skip_system_crash_handler, + const StringMap& extra_parameters) { + SetUploadParameters(extra_parameters); + + InitBreakpad(product_name, ATOM_VERSION_STRING, company_name, submit_url, + auto_submit, skip_system_crash_handler); +} + +void CrashReporter::SetUploadParameters(const StringMap& parameters) { + upload_parameters_ = parameters; + upload_parameters_["process_type"] = is_browser_ ? "browser" : "renderer"; + + // Setting platform dependent parameters. + SetUploadParameters(); +} + +} // namespace crash_reporter diff --git a/common/crash_reporter/crash_reporter.h b/common/crash_reporter/crash_reporter.h new file mode 100644 index 000000000000..bc549b949a8a --- /dev/null +++ b/common/crash_reporter/crash_reporter.h @@ -0,0 +1,51 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_H_ +#define ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_H_ + +#include +#include + +#include "base/basictypes.h" + +namespace crash_reporter { + +class CrashReporter { + public: + typedef std::map StringMap; + + static CrashReporter* GetInstance(); + + void Start(const std::string& product_name, + const std::string& company_name, + const std::string& submit_url, + bool auto_submit, + bool skip_system_crash_handler, + const StringMap& extra_parameters); + + protected: + CrashReporter(); + virtual ~CrashReporter(); + + virtual void InitBreakpad(const std::string& product_name, + const std::string& version, + const std::string& company_name, + const std::string& submit_url, + bool auto_submit, + bool skip_system_crash_handler) = 0; + virtual void SetUploadParameters() = 0; + + StringMap upload_parameters_; + bool is_browser_; + + private: + void SetUploadParameters(const StringMap& parameters); + + DISALLOW_COPY_AND_ASSIGN(CrashReporter); +}; + +} // namespace crash_reporter + +#endif // ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_H_ diff --git a/common/crash_reporter/crash_reporter_mac.h b/common/crash_reporter/crash_reporter_mac.h new file mode 100644 index 000000000000..800de9c86fbd --- /dev/null +++ b/common/crash_reporter/crash_reporter_mac.h @@ -0,0 +1,41 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_MAC_H_ +#define ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_MAC_H_ + +#include "base/compiler_specific.h" +#include "common/crash_reporter/crash_reporter.h" +#import "vendor/breakpad/src/client/mac/Framework/Breakpad.h" + +template struct DefaultSingletonTraits; + +namespace crash_reporter { + +class CrashReporterMac : public CrashReporter { + public: + static CrashReporterMac* GetInstance(); + + virtual void InitBreakpad(const std::string& product_name, + const std::string& version, + const std::string& company_name, + const std::string& submit_url, + bool auto_submit, + bool skip_system_crash_handler) OVERRIDE; + virtual void SetUploadParameters() OVERRIDE; + + private: + friend struct DefaultSingletonTraits; + + CrashReporterMac(); + virtual ~CrashReporterMac(); + + BreakpadRef breakpad_; + + DISALLOW_COPY_AND_ASSIGN(CrashReporterMac); +}; + +} // namespace crash_reporter + +#endif // ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_MAC_H_ diff --git a/common/crash_reporter/crash_reporter_mac.mm b/common/crash_reporter/crash_reporter_mac.mm new file mode 100644 index 000000000000..e43b7bb9dc6d --- /dev/null +++ b/common/crash_reporter/crash_reporter_mac.mm @@ -0,0 +1,81 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "common/crash_reporter/crash_reporter_mac.h" + +#include "base/memory/singleton.h" +#include "base/strings/sys_string_conversions.h" +#import "vendor/breakpad/src/client/apple/Framework/BreakpadDefines.h" + +namespace crash_reporter { + +CrashReporterMac::CrashReporterMac() + : breakpad_(NULL) { +} + +CrashReporterMac::~CrashReporterMac() { + if (breakpad_ != NULL) + BreakpadRelease(breakpad_); +} + +void CrashReporterMac::InitBreakpad(const std::string& product_name, + const std::string& version, + const std::string& company_name, + const std::string& submit_url, + bool auto_submit, + bool skip_system_crash_handler) { + if (breakpad_ != NULL) + BreakpadRelease(breakpad_); + + NSMutableDictionary* parameters = + [NSMutableDictionary dictionaryWithCapacity:4]; + + [parameters setValue:@"Atom-Shell" + forKey:@BREAKPAD_PRODUCT]; + [parameters setValue:base::SysUTF8ToNSString(product_name) + forKey:@BREAKPAD_PRODUCT_DISPLAY]; + [parameters setValue:base::SysUTF8ToNSString(version) + forKey:@BREAKPAD_VERSION]; + [parameters setValue:base::SysUTF8ToNSString(company_name) + forKey:@BREAKPAD_VENDOR]; + [parameters setValue:base::SysUTF8ToNSString(submit_url) + forKey:@BREAKPAD_URL]; + [parameters setValue:(auto_submit ? @"YES" : @"NO") + forKey:@BREAKPAD_SKIP_CONFIRM]; + [parameters setValue:(skip_system_crash_handler ? @"YES" : @"NO") + forKey:@BREAKPAD_SEND_AND_EXIT]; + + // Report all crashes (important for testing the crash reporter). + [parameters setValue:@"0" forKey:@BREAKPAD_REPORT_INTERVAL]; + + // Put dump files under "/tmp/ProductName Crashes". + std::string dump_dir = "/tmp/" + product_name + " Crashes"; + [parameters setValue:base::SysUTF8ToNSString(dump_dir) + forKey:@BREAKPAD_DUMP_DIRECTORY]; + + breakpad_ = BreakpadCreate(parameters); + + for (StringMap::const_iterator iter = upload_parameters_.begin(); + iter != upload_parameters_.end(); ++iter) { + BreakpadAddUploadParameter(breakpad_, + base::SysUTF8ToNSString(iter->first), + base::SysUTF8ToNSString(iter->second)); + } +} + +void CrashReporterMac::SetUploadParameters() { + upload_parameters_["platform"] = "darwin"; +} + +// static +CrashReporterMac* CrashReporterMac::GetInstance() { + return Singleton::get(); +} + +// static +CrashReporter* CrashReporter::GetInstance() { + return CrashReporterMac::GetInstance(); +} + +} // namespace crash_reporter diff --git a/common/crash_reporter/crash_reporter_win.cc b/common/crash_reporter/crash_reporter_win.cc new file mode 100644 index 000000000000..e62986b0800d --- /dev/null +++ b/common/crash_reporter/crash_reporter_win.cc @@ -0,0 +1,129 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "common/crash_reporter/crash_reporter_win.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" + +namespace crash_reporter { + +namespace { + +// Minidump with stacks, PEB, TEB, and unloaded module list. +const MINIDUMP_TYPE kSmallDumpType = static_cast( + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithUnloadedModules); // Get unloaded modules when available. + +const wchar_t kPipeNameFormat[] = L"\\\\.\\pipe\\$1 Crash Service"; + +} // namespace + +CrashReporterWin::CrashReporterWin() { +} + +CrashReporterWin::~CrashReporterWin() { +} + +void CrashReporterWin::InitBreakpad(const std::string& product_name, + const std::string& version, + const std::string& company_name, + const std::string& submit_url, + bool auto_submit, + bool skip_system_crash_handler) { + skip_system_crash_handler_ = skip_system_crash_handler; + + base::FilePath temp_dir; + if (!file_util::GetTempDir(&temp_dir)) { + LOG(ERROR) << "Cannot get temp directory"; + return; + } + + string16 pipe_name = ReplaceStringPlaceholders(kPipeNameFormat, + UTF8ToUTF16(product_name), + NULL); + + // Wait until the crash service is started. + HANDLE waiting_event = + ::CreateEventW(NULL, TRUE, FALSE, L"g_atom_shell_crash_service"); + if (waiting_event != INVALID_HANDLE_VALUE) + WaitForSingleObject(waiting_event, 1000); + + breakpad_.reset(new google_breakpad::ExceptionHandler( + temp_dir.value(), + FilterCallback, + MinidumpCallback, + this, + google_breakpad::ExceptionHandler::HANDLER_ALL, + kSmallDumpType, + pipe_name.c_str(), + GetCustomInfo(product_name, version, company_name))); + + if (!breakpad_->IsOutOfProcess()) + LOG(ERROR) << "Cannot initialize out-of-process crash handler"; +} + +void CrashReporterWin::SetUploadParameters() { + upload_parameters_["platform"] = "win32"; +} + +// static +bool CrashReporterWin::FilterCallback(void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion) { + return true; +} + +// static +bool CrashReporterWin::MinidumpCallback(const wchar_t* dump_path, + const wchar_t* minidump_id, + void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, + bool succeeded) { + CrashReporterWin* self = static_cast(context); + if (succeeded && !self->skip_system_crash_handler_) + return true; + else + return false; +} + +google_breakpad::CustomClientInfo* CrashReporterWin::GetCustomInfo( + const std::string& product_name, + const std::string& version, + const std::string& company_name) { + custom_info_entries_.clear(); + custom_info_entries_.reserve(2 + upload_parameters_.size()); + + custom_info_entries_.push_back(google_breakpad::CustomInfoEntry( + L"prod", L"Atom-Shell")); + custom_info_entries_.push_back(google_breakpad::CustomInfoEntry( + L"ver", UTF8ToWide(version).c_str())); + + for (StringMap::const_iterator iter = upload_parameters_.begin(); + iter != upload_parameters_.end(); ++iter) { + custom_info_entries_.push_back(google_breakpad::CustomInfoEntry( + UTF8ToWide(iter->first).c_str(), + UTF8ToWide(iter->second).c_str())); + } + + custom_info_.entries = &custom_info_entries_.front(); + custom_info_.count = custom_info_entries_.size(); + return &custom_info_; +} + +// static +CrashReporterWin* CrashReporterWin::GetInstance() { + return Singleton::get(); +} + +// static +CrashReporter* CrashReporter::GetInstance() { + return CrashReporterWin::GetInstance(); +} + +} // namespace crash_reporter diff --git a/common/crash_reporter/crash_reporter_win.h b/common/crash_reporter/crash_reporter_win.h new file mode 100644 index 000000000000..6786975546dc --- /dev/null +++ b/common/crash_reporter/crash_reporter_win.h @@ -0,0 +1,64 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_WIN_H_ +#define ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_WIN_H_ + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "common/crash_reporter/crash_reporter.h" +#include "vendor/breakpad/src/client/windows/handler/exception_handler.h" + +template struct DefaultSingletonTraits; + +namespace crash_reporter { + +class CrashReporterWin : public CrashReporter { + public: + static CrashReporterWin* GetInstance(); + + virtual void InitBreakpad(const std::string& product_name, + const std::string& version, + const std::string& company_name, + const std::string& submit_url, + bool auto_submit, + bool skip_system_crash_handler) OVERRIDE; + virtual void SetUploadParameters() OVERRIDE; + + private: + friend struct DefaultSingletonTraits; + + CrashReporterWin(); + virtual ~CrashReporterWin(); + + static bool FilterCallback(void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion); + + static bool MinidumpCallback(const wchar_t* dump_path, + const wchar_t* minidump_id, + void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, + bool succeeded); + + // Returns the custom info structure based on parameters. + google_breakpad::CustomClientInfo* GetCustomInfo( + const std::string& product_name, + const std::string& version, + const std::string& company_name); + + // Custom information to be passed to crash handler. + std::vector custom_info_entries_; + google_breakpad::CustomClientInfo custom_info_; + + bool skip_system_crash_handler_; + scoped_ptr breakpad_; + + DISALLOW_COPY_AND_ASSIGN(CrashReporterWin); +}; + +} // namespace crash_reporter + +#endif // ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_WIN_H_ diff --git a/common/crash_reporter/win/crash_service.cc b/common/crash_reporter/win/crash_service.cc new file mode 100644 index 000000000000..3cbbf33242fd --- /dev/null +++ b/common/crash_reporter/win/crash_service.cc @@ -0,0 +1,490 @@ +// Copyright (c) 2012 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. + +#include "common/crash_reporter/win/crash_service.h" + +#include + +#include +#include // NOLINT +#include + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/win/windows_version.h" +#include "vendor/breakpad/src/client/windows/crash_generation/client_info.h" +#include "vendor/breakpad/src/client/windows/crash_generation/crash_generation_server.h" +#include "vendor/breakpad/src/client/windows/sender/crash_report_sender.h" + +namespace breakpad { + +namespace { + +const wchar_t kTestPipeName[] = L"\\\\.\\pipe\\ChromeCrashServices"; + +const wchar_t kGoogleReportURL[] = L"https://clients2.google.com/cr/report"; +const wchar_t kCheckPointFile[] = L"crash_checkpoint.txt"; + +typedef std::map CrashMap; + +bool CustomInfoToMap(const google_breakpad::ClientInfo* client_info, + const std::wstring& reporter_tag, CrashMap* map) { + google_breakpad::CustomClientInfo info = client_info->GetCustomInfo(); + + for (uintptr_t i = 0; i < info.count; ++i) { + (*map)[info.entries[i].name] = info.entries[i].value; + } + + (*map)[L"rept"] = reporter_tag; + + return !map->empty(); +} + +bool WriteCustomInfoToFile(const std::wstring& dump_path, const CrashMap& map) { + std::wstring file_path(dump_path); + size_t last_dot = file_path.rfind(L'.'); + if (last_dot == std::wstring::npos) + return false; + file_path.resize(last_dot); + file_path += L".txt"; + + std::wofstream file(file_path.c_str(), + std::ios_base::out | std::ios_base::app | std::ios::binary); + if (!file.is_open()) + return false; + + CrashMap::const_iterator pos; + for (pos = map.begin(); pos != map.end(); ++pos) { + std::wstring line = pos->first; + line += L':'; + line += pos->second; + line += L'\n'; + file.write(line.c_str(), static_cast(line.length())); + } + return true; +} + +// The window procedure task is to handle when a) the user logs off. +// b) the system shuts down or c) when the user closes the window. +LRESULT __stdcall CrashSvcWndProc(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + switch (message) { + case WM_CLOSE: + case WM_ENDSESSION: + case WM_DESTROY: + PostQuitMessage(0); + break; + default: + return DefWindowProc(hwnd, message, wparam, lparam); + } + return 0; +} + +// This is the main and only application window. +HWND g_top_window = NULL; + +bool CreateTopWindow(HINSTANCE instance, bool visible) { + WNDCLASSEXW wcx = {0}; + wcx.cbSize = sizeof(wcx); + wcx.style = CS_HREDRAW | CS_VREDRAW; + wcx.lpfnWndProc = CrashSvcWndProc; + wcx.hInstance = instance; + wcx.lpszClassName = L"crash_svc_class"; + ATOM atom = ::RegisterClassExW(&wcx); + DWORD style = visible ? WS_POPUPWINDOW | WS_VISIBLE : WS_OVERLAPPED; + + // The window size is zero but being a popup window still shows in the + // task bar and can be closed using the system menu or using task manager. + HWND window = CreateWindowExW(0, wcx.lpszClassName, L"crash service", style, + CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, + NULL, NULL, instance, NULL); + if (!window) + return false; + + ::UpdateWindow(window); + VLOG(1) << "window handle is " << window; + g_top_window = window; + return true; +} + +// Simple helper class to keep the process alive until the current request +// finishes. +class ProcessingLock { + public: + ProcessingLock() { + ::InterlockedIncrement(&op_count_); + } + ~ProcessingLock() { + ::InterlockedDecrement(&op_count_); + } + static bool IsWorking() { + return (op_count_ != 0); + } + private: + static volatile LONG op_count_; +}; + +volatile LONG ProcessingLock::op_count_ = 0; + +// This structure contains the information that the worker thread needs to +// send a crash dump to the server. +struct DumpJobInfo { + DWORD pid; + CrashService* self; + CrashMap map; + std::wstring dump_path; + + DumpJobInfo(DWORD process_id, CrashService* service, + const CrashMap& crash_map, const std::wstring& path) + : pid(process_id), self(service), map(crash_map), dump_path(path) { + } +}; + +} // namespace + +// Command line switches: +const char CrashService::kMaxReports[] = "max-reports"; +const char CrashService::kNoWindow[] = "no-window"; +const char CrashService::kReporterTag[] = "reporter"; +const char CrashService::kDumpsDir[] = "dumps-dir"; +const char CrashService::kPipeName[] = "pipe-name"; +const char CrashService::kReporterURL[] = "reporter-url"; + +CrashService::CrashService() + : sender_(NULL), + dumper_(NULL), + requests_handled_(0), + requests_sent_(0), + clients_connected_(0), + clients_terminated_(0) { +} + +CrashService::~CrashService() { + base::AutoLock lock(sending_); + delete dumper_; + delete sender_; +} + +bool CrashService::Initialize(const base::FilePath& operating_dir, + const base::FilePath& dumps_path) { + using google_breakpad::CrashReportSender; + using google_breakpad::CrashGenerationServer; + + std::wstring pipe_name = kTestPipeName; + int max_reports = -1; + + // The checkpoint file allows CrashReportSender to enforce the the maximum + // reports per day quota. Does not seem to serve any other purpose. + base::FilePath checkpoint_path = operating_dir.Append(kCheckPointFile); + + CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); + + base::FilePath dumps_path_to_use = dumps_path; + + if (cmd_line.HasSwitch(kDumpsDir)) { + dumps_path_to_use = + base::FilePath(cmd_line.GetSwitchValueNative(kDumpsDir)); + } + + // We can override the send reports quota with a command line switch. + if (cmd_line.HasSwitch(kMaxReports)) + max_reports = _wtoi(cmd_line.GetSwitchValueNative(kMaxReports).c_str()); + + // Allow the global pipe name to be overridden for better testability. + if (cmd_line.HasSwitch(kPipeName)) + pipe_name = cmd_line.GetSwitchValueNative(kPipeName); + + if (max_reports > 0) { + // Create the http sender object. + sender_ = new CrashReportSender(checkpoint_path.value()); + sender_->set_max_reports_per_day(max_reports); + } + + SECURITY_ATTRIBUTES security_attributes = {0}; + SECURITY_ATTRIBUTES* security_attributes_actual = NULL; + + if (base::win::GetVersion() >= base::win::VERSION_VISTA) { + SECURITY_DESCRIPTOR* security_descriptor = + reinterpret_cast( + GetSecurityDescriptorForLowIntegrity()); + DCHECK(security_descriptor != NULL); + + security_attributes.nLength = sizeof(security_attributes); + security_attributes.lpSecurityDescriptor = security_descriptor; + security_attributes.bInheritHandle = FALSE; + + security_attributes_actual = &security_attributes; + } + + // Create the OOP crash generator object. + dumper_ = new CrashGenerationServer(pipe_name, security_attributes_actual, + &CrashService::OnClientConnected, this, + &CrashService::OnClientDumpRequest, this, + &CrashService::OnClientExited, this, + NULL, NULL, + true, &dumps_path_to_use.value()); + + if (!dumper_) { + LOG(ERROR) << "could not create dumper"; + if (security_attributes.lpSecurityDescriptor) + LocalFree(security_attributes.lpSecurityDescriptor); + return false; + } + + if (!CreateTopWindow(::GetModuleHandleW(NULL), + !cmd_line.HasSwitch(kNoWindow))) { + LOG(ERROR) << "could not create window"; + if (security_attributes.lpSecurityDescriptor) + LocalFree(security_attributes.lpSecurityDescriptor); + return false; + } + + reporter_tag_ = L"crash svc"; + if (cmd_line.HasSwitch(kReporterTag)) + reporter_tag_ = cmd_line.GetSwitchValueNative(kReporterTag); + + reporter_url_ = kGoogleReportURL; + if (cmd_line.HasSwitch(kReporterURL)) + reporter_url_ = cmd_line.GetSwitchValueNative(kReporterURL); + + // Log basic information. + VLOG(1) << "pipe name is " << pipe_name + << "\ndumps at " << dumps_path_to_use.value(); + + if (sender_) { + VLOG(1) << "checkpoint is " << checkpoint_path.value() + << "\nserver is " << reporter_url_ + << "\nmaximum " << sender_->max_reports_per_day() << " reports/day" + << "\nreporter is " << reporter_tag_; + } + // Start servicing clients. + if (!dumper_->Start()) { + LOG(ERROR) << "could not start dumper"; + if (security_attributes.lpSecurityDescriptor) + LocalFree(security_attributes.lpSecurityDescriptor); + return false; + } + + if (security_attributes.lpSecurityDescriptor) + LocalFree(security_attributes.lpSecurityDescriptor); + + // Create or open an event to signal the browser process that the crash + // service is initialized. + HANDLE running_event = + ::CreateEventW(NULL, TRUE, TRUE, L"g_atom_shell_crash_service"); + // If the browser already had the event open, the CreateEvent call did not + // signal it. We need to do it manually. + ::SetEvent(running_event); + + return true; +} + +void CrashService::OnClientConnected(void* context, + const google_breakpad::ClientInfo* client_info) { + ProcessingLock lock; + VLOG(1) << "client start. pid = " << client_info->pid(); + CrashService* self = static_cast(context); + ::InterlockedIncrement(&self->clients_connected_); +} + +void CrashService::OnClientExited(void* context, + const google_breakpad::ClientInfo* client_info) { + ProcessingLock lock; + VLOG(1) << "client end. pid = " << client_info->pid(); + CrashService* self = static_cast(context); + ::InterlockedIncrement(&self->clients_terminated_); + + if (!self->sender_) + return; + + // When we are instructed to send reports we need to exit if there are + // no more clients to service. The next client that runs will start us. + // Only chrome.exe starts crash_service with a non-zero max_reports. + if (self->clients_connected_ > self->clients_terminated_) + return; + if (self->sender_->max_reports_per_day() > 0) { + // Wait for the other thread to send crashes, if applicable. The sender + // thread takes the sending_ lock, so the sleep is just to give it a + // chance to start. + ::Sleep(1000); + base::AutoLock lock(self->sending_); + // Some people can restart chrome very fast, check again if we have + // a new client before exiting for real. + if (self->clients_connected_ == self->clients_terminated_) { + VLOG(1) << "zero clients. exiting"; + ::PostMessage(g_top_window, WM_CLOSE, 0, 0); + } + } +} + +void CrashService::OnClientDumpRequest(void* context, + const google_breakpad::ClientInfo* client_info, + const std::wstring* file_path) { + ProcessingLock lock; + + if (!file_path) { + LOG(ERROR) << "dump with no file path"; + return; + } + if (!client_info) { + LOG(ERROR) << "dump with no client info"; + return; + } + + CrashService* self = static_cast(context); + if (!self) { + LOG(ERROR) << "dump with no context"; + return; + } + + CrashMap map; + CustomInfoToMap(client_info, self->reporter_tag_, &map); + + // Move dump file to the directory under client breakpad dump location. + base::FilePath dump_location = base::FilePath(*file_path); + CrashMap::const_iterator it = map.find(L"breakpad-dump-location"); + if (it != map.end()) { + base::FilePath alternate_dump_location = base::FilePath(it->second); + file_util::CreateDirectoryW(alternate_dump_location); + alternate_dump_location = alternate_dump_location.Append( + dump_location.BaseName()); + file_util::Move(dump_location, alternate_dump_location); + dump_location = alternate_dump_location; + } + + DWORD pid = client_info->pid(); + VLOG(1) << "dump for pid = " << pid << " is " << dump_location.value(); + + if (!WriteCustomInfoToFile(dump_location.value(), map)) { + LOG(ERROR) << "could not write custom info file"; + } + + if (!self->sender_) + return; + + // Send the crash dump using a worker thread. This operation has retry + // logic in case there is no internet connection at the time. + DumpJobInfo* dump_job = new DumpJobInfo(pid, self, map, + dump_location.value()); + if (!::QueueUserWorkItem(&CrashService::AsyncSendDump, + dump_job, WT_EXECUTELONGFUNCTION)) { + LOG(ERROR) << "could not queue job"; + } +} + +// We are going to try sending the report several times. If we can't send, +// we sleep from one minute to several hours depending on the retry round. +DWORD CrashService::AsyncSendDump(void* context) { + if (!context) + return 0; + + DumpJobInfo* info = static_cast(context); + + std::wstring report_id = L""; + + const DWORD kOneMinute = 60*1000; + const DWORD kOneHour = 60*kOneMinute; + + const DWORD kSleepSchedule[] = { + 24*kOneHour, + 8*kOneHour, + 4*kOneHour, + kOneHour, + 15*kOneMinute, + 0}; + + int retry_round = arraysize(kSleepSchedule) - 1; + + do { + ::Sleep(kSleepSchedule[retry_round]); + { + // Take the server lock while sending. This also prevent early + // termination of the service object. + base::AutoLock lock(info->self->sending_); + VLOG(1) << "trying to send report for pid = " << info->pid; + google_breakpad::ReportResult send_result + = info->self->sender_->SendCrashReport(info->self->reporter_url_, + info->map, + info->dump_path, + &report_id); + switch (send_result) { + case google_breakpad::RESULT_FAILED: + report_id = L""; + break; + case google_breakpad::RESULT_REJECTED: + report_id = L""; + ++info->self->requests_handled_; + retry_round = 0; + break; + case google_breakpad::RESULT_SUCCEEDED: + ++info->self->requests_sent_; + ++info->self->requests_handled_; + retry_round = 0; + break; + case google_breakpad::RESULT_THROTTLED: + report_id = L""; + break; + default: + report_id = L""; + break; + }; + } + + VLOG(1) << "dump for pid =" << info->pid << " crash2 id =" << report_id; + --retry_round; + } while (retry_round >= 0); + + if (!::DeleteFileW(info->dump_path.c_str())) + LOG(WARNING) << "could not delete " << info->dump_path; + + delete info; + return 0; +} + +int CrashService::ProcessingLoop() { + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + VLOG(1) << "session ending.."; + while (ProcessingLock::IsWorking()) { + ::Sleep(50); + } + + VLOG(1) << "clients connected :" << clients_connected_ + << "\nclients terminated :" << clients_terminated_ + << "\ndumps serviced :" << requests_handled_ + << "\ndumps reported :" << requests_sent_; + + return static_cast(msg.wParam); +} + +PSECURITY_DESCRIPTOR CrashService::GetSecurityDescriptorForLowIntegrity() { + // Build the SDDL string for the label. + std::wstring sddl = L"S:(ML;;NW;;;S-1-16-4096)"; + + DWORD error = ERROR_SUCCESS; + PSECURITY_DESCRIPTOR sec_desc = NULL; + + PACL sacl = NULL; + BOOL sacl_present = FALSE; + BOOL sacl_defaulted = FALSE; + + if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(), + SDDL_REVISION, + &sec_desc, NULL)) { + if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl, + &sacl_defaulted)) { + return sec_desc; + } + } + + return NULL; +} + +} // namespace breakpad + diff --git a/common/crash_reporter/win/crash_service.h b/common/crash_reporter/win/crash_service.h new file mode 100644 index 000000000000..195857155665 --- /dev/null +++ b/common/crash_reporter/win/crash_service.h @@ -0,0 +1,131 @@ +// Copyright (c) 2011 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. + +#ifndef COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_H_ +#define COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_H_ + +#include + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/synchronization/lock.h" + +namespace google_breakpad { + +class CrashReportSender; +class CrashGenerationServer; +class ClientInfo; + +} + +namespace breakpad { + +// This class implements an out-of-process crash server. It uses breakpad's +// CrashGenerationServer and CrashReportSender to generate and then send the +// crash dumps. Internally, it uses OS specific pipe to allow applications to +// register for crash dumps and later on when a registered application crashes +// it will signal an event that causes this code to wake up and perform a +// crash dump on the signaling process. The dump is then stored on disk and +// possibly sent to the crash2 servers. +class CrashService { + public: + CrashService(); + ~CrashService(); + + // Starts servicing crash dumps. Returns false if it failed. Do not use + // other members in that case. |operating_dir| is where the CrashService + // should store breakpad's checkpoint file. |dumps_path| is the directory + // where the crash dumps should be stored. + bool Initialize(const base::FilePath& operating_dir, + const base::FilePath& dumps_path); + + // Command line switches: + // + // --max-reports= + // Allows to override the maximum number for reports per day. Normally + // the crash dumps are never sent so if you want to send any you must + // specify a positive number here. + static const char kMaxReports[]; + // --no-window + // Does not create a visible window on the desktop. The window does not have + // any other functionality other than allowing the crash service to be + // gracefully closed. + static const char kNoWindow[]; + // --reporter= + // Allows to specify a custom string that appears on the detail crash report + // page in the crash server. This should be a 25 chars or less string. + // The default tag if not specified is 'crash svc'. + static const char kReporterTag[]; + // --dumps-dir= + // Override the directory to which crash dump files will be written. + static const char kDumpsDir[]; + // --pipe-name= + // Override the name of the Windows named pipe on which we will + // listen for crash dump request messages. + static const char kPipeName[]; + // --reporter-url= + // Override the URL to which crash reports will be sent to. + static const char kReporterURL[]; + + // Returns number of crash dumps handled. + int requests_handled() const { + return requests_handled_; + } + // Returns number of crash clients registered. + int clients_connected() const { + return clients_connected_; + } + // Returns number of crash clients terminated. + int clients_terminated() const { + return clients_terminated_; + } + + // Starts the processing loop. This function does not return unless the + // user is logging off or the user closes the crash service window. The + // return value is a good number to pass in ExitProcess(). + int ProcessingLoop(); + + private: + static void OnClientConnected(void* context, + const google_breakpad::ClientInfo* client_info); + + static void OnClientDumpRequest( + void* context, + const google_breakpad::ClientInfo* client_info, + const std::wstring* file_path); + + static void OnClientExited(void* context, + const google_breakpad::ClientInfo* client_info); + + // This routine sends the crash dump to the server. It takes the sending_ + // lock when it is performing the send. + static DWORD __stdcall AsyncSendDump(void* context); + + // Returns the security descriptor which access to low integrity processes + // The caller is supposed to free the security descriptor by calling + // LocalFree. + PSECURITY_DESCRIPTOR GetSecurityDescriptorForLowIntegrity(); + + google_breakpad::CrashGenerationServer* dumper_; + google_breakpad::CrashReportSender* sender_; + + // the extra tag sent to the server with each dump. + std::wstring reporter_tag_; + + // receiver URL of crash reports. + std::wstring reporter_url_; + + // clients serviced statistics: + int requests_handled_; + int requests_sent_; + volatile LONG clients_connected_; + volatile LONG clients_terminated_; + base::Lock sending_; + + DISALLOW_COPY_AND_ASSIGN(CrashService); +}; + +} // namespace breakpad + +#endif // COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_H_ diff --git a/common/crash_reporter/win/crash_service_main.cc b/common/crash_reporter/win/crash_service_main.cc new file mode 100644 index 000000000000..649f35847480 --- /dev/null +++ b/common/crash_reporter/win/crash_service_main.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "common/crash_reporter/win/crash_service_main.h" + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "common/crash_reporter/win/crash_service.h" + +namespace crash_service { + +namespace { + +const char kApplicationName[] = "application-name"; + +const wchar_t kPipeNameFormat[] = L"\\\\.\\pipe\\$1 Crash Service"; +const wchar_t kStandardLogFile[] = L"operation_log.txt"; + +bool GetCrashServiceDirectory(const std::wstring& application_name, + base::FilePath* dir) { + base::FilePath temp_dir; + if (!file_util::GetTempDir(&temp_dir)) + return false; + temp_dir = temp_dir.Append(application_name + L" Crashes"); + if (!file_util::PathExists(temp_dir)) { + if (!file_util::CreateDirectory(temp_dir)) + return false; + } + *dir = temp_dir; + return true; +} + +} // namespace. + +int Main(const wchar_t* cmd) { + // Initialize all Chromium things. + base::AtExitManager exit_manager; + CommandLine::Init(0, NULL); + CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); + + // Use the application's name as pipe name and output directory. + if (!cmd_line.HasSwitch(kApplicationName)) { + LOG(ERROR) << "Application's name must be specified with --" + << kApplicationName; + return 1; + } + std::wstring application_name = cmd_line.GetSwitchValueNative( + kApplicationName); + + // We use/create a directory under the user's temp folder, for logging. + base::FilePath operating_dir; + GetCrashServiceDirectory(application_name, &operating_dir); + base::FilePath log_file = operating_dir.Append(kStandardLogFile); + + // Logging out to a file. + logging::InitLogging( + log_file.value().c_str(), + logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG , + logging::LOCK_LOG_FILE, + logging::DELETE_OLD_LOG_FILE, + logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS); + logging::SetLogItems(true, false, true, false); + + VLOG(1) << "Session start. cmdline is [" << cmd << "]"; + + // Setting the crash reporter. + string16 pipe_name = ReplaceStringPlaceholders(kPipeNameFormat, + application_name, + NULL); + cmd_line.AppendSwitch("no-window"); + cmd_line.AppendSwitchASCII("max-reports", "128"); + cmd_line.AppendSwitchASCII("reporter", "atom-shell-crash-service"); + cmd_line.AppendSwitchNative("pipe-name", pipe_name); + + breakpad::CrashService crash_service; + if (!crash_service.Initialize(operating_dir, operating_dir)) + return 2; + + VLOG(1) << "Ready to process crash requests"; + + // Enter the message loop. + int retv = crash_service.ProcessingLoop(); + // Time to exit. + VLOG(1) << "Session end. return code is " << retv; + return retv; +} + +} // namespace crash_service diff --git a/common/crash_reporter/win/crash_service_main.h b/common/crash_reporter/win/crash_service_main.h new file mode 100644 index 000000000000..3dd06f207f23 --- /dev/null +++ b/common/crash_reporter/win/crash_service_main.h @@ -0,0 +1,15 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_MAIN_H_ +#define COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_MAIN_H_ + +namespace crash_service { + +// Program entry, should be called by main(); +int Main(const wchar_t* cmd_line); + +} // namespace crash_service + +#endif // COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_MAIN_H_ diff --git a/common/v8_conversions.h b/common/v8_conversions.h index b56061119f0e..57d69c2daae6 100644 --- a/common/v8_conversions.h +++ b/common/v8_conversions.h @@ -5,6 +5,7 @@ #ifndef COMMON_V8_CONVERSIONS_H_ #define COMMON_V8_CONVERSIONS_H_ +#include #include #include @@ -62,6 +63,19 @@ struct FromV8Value { return array; } + operator std::map() { + std::map dict; + v8::Handle v8_dict = value_->ToObject(); + v8::Handle v8_keys = v8_dict->GetOwnPropertyNames(); + for (uint32_t i = 0; i < v8_keys->Length(); ++i) { + v8::Handle v8_key = v8_keys->Get(i); + std::string key = FromV8Value(v8_key); + dict[key] = std::string(FromV8Value(v8_dict->Get(v8_key))); + } + + return dict; + } + operator atom::NativeWindow*() { using atom::api::Window; if (value_->IsObject()) { @@ -161,6 +175,12 @@ bool V8ValueCanBeConvertedTo>( return value->IsArray(); } +template<> inline +bool V8ValueCanBeConvertedTo>( + v8::Handle value) { + return value->IsObject(); +} + template<> inline bool V8ValueCanBeConvertedTo(v8::Handle value) { using atom::api::Window; diff --git a/docs/README.md b/docs/README.md index b10054f88ca5..77b2eb449c61 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,7 +25,6 @@ Browser side modules: * [atom-delegate](api/browser/atom-delegate.md) * [auto-updater](api/browser/auto-updater.md) * [browser-window](api/browser/browser-window.md) -* [crash-reporter](api/browser/crash-reporter.md) * [dialog](api/browser/dialog.md) * [ipc (browser)](api/browser/ipc-browser.md) * [menu](api/browser/menu.md) @@ -36,4 +35,5 @@ Browser side modules: Common modules: * [clipboard](api/common/clipboard.md) +* [crash-reporter](api/common/crash-reporter.md) * [shell](api/common/shell.md) diff --git a/docs/api/browser/crash-reporter.md b/docs/api/browser/crash-reporter.md deleted file mode 100644 index b6785fa33490..000000000000 --- a/docs/api/browser/crash-reporter.md +++ /dev/null @@ -1,22 +0,0 @@ -# crash-reporter - -An example of automatically submitting crash reporters to remote server: - -```javascript -crashReporter = require('crash-reporter'); -crashReporter.setCompanyName('YourCompany'); -crashReporter.setSubmissionUrl('https://your-domain.com/url-to-submit'); -crashReporter.setAutoSubmit(true); -``` - -## crashReporter.setCompanyName(company) - -* `company` String - -## crashReporter.setSubmissionUrl(url) - -* `url` String - -## crashReporter.setAutoSubmit(is) - -* `is` Boolean diff --git a/docs/api/common/crash-reporter.md b/docs/api/common/crash-reporter.md new file mode 100644 index 000000000000..b966dd4b2a43 --- /dev/null +++ b/docs/api/common/crash-reporter.md @@ -0,0 +1,22 @@ +# crash-reporter + +An example of automatically submitting crash reporters to remote server: + +```javascript +crashReporter = require('crash-reporter'); +crashReporter.start({ + productName: 'YourName', + companyName: 'YourCompany', + submitUrl: 'https://your-domain.com/url-to-submit', + autoSubmit: true +}); +``` + +## crashReporter.start(options) + +* `options` Object + * `productName` String + * `companyName` String + * `submitUrl` String - URL that crash reports would be sent to + * `autoSubmit` Boolean - Send the crash report without user interaction + * `ignoreSystemCrashHandler` Boolean diff --git a/package.json b/package.json index f5b0e0f49a9c..81f9c6189740 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "coffeelint": "~0.6.1", "mocha": "~1.13.0", "walkdir": "~0.0.7", + "runas": "~0.2.0", + "formidable": "~1.0.14", "unzip": "~0.1.9", "d3": "~3.3.8", "int64-native": "~0.2.0" diff --git a/script/bootstrap.py b/script/bootstrap.py index 4ee47787effe..10dfde5b3dd4 100755 --- a/script/bootstrap.py +++ b/script/bootstrap.py @@ -58,7 +58,7 @@ def bootstrap_brightray(url): def update_apm(): ## NB: Without this, subprocess incorrectly searches for npm.exe npm_cmd = 'npm' - if sys.platform == 'win32': + if sys.platform in ['win32', 'cygwin']: npm_cmd += '.cmd' with scoped_cwd(os.path.join('vendor', 'apm')): diff --git a/script/cibuild b/script/cibuild index 7a6bbf97576a..61ed82c3ab6a 100755 --- a/script/cibuild +++ b/script/cibuild @@ -4,11 +4,17 @@ import os import subprocess import sys +from lib.util import rm_rf + SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) def main(): + rm_rf(os.path.join(SOURCE_ROOT, 'out')) + rm_rf(os.path.join(SOURCE_ROOT, 'vendor', 'brightray', 'vendor', 'download', + 'libchromiumcontent')) + run_script('bootstrap.py') run_script('cpplint.py') run_script('pylint.py') diff --git a/script/create-dist.py b/script/create-dist.py index c2724d9f5058..a3c44be5aa02 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import argparse import os import shutil import subprocess @@ -12,9 +13,11 @@ from lib.util import scoped_cwd, rm_rf, get_atom_shell_version, make_zip, \ ATOM_SHELL_VRESION = get_atom_shell_version() NODE_VERSION = 'v0.10.18' +BASE_URL = 'https://gh-contractor-zcbenz.s3.amazonaws.com/libchromiumcontent' SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) DIST_DIR = os.path.join(SOURCE_ROOT, 'dist') +OUT_DIR = os.path.join(SOURCE_ROOT, 'out', 'Release') NODE_DIR = os.path.join(SOURCE_ROOT, 'vendor', 'node') DIST_HEADERS_NAME = 'node-{0}'.format(NODE_VERSION) DIST_HEADERS_DIR = os.path.join(DIST_DIR, DIST_HEADERS_NAME) @@ -68,28 +71,42 @@ def main(): rm_rf(DIST_DIR) os.makedirs(DIST_DIR) + args = parse_args() + force_build() + download_libchromiumcontent_symbols(args.url) + create_symbols() copy_binaries() copy_headers() copy_license() create_version() - create_zip() + create_dist_zip() + create_symbols_zip() create_header_tarball() +def parse_args(): + parser = argparse.ArgumentParser(description='Create distributions') + parser.add_argument('-u', '--url', + help='The base URL from which to download ' + 'libchromiumcontent (i.e., the URL you passed to ' + 'libchromiumcontent\'s script/upload script', + default=BASE_URL, + required=False) + return parser.parse_args() + + def force_build(): build = os.path.join(SOURCE_ROOT, 'script', 'build.py') subprocess.check_call([sys.executable, build, '-c', 'Release']) def copy_binaries(): - out_dir = os.path.join(SOURCE_ROOT, 'out', 'Release') - for binary in TARGET_BINARIES[TARGET_PLATFORM]: - shutil.copy2(os.path.join(out_dir, binary), DIST_DIR) + shutil.copy2(os.path.join(OUT_DIR, binary), DIST_DIR) for directory in TARGET_DIRECTORIES[TARGET_PLATFORM]: - shutil.copytree(os.path.join(out_dir, directory), + shutil.copytree(os.path.join(OUT_DIR, directory), os.path.join(DIST_DIR, directory), symlinks=True) @@ -131,7 +148,40 @@ def create_version(): version_file.write(ATOM_SHELL_VRESION) -def create_zip(): +def download_libchromiumcontent_symbols(url): + if sys.platform == 'darwin': + symbols_name = 'libchromiumcontent.dylib.dSYM' + else: + symbols_name = 'chromiumcontent.dll.pdb' + + brightray_dir = os.path.join(SOURCE_ROOT, 'vendor', 'brightray', 'vendor') + target_dir = os.path.join(brightray_dir, 'download', 'libchromiumcontent') + symbols_path = os.path.join(target_dir, 'Release', symbols_name) + if os.path.exists(symbols_path): + return + + download = os.path.join(brightray_dir, 'libchromiumcontent', 'script', + 'download') + subprocess.check_call([sys.executable, download, '-f', '-s', url, target_dir]) + + if sys.platform == 'darwin': + shutil.copytree(symbols_path, + os.path.join(OUT_DIR, symbols_name), + symlinks=True) + + +def create_symbols(): + build = os.path.join(SOURCE_ROOT, 'script', 'build.py') + subprocess.check_output([sys.executable, build, '-c', 'Release', + '-t', 'atom_dump_symbols']) + + directory = 'Atom-Shell.breakpad.syms' + shutil.copytree(os.path.join(OUT_DIR, directory), + os.path.join(DIST_DIR, directory), + symlinks=True) + + +def create_dist_zip(): dist_name = 'atom-shell-{0}-{1}.zip'.format(ATOM_SHELL_VRESION, TARGET_PLATFORM) zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name) @@ -142,6 +192,17 @@ def create_zip(): make_zip(zip_file, files, dirs) +def create_symbols_zip(): + dist_name = 'atom-shell-{0}-{1}-symbols.zip'.format(ATOM_SHELL_VRESION, + TARGET_PLATFORM) + zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name) + + with scoped_cwd(DIST_DIR): + files = ['LICENSE', 'version'] + dirs = ['Atom-Shell.breakpad.syms'] + make_zip(zip_file, files, dirs) + + def create_header_tarball(): with scoped_cwd(DIST_DIR): tarball = tarfile.open(name=DIST_HEADERS_DIR + '.tar.gz', mode='w:gz') diff --git a/script/lib/util.py b/script/lib/util.py index 7c890bf87c7c..8eefed3f9fff 100644 --- a/script/lib/util.py +++ b/script/lib/util.py @@ -59,7 +59,7 @@ def extract_tarball(tarball_path, member, destination): def extract_zip(zip_path, destination): if sys.platform == 'darwin': # Use unzip command on Mac to keep symbol links in zip file work. - subprocess.check_call(['unzip', zip_path, '-d', destination]) + subprocess.check_output(['unzip', zip_path, '-d', destination]) else: with zipfile.ZipFile(zip_path) as z: z.extractall(destination) @@ -68,7 +68,7 @@ def make_zip(zip_file_path, files, dirs): safe_unlink(zip_file_path) if sys.platform == 'darwin': files += dirs - subprocess.check_call(['zip', '-r', '-y', zip_file_path] + files) + subprocess.check_output(['zip', '-r', '-y', zip_file_path] + files) else: zip_file = zipfile.ZipFile(zip_file_path, "w", zipfile.ZIP_DEFLATED) for filename in files: diff --git a/script/update-frameworks.py b/script/update-frameworks.py index 3cb142c4b143..e6c311946967 100755 --- a/script/update-frameworks.py +++ b/script/update-frameworks.py @@ -13,7 +13,6 @@ FRAMEWORKS_URL = 'https://gh-contractor-zcbenz.s3.amazonaws.com/frameworks' def main(): os.chdir(SOURCE_ROOT) safe_mkdir('frameworks') - download_and_unzip('Quincy') download_and_unzip('Sparkle') diff --git a/script/upload.py b/script/upload.py index ac95a4859a0b..f43ed002859e 100755 --- a/script/upload.py +++ b/script/upload.py @@ -27,6 +27,8 @@ SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) OUT_DIR = os.path.join(SOURCE_ROOT, 'out', 'Release') DIST_DIR = os.path.join(SOURCE_ROOT, 'dist') DIST_NAME = 'atom-shell-{0}-{1}.zip'.format(ATOM_SHELL_VRESION, TARGET_PLATFORM) +SYMBOLS_NAME = 'atom-shell-{0}-{1}-symbols.zip'.format(ATOM_SHELL_VRESION, + TARGET_PLATFORM) def main(): @@ -48,6 +50,7 @@ def main(): github = GitHub(auth_token()) release_id = create_or_get_release_draft(github, args.version) upload_atom_shell(github, release_id, os.path.join(DIST_DIR, DIST_NAME)) + upload_atom_shell(github, release_id, os.path.join(DIST_DIR, SYMBOLS_NAME)) if not args.no_publish_release: publish_release(github, release_id) diff --git a/spec/api/crash-reporter.coffee b/spec/api/crash-reporter.coffee new file mode 100644 index 000000000000..fc4e91d2d882 --- /dev/null +++ b/spec/api/crash-reporter.coffee @@ -0,0 +1,32 @@ +assert = require 'assert' +path = require 'path' +http = require 'http' +remote = require 'remote' +formidable = require 'formidable' +BrowserWindow = remote.require 'browser-window' + +fixtures = path.resolve __dirname, '..', 'fixtures' + +describe 'crash-reporter module', -> + it 'should send minidump when renderer crashes', (done) -> + w = new BrowserWindow(show: false) + server = http.createServer (req, res) -> + form = new formidable.IncomingForm() + form.parse req, (error, fields, files) -> + assert.equal fields['prod'], 'Atom-Shell' + assert.equal fields['ver'], process.versions['atom-shell'] + assert.equal fields['process_type'], 'renderer' + assert.equal fields['platform'], process.platform + assert.equal fields['extra1'], 'extra1' + assert.equal fields['extra2'], 'extra2' + assert.equal fields['_productName'], 'Zombies' + assert.equal fields['_companyName'], 'Umbrella Corporation' + assert.equal fields['_version'], require('remote').require('app').getVersion() + assert files['upload_file_minidump']['name']? + + w.destroy() + res.end() + server.close() + done() + server.listen 1127, '127.0.0.1', -> + w.loadUrl 'file://' + path.join(fixtures, 'api', 'crash.html') diff --git a/spec/fixtures/api/crash.html b/spec/fixtures/api/crash.html new file mode 100644 index 000000000000..cb6c43586a32 --- /dev/null +++ b/spec/fixtures/api/crash.html @@ -0,0 +1,20 @@ + + + + + diff --git a/tools/mac/generate_breakpad_symbols.py b/tools/mac/generate_breakpad_symbols.py new file mode 100755 index 000000000000..2fbe91f041e9 --- /dev/null +++ b/tools/mac/generate_breakpad_symbols.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python +# Copyright (c) 2013 GitHub, Inc. All rights reserved. +# 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. + +"""A tool to generate symbols for a binary suitable for breakpad. + +Currently, the tool only supports Linux, Android, and Mac. Support for other +platforms is planned. +""" + +import errno +import optparse +import os +import Queue +import re +import shutil +import subprocess +import sys +import threading + + +CONCURRENT_TASKS=4 + + +def GetCommandOutput(command): + """Runs the command list, returning its output. + + Prints the given command (which should be a list of one or more strings), + then runs it and returns its output (stdout) as a string. + + From chromium_utils. + """ + devnull = open(os.devnull, 'w') + proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=devnull, + bufsize=1) + output = proc.communicate()[0] + return output + + +def GetDumpSymsBinary(build_dir=None): + """Returns the path to the dump_syms binary.""" + DUMP_SYMS = 'dump_syms' + dump_syms_bin = os.path.join(os.path.expanduser(build_dir), DUMP_SYMS) + if not os.access(dump_syms_bin, os.X_OK): + print 'Cannot find %s.' % DUMP_SYMS + sys.exit(1) + + return dump_syms_bin + + +def FindBundlePart(full_path): + if full_path.endswith(('.dylib', '.framework', '.app')): + return os.path.basename(full_path) + elif full_path != '' and full_path != '/': + return FindBundlePart(os.path.dirname(full_path)) + else: + return '' + + +def GetDSYMBundle(build_dir, binary_path): + """Finds the .dSYM bundle to the binary.""" + if binary_path[0] == '/' or binary_path == '': + return binary_path + + filename = FindBundlePart(binary_path) + if filename.endswith(('.dylib', '.framework', '.app')): + dsym_path = os.path.join(build_dir, filename) + '.dSYM' + + if dsym_path != None and os.path.exists(dsym_path): + return dsym_path + else: + return binary_path + + +def Resolve(path, exe_path, loader_path, rpaths): + """Resolve a dyld path. + + @executable_path is replaced with |exe_path| + @loader_path is replaced with |loader_path| + @rpath is replaced with the first path in |rpaths| where the referenced file + is found + """ + path = path.replace('@loader_path', loader_path) + path = path.replace('@executable_path', exe_path) + if path.find('@rpath') != -1: + for rpath in rpaths: + new_path = Resolve(path.replace('@rpath', rpath), exe_path, loader_path, + []) + if os.access(new_path, os.F_OK): + return new_path + return '' + return path + + +def GetSharedLibraryDependenciesLinux(binary): + """Return absolute paths to all shared library dependecies of the binary. + + This implementation assumes that we're running on a Linux system.""" + ldd = GetCommandOutput(['ldd', binary]) + lib_re = re.compile('\t.* => (.+) \(.*\)$') + result = [] + for line in ldd.splitlines(): + m = lib_re.match(line) + if m: + result.append(m.group(1)) + return result + + +def GetSharedLibraryDependenciesMac(binary, exe_path): + """Return absolute paths to all shared library dependecies of the binary. + + This implementation assumes that we're running on a Mac system.""" + loader_path = os.path.dirname(binary) + otool = GetCommandOutput(['otool', '-l', binary]).splitlines() + rpaths = [] + for idx, line in enumerate(otool): + if line.find('cmd LC_RPATH') != -1: + m = re.match(' *path (.*) \(offset .*\)$', otool[idx+2]) + rpaths.append(m.group(1)) + + otool = GetCommandOutput(['otool', '-L', binary]).splitlines() + lib_re = re.compile('\t(.*) \(compatibility .*\)$') + deps = [] + for line in otool: + m = lib_re.match(line) + if m: + dep = Resolve(m.group(1), exe_path, loader_path, rpaths) + if dep: + deps.append(os.path.normpath(dep)) + return deps + + +def GetSharedLibraryDependencies(options, binary, exe_path): + """Return absolute paths to all shared library dependecies of the binary.""" + deps = [] + if sys.platform.startswith('linux'): + deps = GetSharedLibraryDependenciesLinux(binary) + elif sys.platform == 'darwin': + deps = GetSharedLibraryDependenciesMac(binary, exe_path) + else: + print "Platform not supported." + sys.exit(1) + + result = [] + build_dir = os.path.abspath(options.build_dir) + for dep in deps: + if (os.access(dep, os.F_OK)): + result.append(dep) + return result + + +def mkdir_p(path): + """Simulates mkdir -p.""" + try: + os.makedirs(path) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(path): + pass + else: raise + + +def GenerateSymbols(options, binaries): + """Dumps the symbols of binary and places them in the given directory.""" + + queue = Queue.Queue() + print_lock = threading.Lock() + + def _Worker(): + while True: + binary = queue.get() + + if options.verbose: + with print_lock: + print "Generating symbols for %s" % binary + + if sys.platform == 'darwin': + binary = GetDSYMBundle(options.build_dir, binary) + + syms = GetCommandOutput([GetDumpSymsBinary(options.build_dir), '-r', '-c', + binary]) + module_line = re.match("MODULE [^ ]+ [^ ]+ ([0-9A-F]+) (.*)\n", syms) + output_path = os.path.join(options.symbols_dir, module_line.group(2), + module_line.group(1)) + mkdir_p(output_path) + symbol_file = "%s.sym" % module_line.group(2) + f = open(os.path.join(output_path, symbol_file), 'w') + f.write(syms) + f.close() + + queue.task_done() + + for binary in binaries: + queue.put(binary) + + for _ in range(options.jobs): + t = threading.Thread(target=_Worker) + t.daemon = True + t.start() + + queue.join() + + +def main(): + parser = optparse.OptionParser() + parser.add_option('', '--build-dir', default='', + help='The build output directory.') + parser.add_option('', '--symbols-dir', default='', + help='The directory where to write the symbols file.') + parser.add_option('', '--binary', default='', + help='The path of the binary to generate symbols for.') + parser.add_option('', '--clear', default=False, action='store_true', + help='Clear the symbols directory before writing new ' + 'symbols.') + parser.add_option('-j', '--jobs', default=CONCURRENT_TASKS, action='store', + type='int', help='Number of parallel tasks to run.') + parser.add_option('-v', '--verbose', action='store_true', + help='Print verbose status output.') + + (options, _) = parser.parse_args() + + if not options.symbols_dir: + print "Required option --symbols-dir missing." + return 1 + + if not options.build_dir: + print "Required option --build-dir missing." + return 1 + + if not options.binary: + print "Required option --binary missing." + return 1 + + if not os.access(options.binary, os.X_OK): + print "Cannot find %s." % options.binary + return 1 + + if options.clear: + try: + shutil.rmtree(options.symbols_dir) + except: + pass + + # Build the transitive closure of all dependencies. + binaries = set([options.binary]) + queue = [options.binary] + exe_path = os.path.dirname(options.binary) + while queue: + deps = GetSharedLibraryDependencies(options, queue.pop(0), exe_path) + new_deps = set(deps) - binaries + binaries |= new_deps + queue.extend(list(new_deps)) + + GenerateSymbols(options, binaries) + + return 0 + + +if '__main__' == __name__: + sys.exit(main()) diff --git a/tools/win/generate_breakpad_symbols.py b/tools/win/generate_breakpad_symbols.py new file mode 100644 index 000000000000..254b3592721b --- /dev/null +++ b/tools/win/generate_breakpad_symbols.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# Copyright (c) 2013 GitHub, Inc. All rights reserved. +# 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. + +"""Convert pdb to sym for given directories""" + +import errno +import glob +import optparse +import os +import Queue +import re +import subprocess +import sys +import threading + + +CONCURRENT_TASKS=4 +SOURCE_ROOT=os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) +DUMP_SYMS=os.path.join(SOURCE_ROOT, 'vendor', 'breakpad', 'dump_syms.exe') + + +def GetCommandOutput(command): + """Runs the command list, returning its output. + + Prints the given command (which should be a list of one or more strings), + then runs it and returns its output (stdout) as a string. + + From chromium_utils. + """ + devnull = open(os.devnull, 'w') + proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=devnull, + bufsize=1) + output = proc.communicate()[0] + return output + + +def mkdir_p(path): + """Simulates mkdir -p.""" + try: + os.makedirs(path) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(path): + pass + else: raise + + +def RegisterRequiredDll(): + register = os.path.join(os.path.dirname(__file__), 'register_msdia80_dll.js') + node = os.path.join(SOURCE_ROOT, 'out', 'Release', 'atom.exe') + os.environ['ATOM_SHELL_INTERNAL_RUN_AS_NODE'] = '1' + subprocess.check_call([node, register]); + + +def GenerateSymbols(options, binaries): + """Dumps the symbols of binary and places them in the given directory.""" + + queue = Queue.Queue() + print_lock = threading.Lock() + + def _Worker(): + while True: + binary = queue.get() + + if options.verbose: + with print_lock: + print "Generating symbols for %s" % binary + + syms = GetCommandOutput([DUMP_SYMS, binary]) + module_line = re.match("MODULE [^ ]+ [^ ]+ ([0-9A-F]+) (.*)\r\n", syms) + if module_line == None: + with print_lock: + print "Failed to get symbols for %s" % binary + queue.task_done() + continue + + output_path = os.path.join(options.symbols_dir, module_line.group(2), + module_line.group(1)) + mkdir_p(output_path) + symbol_file = "%s.sym" % module_line.group(2) + f = open(os.path.join(output_path, symbol_file), 'w') + f.write(syms) + f.close() + + queue.task_done() + + for binary in binaries: + queue.put(binary) + + for _ in range(options.jobs): + t = threading.Thread(target=_Worker) + t.daemon = True + t.start() + + queue.join() + + +def main(): + parser = optparse.OptionParser() + parser.add_option('', '--symbols-dir', default='', + help='The directory where to write the symbols file.') + parser.add_option('', '--clear', default=False, action='store_true', + help='Clear the symbols directory before writing new ' + 'symbols.') + parser.add_option('-j', '--jobs', default=CONCURRENT_TASKS, action='store', + type='int', help='Number of parallel tasks to run.') + parser.add_option('-v', '--verbose', action='store_true', + help='Print verbose status output.') + + (options, directories) = parser.parse_args() + + if not options.symbols_dir: + print "Required option --symbols-dir missing." + return 1 + + if options.clear: + try: + shutil.rmtree(options.symbols_dir) + except: + pass + + pdbs = [] + for directory in directories: + pdbs += glob.glob(os.path.join(directory, '*.pdb')) + + RegisterRequiredDll(); + GenerateSymbols(options, pdbs) + + return 0 + + +if '__main__' == __name__: + sys.exit(main()) diff --git a/tools/win/register_msdia80_dll.js b/tools/win/register_msdia80_dll.js new file mode 100644 index 000000000000..a5a0508e18fc --- /dev/null +++ b/tools/win/register_msdia80_dll.js @@ -0,0 +1,12 @@ +var fs = require('fs'); +var path = require('path'); +var runas = require('runas'); + +var source = path.resolve(__dirname, '..', '..', 'vendor', 'breakpad', 'msdia80.dll'); +var target = 'C:\\Program Files\\Common Files\\Microsoft Shared\\VC\\msdia80.dll'; +if (fs.existsSync(target)) + return; + +var copy = 'copy "' + source + '" "' + target + '"'; +var register = 'regsvr32 "' + target + '"'; +runas('cmd', ['/K', copy + ' & ' + register + ' & exit']); diff --git a/vendor/breakpad b/vendor/breakpad new file mode 160000 index 000000000000..2483f32da1f7 --- /dev/null +++ b/vendor/breakpad @@ -0,0 +1 @@ +Subproject commit 2483f32da1f729ac362fbbcaa9173843379697e9 diff --git a/vendor/depot_tools b/vendor/depot_tools index 7f834ad2f1fd..a738935a12e7 160000 --- a/vendor/depot_tools +++ b/vendor/depot_tools @@ -1 +1 @@ -Subproject commit 7f834ad2f1fdde361379ccdc19dcc2d2b6426e3c +Subproject commit a738935a12e7ef428b3a852f1f3eca6bbfa0182b diff --git a/vendor/gyp b/vendor/gyp index b8c2ab69b417..88202fb4e5db 160000 --- a/vendor/gyp +++ b/vendor/gyp @@ -1 +1 @@ -Subproject commit b8c2ab69b417009ab5d2a4228368563b33c19d37 +Subproject commit 88202fb4e5db8d3ba3051fc2ce36f35aae22f69d