diff --git a/client/crashpad_client_win.cc b/client/crashpad_client_win.cc index 235ef185..6f93f7ae 100644 --- a/client/crashpad_client_win.cc +++ b/client/crashpad_client_win.cc @@ -15,6 +15,7 @@ #include "client/crashpad_client.h" #include +#include #include #include @@ -31,6 +32,7 @@ #include "util/file/file_io.h" #include "util/misc/random_string.h" #include "util/win/address_types.h" +#include "util/win/capture_context.h" #include "util/win/command_line.h" #include "util/win/critical_section_with_debug_info.h" #include "util/win/get_function.h" @@ -163,6 +165,28 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) { return EXCEPTION_CONTINUE_SEARCH; } +void HandleAbortSignal(int signum) { + DCHECK_EQ(signum, SIGABRT); + + CONTEXT context; + CaptureContext(&context); + + EXCEPTION_RECORD record = {}; + record.ExceptionCode = STATUS_FATAL_APP_EXIT; + record.ExceptionFlags = EXCEPTION_NONCONTINUABLE; +#if defined(ARCH_CPU_64_BITS) + record.ExceptionAddress = reinterpret_cast(context.Rip); +#else + record.ExceptionAddress = reinterpret_cast(context.Eip); +#endif // ARCH_CPU_64_BITS + + EXCEPTION_POINTERS exception_pointers; + exception_pointers.ContextRecord = &context; + exception_pointers.ExceptionRecord = &record; + + UnhandledExceptionHandler(&exception_pointers); +} + std::wstring FormatArgumentString(const std::string& name, const std::wstring& value) { return std::wstring(L"--") + base::UTF8ToUTF16(name) + L"=" + value; @@ -500,6 +524,32 @@ void CommonInProcessInitialization() { g_non_crash_dump_lock = new base::Lock(); } +void RegisterHandlers() { + SetUnhandledExceptionFilter(&UnhandledExceptionHandler); + + // The Windows CRT's signal.h lists: + // - SIGINT + // - SIGILL + // - SIGFPE + // - SIGSEGV + // - SIGTERM + // - SIGBREAK + // - SIGABRT + // SIGILL and SIGTERM are documented as not being generated. SIGBREAK and + // SIGINT are for Ctrl-Break and Ctrl-C, and aren't something for which + // capturing a dump is warranted. SIGFPE and SIGSEGV are captured as regular + // exceptions through the unhandled exception filter. This leaves SIGABRT. In + // the standard CRT, abort() is implemented as a synchronous call to the + // SIGABRT signal handler if installed, but after doing so, the unhandled + // exception filter is not triggered (it instead __fastfail()s). So, register + // to handle SIGABRT to catch abort() calls, as client code might use this and + // expect it to cause a crash dump. This will only work when the abort() + // that's called in client code is the same (or has the same behavior) as the + // one in use here. + _crt_signal_t rv = signal(SIGABRT, HandleAbortSignal); + DCHECK_NE(rv, SIG_ERR); +} + } // namespace CrashpadClient::CrashpadClient() : ipc_pipe_(), handler_start_thread_() {} @@ -536,7 +586,7 @@ bool CrashpadClient::StartHandler( CommonInProcessInitialization(); - SetUnhandledExceptionFilter(&UnhandledExceptionHandler); + RegisterHandlers(); auto data = new BackgroundHandlerStartThreadData(handler, database, @@ -609,7 +659,8 @@ bool CrashpadClient::SetHandlerIPCPipe(const std::wstring& ipc_pipe) { } SetHandlerStartupState(StartupState::kSucceeded); - SetUnhandledExceptionFilter(&UnhandledExceptionHandler); + + RegisterHandlers(); // The server returns these already duplicated to be valid in this process. g_signal_exception = diff --git a/handler/handler.gyp b/handler/handler.gyp index d6900531..e2d993df 100644 --- a/handler/handler.gyp +++ b/handler/handler.gyp @@ -147,6 +147,20 @@ 'win/crashy_test_program.cc', ], }, + { + 'target_name': 'crashy_signal', + 'type': 'executable', + 'dependencies': [ + '../client/client.gyp:crashpad_client', + '../third_party/mini_chromium/mini_chromium.gyp:base', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'win/crashy_signal.cc', + ], + }, { 'target_name': 'crash_other_program', 'type': 'executable', diff --git a/handler/win/crashy_signal.cc b/handler/win/crashy_signal.cc new file mode 100644 index 00000000..634bce8c --- /dev/null +++ b/handler/win/crashy_signal.cc @@ -0,0 +1,91 @@ +// Copyright 2016 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "client/crashpad_client.h" + +namespace crashpad { +namespace { + +enum WhereToSignalFrom { + kUnknown = -1, + kMain = 0, + kBackground = 1, +}; + +WhereToSignalFrom MainOrBackground(wchar_t* name) { + if (wcscmp(name, L"main") == 0) + return kMain; + if (wcscmp(name, L"background") == 0) + return kBackground; + return kUnknown; +} + +DWORD WINAPI BackgroundThread(void* arg) { + abort(); + return 0; +} + +int CrashySignalMain(int argc, wchar_t* argv[]) { + CrashpadClient client; + + WhereToSignalFrom from; + if (argc == 3 && (from = MainOrBackground(argv[2])) != kUnknown) { + if (!client.SetHandlerIPCPipe(argv[1])) { + LOG(ERROR) << "SetHandler"; + return EXIT_FAILURE; + } + } else { + LOG(ERROR) << "Usage: " << base::UTF16ToUTF8(argv[0]) + << " main|background"; + return EXIT_FAILURE; + } + + // In debug builds part of abort() is to open a dialog. We don't want tests to + // block at that dialog, so disable it. + _set_abort_behavior(0, _WRITE_ABORT_MSG); + + if (from == kBackground) { + HANDLE thread = CreateThread(nullptr, + 0, + &BackgroundThread, + nullptr, + 0, + nullptr); + if (!thread) { + PLOG(ERROR) << "CreateThread"; + return EXIT_FAILURE; + } + if (WaitForSingleObject(thread, INFINITE) != WAIT_OBJECT_0) { + PLOG(ERROR) << "WaitForSingleObject"; + return EXIT_FAILURE; + } + } else { + abort(); + } + + return EXIT_SUCCESS; +} + +} // namespace +} // namespace crashpad + +int wmain(int argc, wchar_t* argv[]) { + return crashpad::CrashySignalMain(argc, argv); +} diff --git a/snapshot/win/end_to_end_test.py b/snapshot/win/end_to_end_test.py index f469bcf4..42999df8 100755 --- a/snapshot/win/end_to_end_test.py +++ b/snapshot/win/end_to_end_test.py @@ -143,6 +143,10 @@ def GetDumpFromOtherProgram(out_dir, pipe_name, *args): *args) +def GetDumpFromSignal(out_dir, pipe_name, *args): + return GetDumpFromProgram(out_dir, pipe_name, 'crashy_signal.exe', *args) + + def GetDumpFromSelfDestroyingProgram(out_dir, pipe_name): return GetDumpFromProgram(out_dir, pipe_name, 'self_destroying_program.exe') @@ -201,6 +205,8 @@ def RunTests(cdb_path, z7_dump_path, other_program_path, other_program_no_exception_path, + sigabrt_main_path, + sigabrt_background_path, pipe_name): """Runs various tests in sequence. Runs a new cdb instance on the dump for each block of tests to reduce the chances that output from one command is @@ -361,6 +367,13 @@ def RunTests(cdb_path, 'other program with no exception given') out.Check('!RaiseException', 'other program in RaiseException()') + out = CdbRun(cdb_path, sigabrt_main_path, '.ecxr') + out.Check('code 40000015', 'got sigabrt signal') + out.Check('::HandleAbortSignal', ' stack in expected location') + + out = CdbRun(cdb_path, sigabrt_background_path, '.ecxr') + out.Check('code 40000015', 'got sigabrt signal from background thread') + def main(args): try: @@ -411,6 +424,15 @@ def main(args): if not other_program_no_exception_path: return 1 + sigabrt_main_path = GetDumpFromSignal(args[0], pipe_name, 'main') + if not sigabrt_main_path: + return 1 + + sigabrt_background_path = GetDumpFromSignal( + args[0], pipe_name, 'background') + if not sigabrt_background_path: + return 1 + RunTests(cdb_path, crashy_dump_path, start_handler_dump_path, @@ -418,6 +440,8 @@ def main(args): z7_dump_path, other_program_path, other_program_no_exception_path, + sigabrt_main_path, + sigabrt_background_path, pipe_name) return 1 if g_had_failures else 0