crashpad/snapshot/mac/mach_o_image_annotations_reader_test.cc

344 lines
13 KiB
C++
Raw Normal View History

// 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 "test/errors.h"
#include "test/mac/mach_errors.h"
#include "test/mac/mach_multiprocess.h"
#include "util/file/file_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.h"
#include "util/mach/mach_message_server.h"
namespace crashpad {
namespace test {
namespace {
class TestMachOImageAnnotationsReader final
: public MachMultiprocess,
public UniversalMachExcServer::Interface {
public:
enum TestType {
// Dont 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::Interface(),
test_type_(test_type) {
}
~TestMachOImageAnnotationsReader() {}
// UniversalMachExcServer::Interface:
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,
const mach_msg_trailer_t* trailer,
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) {
if (module.reader) {
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());
} else {
EXPECT_TRUE(module.reader);
}
}
// Mac OS X 10.6 doesnt have support for CrashReporter annotations
// (CrashReporterClient.h), so dont 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 dylds 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 librarys 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 its done setting up its
// annotations via the CrashpadInfo interface.
char c;
CheckedReadFile(ReadPipeHandle(), &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 its permitted to crash.
CheckedWriteFile(WritePipeHandle(), &c, sizeof(c));
if (test_type_ != kDontCrash) {
// Handle the childs crash. Further validation will be done in
// CatchMachException().
UniversalMachExcServer universal_mach_exc_server(this);
mach_msg_return_t mr =
MachMessageServer::Run(&universal_mach_exc_server,
LocalPort(),
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kReceiveLargeError,
kMachMessageTimeoutWaitIndefinitely);
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';
CheckedWriteFile(WritePipeHandle(), &c, sizeof(c));
// Wait for the parent to indicate that its safe to crash.
CheckedReadFile(ReadPipeHandle(), &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 doesnt matter very much, because dyld wont
// 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