win: Add signal handler for SIGABRT to handle abort() calls

R=mark@chromium.org
BUG=crashpad:57

Change-Id: Ib7141f00e74e3db9e5be427cc990847331e09912
Reviewed-on: https://chromium-review.googlesource.com/412058
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Scott Graham 2016-11-17 14:00:21 -08:00
parent 5a21fc1573
commit 8b3eec83e9
4 changed files with 182 additions and 2 deletions

View File

@ -15,6 +15,7 @@
#include "client/crashpad_client.h"
#include <windows.h>
#include <signal.h>
#include <stdint.h>
#include <string.h>
@ -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<void*>(context.Rip);
#else
record.ExceptionAddress = reinterpret_cast<void*>(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 =

View File

@ -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',

View File

@ -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 <stdlib.h>
#include <string.h>
#include <windows.h>
#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])
<< " <server_pipe_name> 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);
}

View File

@ -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