// 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/process_reader_mac.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "base/check_op.h" #include "base/cxx17_backports.h" #include "base/logging.h" #include "base/mac/mach_logging.h" #include "base/posix/eintr_wrapper.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" #include "snapshot/mac/mach_o_image_reader.h" #include "snapshot/mac/mach_o_image_segment_reader.h" #include "test/errors.h" #include "test/mac/dyld.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/mach_extensions.h" #include "util/misc/from_pointer_cast.h" #include "util/synchronization/semaphore.h" namespace crashpad { namespace test { namespace { constexpr char kDyldPath[] = "/usr/lib/dyld"; TEST(ProcessReaderMac, SelfBasic) { ProcessReaderMac 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(process_reader.ProcessID(), getpid()); EXPECT_EQ(process_reader.ParentProcessID(), getppid()); static constexpr char kTestMemory[] = "Some test memory"; char buffer[base::size(kTestMemory)]; ASSERT_TRUE(process_reader.Memory()->Read( FromPointerCast(kTestMemory), sizeof(kTestMemory), &buffer)); EXPECT_STREQ(kTestMemory, buffer); } constexpr char kTestMemory[] = "Read me from another process"; class ProcessReaderChild final : public MachMultiprocess { public: ProcessReaderChild() : MachMultiprocess() {} ~ProcessReaderChild() {} private: void MachMultiprocessParent() override { ProcessReaderMac 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(process_reader.ParentProcessID(), getpid()); EXPECT_EQ(process_reader.ProcessID(), ChildPID()); FileHandle read_handle = ReadPipeHandle(); mach_vm_address_t address; CheckedReadFileExactly(read_handle, &address, sizeof(address)); std::string read_string; ASSERT_TRUE(process_reader.Memory()->ReadCString(address, &read_string)); EXPECT_EQ(read_string, kTestMemory); } void MachMultiprocessChild() override { FileHandle write_handle = WritePipeHandle(); mach_vm_address_t address = FromPointerCast(kTestMemory); CheckedWriteFile(write_handle, &address, sizeof(address)); // Wait for the parent to signal that it’s OK to exit by closing its end of // the pipe. CheckedReadFileAtEOF(ReadPipeHandle()); } DISALLOW_COPY_AND_ASSIGN(ProcessReaderChild); }; TEST(ProcessReaderMac, 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; errno = pthread_threadid_np(pthread, &thread_id); PCHECK(errno == 0) << "pthread_threadid_np"; return thread_id; } TEST(ProcessReaderMac, SelfOneThread) { ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(mach_task_self())); const std::vector& 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(threads[0].id, PthreadToThreadID(pthread_self())); thread_t thread_self = MachThreadSelf(); EXPECT_EQ(threads[0].port, thread_self); EXPECT_EQ(threads[0].suspend_count, 0); } 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 (const auto& 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(kr, KERN_SUCCESS) << MachErrorMessage(kr, "thread_resume"); --thread_info->suspend_count; } } for (const auto& thread_info : thread_infos_) { thread_info->exit_semaphore.Signal(); } for (const auto& 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) { thread_infos_.push_back(std::make_unique()); ThreadInfo* thread_info = thread_infos_.back().get(); int rv = pthread_create( &thread_info->pthread, nullptr, ThreadMain, thread_info); ASSERT_EQ(rv, 0); } for (const auto& 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(kr, KERN_SUCCESS) << 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 auto& 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(argument); thread_info->stack_address = FromPointerCast(&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 vector of pointers because the address of a ThreadInfo object is // passed to each thread’s ThreadMain(), so they cannot move around in memory. std::vector> thread_infos_; DISALLOW_COPY_AND_ASSIGN(TestThreadPool); }; using ThreadMap = std::map; // Verifies that all of the threads in |threads|, obtained from // ProcessReaderMac, 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& threads, const bool tolerate_extra_threads) { if (tolerate_extra_threads) { ASSERT_GE(threads.size(), thread_map->size()); } else { ASSERT_EQ(threads.size(), thread_map->size()); } for (size_t thread_index = 0; thread_index < threads.size(); ++thread_index) { const ProcessReaderMac::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(iterator, thread_map->end()); } 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(thread.suspend_count, iterator->second.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 (other_thread_index == thread_index) { continue; } const ProcessReaderMac::Thread& other_thread = threads[other_thread_index]; EXPECT_NE(other_thread.id, thread.id); EXPECT_NE(other_thread.port, 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(ProcessReaderMac, SelfSeveralThreads) { // Set up the ProcessReaderMac 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. ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(mach_task_self())); TestThreadPool thread_pool; constexpr 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 = FromPointerCast(&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(thread_map.count(thread_id), 0u); thread_map[thread_id] = expectation; } const std::vector& 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 ProcessReaderMac::Thread& thread : threads) { if (thread.port == thread_self) { EXPECT_FALSE(found_thread_self); found_thread_self = true; EXPECT_EQ(thread.id, self_thread_id); } } EXPECT_TRUE(found_thread_self); } uint64_t GetThreadID() { thread_identifier_info info; mach_msg_type_number_t info_count = THREAD_IDENTIFIER_INFO_COUNT; kern_return_t kr = thread_info(MachThreadSelf(), THREAD_IDENTIFIER_INFO, reinterpret_cast(&info), &info_count); MACH_CHECK(kr == KERN_SUCCESS, kr) << "thread_info"; return info.thread_id; } class ProcessReaderThreadedChild final : public MachMultiprocess { public: explicit ProcessReaderThreadedChild(size_t thread_count) : MachMultiprocess(), thread_count_(thread_count) {} ~ProcessReaderThreadedChild() {} private: void MachMultiprocessParent() override { ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(ChildTask())); FileHandle read_handle = ReadPipeHandle(); // 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; CheckedReadFileExactly(read_handle, &thread_id, sizeof(thread_id)); TestThreadPool::ThreadExpectation expectation; CheckedReadFileExactly(read_handle, &expectation.stack_address, sizeof(expectation.stack_address)); CheckedReadFileExactly(read_handle, &expectation.suspend_count, sizeof(expectation.suspend_count)); // There can’t be any duplicate thread IDs. EXPECT_EQ(thread_map.count(thread_id), 0u); thread_map[thread_id] = expectation; } const std::vector& 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_)); FileHandle write_handle = WritePipeHandle(); // 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 = GetThreadID(); CheckedWriteFile(write_handle, &thread_id, sizeof(thread_id)); TestThreadPool::ThreadExpectation expectation; expectation.stack_address = FromPointerCast(&thread_id); expectation.suspend_count = 0; CheckedWriteFile(write_handle, &expectation.stack_address, sizeof(expectation.stack_address)); CheckedWriteFile(write_handle, &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); CheckedWriteFile(write_handle, &thread_id, sizeof(thread_id)); CheckedWriteFile(write_handle, &expectation.stack_address, sizeof(expectation.stack_address)); CheckedWriteFile(write_handle, &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. CheckedReadFileAtEOF(ReadPipeHandle()); } size_t thread_count_; DISALLOW_COPY_AND_ASSIGN(ProcessReaderThreadedChild); }; TEST(ProcessReaderMac, ChildOneThread) { // The main thread plus zero child threads equals one thread. constexpr size_t kChildThreads = 0; ProcessReaderThreadedChild process_reader_threaded_child(kChildThreads); process_reader_threaded_child.Run(); } TEST(ProcessReaderMac, ChildSeveralThreads) { constexpr size_t kChildThreads = 64; ProcessReaderThreadedChild process_reader_threaded_child(kChildThreads); process_reader_threaded_child.Run(); } template T GetDyldFunction(const char* symbol) { static void* dl_handle = []() -> void* { Dl_info dl_info; if (!dladdr(reinterpret_cast(dlopen), &dl_info)) { LOG(ERROR) << "dladdr: failed"; return nullptr; } void* dl_handle = dlopen(dl_info.dli_fname, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); DCHECK(dl_handle) << "dlopen: " << dlerror(); return dl_handle; }(); if (!dl_handle) { return nullptr; } return reinterpret_cast(dlsym(dl_handle, symbol)); } void VerifyImageExistenceAndTimestamp(const char* path, time_t timestamp) { const char* stat_path; bool timestamp_may_be_0; #if __MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_16 static auto _dyld_shared_cache_contains_path = GetDyldFunction( "_dyld_shared_cache_contains_path"); #endif #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" if (_dyld_shared_cache_contains_path && _dyld_shared_cache_contains_path(path)) { #pragma clang diagnostic pop // The timestamp will either match the timestamp of the dyld_shared_cache // file in use, or be 0. static const char* dyld_shared_cache_file_path = []() -> const char* { auto dyld_shared_cache_file_path_f = GetDyldFunction("dyld_shared_cache_file_path"); // dyld_shared_cache_file_path should always be present if // _dyld_shared_cache_contains_path is. DCHECK(dyld_shared_cache_file_path_f); const char* dyld_shared_cache_file_path = dyld_shared_cache_file_path_f(); DCHECK(dyld_shared_cache_file_path); return dyld_shared_cache_file_path; }(); stat_path = dyld_shared_cache_file_path; timestamp_may_be_0 = true; } else { stat_path = path; timestamp_may_be_0 = false; } struct stat stat_buf; int rv = stat(stat_path, &stat_buf); EXPECT_EQ(rv, 0) << ErrnoMessage("stat"); if (rv == 0 && (!timestamp_may_be_0 || timestamp != 0)) { EXPECT_EQ(timestamp, stat_buf.st_mtime); } } // cl_kernels images (OpenCL kernels) are weird. They’re not ld output and don’t // exist as files on disk. On OS X 10.10 and 10.11, their Mach-O structure isn’t // perfect. They show up loaded into many executables, so these quirks should be // tolerated. // // Create an object of this class to ensure that at least one cl_kernels image // is present in a process, to be able to test that all of the process-reading // machinery tolerates them. On systems where cl_kernels modules have known // quirks, the image that an object of this class produces will also have those // quirks. // // https://openradar.appspot.com/20239912 class ScopedOpenCLNoOpKernel { public: ScopedOpenCLNoOpKernel() : context_(nullptr), program_(nullptr), kernel_(nullptr), success_(false) {} ~ScopedOpenCLNoOpKernel() { if (kernel_) { cl_int rv = clReleaseKernel(kernel_); EXPECT_EQ(rv, CL_SUCCESS) << "clReleaseKernel"; } if (program_) { cl_int rv = clReleaseProgram(program_); EXPECT_EQ(rv, CL_SUCCESS) << "clReleaseProgram"; } if (context_) { cl_int rv = clReleaseContext(context_); EXPECT_EQ(rv, CL_SUCCESS) << "clReleaseContext"; } } void SetUp() { cl_platform_id platform_id; cl_int rv = clGetPlatformIDs(1, &platform_id, nullptr); ASSERT_EQ(rv, CL_SUCCESS) << "clGetPlatformIDs"; #if __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_10 && \ __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_10 // cl_device_id is really available in OpenCL.framework back to 10.5, but in // the 10.10 SDK and later, OpenCL.framework includes , // which has its own cl_device_id that was introduced in 10.10. That // triggers erroneous availability warnings. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" #define DISABLED_WUNGUARDED_AVAILABILITY #endif // SDK >= 10.10 && DT < 10.10 // Use CL_DEVICE_TYPE_CPU to ensure that the kernel would execute on the // CPU. This is the only device type that a cl_kernels image will be created // for. cl_device_id device_id; #if defined(DISABLED_WUNGUARDED_AVAILABILITY) #pragma clang diagnostic pop #undef DISABLED_WUNGUARDED_AVAILABILITY #endif // DISABLED_WUNGUARDED_AVAILABILITY rv = clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_CPU, 1, &device_id, nullptr); #if defined(ARCH_CPU_ARM64) // CL_DEVICE_TYPE_CPU doesn’t seem to work at all on arm64, meaning that // these weird OpenCL modules probably don’t show up there at all. Keep this // test even on arm64 in case this ever does start working. if (rv == CL_INVALID_VALUE) { return; } #endif // ARCH_CPU_ARM64 ASSERT_EQ(rv, CL_SUCCESS) << "clGetDeviceIDs"; context_ = clCreateContext(nullptr, 1, &device_id, nullptr, nullptr, &rv); ASSERT_EQ(rv, CL_SUCCESS) << "clCreateContext"; // The goal of the program in |sources| is to produce a cl_kernels image // that doesn’t strictly conform to Mach-O expectations. On OS X 10.10, // cl_kernels modules show up with an __LD,__compact_unwind section, showing // up in the __TEXT segment. MachOImageSegmentReader would normally reject // modules for this problem, but a special exception is made when this // occurs in cl_kernels images. This portion of the test is aimed at making // sure that this exception works correctly. // // A true no-op program doesn’t actually produce unwind data, so there would // be no errant __LD,__compact_unwind section on 10.10, and the test // wouldn’t be complete. This simple no-op, which calls a built-in function, // does produce unwind data provided optimization is disabled. // "-cl-opt-disable" is given to clBuildProgram() below. const char* sources[] = { "__kernel void NoOp(void) {barrier(CLK_LOCAL_MEM_FENCE);}", }; const size_t source_lengths[] = { strlen(sources[0]), }; static_assert(base::size(sources) == base::size(source_lengths), "arrays must be parallel"); program_ = clCreateProgramWithSource( context_, base::size(sources), sources, source_lengths, &rv); ASSERT_EQ(rv, CL_SUCCESS) << "clCreateProgramWithSource"; rv = clBuildProgram( program_, 1, &device_id, "-cl-opt-disable", nullptr, nullptr); ASSERT_EQ(rv, CL_SUCCESS) << "clBuildProgram"; kernel_ = clCreateKernel(program_, "NoOp", &rv); ASSERT_EQ(rv, CL_SUCCESS) << "clCreateKernel"; success_ = true; } bool success() const { return success_; } private: cl_context context_; cl_program program_; cl_kernel kernel_; bool success_; DISALLOW_COPY_AND_ASSIGN(ScopedOpenCLNoOpKernel); }; // Although Mac OS X 10.6 has OpenCL and can compile and execute OpenCL code, // OpenCL kernels that run on the CPU do not result in cl_kernels images // appearing on that OS version. bool ExpectCLKernels() { return __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_7 || MacOSVersionNumber() >= 10'07'00; } TEST(ProcessReaderMac, SelfModules) { ScopedOpenCLNoOpKernel ensure_cl_kernels; ASSERT_NO_FATAL_FAILURE(ensure_cl_kernels.SetUp()); ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(mach_task_self())); uint32_t dyld_image_count = _dyld_image_count(); const std::vector& 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(modules.size(), dyld_image_count + 1); bool found_cl_kernels = false; 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(modules[index].name, dyld_image_name); ASSERT_TRUE(modules[index].reader); EXPECT_EQ( modules[index].reader->Address(), FromPointerCast(_dyld_get_image_header(index))); bool expect_timestamp; 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(modules[index].timestamp, 0); } else if (IsMalformedCLKernelsModule(modules[index].reader->FileType(), modules[index].name, &expect_timestamp)) { // cl_kernels doesn’t exist as a file, but may still have a timestamp. if (!expect_timestamp) { EXPECT_EQ(modules[index].timestamp, 0); } else { EXPECT_NE(modules[index].timestamp, 0); } found_cl_kernels = true; } else { // Hope that the module didn’t change on disk. VerifyImageExistenceAndTimestamp(dyld_image_name, modules[index].timestamp); } } EXPECT_EQ(found_cl_kernels, ExpectCLKernels() && ensure_cl_kernels.success()); size_t index = modules.size() - 1; EXPECT_EQ(modules[index].name, kDyldPath); // dyld didn’t load itself either, so it couldn’t record its timestamp, and it // is also reported as 0. EXPECT_EQ(modules[index].timestamp, 0); const dyld_all_image_infos* dyld_image_infos = DyldGetAllImageInfos(); if (dyld_image_infos->version >= 2) { ASSERT_TRUE(modules[index].reader); EXPECT_EQ(modules[index].reader->Address(), FromPointerCast( dyld_image_infos->dyldImageLoadAddress)); } } class ProcessReaderModulesChild final : public MachMultiprocess { public: explicit ProcessReaderModulesChild(bool ensure_cl_kernels_success) : MachMultiprocess(), ensure_cl_kernels_success_(ensure_cl_kernels_success) {} ~ProcessReaderModulesChild() {} private: void MachMultiprocessParent() override { ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(ChildTask())); const std::vector& 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); FileHandle read_handle = ReadPipeHandle(); uint32_t expect_modules; CheckedReadFileExactly( read_handle, &expect_modules, sizeof(expect_modules)); ASSERT_EQ(modules.size(), expect_modules); bool found_cl_kernels = false; 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; CheckedReadFileExactly( read_handle, &expect_name_length, sizeof(expect_name_length)); // The NUL terminator is not read. std::string expect_name(expect_name_length, '\0'); CheckedReadFileExactly(read_handle, &expect_name[0], expect_name_length); EXPECT_EQ(modules[index].name, expect_name); mach_vm_address_t expect_address; CheckedReadFileExactly( read_handle, &expect_address, sizeof(expect_address)); ASSERT_TRUE(modules[index].reader); EXPECT_EQ(modules[index].reader->Address(), expect_address); bool expect_timestamp; 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(modules[index].timestamp, 0); } else if (IsMalformedCLKernelsModule(modules[index].reader->FileType(), modules[index].name, &expect_timestamp)) { // cl_kernels doesn’t exist as a file, but may still have a timestamp. if (!expect_timestamp) { EXPECT_EQ(modules[index].timestamp, 0); } else { EXPECT_NE(modules[index].timestamp, 0); } found_cl_kernels = true; } else { // Hope that the module didn’t change on disk. VerifyImageExistenceAndTimestamp(expect_name.c_str(), modules[index].timestamp); } } EXPECT_EQ(found_cl_kernels, ExpectCLKernels() && ensure_cl_kernels_success_); } void MachMultiprocessChild() override { FileHandle write_handle = WritePipeHandle(); uint32_t dyld_image_count = _dyld_image_count(); const dyld_all_image_infos* dyld_image_infos = DyldGetAllImageInfos(); 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; } CheckedWriteFile( write_handle, &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 = FromPointerCast(_dyld_get_image_header(index)); } else { dyld_image_name = kDyldPath; dyld_image_address = FromPointerCast( dyld_image_infos->dyldImageLoadAddress); } uint32_t dyld_image_name_length = strlen(dyld_image_name); CheckedWriteFile(write_handle, &dyld_image_name_length, sizeof(dyld_image_name_length)); // The NUL terminator is not written. CheckedWriteFile(write_handle, dyld_image_name, dyld_image_name_length); CheckedWriteFile( write_handle, &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. CheckedReadFileAtEOF(ReadPipeHandle()); } bool ensure_cl_kernels_success_; DISALLOW_COPY_AND_ASSIGN(ProcessReaderModulesChild); }; TEST(ProcessReaderMac, ChildModules) { ScopedOpenCLNoOpKernel ensure_cl_kernels; ASSERT_NO_FATAL_FAILURE(ensure_cl_kernels.SetUp()); ProcessReaderModulesChild process_reader_modules_child( ensure_cl_kernels.success()); process_reader_modules_child.Run(); } } // namespace } // namespace test } // namespace crashpad