Index: util/mac/process_reader_test.cc |
diff --git a/util/mac/process_reader_test.cc b/util/mac/process_reader_test.cc |
deleted file mode 100644 |
index d33ebbf363f748dd02d4c4358974c0e00a58cc3f..0000000000000000000000000000000000000000 |
--- a/util/mac/process_reader_test.cc |
+++ /dev/null |
@@ -1,692 +0,0 @@ |
-// 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 <mach-o/dyld.h> |
-#include <mach-o/dyld_images.h> |
-#include <mach/mach.h> |
-#include <string.h> |
-#include <sys/stat.h> |
- |
-#include <map> |
-#include <string> |
-#include <vector> |
- |
-#include "base/logging.h" |
-#include "base/mac/scoped_mach_port.h" |
-#include "base/posix/eintr_wrapper.h" |
-#include "base/strings/stringprintf.h" |
-#include "build/build_config.h" |
-#include "gtest/gtest.h" |
-#include "util/file/fd_io.h" |
-#include "util/mac/mach_o_image_reader.h" |
-#include "util/mach/mach_extensions.h" |
-#include "util/stdlib/pointer_container.h" |
-#include "util/synchronization/semaphore.h" |
-#include "util/test/errors.h" |
-#include "util/test/mac/dyld.h" |
-#include "util/test/mac/mach_errors.h" |
-#include "util/test/mac/mach_multiprocess.h" |
- |
-namespace crashpad { |
-namespace test { |
-namespace { |
- |
-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() {} |
- |
- private: |
- void MachMultiprocessParent() 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; |
- CheckedReadFD(read_fd, &address, sizeof(address)); |
- |
- std::string read_string; |
- ASSERT_TRUE(process_reader.Memory()->ReadCString(address, &read_string)); |
- EXPECT_EQ(kTestMemory, read_string); |
- } |
- |
- void MachMultiprocessChild() override { |
- int write_fd = WritePipeFD(); |
- |
- mach_vm_address_t address = |
- reinterpret_cast<mach_vm_address_t>(kTestMemory); |
- CheckedWriteFD(write_fd, &address, sizeof(address)); |
- |
- // Wait for the parent to signal that it’s OK to exit by closing its end of |
- // the pipe. |
- CheckedReadFDAtEOF(ReadPipeFD()); |
- } |
- |
- 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<ProcessReader::Thread>& 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); |
- |
- thread_t thread_self = MachThreadSelf(); |
- 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 thread’s exit semaphore asking it |
- // to exit, and joins each thread, blocking until they have all exited. |
- ~TestThreadPool() { |
- for (ThreadInfo* thread_info : thread_infos_) { |
- thread_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 (ThreadInfo* thread_info : thread_infos_) { |
- thread_info->exit_semaphore.Signal(); |
- } |
- |
- for (const ThreadInfo* thread_info : thread_infos_) { |
- int rv = pthread_join(thread_info->pthread, nullptr); |
- CHECK_EQ(0, rv); |
- } |
- } |
- |
- // Starts |thread_count| threads and waits on each thread’s ready semaphore, |
- // so that when this function returns, all threads have been started and have |
- // all run to the point that they’ve 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, |
- nullptr, |
- ThreadMain, |
- thread_info); |
- ASSERT_EQ(0, rv); |
- } |
- |
- for (ThreadInfo* thread_info : thread_infos_) { |
- thread_info->ready_semaphore.Wait(); |
- } |
- |
- // 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) { |
- thread_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(nullptr), |
- stack_address(0), |
- ready_semaphore(0), |
- exit_semaphore(0), |
- suspend_count(0) { |
- } |
- |
- ~ThreadInfo() {} |
- |
- // The thread’s ID, set at the time the thread is created. |
- pthread_t pthread; |
- |
- // An address somewhere within the thread’s stack. The thread sets this in |
- // its ThreadMain(). |
- mach_vm_address_t stack_address; |
- |
- // The worker thread signals ready_semaphore to indicate that it’s 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. |
- Semaphore ready_semaphore; |
- |
- // The worker thread waits on exit_semaphore to determine when it’s safe to |
- // exit. The main thread signals exit_semaphore when it no longer needs the |
- // worker thread. |
- Semaphore exit_semaphore; |
- |
- // The thread’s 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); |
- |
- thread_info->ready_semaphore.Signal(); |
- thread_info->exit_semaphore.Wait(); |
- |
- // Check this here after everything’s known to be synchronized, otherwise |
- // there’s a race between the parent thread storing this thread’s pthread_t |
- // in thread_info_pthread and this thread starting and attempting to access |
- // it. |
- CHECK_EQ(pthread_self(), thread_info->pthread); |
- |
- return nullptr; |
- } |
- |
- // This is a PointerVector because the address of a ThreadInfo object is |
- // passed to each thread’s 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 test’s control (such as system libraries) may start threads, or may have |
-// started threads prior to a test’s execution. |
-void ExpectSeveralThreads(ThreadMap* thread_map, |
- const std::vector<ProcessReader::Thread>& 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 ProcessReader::Thread& 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 it’s 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 thread’s ID, stack region, and port don’t conflict |
- // with any other thread’s. Each thread should have a unique value for its |
- // ID and port, and each should have its own stack that doesn’t touch any |
- // other thread’s stack. |
- for (size_t other_thread_index = 0; |
- other_thread_index < threads.size(); |
- ++other_thread_index) { |
- if (thread_index == other_thread_index) { |
- continue; |
- } |
- |
- const ProcessReader::Thread& 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; |
- ASSERT_NO_FATAL_FAILURE(thread_pool.StartThreads(kChildThreads)); |
- |
- // Build a map of all expected threads, keyed by each thread’s ID. The values |
- // are addresses that should lie somewhere within each thread’s 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 can’t be any duplicate thread IDs. |
- EXPECT_EQ(0u, thread_map.count(thread_id)); |
- |
- thread_map[thread_id] = expectation; |
- } |
- |
- const std::vector<ProcessReader::Thread>& 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. |
- thread_t thread_self = MachThreadSelf(); |
- bool found_thread_self = false; |
- for (const ProcessReader::Thread& 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() {} |
- |
- private: |
- void MachMultiprocessParent() override { |
- ProcessReader process_reader; |
- ASSERT_TRUE(process_reader.Initialize(ChildTask())); |
- |
- int read_fd = ReadPipeFD(); |
- |
- // Build a map of all expected threads, keyed by each thread’s ID, and with |
- // addresses that should lie somewhere within each thread’s 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; |
- CheckedReadFD(read_fd, &thread_id, sizeof(thread_id)); |
- |
- TestThreadPool::ThreadExpectation expectation; |
- CheckedReadFD(read_fd, |
- &expectation.stack_address, |
- sizeof(expectation.stack_address)); |
- CheckedReadFD(read_fd, |
- &expectation.suspend_count, |
- sizeof(expectation.suspend_count)); |
- |
- // There can’t be any duplicate thread IDs. |
- EXPECT_EQ(0u, thread_map.count(thread_id)); |
- |
- thread_map[thread_id] = expectation; |
- } |
- |
- const std::vector<ProcessReader::Thread>& threads = process_reader.Threads(); |
- |
- // The child shouldn’t 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); |
- } |
- |
- void MachMultiprocessChild() override { |
- TestThreadPool thread_pool; |
- ASSERT_NO_FATAL_FAILURE(thread_pool.StartThreads(thread_count_)); |
- |
- int write_fd = WritePipeFD(); |
- |
- // This thread isn’t 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()); |
- |
- CheckedWriteFD(write_fd, &thread_id, sizeof(thread_id)); |
- |
- TestThreadPool::ThreadExpectation expectation; |
- expectation.stack_address = reinterpret_cast<mach_vm_address_t>(&thread_id); |
- expectation.suspend_count = 0; |
- |
- CheckedWriteFD(write_fd, |
- &expectation.stack_address, |
- sizeof(expectation.stack_address)); |
- CheckedWriteFD(write_fd, |
- &expectation.suspend_count, |
- sizeof(expectation.suspend_count)); |
- |
- // 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); |
- |
- CheckedWriteFD(write_fd, &thread_id, sizeof(thread_id)); |
- CheckedWriteFD(write_fd, |
- &expectation.stack_address, |
- sizeof(expectation.stack_address)); |
- CheckedWriteFD(write_fd, |
- &expectation.suspend_count, |
- sizeof(expectation.suspend_count)); |
- } |
- |
- // Wait for the parent to signal that it’s OK to exit by closing its end of |
- // the pipe. |
- CheckedReadFDAtEOF(ReadPipeFD()); |
- } |
- |
- 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(); |
-} |
- |
-TEST(ProcessReader, SelfModules) { |
- ProcessReader process_reader; |
- ASSERT_TRUE(process_reader.Initialize(mach_task_self())); |
- |
- uint32_t dyld_image_count = _dyld_image_count(); |
- const std::vector<ProcessReader::Module>& modules = process_reader.Modules(); |
- |
- // There needs to be at least an entry for the main executable, for a dylib, |
- // and for dyld. |
- ASSERT_GE(modules.size(), 3u); |
- |
- // dyld_image_count doesn’t include an entry for dyld itself, but |modules| |
- // does. |
- ASSERT_EQ(dyld_image_count + 1, modules.size()); |
- |
- for (uint32_t index = 0; index < dyld_image_count; ++index) { |
- SCOPED_TRACE(base::StringPrintf( |
- "index %u, name %s", index, modules[index].name.c_str())); |
- |
- const char* dyld_image_name = _dyld_get_image_name(index); |
- EXPECT_EQ(dyld_image_name, modules[index].name); |
- EXPECT_EQ( |
- reinterpret_cast<mach_vm_address_t>(_dyld_get_image_header(index)), |
- modules[index].reader->Address()); |
- |
- if (index == 0) { |
- // dyld didn’t load the main executable, so it couldn’t record its |
- // timestamp, and it is reported as 0. |
- EXPECT_EQ(0, modules[index].timestamp); |
- } else { |
- // Hope that the module didn’t change on disk. |
- struct stat stat_buf; |
- int rv = stat(dyld_image_name, &stat_buf); |
- EXPECT_EQ(0, rv) << ErrnoMessage("stat"); |
- if (rv == 0) { |
- EXPECT_EQ(stat_buf.st_mtime, modules[index].timestamp); |
- } |
- } |
- } |
- |
- size_t index = modules.size() - 1; |
- EXPECT_EQ("/usr/lib/dyld", modules[index].name); |
- |
- // dyld didn’t load itself either, so it couldn’t record its timestamp, and it |
- // is also reported as 0. |
- EXPECT_EQ(0, modules[index].timestamp); |
- |
- const struct dyld_all_image_infos* dyld_image_infos = |
- _dyld_get_all_image_infos(); |
- if (dyld_image_infos->version >= 2) { |
- EXPECT_EQ( |
- reinterpret_cast<mach_vm_address_t>( |
- dyld_image_infos->dyldImageLoadAddress), |
- modules[index].reader->Address()); |
- } |
-} |
- |
-class ProcessReaderModulesChild final : public MachMultiprocess { |
- public: |
- ProcessReaderModulesChild() : MachMultiprocess() {} |
- |
- ~ProcessReaderModulesChild() {} |
- |
- private: |
- void MachMultiprocessParent() override { |
- ProcessReader process_reader; |
- ASSERT_TRUE(process_reader.Initialize(ChildTask())); |
- |
- const std::vector<ProcessReader::Module>& modules = |
- process_reader.Modules(); |
- |
- // There needs to be at least an entry for the main executable, for a dylib, |
- // and for dyld. |
- ASSERT_GE(modules.size(), 3u); |
- |
- int read_fd = ReadPipeFD(); |
- |
- uint32_t expect_modules; |
- CheckedReadFD(read_fd, &expect_modules, sizeof(expect_modules)); |
- |
- ASSERT_EQ(expect_modules, modules.size()); |
- |
- for (size_t index = 0; index < modules.size(); ++index) { |
- SCOPED_TRACE(base::StringPrintf( |
- "index %zu, name %s", index, modules[index].name.c_str())); |
- |
- uint32_t expect_name_length; |
- CheckedReadFD( |
- read_fd, &expect_name_length, sizeof(expect_name_length)); |
- |
- // The NUL terminator is not read. |
- std::string expect_name(expect_name_length, '\0'); |
- CheckedReadFD(read_fd, &expect_name[0], expect_name_length); |
- EXPECT_EQ(expect_name, modules[index].name); |
- |
- mach_vm_address_t expect_address; |
- CheckedReadFD(read_fd, &expect_address, sizeof(expect_address)); |
- EXPECT_EQ(expect_address, modules[index].reader->Address()); |
- |
- if (index == 0 || index == modules.size() - 1) { |
- // dyld didn’t load the main executable or itself, so it couldn’t record |
- // these timestamps, and they are reported as 0. |
- EXPECT_EQ(0, modules[index].timestamp); |
- } else { |
- // Hope that the module didn’t change on disk. |
- struct stat stat_buf; |
- int rv = stat(expect_name.c_str(), &stat_buf); |
- EXPECT_EQ(0, rv) << ErrnoMessage("stat"); |
- if (rv == 0) { |
- EXPECT_EQ(stat_buf.st_mtime, modules[index].timestamp); |
- } |
- } |
- } |
- } |
- |
- void MachMultiprocessChild() override { |
- int write_fd = WritePipeFD(); |
- |
- uint32_t dyld_image_count = _dyld_image_count(); |
- const struct dyld_all_image_infos* dyld_image_infos = |
- _dyld_get_all_image_infos(); |
- |
- uint32_t write_image_count = dyld_image_count; |
- if (dyld_image_infos->version >= 2) { |
- // dyld_image_count doesn’t include an entry for dyld itself, but one will |
- // be written. |
- ++write_image_count; |
- } |
- |
- CheckedWriteFD(write_fd, &write_image_count, sizeof(write_image_count)); |
- |
- for (size_t index = 0; index < write_image_count; ++index) { |
- const char* dyld_image_name; |
- mach_vm_address_t dyld_image_address; |
- |
- if (index < dyld_image_count) { |
- dyld_image_name = _dyld_get_image_name(index); |
- dyld_image_address = |
- reinterpret_cast<mach_vm_address_t>(_dyld_get_image_header(index)); |
- } else { |
- dyld_image_name = "/usr/lib/dyld"; |
- dyld_image_address = reinterpret_cast<mach_vm_address_t>( |
- dyld_image_infos->dyldImageLoadAddress); |
- } |
- |
- uint32_t dyld_image_name_length = strlen(dyld_image_name); |
- CheckedWriteFD( |
- write_fd, &dyld_image_name_length, sizeof(dyld_image_name_length)); |
- |
- // The NUL terminator is not written. |
- CheckedWriteFD(write_fd, dyld_image_name, dyld_image_name_length); |
- |
- CheckedWriteFD(write_fd, &dyld_image_address, sizeof(dyld_image_address)); |
- } |
- |
- // Wait for the parent to signal that it’s OK to exit by closing its end of |
- // the pipe. |
- CheckedReadFDAtEOF(ReadPipeFD()); |
- } |
- |
- DISALLOW_COPY_AND_ASSIGN(ProcessReaderModulesChild); |
-}; |
- |
-TEST(ProcessReader, ChildModules) { |
- ProcessReaderModulesChild process_reader_modules_child; |
- process_reader_modules_child.Run(); |
-} |
- |
-} // namespace |
-} // namespace test |
-} // namespace crashpad |