crashpad/util/mac/process_reader_test.cc
2014-08-25 17:51:09 -04:00

573 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 "util/mac/process_reader.h"
#include <dispatch/dispatch.h>
#include <mach/mach.h>
#include <string.h>
#include <map>
#include <string>
#include "base/logging.h"
#include "base/mac/scoped_mach_port.h"
#include "base/posix/eintr_wrapper.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "util/file/fd_io.h"
#include "util/stdlib/pointer_container.h"
#include "util/test/mac/mach_errors.h"
#include "util/test/mac/mach_multiprocess.h"
#include "util/test/errors.h"
namespace {
using namespace crashpad;
using namespace crashpad::test;
TEST(ProcessReader, SelfBasic) {
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
#if !defined(ARCH_CPU_64_BITS)
EXPECT_FALSE(process_reader.Is64Bit());
#else
EXPECT_TRUE(process_reader.Is64Bit());
#endif
EXPECT_EQ(getpid(), process_reader.ProcessID());
EXPECT_EQ(getppid(), process_reader.ParentProcessID());
const char kTestMemory[] = "Some test memory";
char buffer[arraysize(kTestMemory)];
ASSERT_TRUE(process_reader.Memory()->Read(
reinterpret_cast<mach_vm_address_t>(kTestMemory),
sizeof(kTestMemory),
&buffer));
EXPECT_STREQ(kTestMemory, buffer);
}
const char kTestMemory[] = "Read me from another process";
class ProcessReaderChild final : public MachMultiprocess {
public:
ProcessReaderChild() : MachMultiprocess() {}
~ProcessReaderChild() {}
protected:
void Parent() override {
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(ChildTask()));
#if !defined(ARCH_CPU_64_BITS)
EXPECT_FALSE(process_reader.Is64Bit());
#else
EXPECT_TRUE(process_reader.Is64Bit());
#endif
EXPECT_EQ(getpid(), process_reader.ParentProcessID());
EXPECT_EQ(ChildPID(), process_reader.ProcessID());
int read_fd = ReadPipeFD();
mach_vm_address_t address;
int rv = ReadFD(read_fd, &address, sizeof(address));
ASSERT_EQ(static_cast<ssize_t>(sizeof(address)), rv)
<< ErrnoMessage("read");
std::string read_string;
ASSERT_TRUE(process_reader.Memory()->ReadCString(address, &read_string));
EXPECT_EQ(kTestMemory, read_string);
// Tell the child that its OK to exit. The child needed to be kept alive
// until the parent finished working with it.
int write_fd = WritePipeFD();
char c = '\0';
rv = WriteFD(write_fd, &c, 1);
ASSERT_EQ(1, rv) << ErrnoMessage("write");
}
void Child() override {
int write_fd = WritePipeFD();
mach_vm_address_t address =
reinterpret_cast<mach_vm_address_t>(kTestMemory);
int rv = WriteFD(write_fd, &address, sizeof(address));
ASSERT_EQ(static_cast<ssize_t>(sizeof(address)), rv)
<< ErrnoMessage("write");
// Wait for the parent to say that its OK to exit.
int read_fd = ReadPipeFD();
char c;
rv = ReadFD(read_fd, &c, 1);
ASSERT_EQ(1, rv) << ErrnoMessage("read");
}
private:
DISALLOW_COPY_AND_ASSIGN(ProcessReaderChild);
};
TEST(ProcessReader, ChildBasic) {
ProcessReaderChild process_reader_child;
process_reader_child.Run();
}
// Returns a thread ID given a pthread_t. This wraps pthread_threadid_np() but
// that function has a cumbersome interface because it returns a success value.
// This function CHECKs success and returns the thread ID directly.
uint64_t PthreadToThreadID(pthread_t pthread) {
uint64_t thread_id;
int rv = pthread_threadid_np(pthread, &thread_id);
CHECK_EQ(rv, 0);
return thread_id;
}
TEST(ProcessReader, SelfOneThread) {
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
const std::vector<ProcessReaderThread>& threads = process_reader.Threads();
// If other tests ran in this process previously, threads may have been
// created and may still be running. This check must look for at least one
// thread, not exactly one thread.
ASSERT_GE(threads.size(), 1u);
EXPECT_EQ(PthreadToThreadID(pthread_self()), threads[0].id);
base::mac::ScopedMachSendRight thread_self(mach_thread_self());
EXPECT_EQ(thread_self, threads[0].port);
EXPECT_EQ(0, threads[0].suspend_count);
}
class TestThreadPool {
public:
struct ThreadExpectation {
mach_vm_address_t stack_address;
int suspend_count;
};
TestThreadPool() : thread_infos_() {
}
// Resumes suspended threads, signals each threads exit semaphore asking it
// to exit, and joins each thread, blocking until they have all exited.
~TestThreadPool() {
for (ThreadInfo* thread_info : thread_infos_) {
mach_port_t thread_port = pthread_mach_thread_np(thread_info->pthread);
while (thread_info->suspend_count > 0) {
kern_return_t kr = thread_resume(thread_port);
EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "thread_resume");
--thread_info->suspend_count;
}
}
for (const ThreadInfo* thread_info : thread_infos_) {
dispatch_semaphore_signal(thread_info->exit_semaphore);
}
for (const ThreadInfo* thread_info : thread_infos_) {
int rv = pthread_join(thread_info->pthread, NULL);
CHECK_EQ(0, rv);
}
}
// Starts |thread_count| threads and waits on each threads ready semaphore,
// so that when this function returns, all threads have been started and have
// all run to the point that theyve signalled that they are ready.
void StartThreads(size_t thread_count) {
ASSERT_TRUE(thread_infos_.empty());
for (size_t thread_index = 0; thread_index < thread_count; ++thread_index) {
ThreadInfo* thread_info = new ThreadInfo();
thread_infos_.push_back(thread_info);
int rv = pthread_create(&thread_info->pthread,
NULL,
ThreadMain,
thread_info);
ASSERT_EQ(0, rv);
}
for (const ThreadInfo* thread_info : thread_infos_) {
long rv = dispatch_semaphore_wait(thread_info->ready_semaphore,
DISPATCH_TIME_FOREVER);
ASSERT_EQ(0, rv);
}
// If present, suspend the thread at indices 1 through 3 the same number of
// times as their index. This tests reporting of suspend counts.
for (size_t thread_index = 1;
thread_index < thread_infos_.size() && thread_index < 4;
++thread_index) {
mach_port_t thread_port =
pthread_mach_thread_np(thread_infos_[thread_index]->pthread);
for (size_t suspend_count = 0;
suspend_count < thread_index;
++suspend_count) {
kern_return_t kr = thread_suspend(thread_port);
EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "thread_suspend");
if (kr == KERN_SUCCESS) {
++thread_infos_[thread_index]->suspend_count;
}
}
}
}
uint64_t GetThreadInfo(size_t thread_index,
ThreadExpectation* expectation) {
CHECK_LT(thread_index, thread_infos_.size());
const ThreadInfo* thread_info = thread_infos_[thread_index];
expectation->stack_address = thread_info->stack_address;
expectation->suspend_count = thread_info->suspend_count;
return PthreadToThreadID(thread_info->pthread);
}
private:
struct ThreadInfo {
ThreadInfo()
: pthread(NULL),
stack_address(0),
ready_semaphore(dispatch_semaphore_create(0)),
exit_semaphore(dispatch_semaphore_create(0)),
suspend_count(0) {
}
~ThreadInfo() {
dispatch_release(exit_semaphore);
dispatch_release(ready_semaphore);
}
// The threads ID, set at the time the thread is created.
pthread_t pthread;
// An address somewhere within the threads stack. The thread sets this in
// its ThreadMain().
mach_vm_address_t stack_address;
// The worker thread signals ready_semaphore to indicate that its done
// setting up its ThreadInfo structure. The main thread waits on this
// semaphore before using any data that the worker thread is responsible for
// setting.
dispatch_semaphore_t ready_semaphore;
// The worker thread waits on exit_semaphore to determine when its safe to
// exit. The main thread signals exit_semaphore when it no longer needs the
// worker thread.
dispatch_semaphore_t exit_semaphore;
// The threads suspend count.
int suspend_count;
};
static void* ThreadMain(void* argument) {
ThreadInfo* thread_info = static_cast<ThreadInfo*>(argument);
thread_info->stack_address =
reinterpret_cast<mach_vm_address_t>(&thread_info);
dispatch_semaphore_signal(thread_info->ready_semaphore);
dispatch_semaphore_wait(thread_info->exit_semaphore, DISPATCH_TIME_FOREVER);
// Check this here after everythings known to be synchronized, otherwise
// theres a race between the parent thread storing this threads pthread_t
// in thread_info_pthread and this thread starting and attempting to access
// it.
CHECK_EQ(pthread_self(), thread_info->pthread);
return NULL;
}
// This is a PointerVector because the address of a ThreadInfo object is
// passed to each threads ThreadMain(), so they cannot move around in memory.
PointerVector<ThreadInfo> thread_infos_;
DISALLOW_COPY_AND_ASSIGN(TestThreadPool);
};
typedef std::map<uint64_t, TestThreadPool::ThreadExpectation> ThreadMap;
// Verifies that all of the threads in |threads|, obtained from ProcessReader,
// agree with the expectation in |thread_map|. If |tolerate_extra_threads| is
// true, |threads| is allowed to contain threads that are not listed in
// |thread_map|. This is useful when testing situations where code outside of
// the tests control (such as system libraries) may start threads, or may have
// started threads prior to a tests execution.
void ExpectSeveralThreads(ThreadMap* thread_map,
const std::vector<ProcessReaderThread>& threads,
const bool tolerate_extra_threads) {
if (tolerate_extra_threads) {
ASSERT_GE(threads.size(), thread_map->size());
} else {
ASSERT_EQ(thread_map->size(), threads.size());
}
for (size_t thread_index = 0; thread_index < threads.size(); ++thread_index) {
const ProcessReaderThread& thread = threads[thread_index];
mach_vm_address_t thread_stack_region_end =
thread.stack_region_address + thread.stack_region_size;
const auto& iterator = thread_map->find(thread.id);
if (!tolerate_extra_threads) {
// Make sure that the thread is in the expectation map.
ASSERT_NE(thread_map->end(), iterator);
}
if (iterator != thread_map->end()) {
EXPECT_GE(iterator->second.stack_address, thread.stack_region_address);
EXPECT_LT(iterator->second.stack_address, thread_stack_region_end);
EXPECT_EQ(iterator->second.suspend_count, thread.suspend_count);
// Remove the thread from the expectation map since its already been
// found. This makes it easy to check for duplicate thread IDs, and makes
// it easy to check that all expected threads were found.
thread_map->erase(iterator);
}
// Make sure that this threads ID, stack region, and port dont conflict
// with any other threads. Each thread should have a unique value for its
// ID and port, and each should have its own stack that doesnt touch any
// other threads stack.
for (size_t other_thread_index = 0;
other_thread_index < threads.size();
++other_thread_index) {
if (thread_index == other_thread_index) {
continue;
}
const ProcessReaderThread& other_thread = threads[other_thread_index];
EXPECT_NE(thread.id, other_thread.id);
EXPECT_NE(thread.port, other_thread.port);
mach_vm_address_t other_thread_stack_region_end =
other_thread.stack_region_address + other_thread.stack_region_size;
EXPECT_FALSE(
thread.stack_region_address >= other_thread.stack_region_address &&
thread.stack_region_address < other_thread_stack_region_end);
EXPECT_FALSE(
thread_stack_region_end > other_thread.stack_region_address &
thread_stack_region_end <= other_thread_stack_region_end);
}
}
// Make sure that each expected thread was found.
EXPECT_TRUE(thread_map->empty());
}
TEST(ProcessReader, SelfSeveralThreads) {
// Set up the ProcessReader here, before any other threads are running. This
// tests that the threads it returns are lazily initialized as a snapshot of
// the threads at the time of the first call to Threads(), and not at the
// time the ProcessReader was created or initialized.
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
TestThreadPool thread_pool;
const size_t kChildThreads = 16;
thread_pool.StartThreads(kChildThreads);
if (Test::HasFatalFailure()) {
return;
}
// Build a map of all expected threads, keyed by each threads ID. The values
// are addresses that should lie somewhere within each threads stack.
ThreadMap thread_map;
const uint64_t self_thread_id = PthreadToThreadID(pthread_self());
TestThreadPool::ThreadExpectation expectation;
expectation.stack_address = reinterpret_cast<mach_vm_address_t>(&thread_map);
expectation.suspend_count = 0;
thread_map[self_thread_id] = expectation;
for (size_t thread_index = 0; thread_index < kChildThreads; ++thread_index) {
uint64_t thread_id = thread_pool.GetThreadInfo(thread_index, &expectation);
// There cant be any duplicate thread IDs.
EXPECT_EQ(0u, thread_map.count(thread_id));
thread_map[thread_id] = expectation;
}
const std::vector<ProcessReaderThread>& threads = process_reader.Threads();
// Other tests that have run previously may have resulted in the creation of
// threads that still exist, so pass true for |tolerate_extra_threads|.
ExpectSeveralThreads(&thread_map, threads, true);
// When testing in-process, verify that when this thread shows up in the
// vector, it has the expected thread port, and that this thread port only
// shows up once.
base::mac::ScopedMachSendRight thread_self(mach_thread_self());
bool found_thread_self = false;
for (const ProcessReaderThread& thread : threads) {
if (thread.port == thread_self) {
EXPECT_FALSE(found_thread_self);
found_thread_self = true;
EXPECT_EQ(self_thread_id, thread.id);
}
}
EXPECT_TRUE(found_thread_self);
}
class ProcessReaderThreadedChild final : public MachMultiprocess {
public:
explicit ProcessReaderThreadedChild(size_t thread_count)
: MachMultiprocess(),
thread_count_(thread_count) {
}
~ProcessReaderThreadedChild() {}
protected:
void Parent() override {
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(ChildTask()));
int read_fd = ReadPipeFD();
// Build a map of all expected threads, keyed by each threads ID, and with
// addresses that should lie somewhere within each threads stack as values.
// These IDs and addresses all come from the child process via the pipe.
ThreadMap thread_map;
for (size_t thread_index = 0;
thread_index < thread_count_ + 1;
++thread_index) {
uint64_t thread_id;
int rv = ReadFD(read_fd, &thread_id, sizeof(thread_id));
ASSERT_EQ(static_cast<ssize_t>(sizeof(thread_id)), rv)
<< ErrnoMessage("read");
TestThreadPool::ThreadExpectation expectation;
rv = ReadFD(read_fd,
&expectation.stack_address,
sizeof(expectation.stack_address));
ASSERT_EQ(static_cast<ssize_t>(sizeof(expectation.stack_address)), rv)
<< ErrnoMessage("read");
rv = ReadFD(read_fd,
&expectation.suspend_count,
sizeof(expectation.suspend_count));
ASSERT_EQ(static_cast<ssize_t>(sizeof(expectation.suspend_count)), rv)
<< ErrnoMessage("read");
// There cant be any duplicate thread IDs.
EXPECT_EQ(0u, thread_map.count(thread_id));
thread_map[thread_id] = expectation;
}
const std::vector<ProcessReaderThread>& threads = process_reader.Threads();
// The child shouldnt have any threads other than its main thread and the
// ones it created in its pool, so pass false for |tolerate_extra_threads|.
ExpectSeveralThreads(&thread_map, threads, false);
// Tell the child that its OK to exit. The child needed to be kept alive
// until the parent finished working with it.
int write_fd = WritePipeFD();
char c = '\0';
int rv = WriteFD(write_fd, &c, 1);
ASSERT_EQ(1, rv) << ErrnoMessage("write");
}
void Child() override {
TestThreadPool thread_pool;
thread_pool.StartThreads(thread_count_);
if (testing::Test::HasFatalFailure()) {
return;
}
int write_fd = WritePipeFD();
// This thread isnt part of the thread pool, but the parent will be able
// to inspect it. Write an entry for it.
uint64_t thread_id = PthreadToThreadID(pthread_self());
int rv = WriteFD(write_fd, &thread_id, sizeof(thread_id));
ASSERT_EQ(static_cast<ssize_t>(sizeof(thread_id)), rv)
<< ErrnoMessage("write");
TestThreadPool::ThreadExpectation expectation;
expectation.stack_address = reinterpret_cast<mach_vm_address_t>(&thread_id);
expectation.suspend_count = 0;
rv = WriteFD(write_fd,
&expectation.stack_address,
sizeof(expectation.stack_address));
ASSERT_EQ(static_cast<ssize_t>(sizeof(expectation.stack_address)), rv)
<< ErrnoMessage("write");
rv = WriteFD(write_fd,
&expectation.suspend_count,
sizeof(expectation.suspend_count));
ASSERT_EQ(static_cast<ssize_t>(sizeof(expectation.suspend_count)), rv)
<< ErrnoMessage("write");
// Write an entry for everything in the thread pool.
for (size_t thread_index = 0;
thread_index < thread_count_;
++thread_index) {
uint64_t thread_id =
thread_pool.GetThreadInfo(thread_index, &expectation);
rv = WriteFD(write_fd, &thread_id, sizeof(thread_id));
ASSERT_EQ(static_cast<ssize_t>(sizeof(thread_id)), rv)
<< ErrnoMessage("write");
rv = WriteFD(write_fd,
&expectation.stack_address,
sizeof(expectation.stack_address));
ASSERT_EQ(static_cast<ssize_t>(sizeof(expectation.stack_address)), rv)
<< ErrnoMessage("write");
rv = WriteFD(write_fd,
&expectation.suspend_count,
sizeof(expectation.suspend_count));
ASSERT_EQ(static_cast<ssize_t>(sizeof(expectation.suspend_count)), rv)
<< ErrnoMessage("write");
}
// Wait for the parent to say that its OK to exit.
int read_fd = ReadPipeFD();
char c;
rv = ReadFD(read_fd, &c, 1);
ASSERT_EQ(1, rv) << ErrnoMessage("read");
}
private:
size_t thread_count_;
DISALLOW_COPY_AND_ASSIGN(ProcessReaderThreadedChild);
};
TEST(ProcessReader, ChildOneThread) {
// The main thread plus zero child threads equals one thread.
const size_t kChildThreads = 0;
ProcessReaderThreadedChild process_reader_threaded_child(kChildThreads);
process_reader_threaded_child.Run();
}
TEST(ProcessReader, ChildSeveralThreads) {
const size_t kChildThreads = 64;
ProcessReaderThreadedChild process_reader_threaded_child(kChildThreads);
process_reader_threaded_child.Run();
}
} // namespace