| // Copyright 2017 The Crashpad Authors |
| // |
| // 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/linux/process_reader_linux.h" |
| |
| #include <dlfcn.h> |
| #include <elf.h> |
| #include <errno.h> |
| #include <link.h> |
| #include <pthread.h> |
| #include <sched.h> |
| #include <stdlib.h> |
| #include <sys/mman.h> |
| #include <sys/resource.h> |
| #include <sys/syscall.h> |
| #include <unistd.h> |
| |
| #include <iterator> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/format_macros.h" |
| #include "base/memory/free_deleter.h" |
| #include "base/strings/stringprintf.h" |
| #include "build/build_config.h" |
| #include "gtest/gtest.h" |
| #include "snapshot/linux/test_modules.h" |
| #include "test/errors.h" |
| #include "test/linux/fake_ptrace_connection.h" |
| #include "test/linux/get_tls.h" |
| #include "test/multiprocess.h" |
| #include "test/scoped_module_handle.h" |
| #include "test/scoped_set_thread_name.h" |
| #include "test/test_paths.h" |
| #include "util/file/file_io.h" |
| #include "util/file/file_writer.h" |
| #include "util/file/filesystem.h" |
| #include "util/linux/direct_ptrace_connection.h" |
| #include "util/misc/address_sanitizer.h" |
| #include "util/misc/from_pointer_cast.h" |
| #include "util/misc/memory_sanitizer.h" |
| #include "util/posix/scoped_mmap.h" |
| #include "util/synchronization/semaphore.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include <android/api-level.h> |
| #include <android/set_abort_message.h> |
| #include "dlfcn_internal.h" |
| |
| // Normally this comes from set_abort_message.h, but only at API level 21. |
| extern "C" void android_set_abort_message(const char* msg) |
| __attribute__((weak)); |
| #endif |
| |
| namespace crashpad { |
| namespace test { |
| namespace { |
| |
| pid_t gettid() { |
| return syscall(SYS_gettid); |
| } |
| |
| TEST(ProcessReaderLinux, SelfBasic) { |
| FakePtraceConnection connection; |
| connection.Initialize(getpid()); |
| |
| ProcessReaderLinux process_reader; |
| ASSERT_TRUE(process_reader.Initialize(&connection)); |
| |
| #if defined(ARCH_CPU_64_BITS) |
| EXPECT_TRUE(process_reader.Is64Bit()); |
| #else |
| EXPECT_FALSE(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[std::size(kTestMemory)]; |
| ASSERT_TRUE(process_reader.Memory()->Read( |
| reinterpret_cast<LinuxVMAddress>(kTestMemory), |
| sizeof(kTestMemory), |
| &buffer)); |
| EXPECT_STREQ(kTestMemory, buffer); |
| |
| EXPECT_EQ("", process_reader.AbortMessage()); |
| } |
| |
| constexpr char kTestMemory[] = "Read me from another process"; |
| |
| class BasicChildTest : public Multiprocess { |
| public: |
| BasicChildTest() : Multiprocess() {} |
| |
| BasicChildTest(const BasicChildTest&) = delete; |
| BasicChildTest& operator=(const BasicChildTest&) = delete; |
| |
| ~BasicChildTest() {} |
| |
| private: |
| void MultiprocessParent() override { |
| DirectPtraceConnection connection; |
| ASSERT_TRUE(connection.Initialize(ChildPID())); |
| |
| ProcessReaderLinux process_reader; |
| ASSERT_TRUE(process_reader.Initialize(&connection)); |
| |
| #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()); |
| |
| std::string read_string; |
| ASSERT_TRUE(process_reader.Memory()->ReadCString( |
| reinterpret_cast<LinuxVMAddress>(kTestMemory), &read_string)); |
| EXPECT_EQ(read_string, kTestMemory); |
| } |
| |
| void MultiprocessChild() override { CheckedReadFileAtEOF(ReadPipeHandle()); } |
| }; |
| |
| TEST(ProcessReaderLinux, ChildBasic) { |
| BasicChildTest test; |
| test.Run(); |
| } |
| |
| class TestThreadPool { |
| public: |
| struct ThreadExpectation { |
| LinuxVMAddress tls = 0; |
| LinuxVMAddress stack_address = 0; |
| LinuxVMSize max_stack_size = 0; |
| int sched_policy = 0; |
| int static_priority = 0; |
| int nice_value = 0; |
| }; |
| |
| TestThreadPool() : threads_() {} |
| |
| TestThreadPool(const TestThreadPool&) = delete; |
| TestThreadPool& operator=(const TestThreadPool&) = delete; |
| |
| ~TestThreadPool() { |
| for (const auto& thread : threads_) { |
| thread->exit_semaphore.Signal(); |
| } |
| |
| for (const auto& thread : threads_) { |
| EXPECT_EQ(pthread_join(thread->pthread, nullptr), 0) |
| << ErrnoMessage("pthread_join"); |
| } |
| } |
| |
| void StartThreads(size_t thread_count, size_t stack_size = 0) { |
| for (size_t thread_index = 0; thread_index < thread_count; ++thread_index) { |
| const std::string thread_name = |
| base::StringPrintf("ThreadPool-%zu", thread_index); |
| threads_.push_back(std::make_unique<Thread>(thread_name)); |
| Thread* thread = threads_.back().get(); |
| |
| pthread_attr_t attr; |
| ASSERT_EQ(pthread_attr_init(&attr), 0) |
| << ErrnoMessage("pthread_attr_init"); |
| |
| if (stack_size > 0) { |
| const size_t page_size = getpagesize(); |
| DCHECK_EQ(stack_size % page_size, 0u); |
| size_t stack_alloc_size = 2 * page_size + stack_size; |
| |
| ASSERT_TRUE(thread->stack.ResetMmap(nullptr, |
| stack_alloc_size, |
| PROT_NONE, |
| MAP_PRIVATE | MAP_ANONYMOUS, |
| -1, |
| 0)); |
| char* stack_ptr = thread->stack.addr_as<char*>() + page_size; |
| ASSERT_EQ(mprotect(stack_ptr, stack_size, PROT_READ | PROT_WRITE), 0) |
| << "mprotect"; |
| |
| ASSERT_EQ(pthread_attr_setstack(&attr, stack_ptr, stack_size), 0) |
| << ErrnoMessage("pthread_attr_setstack"); |
| thread->expectation.max_stack_size = stack_size; |
| } |
| |
| ASSERT_EQ(pthread_attr_setschedpolicy(&attr, SCHED_OTHER), 0) |
| << ErrnoMessage("pthread_attr_setschedpolicy"); |
| thread->expectation.sched_policy = SCHED_OTHER; |
| |
| sched_param param; |
| param.sched_priority = 0; |
| ASSERT_EQ(pthread_attr_setschedparam(&attr, ¶m), 0) |
| << ErrnoMessage("pthread_attr_setschedparam"); |
| thread->expectation.static_priority = 0; |
| |
| thread->expectation.nice_value = thread_index % 20; |
| |
| ASSERT_EQ(pthread_create(&thread->pthread, &attr, ThreadMain, thread), 0) |
| << ErrnoMessage("pthread_create"); |
| } |
| |
| for (const auto& thread : threads_) { |
| thread->ready_semaphore.Wait(); |
| } |
| } |
| |
| pid_t GetThreadExpectation(size_t thread_index, |
| ThreadExpectation* expectation, |
| std::string* thread_name_expectation) { |
| CHECK_LT(thread_index, threads_.size()); |
| |
| const Thread* thread = threads_[thread_index].get(); |
| *expectation = thread->expectation; |
| *thread_name_expectation = thread->name; |
| return thread->tid; |
| } |
| |
| private: |
| struct Thread { |
| explicit Thread(const std::string& name) |
| : pthread(), |
| expectation(), |
| ready_semaphore(0), |
| exit_semaphore(0), |
| tid(-1), |
| name(name) { |
| } |
| ~Thread() {} |
| |
| pthread_t pthread; |
| ThreadExpectation expectation; |
| ScopedMmap stack; |
| Semaphore ready_semaphore; |
| Semaphore exit_semaphore; |
| pid_t tid; |
| const std::string name; |
| }; |
| |
| static void* ThreadMain(void* argument) { |
| Thread* thread = static_cast<Thread*>(argument); |
| const ScopedSetThreadName scoped_set_thread_name(thread->name); |
| |
| CHECK_EQ(setpriority(PRIO_PROCESS, 0, thread->expectation.nice_value), 0) |
| << ErrnoMessage("setpriority"); |
| |
| thread->expectation.tls = GetTLS(); |
| thread->expectation.stack_address = |
| reinterpret_cast<LinuxVMAddress>(&thread); |
| thread->tid = gettid(); |
| |
| thread->ready_semaphore.Signal(); |
| thread->exit_semaphore.Wait(); |
| |
| CHECK_EQ(pthread_self(), thread->pthread); |
| |
| return nullptr; |
| } |
| |
| std::vector<std::unique_ptr<Thread>> threads_; |
| }; |
| |
| using ThreadMap = std::map<pid_t, TestThreadPool::ThreadExpectation>; |
| using ThreadNameMap = std::map<pid_t, std::string>; |
| |
| void ExpectThreads(const ThreadMap& thread_map, |
| const ThreadNameMap& thread_name_map, |
| const std::vector<ProcessReaderLinux::Thread>& threads, |
| PtraceConnection* connection) { |
| ASSERT_EQ(threads.size(), thread_map.size()); |
| ASSERT_EQ(threads.size(), thread_name_map.size()); |
| |
| MemoryMap memory_map; |
| ASSERT_TRUE(memory_map.Initialize(connection)); |
| |
| for (const auto& thread : threads) { |
| SCOPED_TRACE( |
| base::StringPrintf("Thread id %d, name %s, tls 0x%" PRIx64 |
| ", stack addr 0x%" PRIx64 ", stack size 0x%" PRIx64, |
| thread.tid, |
| thread.name.c_str(), |
| thread.thread_info.thread_specific_data_address, |
| thread.stack_region_address, |
| thread.stack_region_size)); |
| |
| const auto& iterator = thread_map.find(thread.tid); |
| ASSERT_NE(iterator, thread_map.end()); |
| |
| EXPECT_EQ(thread.thread_info.thread_specific_data_address, |
| iterator->second.tls); |
| |
| ASSERT_TRUE(memory_map.FindMapping(thread.stack_region_address)); |
| ASSERT_TRUE(memory_map.FindMapping(thread.stack_region_address + |
| thread.stack_region_size - 1)); |
| |
| #if !defined(ADDRESS_SANITIZER) |
| // AddressSanitizer causes stack variables to be stored separately from the |
| // call stack. |
| EXPECT_LE( |
| thread.stack_region_address, |
| connection->Memory()->PointerToAddress(iterator->second.stack_address)); |
| EXPECT_GE( |
| thread.stack_region_address + thread.stack_region_size, |
| connection->Memory()->PointerToAddress(iterator->second.stack_address)); |
| #endif // !defined(ADDRESS_SANITIZER) |
| |
| if (iterator->second.max_stack_size) { |
| EXPECT_LT(thread.stack_region_size, iterator->second.max_stack_size); |
| } |
| |
| EXPECT_EQ(thread.sched_policy, iterator->second.sched_policy); |
| EXPECT_EQ(thread.static_priority, iterator->second.static_priority); |
| EXPECT_EQ(thread.nice_value, iterator->second.nice_value); |
| |
| const auto& thread_name_iterator = thread_name_map.find(thread.tid); |
| ASSERT_NE(thread_name_iterator, thread_name_map.end()); |
| EXPECT_EQ(thread.name, thread_name_iterator->second); |
| } |
| } |
| |
| class ChildThreadTest : public Multiprocess { |
| public: |
| ChildThreadTest(size_t stack_size = 0) |
| : Multiprocess(), stack_size_(stack_size) {} |
| |
| ChildThreadTest(const ChildThreadTest&) = delete; |
| ChildThreadTest& operator=(const ChildThreadTest&) = delete; |
| |
| ~ChildThreadTest() {} |
| |
| private: |
| void MultiprocessParent() override { |
| ThreadMap thread_map; |
| ThreadNameMap thread_name_map; |
| for (size_t thread_index = 0; thread_index < kThreadCount + 1; |
| ++thread_index) { |
| pid_t tid; |
| TestThreadPool::ThreadExpectation expectation; |
| |
| CheckedReadFileExactly(ReadPipeHandle(), &tid, sizeof(tid)); |
| CheckedReadFileExactly( |
| ReadPipeHandle(), &expectation, sizeof(expectation)); |
| thread_map[tid] = expectation; |
| |
| std::string::size_type thread_name_length; |
| CheckedReadFileExactly( |
| ReadPipeHandle(), &thread_name_length, sizeof(thread_name_length)); |
| std::string thread_name(thread_name_length, '\0'); |
| CheckedReadFileExactly( |
| ReadPipeHandle(), thread_name.data(), thread_name_length); |
| thread_name_map[tid] = thread_name; |
| } |
| |
| DirectPtraceConnection connection; |
| ASSERT_TRUE(connection.Initialize(ChildPID())); |
| |
| ProcessReaderLinux process_reader; |
| ASSERT_TRUE(process_reader.Initialize(&connection)); |
| const std::vector<ProcessReaderLinux::Thread>& threads = |
| process_reader.Threads(); |
| ExpectThreads(thread_map, thread_name_map, threads, &connection); |
| } |
| |
| void MultiprocessChild() override { |
| TestThreadPool thread_pool; |
| thread_pool.StartThreads(kThreadCount, stack_size_); |
| |
| const std::string current_thread_name = "MultiprocChild"; |
| const ScopedSetThreadName scoped_set_thread_name(current_thread_name); |
| |
| TestThreadPool::ThreadExpectation expectation; |
| #if defined(MEMORY_SANITIZER) |
| // memset() + re-initialization is required to zero padding bytes for MSan. |
| memset(&expectation, 0, sizeof(expectation)); |
| #endif // defined(MEMORY_SANITIZER) |
| expectation = {0}; |
| expectation.tls = GetTLS(); |
| expectation.stack_address = reinterpret_cast<LinuxVMAddress>(&thread_pool); |
| |
| int res = sched_getscheduler(0); |
| ASSERT_GE(res, 0) << ErrnoMessage("sched_getscheduler"); |
| expectation.sched_policy = res; |
| |
| sched_param param; |
| ASSERT_EQ(sched_getparam(0, ¶m), 0) << ErrnoMessage("sched_getparam"); |
| expectation.static_priority = param.sched_priority; |
| |
| errno = 0; |
| res = getpriority(PRIO_PROCESS, 0); |
| ASSERT_FALSE(res == -1 && errno) << ErrnoMessage("getpriority"); |
| expectation.nice_value = res; |
| |
| pid_t tid = gettid(); |
| |
| CheckedWriteFile(WritePipeHandle(), &tid, sizeof(tid)); |
| CheckedWriteFile(WritePipeHandle(), &expectation, sizeof(expectation)); |
| const std::string::size_type current_thread_name_length = |
| current_thread_name.length(); |
| CheckedWriteFile(WritePipeHandle(), |
| ¤t_thread_name_length, |
| sizeof(current_thread_name_length)); |
| CheckedWriteFile(WritePipeHandle(), |
| current_thread_name.data(), |
| current_thread_name_length); |
| |
| for (size_t thread_index = 0; thread_index < kThreadCount; ++thread_index) { |
| std::string thread_name_expectation; |
| tid = thread_pool.GetThreadExpectation( |
| thread_index, &expectation, &thread_name_expectation); |
| CheckedWriteFile(WritePipeHandle(), &tid, sizeof(tid)); |
| CheckedWriteFile(WritePipeHandle(), &expectation, sizeof(expectation)); |
| const std::string::size_type thread_name_length = |
| thread_name_expectation.length(); |
| CheckedWriteFile( |
| WritePipeHandle(), &thread_name_length, sizeof(thread_name_length)); |
| CheckedWriteFile(WritePipeHandle(), |
| thread_name_expectation.data(), |
| thread_name_length); |
| } |
| |
| CheckedReadFileAtEOF(ReadPipeHandle()); |
| } |
| |
| static constexpr size_t kThreadCount = 3; |
| const size_t stack_size_; |
| }; |
| |
| TEST(ProcessReaderLinux, ChildWithThreads) { |
| ChildThreadTest test; |
| test.Run(); |
| } |
| |
| TEST(ProcessReaderLinux, ChildThreadsWithSmallUserStacks) { |
| ChildThreadTest test(PTHREAD_STACK_MIN); |
| test.Run(); |
| } |
| |
| // Tests a thread with a stack that spans multiple mappings. |
| class ChildWithSplitStackTest : public Multiprocess { |
| public: |
| ChildWithSplitStackTest() : Multiprocess(), page_size_(getpagesize()) {} |
| |
| ChildWithSplitStackTest(const ChildWithSplitStackTest&) = delete; |
| ChildWithSplitStackTest& operator=(const ChildWithSplitStackTest&) = delete; |
| |
| ~ChildWithSplitStackTest() {} |
| |
| private: |
| void MultiprocessParent() override { |
| LinuxVMAddress stack_addr1; |
| LinuxVMAddress stack_addr2; |
| LinuxVMAddress stack_addr3; |
| |
| CheckedReadFileExactly(ReadPipeHandle(), &stack_addr1, sizeof(stack_addr1)); |
| CheckedReadFileExactly(ReadPipeHandle(), &stack_addr2, sizeof(stack_addr2)); |
| CheckedReadFileExactly(ReadPipeHandle(), &stack_addr3, sizeof(stack_addr3)); |
| |
| DirectPtraceConnection connection; |
| ASSERT_TRUE(connection.Initialize(ChildPID())); |
| |
| ProcessReaderLinux process_reader; |
| ASSERT_TRUE(process_reader.Initialize(&connection)); |
| |
| const std::vector<ProcessReaderLinux::Thread>& threads = |
| process_reader.Threads(); |
| ASSERT_EQ(threads.size(), 1u); |
| |
| LinuxVMAddress thread_stack_start = threads[0].stack_region_address; |
| EXPECT_LE(thread_stack_start, stack_addr1); |
| EXPECT_LE(thread_stack_start, stack_addr2); |
| EXPECT_LE(thread_stack_start, stack_addr3); |
| |
| LinuxVMAddress thread_stack_end = |
| thread_stack_start + threads[0].stack_region_size; |
| EXPECT_GE(thread_stack_end, stack_addr1); |
| EXPECT_GE(thread_stack_end, stack_addr2); |
| EXPECT_GE(thread_stack_end, stack_addr3); |
| } |
| |
| void MultiprocessChild() override { |
| const LinuxVMSize stack_size = page_size_ * 4; |
| GrowStack(stack_size, reinterpret_cast<LinuxVMAddress>(&stack_size)); |
| } |
| |
| void GrowStack(LinuxVMSize stack_size, LinuxVMAddress bottom_of_stack) { |
| char stack_contents[4096]; |
| auto stack_address = reinterpret_cast<LinuxVMAddress>(&stack_contents); |
| |
| if (bottom_of_stack - stack_address < stack_size) { |
| GrowStack(stack_size, bottom_of_stack); |
| } else { |
| // Write-protect a page on our stack to split up the mapping |
| LinuxVMAddress page_addr = |
| stack_address - (stack_address % page_size_) + 2 * page_size_; |
| ASSERT_EQ( |
| mprotect(reinterpret_cast<void*>(page_addr), page_size_, PROT_READ), |
| 0) |
| << ErrnoMessage("mprotect"); |
| |
| CheckedWriteFile( |
| WritePipeHandle(), &bottom_of_stack, sizeof(bottom_of_stack)); |
| CheckedWriteFile(WritePipeHandle(), &page_addr, sizeof(page_addr)); |
| CheckedWriteFile( |
| WritePipeHandle(), &stack_address, sizeof(stack_address)); |
| |
| // Wait for parent to read us |
| CheckedReadFileAtEOF(ReadPipeHandle()); |
| |
| ASSERT_EQ(mprotect(reinterpret_cast<void*>(page_addr), |
| page_size_, |
| PROT_READ | PROT_WRITE), |
| 0) |
| << ErrnoMessage("mprotect"); |
| } |
| } |
| |
| const size_t page_size_; |
| }; |
| |
| // AddressSanitizer with use-after-return detection causes stack variables to |
| // be allocated on the heap. |
| #if defined(ADDRESS_SANITIZER) |
| #define MAYBE_ChildWithSplitStack DISABLED_ChildWithSplitStack |
| #else |
| #define MAYBE_ChildWithSplitStack ChildWithSplitStack |
| #endif |
| TEST(ProcessReaderLinux, MAYBE_ChildWithSplitStack) { |
| ChildWithSplitStackTest test; |
| test.Run(); |
| } |
| |
| // Android doesn't provide dl_iterate_phdr on ARM until API 21. |
| #if !BUILDFLAG(IS_ANDROID) || !defined(ARCH_CPU_ARMEL) || __ANDROID_API__ >= 21 |
| int ExpectFindModule(dl_phdr_info* info, size_t size, void* data) { |
| SCOPED_TRACE( |
| base::StringPrintf("module %s at 0x%" PRIx64 " phdrs 0x%" PRIx64, |
| info->dlpi_name, |
| LinuxVMAddress{info->dlpi_addr}, |
| FromPointerCast<LinuxVMAddress>(info->dlpi_phdr))); |
| auto modules = |
| reinterpret_cast<const std::vector<ProcessReaderLinux::Module>*>(data); |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // Prior to API 27, Bionic includes a null entry for /system/bin/linker. |
| if (!info->dlpi_name) { |
| EXPECT_EQ(info->dlpi_addr, 0u); |
| EXPECT_EQ(info->dlpi_phnum, 0u); |
| EXPECT_EQ(info->dlpi_phdr, nullptr); |
| return 0; |
| } |
| #endif |
| |
| // Bionic doesn't always set both of these addresses for the vdso and |
| // /system/bin/linker, but it does always set one of them. |
| VMAddress module_addr = info->dlpi_phdr |
| ? FromPointerCast<LinuxVMAddress>(info->dlpi_phdr) |
| : info->dlpi_addr; |
| |
| // TODO(jperaza): This can use a range map when one is available. |
| bool found = false; |
| for (const auto& module : *modules) { |
| if (module.elf_reader && module_addr >= module.elf_reader->Address() && |
| module_addr < |
| module.elf_reader->Address() + module.elf_reader->Size()) { |
| found = true; |
| break; |
| } |
| } |
| EXPECT_TRUE(found); |
| return 0; |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) || !ARCH_CPU_ARMEL || __ANDROID_API__ >= 21 |
| |
| void ExpectModulesFromSelf( |
| const std::vector<ProcessReaderLinux::Module>& modules) { |
| for (const auto& module : modules) { |
| EXPECT_FALSE(module.name.empty()); |
| EXPECT_NE(module.type, ModuleSnapshot::kModuleTypeUnknown); |
| } |
| |
| // Android doesn't provide dl_iterate_phdr on ARM until API 21. |
| #if !BUILDFLAG(IS_ANDROID) || !defined(ARCH_CPU_ARMEL) || __ANDROID_API__ >= 21 |
| EXPECT_EQ( |
| dl_iterate_phdr( |
| ExpectFindModule, |
| reinterpret_cast<void*>( |
| const_cast<std::vector<ProcessReaderLinux::Module>*>(&modules))), |
| 0); |
| #endif // !BUILDFLAG(IS_ANDROID) || !ARCH_CPU_ARMEL || __ANDROID_API__ >= 21 |
| } |
| |
| #if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) |
| void ExpectTestModule(ProcessReaderLinux* reader, |
| const std::string& module_name) { |
| for (const auto& module : reader->Modules()) { |
| if (module.name.find(module_name) != std::string::npos) { |
| ASSERT_TRUE(module.elf_reader); |
| |
| VMAddress dynamic_addr; |
| ASSERT_TRUE(module.elf_reader->GetDynamicArrayAddress(&dynamic_addr)); |
| |
| auto dynamic_mapping = reader->GetMemoryMap()->FindMapping(dynamic_addr); |
| auto mappings = |
| reader->GetMemoryMap()->FindFilePossibleMmapStarts(*dynamic_mapping); |
| EXPECT_EQ(mappings->Count(), 2u); |
| return; |
| } |
| } |
| ADD_FAILURE() << "Test module not found"; |
| } |
| #endif // !ADDRESS_SANITIZER && !MEMORY_SANITIZER |
| |
| TEST(ProcessReaderLinux, SelfModules) { |
| #if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) |
| const std::string module_name = "test_module.so"; |
| const std::string module_soname = "test_module_soname"; |
| ScopedModuleHandle empty_test_module( |
| LoadTestModule(module_name, module_soname)); |
| ASSERT_TRUE(empty_test_module.valid()); |
| #endif // !ADDRESS_SANITIZER && !MEMORY_SANITIZER |
| |
| FakePtraceConnection connection; |
| connection.Initialize(getpid()); |
| |
| ProcessReaderLinux process_reader; |
| ASSERT_TRUE(process_reader.Initialize(&connection)); |
| |
| ExpectModulesFromSelf(process_reader.Modules()); |
| #if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) |
| ExpectTestModule(&process_reader, module_soname); |
| #endif // !ADDRESS_SANITIZER && !MEMORY_SANITIZER |
| } |
| |
| class ChildModuleTest : public Multiprocess { |
| public: |
| ChildModuleTest() : Multiprocess(), module_soname_("test_module_soname") {} |
| |
| ChildModuleTest(const ChildModuleTest&) = delete; |
| ChildModuleTest& operator=(const ChildModuleTest&) = delete; |
| |
| ~ChildModuleTest() = default; |
| |
| private: |
| void MultiprocessParent() override { |
| char c; |
| ASSERT_TRUE(LoggingReadFileExactly(ReadPipeHandle(), &c, sizeof(c))); |
| |
| DirectPtraceConnection connection; |
| ASSERT_TRUE(connection.Initialize(ChildPID())); |
| |
| ProcessReaderLinux process_reader; |
| ASSERT_TRUE(process_reader.Initialize(&connection)); |
| |
| ExpectModulesFromSelf(process_reader.Modules()); |
| #if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) |
| ExpectTestModule(&process_reader, module_soname_); |
| #endif // !ADDRESS_SANITIZER && !MEMORY_SANITIZER |
| } |
| |
| void MultiprocessChild() override { |
| #if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) |
| ScopedModuleHandle empty_test_module( |
| LoadTestModule("test_module.so", module_soname_)); |
| ASSERT_TRUE(empty_test_module.valid()); |
| #endif // !ADDRESS_SANITIZER && !MEMORY_SANITIZER |
| |
| char c = 0; |
| ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &c, sizeof(c))); |
| |
| CheckedReadFileAtEOF(ReadPipeHandle()); |
| } |
| |
| const std::string module_soname_; |
| }; |
| |
| TEST(ProcessReaderLinux, ChildModules) { |
| ChildModuleTest test; |
| test.Run(); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| const char kTestAbortMessage[] = "test abort message"; |
| |
| TEST(ProcessReaderLinux, AbortMessage) { |
| // This test requires Q. The API level on Q devices will be 28 until the API |
| // is finalized, so we can't check API level yet. For now, test for the |
| // presence of a libc symbol which was introduced in Q. |
| if (!crashpad::internal::Dlsym(RTLD_DEFAULT, |
| "android_fdsan_close_with_tag")) { |
| GTEST_SKIP(); |
| } |
| |
| android_set_abort_message(kTestAbortMessage); |
| |
| FakePtraceConnection connection; |
| connection.Initialize(getpid()); |
| |
| ProcessReaderLinux process_reader; |
| ASSERT_TRUE(process_reader.Initialize(&connection)); |
| |
| EXPECT_EQ(kTestAbortMessage, process_reader.AbortMessage()); |
| } |
| #endif |
| |
| } // namespace |
| } // namespace test |
| } // namespace crashpad |