| Index: snapshot/mac/mach_o_image_annotations_reader_test.cc
|
| diff --git a/snapshot/mac/mach_o_image_annotations_reader_test.cc b/snapshot/mac/mach_o_image_annotations_reader_test.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..33fac0055d47689aeba5caeceba0951d0845a60f
|
| --- /dev/null
|
| +++ b/snapshot/mac/mach_o_image_annotations_reader_test.cc
|
| @@ -0,0 +1,334 @@
|
| +// 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 "snapshot/mac/mach_o_image_annotations_reader.h"
|
| +
|
| +#include <dlfcn.h>
|
| +#include <mach/mach.h>
|
| +#include <signal.h>
|
| +#include <stdlib.h>
|
| +#include <string.h>
|
| +#include <unistd.h>
|
| +
|
| +#include <map>
|
| +#include <string>
|
| +#include <vector>
|
| +
|
| +#include "base/basictypes.h"
|
| +#include "client/crashpad_info.h"
|
| +#include "client/simple_string_dictionary.h"
|
| +#include "gtest/gtest.h"
|
| +#include "snapshot/mac/process_reader.h"
|
| +#include "util/file/fd_io.h"
|
| +#include "util/mac/mac_util.h"
|
| +#include "util/mach/exc_server_variants.h"
|
| +#include "util/mach/exception_ports.h"
|
| +#include "util/mach/mach_message_server.h"
|
| +#include "util/test/errors.h"
|
| +#include "util/test/mac/mach_errors.h"
|
| +#include "util/test/mac/mach_multiprocess.h"
|
| +
|
| +namespace crashpad {
|
| +namespace test {
|
| +namespace {
|
| +
|
| +class TestMachOImageAnnotationsReader final : public MachMultiprocess,
|
| + public UniversalMachExcServer {
|
| + public:
|
| + enum TestType {
|
| + // Don’t crash, just test the CrashpadInfo interface.
|
| + kDontCrash = 0,
|
| +
|
| + // The child process should crash by calling abort(). The parent verifies
|
| + // that the system libraries set the expected annotations.
|
| + kCrashAbort,
|
| +
|
| + // The child process should crash by setting DYLD_INSERT_LIBRARIES to
|
| + // contain a nonexistent library. The parent verifies that dyld sets the
|
| + // expected annotations.
|
| + kCrashDyld,
|
| + };
|
| +
|
| + explicit TestMachOImageAnnotationsReader(TestType test_type)
|
| + : MachMultiprocess(),
|
| + UniversalMachExcServer(),
|
| + test_type_(test_type) {
|
| + }
|
| +
|
| + ~TestMachOImageAnnotationsReader() {}
|
| +
|
| + // 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;
|
| +
|
| + EXPECT_EQ(ChildTask(), task);
|
| +
|
| + ProcessReader process_reader;
|
| + bool rv = process_reader.Initialize(task);
|
| + if (!rv) {
|
| + ADD_FAILURE();
|
| + } else {
|
| + const std::vector<ProcessReader::Module>& modules =
|
| + process_reader.Modules();
|
| + std::vector<std::string> all_annotations_vector;
|
| + for (const ProcessReader::Module& module : modules) {
|
| + MachOImageAnnotationsReader module_annotations_reader(
|
| + &process_reader, module.reader, module.name);
|
| + std::vector<std::string> module_annotations_vector =
|
| + module_annotations_reader.Vector();
|
| + all_annotations_vector.insert(all_annotations_vector.end(),
|
| + module_annotations_vector.begin(),
|
| + module_annotations_vector.end());
|
| + }
|
| +
|
| + // Mac OS X 10.6 doesn’t have support for CrashReporter annotations
|
| + // (CrashReporterClient.h), so don’t look for any special annotations in
|
| + // that version.
|
| + int mac_os_x_minor_version = MacOSXMinorVersion();
|
| + if (mac_os_x_minor_version > 7) {
|
| + EXPECT_GE(all_annotations_vector.size(), 1u);
|
| +
|
| + const char* expected_annotation = nullptr;
|
| + switch (test_type_) {
|
| + case kCrashAbort:
|
| + // The child process calls abort(), so the expected annotation
|
| + // reflects this, with a string set by 10.7.5
|
| + // Libc-763.13/stdlib/abort-fbsd.c abort(). This string is still
|
| + // present in 10.9.5 Libc-997.90.3/stdlib/FreeBSD/abort.c abort(),
|
| + // but because abort() tests to see if a message is already set and
|
| + // something else in Libc will have set a message, this string is
|
| + // not the expectation on 10.9 or higher. Instead, after fork(), the
|
| + // child process has a message indicating that a fork() without
|
| + // exec() occurred. See 10.9.5 Libc-997.90.3/sys/_libc_fork_child.c
|
| + // _libc_fork_child().
|
| + expected_annotation =
|
| + mac_os_x_minor_version <= 8
|
| + ? "abort() called"
|
| + : "crashed on child side of fork pre-exec";
|
| + break;
|
| +
|
| + case kCrashDyld:
|
| + // This is independent of dyld’s error_string, which is tested
|
| + // below.
|
| + expected_annotation = "dyld: launch, loading dependent libraries";
|
| + break;
|
| +
|
| + default:
|
| + ADD_FAILURE();
|
| + break;
|
| + }
|
| +
|
| + size_t expected_annotation_length = strlen(expected_annotation);
|
| + bool found = false;
|
| + for (const std::string& annotation : all_annotations_vector) {
|
| + // Look for the expectation as a leading susbtring, because the actual
|
| + // string that dyld uses will have the contents of the
|
| + // DYLD_INSERT_LIBRARIES environment variable appended to it on Mac
|
| + // OS X 10.10.
|
| + if (annotation.substr(0, expected_annotation_length) ==
|
| + expected_annotation) {
|
| + found = true;
|
| + break;
|
| + }
|
| + }
|
| + EXPECT_TRUE(found);
|
| + }
|
| +
|
| + // dyld exposes its error_string at least as far back as Mac OS X 10.4.
|
| + if (test_type_ == kCrashDyld) {
|
| + const char kExpectedAnnotation[] = "could not load inserted library";
|
| + size_t expected_annotation_length = strlen(kExpectedAnnotation);
|
| + bool found = false;
|
| + for (const std::string& annotation : all_annotations_vector) {
|
| + // Look for the expectation as a leading substring, because the actual
|
| + // string will contain the library’s pathname and, on Mac OS X 10.9
|
| + // and later, a reason.
|
| + if (annotation.substr(0, expected_annotation_length) ==
|
| + kExpectedAnnotation) {
|
| + found = true;
|
| + break;
|
| + }
|
| + }
|
| +
|
| + EXPECT_TRUE(found);
|
| + }
|
| + }
|
| +
|
| + return ExcServerSuccessfulReturnValue(behavior, false);
|
| + }
|
| +
|
| + private:
|
| + // MachMultiprocess:
|
| +
|
| + void MachMultiprocessParent() override {
|
| + ProcessReader process_reader;
|
| + ASSERT_TRUE(process_reader.Initialize(ChildTask()));
|
| +
|
| + // Wait for the child process to indicate that it’s done setting up its
|
| + // annotations via the CrashpadInfo interface.
|
| + char c;
|
| + CheckedReadFD(ReadPipeFD(), &c, sizeof(c));
|
| +
|
| + // Verify the “simple map” annotations set via the CrashpadInfo interface.
|
| + const std::vector<ProcessReader::Module>& modules =
|
| + process_reader.Modules();
|
| + std::map<std::string, std::string> all_annotations_simple_map;
|
| + for (const ProcessReader::Module& module : modules) {
|
| + MachOImageAnnotationsReader module_annotations_reader(
|
| + &process_reader, module.reader, module.name);
|
| + std::map<std::string, std::string> module_annotations_simple_map =
|
| + module_annotations_reader.SimpleMap();
|
| + all_annotations_simple_map.insert(module_annotations_simple_map.begin(),
|
| + module_annotations_simple_map.end());
|
| + }
|
| +
|
| + EXPECT_GE(all_annotations_simple_map.size(), 5u);
|
| + EXPECT_EQ("crash", all_annotations_simple_map["#TEST# pad"]);
|
| + EXPECT_EQ("value", all_annotations_simple_map["#TEST# key"]);
|
| + EXPECT_EQ("y", all_annotations_simple_map["#TEST# x"]);
|
| + EXPECT_EQ("shorter", all_annotations_simple_map["#TEST# longer"]);
|
| + EXPECT_EQ("", all_annotations_simple_map["#TEST# empty_value"]);
|
| +
|
| + // Tell the child process that it’s permitted to crash.
|
| + CheckedWriteFD(WritePipeFD(), &c, sizeof(c));
|
| +
|
| + if (test_type_ != kDontCrash) {
|
| + // Handle the child’s crash. Further validation will be done in
|
| + // CatchMachException().
|
| + mach_msg_return_t 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");
|
| +
|
| + switch (test_type_) {
|
| + case kCrashAbort:
|
| + SetExpectedChildTermination(kTerminationSignal, SIGABRT);
|
| + break;
|
| +
|
| + case kCrashDyld:
|
| + // dyld fatal errors result in the execution of an int3 instruction on
|
| + // x86 and a trap instruction on ARM, both of which raise SIGTRAP.
|
| + // 10.9.5 dyld-239.4/src/dyldStartup.s _dyld_fatal_error.
|
| + SetExpectedChildTermination(kTerminationSignal, SIGTRAP);
|
| + break;
|
| +
|
| + default:
|
| + FAIL();
|
| + break;
|
| + }
|
| + }
|
| + }
|
| +
|
| + void MachMultiprocessChild() override {
|
| + CrashpadInfo* crashpad_info = CrashpadInfo::GetCrashpadInfo();
|
| +
|
| + // This is “leaked” to crashpad_info.
|
| + SimpleStringDictionary* simple_annotations = new SimpleStringDictionary();
|
| + simple_annotations->SetKeyValue("#TEST# pad", "break");
|
| + simple_annotations->SetKeyValue("#TEST# key", "value");
|
| + simple_annotations->SetKeyValue("#TEST# pad", "crash");
|
| + simple_annotations->SetKeyValue("#TEST# x", "y");
|
| + simple_annotations->SetKeyValue("#TEST# longer", "shorter");
|
| + simple_annotations->SetKeyValue("#TEST# empty_value", "");
|
| +
|
| + crashpad_info->set_simple_annotations(simple_annotations);
|
| +
|
| + // Tell the parent that the environment has been set up.
|
| + char c = '\0';
|
| + CheckedWriteFD(WritePipeFD(), &c, sizeof(c));
|
| +
|
| + // Wait for the parent to indicate that it’s safe to crash.
|
| + CheckedReadFD(ReadPipeFD(), &c, sizeof(c));
|
| +
|
| + // Direct an exception message to the exception server running in the
|
| + // parent.
|
| + ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask,
|
| + mach_task_self());
|
| + ASSERT_TRUE(exception_ports.SetExceptionPort(
|
| + EXC_MASK_CRASH, RemotePort(), EXCEPTION_DEFAULT, THREAD_STATE_NONE));
|
| +
|
| + switch (test_type_) {
|
| + case kDontCrash:
|
| + break;
|
| +
|
| + case kCrashAbort:
|
| + abort();
|
| + break;
|
| +
|
| + case kCrashDyld: {
|
| + // Set DYLD_INSERT_LIBRARIES to contain a library that does not exist.
|
| + // Unable to load it, dyld will abort with a fatal error.
|
| + ASSERT_EQ(
|
| + 0,
|
| + setenv(
|
| + "DYLD_INSERT_LIBRARIES", "/var/empty/NoDirectory/NoLibrary", 1))
|
| + << ErrnoMessage("setenv");
|
| +
|
| + // The actual executable doesn’t matter very much, because dyld won’t
|
| + // ever launch it. It just needs to be an executable that uses dyld as
|
| + // its LC_LOAD_DYLINKER (all normal executables do). /usr/bin/true is on
|
| + // every system, so use it.
|
| + ASSERT_EQ(0, execl("/usr/bin/true", "true", nullptr))
|
| + << ErrnoMessage("execl");
|
| + break;
|
| + }
|
| +
|
| + default:
|
| + break;
|
| + }
|
| + }
|
| +
|
| + TestType test_type_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(TestMachOImageAnnotationsReader);
|
| +};
|
| +
|
| +TEST(MachOImageAnnotationsReader, DontCrash) {
|
| + TestMachOImageAnnotationsReader test_mach_o_image_annotations_reader(
|
| + TestMachOImageAnnotationsReader::kDontCrash);
|
| + test_mach_o_image_annotations_reader.Run();
|
| +}
|
| +
|
| +TEST(MachOImageAnnotationsReader, CrashAbort) {
|
| + TestMachOImageAnnotationsReader test_mach_o_image_annotations_reader(
|
| + TestMachOImageAnnotationsReader::kCrashAbort);
|
| + test_mach_o_image_annotations_reader.Run();
|
| +}
|
| +
|
| +TEST(MachOImageAnnotationsReader, CrashDyld) {
|
| + TestMachOImageAnnotationsReader test_mach_o_image_annotations_reader(
|
| + TestMachOImageAnnotationsReader::kCrashDyld);
|
| + test_mach_o_image_annotations_reader.Run();
|
| +}
|
| +
|
| +} // namespace
|
| +} // namespace test
|
| +} // namespace crashpad
|
|
|