mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 07:14:10 +08:00
6278690abe
sed -i '' -E -e 's/Copyright (.+) The Crashpad Authors\. All rights reserved\.$/Copyright \1 The Crashpad Authors/' $(git grep -El 'Copyright (.+) The Crashpad Authors\. All rights reserved\.$') Bug: chromium:1098010 Change-Id: I8d6138469ddbe3d281a5d83f64cf918ec2491611 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3878262 Reviewed-by: Joshua Peraza <jperaza@chromium.org> Commit-Queue: Mark Mentovai <mark@chromium.org>
301 lines
10 KiB
C++
301 lines
10 KiB
C++
// Copyright 2014 The Crashpad Authors
|
||
//
|
||
// 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/exc_client_variants.h"
|
||
|
||
#include <mach/mach.h>
|
||
#include <pthread.h>
|
||
#include <string.h>
|
||
#include <sys/types.h>
|
||
|
||
#include <iterator>
|
||
|
||
#include "base/strings/stringprintf.h"
|
||
#include "gtest/gtest.h"
|
||
#include "test/mac/mach_errors.h"
|
||
#include "test/mac/mach_multiprocess.h"
|
||
#include "util/mach/exc_server_variants.h"
|
||
#include "util/mach/exception_behaviors.h"
|
||
#include "util/mach/mach_extensions.h"
|
||
#include "util/mach/mach_message.h"
|
||
#include "util/mach/mach_message_server.h"
|
||
#include "util/misc/implicit_cast.h"
|
||
|
||
namespace crashpad {
|
||
namespace test {
|
||
namespace {
|
||
|
||
class TestExcClientVariants : public MachMultiprocess,
|
||
public UniversalMachExcServer::Interface {
|
||
public:
|
||
TestExcClientVariants(exception_behavior_t behavior, bool all_fields)
|
||
: MachMultiprocess(),
|
||
UniversalMachExcServer::Interface(),
|
||
behavior_(behavior),
|
||
all_fields_(all_fields),
|
||
handled_(false) {
|
||
++exception_;
|
||
++exception_code_;
|
||
++exception_subcode_;
|
||
}
|
||
|
||
TestExcClientVariants(const TestExcClientVariants&) = delete;
|
||
TestExcClientVariants& operator=(const TestExcClientVariants&) = delete;
|
||
|
||
// UniversalMachExcServer::Interface:
|
||
|
||
virtual 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,
|
||
ConstThreadState old_state,
|
||
mach_msg_type_number_t old_state_count,
|
||
thread_state_t new_state,
|
||
mach_msg_type_number_t* new_state_count,
|
||
const mach_msg_trailer_t* trailer,
|
||
bool* destroy_complex_request) override {
|
||
*destroy_complex_request = true;
|
||
|
||
EXPECT_FALSE(handled_);
|
||
handled_ = true;
|
||
|
||
EXPECT_EQ(behavior, behavior_);
|
||
EXPECT_EQ(exception_port, LocalPort());
|
||
|
||
if (HasIdentity()) {
|
||
EXPECT_NE(thread, THREAD_NULL);
|
||
EXPECT_EQ(task, ChildTask());
|
||
} else {
|
||
EXPECT_EQ(thread, THREAD_NULL);
|
||
EXPECT_EQ(task, TASK_NULL);
|
||
}
|
||
|
||
mach_exception_code_t expect_code = exception_code_;
|
||
mach_exception_subcode_t expect_subcode = exception_subcode_;
|
||
if ((behavior & MACH_EXCEPTION_CODES) == 0) {
|
||
expect_code = implicit_cast<exception_data_type_t>(expect_code);
|
||
expect_subcode = implicit_cast<exception_data_type_t>(expect_subcode);
|
||
}
|
||
|
||
EXPECT_EQ(exception, exception_);
|
||
EXPECT_EQ(code_count, 2u);
|
||
|
||
// The code_count check above would ideally use ASSERT_EQ so that the next
|
||
// conditionals would not be necessary, but ASSERT_* requires a function
|
||
// returning type void, and the interface dictates otherwise here.
|
||
if (code_count >= 1) {
|
||
EXPECT_EQ(code[0], expect_code);
|
||
}
|
||
if (code_count >= 2) {
|
||
EXPECT_EQ(code[1], expect_subcode);
|
||
}
|
||
|
||
if (HasState()) {
|
||
EXPECT_EQ(*flavor, exception_ + 10);
|
||
EXPECT_EQ(old_state_count, MACHINE_THREAD_STATE_COUNT);
|
||
EXPECT_NE(old_state, nullptr);
|
||
EXPECT_EQ(*new_state_count,
|
||
implicit_cast<mach_msg_type_number_t>(THREAD_STATE_MAX));
|
||
EXPECT_NE(new_state, nullptr);
|
||
|
||
for (size_t index = 0; index < old_state_count; ++index) {
|
||
EXPECT_EQ(old_state[index], index);
|
||
}
|
||
|
||
// Use a flavor known to be different from the incoming flavor, for a test
|
||
// of the “out” side of the in-out flavor parameter.
|
||
*flavor = exception_ + 20;
|
||
*new_state_count = MACHINE_THREAD_STATE_COUNT;
|
||
|
||
// Send a new state back to the client.
|
||
for (size_t index = 0; index < *new_state_count; ++index) {
|
||
new_state[index] = MACHINE_THREAD_STATE_COUNT - index;
|
||
}
|
||
} else {
|
||
EXPECT_EQ(*flavor, THREAD_STATE_NONE);
|
||
EXPECT_EQ(old_state_count, 0u);
|
||
EXPECT_EQ(old_state, nullptr);
|
||
EXPECT_EQ(*new_state_count, 0u);
|
||
EXPECT_EQ(new_state, nullptr);
|
||
}
|
||
|
||
return KERN_SUCCESS;
|
||
}
|
||
|
||
private:
|
||
// MachMultiprocess:
|
||
|
||
void MachMultiprocessParent() override {
|
||
UniversalMachExcServer universal_mach_exc_server(this);
|
||
|
||
kern_return_t kr =
|
||
MachMessageServer::Run(&universal_mach_exc_server,
|
||
LocalPort(),
|
||
MACH_MSG_OPTION_NONE,
|
||
MachMessageServer::kOneShot,
|
||
MachMessageServer::kReceiveLargeError,
|
||
kMachMessageTimeoutWaitIndefinitely);
|
||
EXPECT_EQ(kr, KERN_SUCCESS)
|
||
<< MachErrorMessage(kr, "MachMessageServer::Run");
|
||
|
||
EXPECT_TRUE(handled_);
|
||
}
|
||
|
||
void MachMultiprocessChild() override {
|
||
const exception_type_t exception = exception_;
|
||
const mach_exception_data_type_t code[] = {
|
||
exception_code_,
|
||
exception_subcode_
|
||
};
|
||
|
||
thread_t thread = THREAD_NULL;
|
||
task_t task = TASK_NULL;
|
||
if (all_fields_ || HasIdentity()) {
|
||
thread = MachThreadSelf();
|
||
task = mach_task_self();
|
||
}
|
||
|
||
thread_state_flavor_t flavor;
|
||
thread_state_flavor_t* flavor_p = nullptr;
|
||
natural_t old_state[MACHINE_THREAD_STATE_COUNT];
|
||
thread_state_t old_state_p = nullptr;
|
||
mach_msg_type_number_t old_state_count = 0;
|
||
natural_t new_state[THREAD_STATE_MAX];
|
||
thread_state_t new_state_p = nullptr;
|
||
mach_msg_type_number_t new_state_count;
|
||
mach_msg_type_number_t* new_state_count_p = nullptr;
|
||
if (all_fields_ || HasState()) {
|
||
// Pick a different flavor each time based on the value of exception_.
|
||
// These aren’t real flavors, it’s just for testing.
|
||
flavor = exception_ + 10;
|
||
flavor_p = &flavor;
|
||
for (size_t index = 0; index < std::size(old_state); ++index) {
|
||
old_state[index] = index;
|
||
}
|
||
old_state_p = reinterpret_cast<thread_state_t>(&old_state);
|
||
old_state_count = std::size(old_state);
|
||
|
||
// new_state and new_state_count are out parameters that the server should
|
||
// never see or use, so set them to bogus values. The call to the server
|
||
// should overwrite these.
|
||
memset(new_state, 0xa5, sizeof(new_state));
|
||
new_state_p = reinterpret_cast<thread_state_t>(&new_state);
|
||
new_state_count = 0x5a;
|
||
new_state_count_p = &new_state_count;
|
||
}
|
||
|
||
EXPECT_EQ(UniversalExceptionRaise(behavior_,
|
||
RemotePort(),
|
||
thread,
|
||
task,
|
||
exception,
|
||
code,
|
||
std::size(code),
|
||
flavor_p,
|
||
old_state_p,
|
||
old_state_count,
|
||
new_state_p,
|
||
new_state_count_p),
|
||
KERN_SUCCESS);
|
||
|
||
if (HasState()) {
|
||
// Verify the out parameters.
|
||
|
||
EXPECT_EQ(flavor, exception_ + 20);
|
||
EXPECT_EQ(new_state_count, MACHINE_THREAD_STATE_COUNT);
|
||
|
||
for (size_t index = 0; index < new_state_count; ++index) {
|
||
EXPECT_EQ(new_state[index], MACHINE_THREAD_STATE_COUNT - index);
|
||
}
|
||
}
|
||
}
|
||
|
||
bool HasIdentity() const {
|
||
return ExceptionBehaviorHasIdentity(behavior_);
|
||
}
|
||
|
||
bool HasState() const {
|
||
return ExceptionBehaviorHasState(behavior_);
|
||
}
|
||
|
||
// The behavior to test.
|
||
exception_behavior_t behavior_;
|
||
|
||
// If false, only fields required for the current value of behavior_ are set
|
||
// in a call to UniversalExceptionRaise(). The thread and task fields are only
|
||
// set for identity-carrying behaviors, and the flavor and state fields are
|
||
// only set for state-carrying behaviors. If true, all fields are set
|
||
// regardless of the behavior. Testing in both ways verifies that
|
||
// UniversalExceptionRaise() can tolerate the null arguments documented as
|
||
// usable when the behavior allows it, and that it ignores these arguments
|
||
// even when set when the behavior does not make use of them.
|
||
bool all_fields_;
|
||
|
||
// true if an exception message was handled.
|
||
bool handled_;
|
||
|
||
// These fields will increment for each instantiation of the test class.
|
||
static exception_type_t exception_;
|
||
static mach_exception_code_t exception_code_;
|
||
static mach_exception_subcode_t exception_subcode_;
|
||
};
|
||
|
||
exception_type_t TestExcClientVariants::exception_ = 0;
|
||
|
||
// exception_code_ and exception_subcode_ are always large enough to require
|
||
// 64 bits, so that when the 32-bit-only variants not using MACH_EXCEPITON_CODES
|
||
// are tested, the code and subcode fields can be checked for proper truncation.
|
||
mach_exception_code_t TestExcClientVariants::exception_code_ = 0x100000000;
|
||
mach_exception_subcode_t TestExcClientVariants::exception_subcode_ =
|
||
0xffffffff00000000;
|
||
|
||
TEST(ExcClientVariants, UniversalExceptionRaise) {
|
||
static constexpr exception_behavior_t kBehaviors[] = {
|
||
EXCEPTION_DEFAULT,
|
||
EXCEPTION_STATE,
|
||
EXCEPTION_STATE_IDENTITY,
|
||
kMachExceptionCodes | EXCEPTION_DEFAULT,
|
||
kMachExceptionCodes | EXCEPTION_STATE,
|
||
kMachExceptionCodes | EXCEPTION_STATE_IDENTITY,
|
||
};
|
||
|
||
for (size_t index = 0; index < std::size(kBehaviors); ++index) {
|
||
exception_behavior_t behavior = kBehaviors[index];
|
||
SCOPED_TRACE(base::StringPrintf("index %zu, behavior %d", index, behavior));
|
||
|
||
{
|
||
SCOPED_TRACE("all_fields = false");
|
||
|
||
TestExcClientVariants test_exc_client_variants(behavior, false);
|
||
test_exc_client_variants.Run();
|
||
}
|
||
|
||
{
|
||
SCOPED_TRACE("all_fields = true");
|
||
|
||
TestExcClientVariants test_exc_client_variants(behavior, true);
|
||
test_exc_client_variants.Run();
|
||
}
|
||
}
|
||
}
|
||
|
||
} // namespace
|
||
} // namespace test
|
||
} // namespace crashpad
|