mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
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:
parent
5a21fc1573
commit
8b3eec83e9
@ -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 =
|
||||
|
@ -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',
|
||||
|
91
handler/win/crashy_signal.cc
Normal file
91
handler/win/crashy_signal.cc
Normal 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);
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user