| // Copyright 2017 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/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 <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/format_macros.h" |
| #include "base/memory/free_deleter.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "build/build_config.h" |
| #include "gtest/gtest.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/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/synchronization/semaphore.h" |
| |
| #if defined(OS_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[base::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() {} |
| |
| 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()); } |
| |
| DISALLOW_COPY_AND_ASSIGN(BasicChildTest); |
| }; |
| |
| 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() { |
| 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) { |
| threads_.push_back(std::make_unique<Thread>()); |
| Thread* thread = threads_.back().get(); |
| |
| pthread_attr_t attr; |
| ASSERT_EQ(pthread_attr_init(&attr), 0) |
| << ErrnoMessage("pthread_attr_init"); |
| |
| if (stack_size > 0) { |
| void* stack_ptr; |
| errno = posix_memalign(&stack_ptr, getpagesize(), stack_size); |
| ASSERT_EQ(errno, 0) << ErrnoMessage("posix_memalign"); |
| |
| thread->stack.reset(reinterpret_cast<char*>(stack_ptr)); |
| |
| ASSERT_EQ(pthread_attr_setstack(&attr, thread->stack.get(), 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) { |
| CHECK_LT(thread_index, threads_.size()); |
| |
| const Thread* thread = threads_[thread_index].get(); |
| *expectation = thread->expectation; |
| return thread->tid; |
| } |
| |
| private: |
| struct Thread { |
| Thread() |
| : pthread(), |
| expectation(), |
| ready_semaphore(0), |
| exit_semaphore(0), |
| tid(-1) {} |
| ~Thread() {} |
| |
| pthread_t pthread; |
| ThreadExpectation expectation; |
| std::unique_ptr<char[], base::FreeDeleter> stack; |
| Semaphore ready_semaphore; |
| Semaphore exit_semaphore; |
| pid_t tid; |
| }; |
| |
| static void* ThreadMain(void* argument) { |
| Thread* thread = static_cast<Thread*>(argument); |
| |
| 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_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestThreadPool); |
| }; |
| |
| using ThreadMap = std::map<pid_t, TestThreadPool::ThreadExpectation>; |
| |
| void ExpectThreads(const ThreadMap& thread_map, |
| const std::vector<ProcessReaderLinux::Thread>& threads, |
| PtraceConnection* connection) { |
| ASSERT_EQ(threads.size(), thread_map.size()); |
| |
| MemoryMap memory_map; |
| ASSERT_TRUE(memory_map.Initialize(connection)); |
| |
| for (const auto& thread : threads) { |
| SCOPED_TRACE( |
| base::StringPrintf("Thread id %d, tls 0x%" PRIx64 |
| ", stack addr 0x%" PRIx64 ", stack size 0x%" PRIx64, |
| thread.tid, |
| 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, iterator->second.stack_address); |
| EXPECT_GE(thread.stack_region_address + thread.stack_region_size, |
| 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); |
| } |
| } |
| |
| class ChildThreadTest : public Multiprocess { |
| public: |
| ChildThreadTest(size_t stack_size = 0) |
| : Multiprocess(), stack_size_(stack_size) {} |
| ~ChildThreadTest() {} |
| |
| private: |
| void MultiprocessParent() override { |
| ThreadMap thread_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; |
| } |
| |
| 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, threads, &connection); |
| } |
| |
| void MultiprocessChild() override { |
| TestThreadPool thread_pool; |
| thread_pool.StartThreads(kThreadCount, stack_size_); |
| |
| 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 = {}; |
| 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)); |
| |
| for (size_t thread_index = 0; thread_index < kThreadCount; ++thread_index) { |
| tid = thread_pool.GetThreadExpectation(thread_index, &expectation); |
| CheckedWriteFile(WritePipeHandle(), &tid, sizeof(tid)); |
| CheckedWriteFile(WritePipeHandle(), &expectation, sizeof(expectation)); |
| } |
| |
| CheckedReadFileAtEOF(ReadPipeHandle()); |
| } |
| |
| static constexpr size_t kThreadCount = 3; |
| const size_t stack_size_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ChildThreadTest); |
| }; |
| |
| 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() {} |
| |
| 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_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ChildWithSplitStackTest); |
| }; |
| |
| // 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 !defined(OS_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); |
| |
| auto phdr_addr = FromPointerCast<LinuxVMAddress>(info->dlpi_phdr); |
| |
| #if defined(OS_ANDROID) |
| // Bionic includes a null entry. |
| if (!phdr_addr) { |
| EXPECT_EQ(info->dlpi_name, nullptr); |
| EXPECT_EQ(info->dlpi_addr, 0u); |
| EXPECT_EQ(info->dlpi_phnum, 0u); |
| return 0; |
| } |
| #endif |
| |
| // TODO(jperaza): This can use a range map when one is available. |
| bool found = false; |
| for (const auto& module : *modules) { |
| if (module.elf_reader && phdr_addr >= module.elf_reader->Address() && |
| phdr_addr < module.elf_reader->Address() + module.elf_reader->Size()) { |
| found = true; |
| break; |
| } |
| } |
| EXPECT_TRUE(found); |
| return 0; |
| } |
| #endif // !OS_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 !defined(OS_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 // !OS_ANDROID || !ARCH_CPU_ARMEL || __ANDROID_API__ >= 21 |
| } |
| |
| bool WriteTestModule(const base::FilePath& module_path) { |
| #if defined(ARCH_CPU_64_BITS) |
| using Ehdr = Elf64_Ehdr; |
| using Phdr = Elf64_Phdr; |
| using Shdr = Elf64_Shdr; |
| using Dyn = Elf64_Dyn; |
| using Sym = Elf64_Sym; |
| unsigned char elf_class = ELFCLASS64; |
| #else |
| using Ehdr = Elf32_Ehdr; |
| using Phdr = Elf32_Phdr; |
| using Shdr = Elf32_Shdr; |
| using Dyn = Elf32_Dyn; |
| using Sym = Elf32_Sym; |
| unsigned char elf_class = ELFCLASS32; |
| #endif |
| |
| struct { |
| Ehdr ehdr; |
| struct { |
| Phdr load1; |
| Phdr load2; |
| Phdr dynamic; |
| } phdr_table; |
| struct { |
| Dyn hash; |
| Dyn strtab; |
| Dyn symtab; |
| Dyn strsz; |
| Dyn syment; |
| Dyn null; |
| } dynamic_array; |
| struct { |
| Elf32_Word nbucket; |
| Elf32_Word nchain; |
| Elf32_Word bucket; |
| Elf32_Word chain; |
| } hash_table; |
| struct { |
| } string_table; |
| struct { |
| Sym und_symbol; |
| } symbol_table; |
| struct { |
| Shdr null; |
| Shdr dynamic; |
| Shdr string_table; |
| } shdr_table; |
| } module = {}; |
| |
| module.ehdr.e_ident[EI_MAG0] = ELFMAG0; |
| module.ehdr.e_ident[EI_MAG1] = ELFMAG1; |
| module.ehdr.e_ident[EI_MAG2] = ELFMAG2; |
| module.ehdr.e_ident[EI_MAG3] = ELFMAG3; |
| |
| module.ehdr.e_ident[EI_CLASS] = elf_class; |
| |
| #if defined(ARCH_CPU_LITTLE_ENDIAN) |
| module.ehdr.e_ident[EI_DATA] = ELFDATA2LSB; |
| #else |
| module.ehdr.e_ident[EI_DATA] = ELFDATA2MSB; |
| #endif // ARCH_CPU_LITTLE_ENDIAN |
| |
| module.ehdr.e_ident[EI_VERSION] = EV_CURRENT; |
| |
| module.ehdr.e_type = ET_DYN; |
| |
| #if defined(ARCH_CPU_X86) |
| module.ehdr.e_machine = EM_386; |
| #elif defined(ARCH_CPU_X86_64) |
| module.ehdr.e_machine = EM_X86_64; |
| #elif defined(ARCH_CPU_ARMEL) |
| module.ehdr.e_machine = EM_ARM; |
| #elif defined(ARCH_CPU_ARM64) |
| module.ehdr.e_machine = EM_AARCH64; |
| #elif defined(ARCH_CPU_MIPSEL) || defined(ARCH_CPU_MIPS64EL) |
| module.ehdr.e_machine = EM_MIPS; |
| #endif |
| |
| module.ehdr.e_version = EV_CURRENT; |
| module.ehdr.e_ehsize = sizeof(module.ehdr); |
| |
| module.ehdr.e_phoff = offsetof(decltype(module), phdr_table); |
| module.ehdr.e_phnum = sizeof(module.phdr_table) / sizeof(Phdr); |
| module.ehdr.e_phentsize = sizeof(Phdr); |
| |
| module.ehdr.e_shoff = offsetof(decltype(module), shdr_table); |
| module.ehdr.e_shentsize = sizeof(Shdr); |
| module.ehdr.e_shnum = sizeof(module.shdr_table) / sizeof(Shdr); |
| module.ehdr.e_shstrndx = SHN_UNDEF; |
| |
| constexpr size_t load2_vaddr = 0x200000; |
| |
| module.phdr_table.load1.p_type = PT_LOAD; |
| module.phdr_table.load1.p_offset = 0; |
| module.phdr_table.load1.p_vaddr = 0; |
| module.phdr_table.load1.p_filesz = sizeof(module); |
| module.phdr_table.load1.p_memsz = sizeof(module); |
| module.phdr_table.load1.p_flags = PF_R; |
| module.phdr_table.load1.p_align = load2_vaddr; |
| |
| module.phdr_table.load2.p_type = PT_LOAD; |
| module.phdr_table.load2.p_offset = 0; |
| module.phdr_table.load2.p_vaddr = load2_vaddr; |
| module.phdr_table.load2.p_filesz = sizeof(module); |
| module.phdr_table.load2.p_memsz = sizeof(module); |
| module.phdr_table.load2.p_flags = PF_R | PF_W; |
| module.phdr_table.load2.p_align = load2_vaddr; |
| |
| module.phdr_table.dynamic.p_type = PT_DYNAMIC; |
| module.phdr_table.dynamic.p_offset = |
| offsetof(decltype(module), dynamic_array); |
| module.phdr_table.dynamic.p_vaddr = |
| load2_vaddr + module.phdr_table.dynamic.p_offset; |
| module.phdr_table.dynamic.p_filesz = sizeof(module.dynamic_array); |
| module.phdr_table.dynamic.p_memsz = sizeof(module.dynamic_array); |
| module.phdr_table.dynamic.p_flags = PF_R | PF_W; |
| module.phdr_table.dynamic.p_align = 8; |
| |
| module.dynamic_array.hash.d_tag = DT_HASH; |
| module.dynamic_array.hash.d_un.d_ptr = offsetof(decltype(module), hash_table); |
| module.dynamic_array.strtab.d_tag = DT_STRTAB; |
| module.dynamic_array.strtab.d_un.d_ptr = |
| offsetof(decltype(module), string_table); |
| module.dynamic_array.symtab.d_tag = DT_SYMTAB; |
| module.dynamic_array.symtab.d_un.d_ptr = |
| offsetof(decltype(module), symbol_table); |
| module.dynamic_array.strsz.d_tag = DT_STRSZ; |
| module.dynamic_array.strsz.d_un.d_val = sizeof(module.string_table); |
| module.dynamic_array.syment.d_tag = DT_SYMENT; |
| module.dynamic_array.syment.d_un.d_val = sizeof(Sym); |
| |
| module.dynamic_array.null.d_tag = DT_NULL; |
| |
| module.hash_table.nbucket = 1; |
| module.hash_table.nchain = 1; |
| module.hash_table.bucket = 0; |
| module.hash_table.chain = 0; |
| |
| module.shdr_table.null.sh_type = SHT_NULL; |
| |
| module.shdr_table.dynamic.sh_name = 0; |
| module.shdr_table.dynamic.sh_type = SHT_DYNAMIC; |
| module.shdr_table.dynamic.sh_flags = SHF_WRITE | SHF_ALLOC; |
| module.shdr_table.dynamic.sh_addr = module.phdr_table.dynamic.p_vaddr; |
| module.shdr_table.dynamic.sh_offset = module.phdr_table.dynamic.p_offset; |
| module.shdr_table.dynamic.sh_size = module.phdr_table.dynamic.p_filesz; |
| module.shdr_table.dynamic.sh_link = |
| offsetof(decltype(module.shdr_table), string_table) / sizeof(Shdr); |
| |
| module.shdr_table.string_table.sh_name = 0; |
| module.shdr_table.string_table.sh_type = SHT_STRTAB; |
| module.shdr_table.string_table.sh_offset = |
| offsetof(decltype(module), string_table); |
| |
| FileWriter writer; |
| if (!writer.Open(module_path, |
| FileWriteMode::kCreateOrFail, |
| FilePermissions::kWorldReadable)) { |
| ADD_FAILURE(); |
| return false; |
| } |
| |
| if (!writer.Write(&module, sizeof(module))) { |
| ADD_FAILURE(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| ScopedModuleHandle LoadTestModule(const std::string& module_name) { |
| base::FilePath module_path( |
| TestPaths::Executable().DirName().Append(module_name)); |
| |
| if (!WriteTestModule(module_path)) { |
| return ScopedModuleHandle(nullptr); |
| } |
| EXPECT_TRUE(IsRegularFile(module_path)); |
| |
| ScopedModuleHandle handle( |
| dlopen(module_path.value().c_str(), RTLD_LAZY | RTLD_LOCAL)); |
| EXPECT_TRUE(handle.valid()) |
| << "dlopen: " << module_path.value() << " " << dlerror(); |
| |
| EXPECT_TRUE(LoggingRemoveFile(module_path)); |
| |
| return handle; |
| } |
| |
| 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"; |
| } |
| |
| TEST(ProcessReaderLinux, SelfModules) { |
| const std::string module_name = "test_module.so"; |
| ScopedModuleHandle empty_test_module(LoadTestModule(module_name)); |
| ASSERT_TRUE(empty_test_module.valid()); |
| |
| FakePtraceConnection connection; |
| connection.Initialize(getpid()); |
| |
| ProcessReaderLinux process_reader; |
| ASSERT_TRUE(process_reader.Initialize(&connection)); |
| |
| ExpectModulesFromSelf(process_reader.Modules()); |
| ExpectTestModule(&process_reader, module_name); |
| } |
| |
| class ChildModuleTest : public Multiprocess { |
| public: |
| ChildModuleTest() : Multiprocess(), module_name_("test_module.so") {} |
| ~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()); |
| ExpectTestModule(&process_reader, module_name_); |
| } |
| |
| void MultiprocessChild() override { |
| ScopedModuleHandle empty_test_module(LoadTestModule(module_name_)); |
| ASSERT_TRUE(empty_test_module.valid()); |
| |
| char c = 0; |
| ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &c, sizeof(c))); |
| |
| CheckedReadFileAtEOF(ReadPipeHandle()); |
| } |
| |
| const std::string module_name_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ChildModuleTest); |
| }; |
| |
| TEST(ProcessReaderLinux, ChildModules) { |
| ChildModuleTest test; |
| test.Run(); |
| } |
| |
| #if defined(OS_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 |