Accept non-fatal resource exceptions without generating crash reports.

This adds IsExceptionNonfatalResource() and its test, and uses it in
crashpad_handler. When non-fatal resource exceptions are encountered, no
crash report is generated. crashpad_handler swallows these exceptions.
Alternatively, it could allow them to be sent to the system’s host-level
resource exception handler, normally com.apple.ReportCrash.root, which
would allow them to be processed in the same way as when Crashpad is not
in use. I’m not sure which option is better. I chose to swallow them
because there doesn’t appear to be much value in letting
com.apple.ReportCrash.root and spindump look at them.

This also moves ExcCrashRecoverOriginalException() to the new file as a
sibling of IsExceptionNonfatalResource(). This provides better
organization.

BUG=crashpad:35, chromium:474163, chromium:474326
TEST=crashpad_util_test ExceptionTypes.IsExceptionNonfatalResource
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/1066243002
This commit is contained in:
Mark Mentovai 2015-04-08 17:46:09 -04:00
parent ab23906193
commit 1baff4ff92
16 changed files with 546 additions and 123 deletions

View File

@ -22,10 +22,12 @@
'type': 'static_library',
'sources': [
'mac/AvailabilityMacros.h',
'mac/kern/exc_resource.h'
'mac/mach/mach.h',
'mac/mach-o/getsect.cc',
'mac/mach-o/getsect.h',
'mac/mach-o/loader.h',
'mac/sys/resource.h',
'non_mac/mach/mach.h',
'non_win/dbghelp.h',
'non_win/minwinbase.h',

View File

@ -0,0 +1,62 @@
// Copyright 2015 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.
#ifndef CRASHPAD_COMPAT_MAC_KERN_EXC_RESOURCE_H_
#define CRASHPAD_COMPAT_MAC_KERN_EXC_RESOURCE_H_
#if __has_include_next(<kern/exc_resource.h>)
#include_next <kern/exc_resource.h>
#endif
// 10.9 SDK
#ifndef EXC_RESOURCE_DECODE_RESOURCE_TYPE
#define EXC_RESOURCE_DECODE_RESOURCE_TYPE(code) (((code) >> 61) & 0x7ull)
#endif
#ifndef EXC_RESOURCE_DECODE_FLAVOR
#define EXC_RESOURCE_DECODE_FLAVOR(code) (((code) >> 58) & 0x7ull)
#endif
#ifndef RESOURCE_TYPE_CPU
#define RESOURCE_TYPE_CPU 1
#endif
#ifndef RESOURCE_TYPE_WAKEUPS
#define RESOURCE_TYPE_WAKEUPS 2
#endif
#ifndef RESOURCE_TYPE_MEMORY
#define RESOURCE_TYPE_MEMORY 3
#endif
#ifndef FLAVOR_CPU_MONITOR
#define FLAVOR_CPU_MONITOR 1
#endif
#ifndef FLAVOR_WAKEUPS_MONITOR
#define FLAVOR_WAKEUPS_MONITOR 1
#endif
#ifndef FLAVOR_HIGH_WATERMARK
#define FLAVOR_HIGH_WATERMARK 1
#endif
// 10.10 SDK
#ifndef FLAVOR_CPU_MONITOR_FATAL
#define FLAVOR_CPU_MONITOR_FATAL 2
#endif
#endif // CRASHPAD_COMPAT_MAC_KERN_EXC_RESOURCE_H_

View File

@ -43,7 +43,7 @@ void* SystemLibMachOHandle() {
if (!dladdr(reinterpret_cast<void*>(getsectbyname), &info)) {
return nullptr;
}
return dlopen(info.dli_fname, RTLD_LAZY | RTLD_LOCAL);
return dlopen(info.dli_fname, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
}
// Returns a function pointer to a function in libmacho based on a lookup of

26
compat/mac/sys/resource.h Normal file
View File

@ -0,0 +1,26 @@
// Copyright 2015 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.
#ifndef CRASHPAD_COMPAT_MAC_SYS_RESOURCE_H_
#define CRASHPAD_COMPAT_MAC_SYS_RESOURCE_H_
#include_next <sys/resource.h>
// 10.9 SDK
#ifndef WAKEMON_MAKE_FATAL
#define WAKEMON_MAKE_FATAL 0x10
#endif
#endif // CRASHPAD_COMPAT_MAC_SYS_RESOURCE_H_

View File

@ -28,6 +28,7 @@
#include "util/file/file_writer.h"
#include "util/mach/exc_client_variants.h"
#include "util/mach/exception_behaviors.h"
#include "util/mach/exception_types.h"
#include "util/mach/mach_extensions.h"
#include "util/mach/mach_message.h"
#include "util/mach/scoped_task_suspend.h"
@ -101,15 +102,32 @@ kern_return_t CrashReportExceptionHandler::CatchMachException(
// TODO(mark): Consider exceptions outside of the range (0, 32) from the
// kernel to be suspicious, and exceptions other than kMachExceptionSimulated
// from the process itself to be suspicious.
const pid_t pid = process_snapshot.ProcessID();
pid_t audit_pid = AuditPIDFromMachMessageTrailer(trailer);
if (audit_pid != -1 && audit_pid != 0) {
pid_t exception_pid = process_snapshot.ProcessID();
if (exception_pid != audit_pid) {
LOG(WARNING) << "exception for pid " << exception_pid << " sent by pid "
if (audit_pid != pid) {
LOG(WARNING) << "exception for pid " << pid << " sent by pid "
<< audit_pid;
}
}
if (IsExceptionNonfatalResource(exception, code[0], pid)) {
// Swallow non-fatal resource exceptions.
//
// Normally, all EXC_RESOURCE exceptions go to the host-level EXC_RESOURCE
// handler, com.apple.ReportCrash.root, which invokes spindump to handle
// them. These non-fatal exceptions are never user-visible and are not
// currently of interest to Crashpad. Returning success here gets the
// process going again quickly, without generating a crash report.
//
// Alternatively, this could return KERN_FAILURE to let the exception go to
// the host-level handler, but there doesnt seem to be much value in doing
// so.
ExcServerCopyState(
behavior, old_state, old_state_count, new_state, new_state_count);
return ExcServerSuccessfulReturnValue(behavior, false);
}
CrashpadInfoClientOptions client_options;
process_snapshot.GetCrashpadOptions(&client_options);

View File

@ -18,8 +18,8 @@
#include "base/strings/stringprintf.h"
#include "snapshot/mac/cpu_context_mac.h"
#include "snapshot/mac/process_reader.h"
#include "util/mach/exc_server_variants.h"
#include "util/mach/exception_behaviors.h"
#include "util/mach/exception_types.h"
#include "util/mach/symbolic_constants_mach.h"
#include "util/numeric/safe_assignment.h"

View File

@ -29,6 +29,7 @@
#include "tools/tool_support.h"
#include "util/mach/exc_server_variants.h"
#include "util/mach/exception_behaviors.h"
#include "util/mach/exception_types.h"
#include "util/mach/mach_extensions.h"
#include "util/mach/mach_message.h"
#include "util/mach/mach_message_server.h"

View File

@ -767,33 +767,6 @@ mach_msg_size_t UniversalMachExcServer::MachMessageServerReplySize() {
return impl_->MachMessageServerReplySize();
}
exception_type_t ExcCrashRecoverOriginalException(
mach_exception_code_t code_0,
mach_exception_code_t* original_code_0,
int* signal) {
// 10.9.4 xnu-2422.110.17/bsd/kern/kern_exit.c proc_prepareexit() sets code[0]
// based on the signal value, original exception type, and low 20 bits of the
// original code[0] before calling xnu-2422.110.17/osfmk/kern/exception.c
// task_exception_notify() to raise an EXC_CRASH.
//
// The list of core-generating signals (as used in proc_prepareexit()s call
// to hassigprop()) is in 10.9.4 xnu-2422.110.17/bsd/sys/signalvar.h sigprop:
// entires with SA_CORE are in the set. These signals are SIGQUIT, SIGILL,
// SIGTRAP, SIGABRT, SIGEMT, SIGFPE, SIGBUS, SIGSEGV, and SIGSYS. Processes
// killed for code-signing reasons will be killed by SIGKILL and are also
// eligible for EXC_CRASH handling, but processes killed by SIGKILL for other
// reasons are not.
if (signal) {
*signal = (code_0 >> 24) & 0xff;
}
if (original_code_0) {
*original_code_0 = code_0 & 0xfffff;
}
return (code_0 >> 20) & 0xf;
}
kern_return_t ExcServerSuccessfulReturnValue(exception_behavior_t behavior,
bool set_thread_state) {
if (!set_thread_state && ExceptionBehaviorHasState(behavior)) {

View File

@ -116,40 +116,6 @@ class UniversalMachExcServer final : public MachMessageServer::Interface {
DISALLOW_COPY_AND_ASSIGN(UniversalMachExcServer);
};
//! \brief Recovers the original exception, first exception code, and signal
//! from the encoded form of the first exception code delivered with
//! `EXC_CRASH` exceptions.
//!
//! `EXC_CRASH` exceptions are generated when the kernel has committed to
//! terminating a process as a result of a core-generating POSIX signal and, for
//! hardware exceptions, an earlier Mach exception. Information about this
//! earlier exception and signal is made available to the `EXC_CRASH` handler
//! via its `code[0]` parameter. This function recovers the original exception,
//! the value of `code[0]` from the original exception, and the value of the
//! signal responsible for process termination.
//!
//! \param[in] code_0 The first exception code (`code[0]`) passed to a Mach
//! exception handler in an `EXC_CRASH` exception. It is invalid to call
//! this function with an exception code from any exception other than
//! `EXC_CRASH`.
//! \param[out] original_code_0 The first exception code (`code[0]`) passed to
//! the Mach exception handler for a hardware exception that resulted in the
//! generation of a POSIX signal that caused process termination. If the
//! signal that caused termination was not sent as a result of a hardware
//! exception, this will be `0`. Callers that do not need this value may
//! pass `nullptr`.
//! \param[out] signal The POSIX signal that caused process termination. Callers
//! that do not need this value may pass `nullptr`.
//!
//! \return The original exception for a hardware exception that resulted in the
//! generation of a POSIX signal that caused process termination. If the
//! signal that caused termination was not sent as a result of a hardware
//! exception, this will be `0`.
exception_type_t ExcCrashRecoverOriginalException(
mach_exception_code_t code_0,
mach_exception_code_t* original_code_0,
int* signal);
//! \brief Computes an approriate successful return value for an exception
//! handler function.
//!

View File

@ -15,7 +15,6 @@
#include "util/mach/exc_server_variants.h"
#include <mach/mach.h>
#include <signal.h>
#include <string.h>
#include "base/strings/stringprintf.h"
@ -24,6 +23,7 @@
#include "test/mac/mach_errors.h"
#include "test/mac/mach_multiprocess.h"
#include "util/mach/exception_behaviors.h"
#include "util/mach/exception_types.h"
#include "util/mach/mach_message.h"
namespace crashpad {
@ -1185,62 +1185,6 @@ TEST(ExcServerVariants, ThreadStates) {
}
}
TEST(ExcServerVariants, ExcCrashRecoverOriginalException) {
struct TestData {
mach_exception_code_t code_0;
exception_type_t exception;
mach_exception_code_t original_code_0;
int signal;
};
const TestData kTestData[] = {
{0xb100001, EXC_BAD_ACCESS, KERN_INVALID_ADDRESS, SIGSEGV},
{0xb100002, EXC_BAD_ACCESS, KERN_PROTECTION_FAILURE, SIGSEGV},
{0xa100002, EXC_BAD_ACCESS, KERN_PROTECTION_FAILURE, SIGBUS},
{0x4200001, EXC_BAD_INSTRUCTION, 1, SIGILL},
{0x8300001, EXC_ARITHMETIC, 1, SIGFPE},
{0x5600002, EXC_BREAKPOINT, 2, SIGTRAP},
{0x3000000, 0, 0, SIGQUIT},
{0x6000000, 0, 0, SIGABRT},
{0xc000000, 0, 0, SIGSYS},
{0, 0, 0, 0},
};
for (size_t index = 0; index < arraysize(kTestData); ++index) {
const TestData& test_data = kTestData[index];
SCOPED_TRACE(base::StringPrintf(
"index %zu, code_0 0x%llx", index, test_data.code_0));
mach_exception_code_t original_code_0;
int signal;
exception_type_t exception = ExcCrashRecoverOriginalException(
test_data.code_0, &original_code_0, &signal);
EXPECT_EQ(test_data.exception, exception);
EXPECT_EQ(test_data.original_code_0, original_code_0);
EXPECT_EQ(test_data.signal, signal);
}
// Now make sure that ExcCrashRecoverOriginalException() properly ignores
// optional arguments.
static_assert(arraysize(kTestData) >= 1, "must have something to test");
const TestData& test_data = kTestData[0];
EXPECT_EQ(
test_data.exception,
ExcCrashRecoverOriginalException(test_data.code_0, nullptr, nullptr));
mach_exception_code_t original_code_0;
EXPECT_EQ(test_data.exception,
ExcCrashRecoverOriginalException(
test_data.code_0, &original_code_0, nullptr));
EXPECT_EQ(test_data.original_code_0, original_code_0);
int signal;
EXPECT_EQ(
test_data.exception,
ExcCrashRecoverOriginalException(test_data.code_0, nullptr, &signal));
EXPECT_EQ(test_data.signal, signal);
}
TEST(ExcServerVariants, ExcServerSuccessfulReturnValue) {
struct TestData {
exception_behavior_t behavior;

View File

@ -29,6 +29,7 @@
#include "test/mac/mach_multiprocess.h"
#include "util/file/file_io.h"
#include "util/mach/exc_server_variants.h"
#include "util/mach/exception_types.h"
#include "util/mach/mach_extensions.h"
#include "util/mach/mach_message.h"
#include "util/mach/mach_message_server.h"

View File

@ -0,0 +1,208 @@
// Copyright 2015 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 "util/mach/exception_types.h"
#include <Availability.h>
#include <AvailabilityMacros.h>
#include <dlfcn.h>
#include <errno.h>
#include <libproc.h>
#include <kern/exc_resource.h>
#include "base/logging.h"
#include "base/mac/mach_logging.h"
#include "util/mac/mac_util.h"
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
extern "C" {
// proc_get_wakemon_params() is present in the Mac OS X 10.9 SDK, but no
// declaration is provided. This provides a declaration and marks it for weak
// import if the deployment target is below 10.9.
int proc_get_wakemon_params(pid_t pid, int* rate_hz, int* flags)
__OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0);
} // extern "C"
#else
namespace {
using ProcGetWakemonParamsType = int (*)(pid_t, int*, int*);
// The SDK doesnt have proc_get_wakemon_params() to link against, even with
// weak import. This function returns a function pointer to it if it exists at
// runtime, or nullptr if it doesnt. proc_get_wakemon_params() is looked up in
// the same module that provides proc_pidinfo().
ProcGetWakemonParamsType GetProcGetWakemonParams() {
Dl_info dl_info;
if (!dladdr(reinterpret_cast<void*>(proc_pidinfo), &dl_info)) {
return nullptr;
}
void* dl_handle =
dlopen(dl_info.dli_fname, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
if (!dl_handle) {
return nullptr;
}
ProcGetWakemonParamsType proc_get_wakemon_params =
reinterpret_cast<ProcGetWakemonParamsType>(
dlsym(dl_handle, "proc_get_wakemon_params"));
return proc_get_wakemon_params;
}
} // namespace
#endif
namespace {
// Wraps proc_get_wakemon_params(), calling it if the system provides it. Its
// present on Mac OS X 10.9 and later. If its not available, sets errno to
// ENOSYS and returns -1.
int ProcGetWakemonParams(pid_t pid, int* rate_hz, int* flags) {
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9
// proc_get_wakemon_params() isnt in the SDK. Look it up dynamically.
static ProcGetWakemonParamsType proc_get_wakemon_params =
GetProcGetWakemonParams();
#endif
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9
// proc_get_wakemon_params() is definitely available if the deployment target
// is 10.9 or newer.
if (!proc_get_wakemon_params) {
errno = ENOSYS;
return -1;
}
#endif
return proc_get_wakemon_params(pid, rate_hz, flags);
}
} // namespace
namespace crashpad {
exception_type_t ExcCrashRecoverOriginalException(
mach_exception_code_t code_0,
mach_exception_code_t* original_code_0,
int* signal) {
// 10.9.4 xnu-2422.110.17/bsd/kern/kern_exit.c proc_prepareexit() sets code[0]
// based on the signal value, original exception type, and low 20 bits of the
// original code[0] before calling xnu-2422.110.17/osfmk/kern/exception.c
// task_exception_notify() to raise an EXC_CRASH.
//
// The list of core-generating signals (as used in proc_prepareexit()s call
// to hassigprop()) is in 10.9.4 xnu-2422.110.17/bsd/sys/signalvar.h sigprop:
// entires with SA_CORE are in the set. These signals are SIGQUIT, SIGILL,
// SIGTRAP, SIGABRT, SIGEMT, SIGFPE, SIGBUS, SIGSEGV, and SIGSYS. Processes
// killed for code-signing reasons will be killed by SIGKILL and are also
// eligible for EXC_CRASH handling, but processes killed by SIGKILL for other
// reasons are not.
if (signal) {
*signal = (code_0 >> 24) & 0xff;
}
if (original_code_0) {
*original_code_0 = code_0 & 0xfffff;
}
return (code_0 >> 20) & 0xf;
}
bool IsExceptionNonfatalResource(exception_type_t exception,
mach_exception_code_t code_0,
pid_t pid) {
if (exception != EXC_RESOURCE) {
return false;
}
const int resource_type = EXC_RESOURCE_DECODE_RESOURCE_TYPE(code_0);
const int resource_flavor = EXC_RESOURCE_DECODE_FLAVOR(code_0);
if (resource_type == RESOURCE_TYPE_CPU &&
(resource_flavor == FLAVOR_CPU_MONITOR ||
resource_flavor == FLAVOR_CPU_MONITOR_FATAL)) {
// These exceptions may be fatal. They are not fatal by default at task
// creation but can be made fatal by calling proc_rlimit_control() with
// RLIMIT_CPU_USAGE_MONITOR as the second argument and CPUMON_MAKE_FATAL set
// in the flags.
if (MacOSXMinorVersion() >= 10) {
// In Mac OS X 10.10, the exception code indicates whether the exception
// is fatal. See 10.10 xnu-2782.1.97/osfmk/kern/thread.c
// THIS_THREAD_IS_CONSUMING_TOO_MUCH_CPU__SENDING_EXC_RESOURCE().
return resource_flavor == FLAVOR_CPU_MONITOR;
}
// In Mac OS X 10.9, theres no way to determine whether the exception is
// fatal. Unlike RESOURCE_TYPE_WAKEUPS below, theres no way to determine
// this outside the kernel. proc_rlimit_control()s RLIMIT_CPU_USAGE_MONITOR
// is the only interface to modify CPUMON_MAKE_FATAL, but its only able to
// set this bit, not obtain its current value.
//
// Default to assuming that these exceptions are nonfatal. They are nonfatal
// by default and no users of proc_rlimit_control() were found on 10.9.5
// 13F1066 in /System and /usr outside of Metadata.framework and associated
// tools.
return true;
}
if (resource_type == RESOURCE_TYPE_WAKEUPS &&
resource_flavor == FLAVOR_WAKEUPS_MONITOR) {
// These exceptions may be fatal. They are not fatal by default at task
// creation, but can be made fatal by calling proc_rlimit_control() with
// RLIMIT_WAKEUPS_MONITOR as the second argument and WAKEMON_MAKE_FATAL set
// in the flags.
//
// proc_get_wakemon_params() (which calls
// through to proc_rlimit_control() with RLIMIT_WAKEUPS_MONITOR) determines
// whether these exceptions are fatal. See 10.10
// xnu-2782.1.97/osfmk/kern/task.c
// THIS_PROCESS_IS_CAUSING_TOO_MANY_WAKEUPS__SENDING_EXC_RESOURCE().
//
// If proc_get_wakemon_params() fails, default to assuming that these
// exceptions are nonfatal. They are nonfatal by default and no users of
// proc_rlimit_control() were found on 10.9.5 13F1066 in /System and /usr
// outside of Metadata.framework and associated tools.
int wm_rate;
int wm_flags;
int rv = ProcGetWakemonParams(pid, &wm_rate, &wm_flags);
if (rv < 0) {
PLOG(WARNING) << "ProcGetWakemonParams";
return true;
}
return !(wm_flags & WAKEMON_MAKE_FATAL);
}
if (resource_type == RESOURCE_TYPE_MEMORY &&
resource_flavor == FLAVOR_HIGH_WATERMARK) {
// These exceptions are never fatal. See 10.10
// xnu-2782.1.97/osfmk/kern/task.c
// THIS_PROCESS_CROSSED_HIGH_WATERMARK__SENDING_EXC_RESOURCE().
return true;
}
// Treat unknown exceptions as fatal. This is the conservative approach: it
// may result in more crash reports being generated, but the type-flavor
// combinations can be evaluated to determine appropriate handling.
LOG(WARNING) << "unknown resource type " << resource_type << " flavor "
<< resource_flavor;
return false;
}
} // namespace crashpad

View File

@ -0,0 +1,79 @@
// Copyright 2015 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.
#ifndef CRASHPAD_UTIL_MACH_EXCEPTION_TYPES_H_
#define CRASHPAD_UTIL_MACH_EXCEPTION_TYPES_H_
#include <mach/mach.h>
#include <sys/types.h>
namespace crashpad {
//! \brief Recovers the original exception, first exception code, and signal
//! from the encoded form of the first exception code delivered with
//! `EXC_CRASH` exceptions.
//!
//! `EXC_CRASH` exceptions are generated when the kernel has committed to
//! terminating a process as a result of a core-generating POSIX signal and, for
//! hardware exceptions, an earlier Mach exception. Information about this
//! earlier exception and signal is made available to the `EXC_CRASH` handler
//! via its `code[0]` parameter. This function recovers the original exception,
//! the value of `code[0]` from the original exception, and the value of the
//! signal responsible for process termination.
//!
//! \param[in] code_0 The first exception code (`code[0]`) passed to a Mach
//! exception handler in an `EXC_CRASH` exception. It is invalid to call
//! this function with an exception code from any exception other than
//! `EXC_CRASH`.
//! \param[out] original_code_0 The first exception code (`code[0]`) passed to
//! the Mach exception handler for a hardware exception that resulted in the
//! generation of a POSIX signal that caused process termination. If the
//! signal that caused termination was not sent as a result of a hardware
//! exception, this will be `0`. Callers that do not need this value may
//! pass `nullptr`.
//! \param[out] signal The POSIX signal that caused process termination. Callers
//! that do not need this value may pass `nullptr`.
//!
//! \return The original exception for a hardware exception that resulted in the
//! generation of a POSIX signal that caused process termination. If the
//! signal that caused termination was not sent as a result of a hardware
//! exception, this will be `0`.
exception_type_t ExcCrashRecoverOriginalException(
mach_exception_code_t code_0,
mach_exception_code_t* original_code_0,
int* signal);
//! \brief Determines whether an exception is a non-fatal `EXC_RESOURCE`.
//!
//! \param[in] exception The exception type as received by a Mach exception
//! handler.
//! \param[in] code_0 The first exception code (`code[0]`) as received by a
//! Mach exception handler.
//! \param[in] pid The process ID that the exception occurred in. In some cases,
//! process may need to be queried to determine whether an `EXC_RESOURCE`
//! exception is fatal.
//!
//! \return `true` if the exception is a non-fatal `EXC_RESOURCE`. `false`
//! otherwise. If the exception is `EXC_RESOURCE` of a recognized type but
//! it is not possible to determine whether it is fatal, returns `true`
//! under the assumption that all known `EXC_RESOURCE` exceptions are
//! non-fatal by default. If the exception is not `EXC_RESOURCE` or is an
//! unknown `EXC_RESOURCE` type, returns `false`.
bool IsExceptionNonfatalResource(exception_type_t exception,
mach_exception_code_t code_0,
pid_t pid);
} // namespace crashpad
#endif // CRASHPAD_UTIL_MACH_EXCEPTION_TYPES_H_

View File

@ -0,0 +1,140 @@
// Copyright 2015 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 "util/mach/exception_types.h"
#include <kern/exc_resource.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include "base/basictypes.h"
#include "base/strings/stringprintf.h"
#include "gtest/gtest.h"
#include "util/mac/mac_util.h"
#include "util/mach/mach_extensions.h"
namespace crashpad {
namespace test {
namespace {
TEST(ExceptionTypes, ExcCrashRecoverOriginalException) {
struct TestData {
mach_exception_code_t code_0;
exception_type_t exception;
mach_exception_code_t original_code_0;
int signal;
};
const TestData kTestData[] = {
{0xb100001, EXC_BAD_ACCESS, KERN_INVALID_ADDRESS, SIGSEGV},
{0xb100002, EXC_BAD_ACCESS, KERN_PROTECTION_FAILURE, SIGSEGV},
{0xa100002, EXC_BAD_ACCESS, KERN_PROTECTION_FAILURE, SIGBUS},
{0x4200001, EXC_BAD_INSTRUCTION, 1, SIGILL},
{0x8300001, EXC_ARITHMETIC, 1, SIGFPE},
{0x5600002, EXC_BREAKPOINT, 2, SIGTRAP},
{0x3000000, 0, 0, SIGQUIT},
{0x6000000, 0, 0, SIGABRT},
{0xc000000, 0, 0, SIGSYS},
{0, 0, 0, 0},
};
for (size_t index = 0; index < arraysize(kTestData); ++index) {
const TestData& test_data = kTestData[index];
SCOPED_TRACE(base::StringPrintf(
"index %zu, code_0 0x%llx", index, test_data.code_0));
mach_exception_code_t original_code_0;
int signal;
exception_type_t exception = ExcCrashRecoverOriginalException(
test_data.code_0, &original_code_0, &signal);
EXPECT_EQ(test_data.exception, exception);
EXPECT_EQ(test_data.original_code_0, original_code_0);
EXPECT_EQ(test_data.signal, signal);
}
// Now make sure that ExcCrashRecoverOriginalException() properly ignores
// optional arguments.
static_assert(arraysize(kTestData) >= 1, "must have something to test");
const TestData& test_data = kTestData[0];
EXPECT_EQ(
test_data.exception,
ExcCrashRecoverOriginalException(test_data.code_0, nullptr, nullptr));
mach_exception_code_t original_code_0;
EXPECT_EQ(test_data.exception,
ExcCrashRecoverOriginalException(
test_data.code_0, &original_code_0, nullptr));
EXPECT_EQ(test_data.original_code_0, original_code_0);
int signal;
EXPECT_EQ(
test_data.exception,
ExcCrashRecoverOriginalException(test_data.code_0, nullptr, &signal));
EXPECT_EQ(test_data.signal, signal);
}
// These macros come from the #ifdef KERNEL section of <kern/exc_resource.h>:
// 10.10 xnu-2782.1.97/osfmk/kern/exc_resource.h.
#define EXC_RESOURCE_ENCODE_TYPE(code, type) \
((code) |= ((static_cast<uint64_t>(type) & 0x7ull) << 61))
#define EXC_RESOURCE_ENCODE_FLAVOR(code, flavor) \
((code) |= ((static_cast<uint64_t>(flavor) & 0x7ull) << 58))
TEST(ExceptionTypes, IsExceptionNonfatalResource) {
const pid_t pid = getpid();
mach_exception_code_t code = 0;
EXC_RESOURCE_ENCODE_TYPE(code, RESOURCE_TYPE_CPU);
EXC_RESOURCE_ENCODE_FLAVOR(code, FLAVOR_CPU_MONITOR);
EXPECT_TRUE(IsExceptionNonfatalResource(EXC_RESOURCE, code, pid));
if (MacOSXMinorVersion() >= 10) {
// FLAVOR_CPU_MONITOR_FATAL was introduced in Mac OS X 10.10.
code = 0;
EXC_RESOURCE_ENCODE_TYPE(code, RESOURCE_TYPE_CPU);
EXC_RESOURCE_ENCODE_FLAVOR(code, FLAVOR_CPU_MONITOR_FATAL);
EXPECT_FALSE(IsExceptionNonfatalResource(EXC_RESOURCE, code, pid));
}
// This assumes that WAKEMON_MAKE_FATAL is not set for this process. The
// default is for WAKEMON_MAKE_FATAL to not be set, theres no public API to
// enable it, and nothing in this process should have enabled it.
code = 0;
EXC_RESOURCE_ENCODE_TYPE(code, RESOURCE_TYPE_WAKEUPS);
EXC_RESOURCE_ENCODE_FLAVOR(code, FLAVOR_WAKEUPS_MONITOR);
EXPECT_TRUE(IsExceptionNonfatalResource(EXC_RESOURCE, code, pid));
code = 0;
EXC_RESOURCE_ENCODE_TYPE(code, RESOURCE_TYPE_MEMORY);
EXC_RESOURCE_ENCODE_FLAVOR(code, FLAVOR_HIGH_WATERMARK);
EXPECT_TRUE(IsExceptionNonfatalResource(EXC_RESOURCE, code, pid));
// Non-EXC_RESOURCE exceptions should never be considered nonfatal resource
// exceptions, because they arent resource exceptions at all.
EXPECT_FALSE(IsExceptionNonfatalResource(EXC_CRASH, 0xb100001, pid));
EXPECT_FALSE(IsExceptionNonfatalResource(EXC_CRASH, 0x0b00000, pid));
EXPECT_FALSE(IsExceptionNonfatalResource(EXC_CRASH, 0x6000000, pid));
EXPECT_FALSE(
IsExceptionNonfatalResource(EXC_BAD_ACCESS, KERN_INVALID_ADDRESS, pid));
EXPECT_FALSE(IsExceptionNonfatalResource(EXC_BAD_INSTRUCTION, 1, pid));
EXPECT_FALSE(IsExceptionNonfatalResource(EXC_ARITHMETIC, 1, pid));
EXPECT_FALSE(IsExceptionNonfatalResource(EXC_BREAKPOINT, 2, pid));
EXPECT_FALSE(IsExceptionNonfatalResource(0, 0, pid));
EXPECT_FALSE(IsExceptionNonfatalResource(kMachExceptionSimulated, 0, pid));
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -67,6 +67,8 @@
'mach/exception_behaviors.h',
'mach/exception_ports.cc',
'mach/exception_ports.h',
'mach/exception_types.cc',
'mach/exception_types.h',
'mach/mach_extensions.cc',
'mach/mach_extensions.h',
'mach/mach_message.cc',

View File

@ -47,6 +47,7 @@
'mach/exc_server_variants_test.cc',
'mach/exception_behaviors_test.cc',
'mach/exception_ports_test.cc',
'mach/exception_types_test.cc',
'mach/mach_extensions_test.cc',
'mach/mach_message_server_test.cc',
'mach/mach_message_test.cc',