diff --git a/compat/compat.gyp b/compat/compat.gyp index 84b029fc..f691c705 100644 --- a/compat/compat.gyp +++ b/compat/compat.gyp @@ -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', diff --git a/compat/mac/kern/exc_resource.h b/compat/mac/kern/exc_resource.h new file mode 100644 index 00000000..30e9b855 --- /dev/null +++ b/compat/mac/kern/exc_resource.h @@ -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() +#include_next +#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_ diff --git a/compat/mac/mach-o/getsect.cc b/compat/mac/mach-o/getsect.cc index 2990c925..71d3066c 100644 --- a/compat/mac/mach-o/getsect.cc +++ b/compat/mac/mach-o/getsect.cc @@ -43,7 +43,7 @@ void* SystemLibMachOHandle() { if (!dladdr(reinterpret_cast(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 diff --git a/compat/mac/sys/resource.h b/compat/mac/sys/resource.h new file mode 100644 index 00000000..0697e169 --- /dev/null +++ b/compat/mac/sys/resource.h @@ -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 + +// 10.9 SDK + +#ifndef WAKEMON_MAKE_FATAL +#define WAKEMON_MAKE_FATAL 0x10 +#endif + +#endif // CRASHPAD_COMPAT_MAC_SYS_RESOURCE_H_ diff --git a/handler/mac/crash_report_exception_handler.cc b/handler/mac/crash_report_exception_handler.cc index 23f7c067..b7d256a2 100644 --- a/handler/mac/crash_report_exception_handler.cc +++ b/handler/mac/crash_report_exception_handler.cc @@ -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 doesn’t 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); diff --git a/snapshot/mac/exception_snapshot_mac.cc b/snapshot/mac/exception_snapshot_mac.cc index 8f5bb20f..b5725d38 100644 --- a/snapshot/mac/exception_snapshot_mac.cc +++ b/snapshot/mac/exception_snapshot_mac.cc @@ -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" diff --git a/tools/mac/catch_exception_tool.cc b/tools/mac/catch_exception_tool.cc index 08a916bf..d3b2dd58 100644 --- a/tools/mac/catch_exception_tool.cc +++ b/tools/mac/catch_exception_tool.cc @@ -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" diff --git a/util/mach/exc_server_variants.cc b/util/mach/exc_server_variants.cc index e204ca5d..e5916c7d 100644 --- a/util/mach/exc_server_variants.cc +++ b/util/mach/exc_server_variants.cc @@ -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)) { diff --git a/util/mach/exc_server_variants.h b/util/mach/exc_server_variants.h index 5b791688..aaac36ad 100644 --- a/util/mach/exc_server_variants.h +++ b/util/mach/exc_server_variants.h @@ -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. //! diff --git a/util/mach/exc_server_variants_test.cc b/util/mach/exc_server_variants_test.cc index b24f7b6a..a9a30000 100644 --- a/util/mach/exc_server_variants_test.cc +++ b/util/mach/exc_server_variants_test.cc @@ -15,7 +15,6 @@ #include "util/mach/exc_server_variants.h" #include -#include #include #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; diff --git a/util/mach/exception_ports_test.cc b/util/mach/exception_ports_test.cc index 9b81eb2d..6081c02d 100644 --- a/util/mach/exception_ports_test.cc +++ b/util/mach/exception_ports_test.cc @@ -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" diff --git a/util/mach/exception_types.cc b/util/mach/exception_types.cc new file mode 100644 index 00000000..4f6f8f75 --- /dev/null +++ b/util/mach/exception_types.cc @@ -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 +#include +#include +#include +#include +#include + +#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 doesn’t 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 doesn’t. 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(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( + 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. It’s +// present on Mac OS X 10.9 and later. If it’s 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() isn’t 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, there’s no way to determine whether the exception is + // fatal. Unlike RESOURCE_TYPE_WAKEUPS below, there’s 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 it’s 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 diff --git a/util/mach/exception_types.h b/util/mach/exception_types.h new file mode 100644 index 00000000..834f2123 --- /dev/null +++ b/util/mach/exception_types.h @@ -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 +#include + +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_ diff --git a/util/mach/exception_types_test.cc b/util/mach/exception_types_test.cc new file mode 100644 index 00000000..0520c2e0 --- /dev/null +++ b/util/mach/exception_types_test.cc @@ -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 +#include +#include +#include + +#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 : +// 10.10 xnu-2782.1.97/osfmk/kern/exc_resource.h. +#define EXC_RESOURCE_ENCODE_TYPE(code, type) \ + ((code) |= ((static_cast(type) & 0x7ull) << 61)) +#define EXC_RESOURCE_ENCODE_FLAVOR(code, flavor) \ + ((code) |= ((static_cast(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, there’s 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 aren’t 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 diff --git a/util/util.gyp b/util/util.gyp index 3d44cc5a..a82f1944 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -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', diff --git a/util/util_test.gyp b/util/util_test.gyp index 0580e994..c01810bc 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -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',