Add CRASHPAD_SIMULATE_CRASH() and supporting client code.

TEST=client_test SimulateCrash.SimulateCrash
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/658853002
This commit is contained in:
Mark Mentovai 2014-10-17 12:30:55 -04:00
parent f59e3e6b78
commit dd9cf1b367
5 changed files with 711 additions and 0 deletions

View File

@ -28,6 +28,9 @@
'capture_context_mac.S', 'capture_context_mac.S',
'simple_string_dictionary.cc', 'simple_string_dictionary.cc',
'simple_string_dictionary.h', 'simple_string_dictionary.h',
'simulate_crash.h',
'simulate_crash_mac.cc',
'simulate_crash_mac.h',
], ],
}, },
{ {
@ -35,6 +38,8 @@
'type': 'executable', 'type': 'executable',
'dependencies': [ 'dependencies': [
'client', 'client',
'../util/util.gyp:util',
'../util/util.gyp:util_test_lib',
'../third_party/gtest/gtest.gyp:gtest', '../third_party/gtest/gtest.gyp:gtest',
'../third_party/gtest/gtest.gyp:gtest_main', '../third_party/gtest/gtest.gyp:gtest_main',
'../third_party/mini_chromium/mini_chromium/base/base.gyp:base', '../third_party/mini_chromium/mini_chromium/base/base.gyp:base',
@ -45,6 +50,7 @@
'sources': [ 'sources': [
'capture_context_mac_test.cc', 'capture_context_mac_test.cc',
'simple_string_dictionary_test.cc', 'simple_string_dictionary_test.cc',
'simulate_crash_mac_test.cc',
], ],
}, },
], ],

24
client/simulate_crash.h Normal file
View File

@ -0,0 +1,24 @@
// Copyright 2014 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_CLIENT_SIMULATE_CRASH_H_
#define CRASHPAD_CLIENT_SIMULATE_CRASH_H_
#include "build/build_config.h"
#if defined(OS_MACOSX)
#include "client/simulate_crash_mac.h"
#endif
#endif // CRASHPAD_CLIENT_SIMULATE_CRASH_H_

View File

@ -0,0 +1,244 @@
// Copyright 2014 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 "client/simulate_crash_mac.h"
#include <string.h>
#include <vector>
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_port.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "util/mach/exc_client_variants.h"
#include "util/mach/exception_behaviors.h"
#include "util/mach/exception_ports.h"
#include "util/mach/mach_extensions.h"
namespace crashpad {
namespace {
//! \brief Sends an exception message to an exception port in accordance with
//! the behavior and thread state flavor its registered to receive.
//!
//! \param[in] thread, task, exception, code, code_count These parameters will
//! be passed to the exception handler as appropriate.
//! \param[in] cpu_context The value to use for the thread state, if \a behavior
//! indicates that the handler should receive a thread state and if the
//! supplied thread state matches or can be converted to \a flavor. If \a
//! behavior requires a thread state but this argument cannot be converted
//! to match \a flavor, `thread_get_state()` will be called to obtain a
//! suitable thread state value.
//! \param[in] handler The Mach exception handler to deliver the exception to.
//! \param[in] set_state If `true` and \a behavior indicates that the handler
//! should receive and return a thread state, a new thread state will be set
//! by `thread_set_state()` upon successful completion of the exception
//! handler. If `false`, this will be suppressed, even when \a behavior
//! indicates that the handler receives and returns a thread state.
//!
//! \return `true` if the exception was delivered to the handler and the handler
//! indicated success. `false` otherwise, with a warning message logged.
bool DeliverException(thread_t thread,
task_t task,
exception_type_t exception,
const mach_exception_data_t code,
mach_msg_type_number_t code_count,
const NativeCPUContext* cpu_context,
const ExceptionPorts::ExceptionHandler& handler,
bool set_state) {
kern_return_t kr;
bool handler_wants_state = ExceptionBehaviorHasState(handler.behavior);
if (!handler_wants_state) {
// Regardless of the passed-in value of |set_state|, if the handler wont be
// dealing with any state at all, no state should be set.
set_state = false;
}
// A const version of thread_state_t.
typedef const natural_t* ConstThreadState;
// old_state is only used if the context already captured doesnt match (or
// cant be converted to) whats registered for the handler.
thread_state_data_t old_state;
thread_state_flavor_t flavor = handler.flavor;
ConstThreadState state;
mach_msg_type_number_t state_count;
switch (flavor) {
#if defined(ARCH_CPU_X86_FAMILY)
case x86_THREAD_STATE:
state = reinterpret_cast<ConstThreadState>(cpu_context);
state_count = x86_THREAD_STATE_COUNT;
break;
#if defined(ARCH_CPU_X86)
case x86_THREAD_STATE32:
state = reinterpret_cast<ConstThreadState>(&cpu_context->uts.ts32);
state_count = cpu_context->tsh.count;
break;
#elif defined(ARCH_CPU_X86_64)
case x86_THREAD_STATE64:
state = reinterpret_cast<ConstThreadState>(&cpu_context->uts.ts64);
state_count = cpu_context->tsh.count;
break;
#endif
#else
#error Port to your CPU architecture
#endif
case THREAD_STATE_NONE:
// This is only acceptable if the handler doesnt have one of the “state”
// behaviors. Otherwise, if the kernel were attempting to send an
// exception message to this port, it would call thread_getstatus() (known
// outside the kernel as thread_get_state()) which would fail because
// THREAD_STATE_NONE is not a valid state to get. See 10.9.5
// xnu-2422.115.4/osfmk/kern/exception.c exception_deliver() and
// xnu-2422.115.4/osfmk/i386/pcb.c machine_thread_get_state().
if (!handler_wants_state) {
state = nullptr;
state_count = 0;
break;
}
LOG(WARNING) << "exception handler has unexpected state flavor" << flavor;
return false;
default:
if (!handler_wants_state) {
// Dont bother getting any thread state if the handlers not actually
// going to use it.
state = nullptr;
state_count = 0;
} else {
state = old_state;
state_count = THREAD_STATE_MAX;
kr = thread_get_state(thread, flavor, old_state, &state_count);
if (kr != KERN_SUCCESS) {
MACH_LOG(WARNING, kr) << "thread_get_state";
return false;
}
}
break;
}
// new_state is supposed to be an out parameter only, but in case the handler
// doesn't touch it, make sure it's initialized to a valid thread state.
// Otherwise, the thread_set_state() call below would set a garbage thread
// state.
thread_state_data_t new_state;
size_t state_size =
sizeof(natural_t) *
std::min(state_count, static_cast<unsigned int>(THREAD_STATE_MAX));
memcpy(new_state, state, state_size);
mach_msg_type_number_t new_state_count = THREAD_STATE_MAX;
kr = UniversalExceptionRaise(handler.behavior,
handler.port,
thread,
task,
exception,
code,
code_count,
&flavor,
state,
state_count,
new_state,
&new_state_count);
// The kernel treats a return value of MACH_RCV_PORT_DIED as successful,
// although it will not set a new thread state in that case. See 10.9.5
// xnu-2422.115.4/osfmk/kern/exception.c exception_deliver(), and the more
// elaborate comment at util/mach/exc_server_variants.h
// ExcServerSuccessfulReturnValue(). Duplicate that behavior.
bool success = kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED;
MACH_LOG_IF(WARNING, !success, kr) << "UniversalExceptionRaise";
if (kr == KERN_SUCCESS && set_state) {
kr = thread_set_state(thread, flavor, new_state, new_state_count);
MACH_LOG_IF(WARNING, kr != KERN_SUCCESS, kr) << "thread_set_state";
}
return success;
}
} // namespace
void SimulateCrash(const NativeCPUContext* cpu_context) {
#if defined(ARCH_CPU_X86)
DCHECK_EQ(cpu_context->tsh.flavor,
static_cast<thread_state_flavor_t>(x86_THREAD_STATE32));
DCHECK_EQ(static_cast<mach_msg_type_number_t>(cpu_context->tsh.count),
x86_THREAD_STATE32_COUNT);
#elif defined(ARCH_CPU_X86_64)
DCHECK_EQ(cpu_context->tsh.flavor,
static_cast<thread_state_flavor_t>(x86_THREAD_STATE64));
DCHECK_EQ(static_cast<mach_msg_type_number_t>(cpu_context->tsh.count),
x86_THREAD_STATE64_COUNT);
#endif
base::mac::ScopedMachSendRight thread(mach_thread_self());
exception_type_t exception = kMachExceptionSimulated;
mach_exception_data_type_t codes[] = {0, 0};
mach_msg_type_number_t code_count = arraysize(codes);
// Look up the handler for EXC_CRASH exceptions in the same way that the
// kernel would: try a thread handler, then a task handler, and finally a host
// handler. 10.9.5 xnu-2422.115.4/osfmk/kern/exception.c exception_triage().
const ExceptionPorts::TargetType kTargetTypes[] = {
ExceptionPorts::kTargetTypeThread,
ExceptionPorts::kTargetTypeTask,
// This is not expected to succeed, because mach_host_self() doesnt
// return the host_priv port to non-root users, and this is the port
// thats required for host_get_exception_ports().
//
// See 10.9.5 xnu-2422.115.4/bsd/kern/kern_prot.c set_security_token(),
// xnu-2422.115.4/osfmk/kern/task.c host_security_set_task_token(), and
// xnu-2422.115.4/osfmk/kern/ipc_host.c host_get_exception_ports().
ExceptionPorts::kTargetTypeHost,
};
bool success = false;
for (size_t target_type_index = 0;
!success && target_type_index < arraysize(kTargetTypes);
++target_type_index) {
std::vector<ExceptionPorts::ExceptionHandler> handlers;
ExceptionPorts exception_ports(kTargetTypes[target_type_index],
MACH_PORT_NULL);
if (exception_ports.GetExceptionPorts(EXC_MASK_CRASH, &handlers)) {
DCHECK_LE(handlers.size(), 1u);
if (handlers.size() == 1) {
DCHECK(handlers[0].mask & EXC_MASK_CRASH);
success = DeliverException(thread,
mach_task_self(),
exception,
codes,
code_count,
cpu_context,
handlers[0],
false);
}
}
}
LOG_IF(WARNING, !success)
<< "SimulateCrash did not find an appropriate exception handler";
}
} // namespace crashpad

View File

@ -0,0 +1,60 @@
// Copyright 2014 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_CLIENT_SIMULATE_CRASH_MAC_H_
#define CRASHPAD_CLIENT_SIMULATE_CRASH_MAC_H_
#include <mach/mach.h>
#include "client/capture_context_mac.h"
//! \file
namespace crashpad {
//! \brief Simulates a exception without crashing.
//!
//! This function searches for an `EXC_CRASH` handler in the same manner that
//! the kernel does, and sends it an exception message to that handler in the
//! format that the handler expects, considering the behavior and thread state
//! flavor that are registered for it. The exception sent to the handler will be
//! ::kMachExceptionSimulated, not `EXC_CRASH`.
//!
//! Typically, the CRASHPAD_SIMULATE_CRASH() macro will be used in preference to
//! this function, because it combines the context-capture operation with the
//! raising of a simulated exception.
//!
//! This function returns normally after the exception message is processed. If
//! no valid handler was found, or no handler processed the exception
//! successfully, a warning will be logged, but these conditions are not
//! considered fatal.
//!
//! \param[in] cpu_context The thread state to pass to the exception handler as
//! the exception context, provided that it is compatible with the thread
//! state flavor that the exception handler accepts. If it is not
//! compatible, the correct thread state for the handler will be obtained by
//! calling `thread_get_state()`.
void SimulateCrash(const NativeCPUContext* cpu_context);
} // namespace crashpad
//! \brief Captures the CPU context and simulates an exception without crashing.
#define CRASHPAD_SIMULATE_CRASH() \
do { \
NativeCPUContext cpu_context; \
crashpad::CaptureContext(&cpu_context); \
crashpad::SimulateCrash(&cpu_context); \
} while (false)
#endif // CRASHPAD_CLIENT_SIMULATE_CRASH_MAC_H_

View File

@ -0,0 +1,377 @@
// Copyright 2014 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 "client/simulate_crash.h"
#include <mach/mach.h>
#include <string.h>
#include "base/basictypes.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "util/mach/exc_server_variants.h"
#include "util/mach/exception_behaviors.h"
#include "util/mach/exception_ports.h"
#include "util/mach/mach_extensions.h"
#include "util/mach/mach_message_server.h"
#include "util/mach/symbolic_constants_mach.h"
#include "util/test/mac/mach_errors.h"
#include "util/test/mac/mach_multiprocess.h"
namespace crashpad {
namespace test {
namespace {
class TestSimulateCrashMac final : public MachMultiprocess,
public UniversalMachExcServer {
public:
// Defines which targets the child should set an EXC_CRASH exception handler
// for.
enum ExceptionPortsTarget {
// The child should clear its EXC_CRASH handler for both its task and thread
// targets. SimulateCrash() will attempt to deliver the exception to the
// host target, which will fail if not running as root. In any case, the
// parent should not expect to receive any exception message from the child.
kExceptionPortsTargetNone = 0,
// The child will set an EXC_CRASH handler for its task target, and clear it
// for its thread target. The parent runs an exception server to receive
// the childs simulated crash message.
kExceptionPortsTargetTask,
// The child will set an EXC_CRASH handler for its thread target, and clear
// it for its task target. The parent runs an exception server to receive
// the childs simulated crash message.
kExceptionPortsTargetThread,
// The child sets an EXC_CRASH handler for both its task and thread targets.
// The parent runs an exception server to receive the message expected to be
// delivered to the thread target, but returns an error code. The child will
// then fall back to trying the server registered for the task target,
// sending a second message to the parent. The server in the parent will
// handle this one successfully.
kExceptionPortsTargetBoth,
};
TestSimulateCrashMac(ExceptionPortsTarget target,
exception_behavior_t behavior,
thread_state_flavor_t flavor)
: target_(target),
behavior_(behavior),
flavor_(flavor),
succeed_(true) {
}
~TestSimulateCrashMac() {}
// UniversalMachExcServer:
kern_return_t CatchMachException(exception_behavior_t behavior,
exception_handler_t exception_port,
thread_t thread,
task_t task,
exception_type_t exception,
const mach_exception_data_type_t* code,
mach_msg_type_number_t code_count,
thread_state_flavor_t* flavor,
const natural_t* old_state,
mach_msg_type_number_t old_state_count,
thread_state_t new_state,
mach_msg_type_number_t* new_state_count,
bool* destroy_complex_request) override {
*destroy_complex_request = true;
// Check the entire exception message, because most or all of it was
// generated by SimulateCrash() instead of the kernel.
EXPECT_EQ(behavior_, behavior);
EXPECT_EQ(LocalPort(), exception_port);
if (ExceptionBehaviorHasIdentity(behavior)) {
EXPECT_NE(THREAD_NULL, thread);
EXPECT_EQ(ChildTask(), task);
} else {
EXPECT_EQ(THREAD_NULL, thread);
EXPECT_EQ(TASK_NULL, task);
}
EXPECT_EQ(kMachExceptionSimulated, exception);
EXPECT_EQ(2u, code_count);
if (code_count >= 1) {
EXPECT_EQ(0, code[0]);
}
if (code_count >= 2) {
EXPECT_EQ(0, code[1]);
}
if (!ExceptionBehaviorHasState(behavior)) {
EXPECT_EQ(THREAD_STATE_NONE, *flavor);
} else {
EXPECT_EQ(flavor_, *flavor);
switch (*flavor) {
#if defined(ARCH_CPU_X86_FAMILY)
case x86_THREAD_STATE: {
EXPECT_EQ(x86_THREAD_STATE_COUNT, old_state_count);
const x86_thread_state* state =
reinterpret_cast<const x86_thread_state*>(old_state);
switch (state->tsh.flavor) {
case x86_THREAD_STATE32:
EXPECT_EQ(static_cast<int>(x86_THREAD_STATE32_COUNT),
state->tsh.count);
break;
case x86_THREAD_STATE64:
EXPECT_EQ(static_cast<int>(x86_THREAD_STATE64_COUNT),
state->tsh.count);
break;
default:
ADD_FAILURE() << "unexpected tsh.flavor " << state->tsh.flavor;
break;
}
break;
}
case x86_FLOAT_STATE: {
EXPECT_EQ(x86_FLOAT_STATE_COUNT, old_state_count);
const x86_float_state* state =
reinterpret_cast<const x86_float_state*>(old_state);
switch (state->fsh.flavor) {
case x86_FLOAT_STATE32:
EXPECT_EQ(static_cast<int>(x86_FLOAT_STATE32_COUNT),
state->fsh.count);
break;
case x86_FLOAT_STATE64:
EXPECT_EQ(static_cast<int>(x86_FLOAT_STATE64_COUNT),
state->fsh.count);
break;
default:
ADD_FAILURE() << "unexpected fsh.flavor " << state->fsh.flavor;
break;
}
break;
}
case x86_DEBUG_STATE: {
EXPECT_EQ(x86_DEBUG_STATE_COUNT, old_state_count);
const x86_debug_state* state =
reinterpret_cast<const x86_debug_state*>(old_state);
switch (state->dsh.flavor) {
case x86_DEBUG_STATE32:
EXPECT_EQ(static_cast<int>(x86_DEBUG_STATE32_COUNT),
state->dsh.count);
break;
case x86_DEBUG_STATE64:
EXPECT_EQ(static_cast<int>(x86_DEBUG_STATE64_COUNT),
state->dsh.count);
break;
default:
ADD_FAILURE() << "unexpected dsh.flavor " << state->dsh.flavor;
break;
}
break;
}
case x86_THREAD_STATE32:
EXPECT_EQ(x86_THREAD_STATE32_COUNT, old_state_count);
break;
case x86_FLOAT_STATE32:
EXPECT_EQ(x86_FLOAT_STATE32_COUNT, old_state_count);
break;
case x86_DEBUG_STATE32:
EXPECT_EQ(x86_DEBUG_STATE32_COUNT, old_state_count);
break;
case x86_THREAD_STATE64:
EXPECT_EQ(x86_THREAD_STATE64_COUNT, old_state_count);
break;
case x86_FLOAT_STATE64:
EXPECT_EQ(x86_FLOAT_STATE64_COUNT, old_state_count);
break;
case x86_DEBUG_STATE64:
EXPECT_EQ(x86_DEBUG_STATE64_COUNT, old_state_count);
break;
#else
#error Port to your CPU architecture
#endif
default:
ADD_FAILURE() << "unexpected flavor " << *flavor;
break;
}
// Attempt to set a garbage thread state, which would cause the child to
// crash inside SimulateCrash() if it actually succeeded. This tests that
// SimulateCrash() ignores new_state instead of attempting to set the
// state as the kernel would do. This operates in conjunction with the
// |true| argument to ExcServerSuccessfulReturnValue() below.
*new_state_count = old_state_count;
size_t new_state_size = sizeof(natural_t) * old_state_count;
memset(new_state, 0xa5, new_state_size);
}
if (!succeed_) {
// The client has registered EXC_CRASH handlers for both its thread and
// task targets, and sent a simulated exception message to its
// thread-level EXC_CRASH handler. To test that it will fall back to
// trying the task-level EXC_CRASH handler, return a failure code, which
// should cause SimulateCrash() to try the next target.
EXPECT_EQ(kExceptionPortsTargetBoth, target_);
return KERN_ABORTED;
}
return ExcServerSuccessfulReturnValue(behavior, true);
}
private:
// MachMultiprocess:
void MachMultiprocessParent() override {
if (target_ == kExceptionPortsTargetNone) {
// The child does not have any EXC_CRASH handlers registered for its
// thread or task targets, so no exception message is expected to be
// generated. Dont run the server at all.
return;
}
mach_msg_return_t mr;
if (target_ == kExceptionPortsTargetBoth) {
// The client has registered EXC_CRASH handlers for both its thread and
// task targets. Run a server that will return a failure code when the
// exception message is sent to the thread target, which will cause the
// client to fall back to the task target and send another message.
succeed_ = false;
mr = MachMessageServer::Run(this,
LocalPort(),
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kBlocking,
MACH_MSG_TIMEOUT_NONE);
EXPECT_EQ(MACH_MSG_SUCCESS, mr)
<< MachErrorMessage(mr, "MachMessageServer::Run");
}
succeed_ = true;
mr = MachMessageServer::Run(this,
LocalPort(),
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kBlocking,
MACH_MSG_TIMEOUT_NONE);
EXPECT_EQ(MACH_MSG_SUCCESS, mr)
<< MachErrorMessage(mr, "MachMessageServer::Run");
}
void MachMultiprocessChild() override {
bool task_valid = target_ == kExceptionPortsTargetTask ||
target_ == kExceptionPortsTargetBoth;
ExceptionPorts task_exception_ports(ExceptionPorts::kTargetTypeTask,
TASK_NULL);
ASSERT_TRUE(task_exception_ports.SetExceptionPort(
EXC_MASK_CRASH,
task_valid ? RemotePort() : MACH_PORT_NULL,
behavior_,
flavor_));
bool thread_valid = target_ == kExceptionPortsTargetThread ||
target_ == kExceptionPortsTargetBoth;
ExceptionPorts thread_exception_ports(ExceptionPorts::kTargetTypeThread,
THREAD_NULL);
ASSERT_TRUE(thread_exception_ports.SetExceptionPort(
EXC_MASK_CRASH,
thread_valid ? RemotePort() : MACH_PORT_NULL,
behavior_,
flavor_));
CRASHPAD_SIMULATE_CRASH();
}
ExceptionPortsTarget target_;
exception_behavior_t behavior_;
thread_state_flavor_t flavor_;
bool succeed_;
DISALLOW_COPY_AND_ASSIGN(TestSimulateCrashMac);
};
TEST(SimulateCrash, SimulateCrash) {
const TestSimulateCrashMac::ExceptionPortsTarget kTargets[] = {
TestSimulateCrashMac::kExceptionPortsTargetNone,
TestSimulateCrashMac::kExceptionPortsTargetTask,
TestSimulateCrashMac::kExceptionPortsTargetThread,
TestSimulateCrashMac::kExceptionPortsTargetBoth,
};
const exception_behavior_t kBehaviors[] = {
EXCEPTION_DEFAULT,
EXCEPTION_STATE,
EXCEPTION_STATE_IDENTITY,
EXCEPTION_DEFAULT | kMachExceptionCodes,
EXCEPTION_STATE | kMachExceptionCodes,
EXCEPTION_STATE_IDENTITY | kMachExceptionCodes,
};
const thread_state_flavor_t kFlavors[] = {
#if defined(ARCH_CPU_X86_FAMILY)
x86_THREAD_STATE,
x86_FLOAT_STATE,
x86_DEBUG_STATE,
#if defined(ARCH_CPU_X86)
x86_THREAD_STATE32,
x86_FLOAT_STATE32,
x86_DEBUG_STATE32,
#elif defined(ARCH_CPU_X86_64)
x86_THREAD_STATE64,
x86_FLOAT_STATE64,
x86_DEBUG_STATE64,
#endif
#else
#error Port to your CPU architecture
#endif
};
for (size_t target_index = 0;
target_index < arraysize(kTargets);
++target_index) {
TestSimulateCrashMac::ExceptionPortsTarget target = kTargets[target_index];
SCOPED_TRACE(base::StringPrintf(
"target_index %zu, target %d", target_index, target));
for (size_t behavior_index = 0;
behavior_index < arraysize(kBehaviors);
++behavior_index) {
exception_behavior_t behavior = kBehaviors[behavior_index];
SCOPED_TRACE(base::StringPrintf(
"behavior_index %zu, behavior %s",
behavior_index,
ExceptionBehaviorToString(behavior, kUseFullName | kUnknownIsNumeric)
.c_str()));
if (!ExceptionBehaviorHasState(behavior)) {
TestSimulateCrashMac test_simulate_crash_mac(
target, behavior, THREAD_STATE_NONE);
test_simulate_crash_mac.Run();
} else {
for (size_t flavor_index = 0;
flavor_index < arraysize(kFlavors);
++flavor_index) {
thread_state_flavor_t flavor = kFlavors[flavor_index];
SCOPED_TRACE(base::StringPrintf(
"flavor_index %zu, flavor %s",
flavor_index,
ThreadStateFlavorToString(
flavor, kUseFullName | kUnknownIsNumeric).c_str()));
TestSimulateCrashMac test_simulate_crash_mac(
target, behavior, flavor);
test_simulate_crash_mac.Run();
}
}
}
}
}
} // namespace
} // namespace test
} // namespace crashpad