diff --git a/snapshot/elf/elf_image_reader.cc b/snapshot/elf/elf_image_reader.cc index c6816605..bbf9b54e 100644 --- a/snapshot/elf/elf_image_reader.cc +++ b/snapshot/elf/elf_image_reader.cc @@ -31,12 +31,14 @@ class ElfImageReader::ProgramHeaderTable { public: virtual ~ProgramHeaderTable() {} - virtual bool VerifyLoadSegments() const = 0; + virtual bool VerifyLoadSegments(bool verbose) const = 0; virtual size_t Size() const = 0; virtual bool GetDynamicSegment(VMAddress* address, VMSize* size) const = 0; - virtual bool GetPreferredElfHeaderAddress(VMAddress* address) const = 0; + virtual bool GetPreferredElfHeaderAddress(VMAddress* address, + bool verbose) const = 0; virtual bool GetPreferredLoadedMemoryRange(VMAddress* address, - VMSize* size) const = 0; + VMSize* size, + bool verbose) const = 0; // Locate the next PT_NOTE segment starting at segment index start_index. If a // PT_NOTE segment is found, start_index is set to the next index after the @@ -58,14 +60,15 @@ class ElfImageReader::ProgramHeaderTableSpecific bool Initialize(const ProcessMemoryRange& memory, VMAddress address, - VMSize num_segments) { + VMSize num_segments, + bool verbose) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); table_.resize(num_segments); if (!memory.Read(address, sizeof(PhdrType) * num_segments, table_.data())) { return false; } - if (!VerifyLoadSegments()) { + if (!VerifyLoadSegments(verbose)) { return false; } @@ -73,7 +76,7 @@ class ElfImageReader::ProgramHeaderTableSpecific return true; } - bool VerifyLoadSegments() const override { + bool VerifyLoadSegments(bool verbose) const override { constexpr bool is_64_bit = std::is_same::value; VMAddress last_vaddr; bool load_found = false; @@ -83,12 +86,12 @@ class ElfImageReader::ProgramHeaderTableSpecific is_64_bit, header.p_vaddr, header.p_memsz); if (!load_range.IsValid()) { - LOG(ERROR) << "bad load range"; + LOG_IF(ERROR, verbose) << "bad load range"; return false; } if (load_found && header.p_vaddr <= last_vaddr) { - LOG(ERROR) << "out of order load segments"; + LOG_IF(ERROR, verbose) << "out of order load segments"; return false; } load_found = true; @@ -100,7 +103,8 @@ class ElfImageReader::ProgramHeaderTableSpecific size_t Size() const override { return sizeof(PhdrType) * table_.size(); } - bool GetPreferredElfHeaderAddress(VMAddress* address) const override { + bool GetPreferredElfHeaderAddress(VMAddress* address, + bool verbose) const override { INITIALIZATION_STATE_DCHECK_VALID(initialized_); for (const auto& header : table_) { if (header.p_type == PT_LOAD && header.p_offset == 0) { @@ -108,12 +112,13 @@ class ElfImageReader::ProgramHeaderTableSpecific return true; } } - LOG(ERROR) << "no preferred header address"; + LOG_IF(ERROR, verbose) << "no preferred header address"; return false; } bool GetPreferredLoadedMemoryRange(VMAddress* base, - VMSize* size) const override { + VMSize* size, + bool verbose) const override { INITIALIZATION_STATE_DCHECK_VALID(initialized_); VMAddress preferred_base = 0; @@ -133,7 +138,7 @@ class ElfImageReader::ProgramHeaderTableSpecific *size = preferred_end - preferred_base; return true; } - LOG(ERROR) << "no load segments"; + LOG_IF(ERROR, verbose) << "no load segments"; return false; } @@ -340,7 +345,8 @@ ElfImageReader::ElfImageReader() ElfImageReader::~ElfImageReader() {} bool ElfImageReader::Initialize(const ProcessMemoryRange& memory, - VMAddress address) { + VMAddress address, + bool verbose) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); ehdr_address_ = address; if (!memory_.Initialize(memory)) { @@ -354,13 +360,13 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory, if (e_ident[EI_MAG0] != ELFMAG0 || e_ident[EI_MAG1] != ELFMAG1 || e_ident[EI_MAG2] != ELFMAG2 || e_ident[EI_MAG3] != ELFMAG3) { - LOG(ERROR) << "Incorrect ELF magic number"; + LOG_IF(ERROR, verbose) << "Incorrect ELF magic number"; return false; } if (!(memory_.Is64Bit() && e_ident[EI_CLASS] == ELFCLASS64) && !(!memory_.Is64Bit() && e_ident[EI_CLASS] == ELFCLASS32)) { - LOG(ERROR) << "unexpected bitness"; + LOG_IF(ERROR, verbose) << "unexpected bitness"; return false; } @@ -370,12 +376,12 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory, constexpr uint8_t expected_encoding = ELFDATA2MSB; #endif if (e_ident[EI_DATA] != expected_encoding) { - LOG(ERROR) << "unexpected encoding"; + LOG_IF(ERROR, verbose) << "unexpected encoding"; return false; } if (e_ident[EI_VERSION] != EV_CURRENT) { - LOG(ERROR) << "unexpected version"; + LOG_IF(ERROR, verbose) << "unexpected version"; return false; } @@ -388,15 +394,15 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory, #define VERIFY_HEADER(header) \ do { \ if (header.e_type != ET_EXEC && header.e_type != ET_DYN) { \ - LOG(ERROR) << "unexpected image type"; \ + LOG_IF(ERROR, verbose) << "unexpected image type"; \ return false; \ } \ if (header.e_version != EV_CURRENT) { \ - LOG(ERROR) << "unexpected version"; \ + LOG_IF(ERROR, verbose) << "unexpected version"; \ return false; \ } \ if (header.e_ehsize != sizeof(header)) { \ - LOG(ERROR) << "unexpected header size"; \ + LOG_IF(ERROR, verbose) << "unexpected header size"; \ return false; \ } \ } while (false); @@ -407,21 +413,21 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory, VERIFY_HEADER(header_32_); } - if (!InitializeProgramHeaders()) { + if (!InitializeProgramHeaders(verbose)) { return false; } VMAddress preferred_ehdr_address; if (!program_headers_.get()->GetPreferredElfHeaderAddress( - &preferred_ehdr_address)) { + &preferred_ehdr_address, verbose)) { return false; } load_bias_ = ehdr_address_ - preferred_ehdr_address; VMAddress base_address; VMSize loaded_size; - if (!program_headers_.get()->GetPreferredLoadedMemoryRange(&base_address, - &loaded_size)) { + if (!program_headers_.get()->GetPreferredLoadedMemoryRange( + &base_address, &loaded_size, verbose)) { return false; } base_address += load_bias_; @@ -443,12 +449,12 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory, CheckedVMAddressRange range(memory_.Is64Bit(), base_address, loaded_size); if (!range.ContainsRange( CheckedVMAddressRange(memory_.Is64Bit(), ehdr_address_, ehdr_size))) { - LOG(ERROR) << "ehdr out of range"; + LOG_IF(ERROR, verbose) << "ehdr out of range"; return false; } if (!range.ContainsRange(CheckedVMAddressRange( memory.Is64Bit(), phdr_address, program_headers_->Size()))) { - LOG(ERROR) << "phdrs out of range"; + LOG_IF(ERROR, verbose) << "phdrs out of range"; return false; } @@ -545,19 +551,40 @@ bool ElfImageReader::GetDebugAddress(VMAddress* debug) { return GetAddressFromDynamicArray(DT_DEBUG, true, debug); } -bool ElfImageReader::InitializeProgramHeaders() { -#define INITIALIZE_PROGRAM_HEADERS(PhdrType, header) \ - do { \ - if (header.e_phentsize != sizeof(PhdrType)) { \ - LOG(ERROR) << "unexpected phdr size"; \ - return false; \ - } \ - auto phdrs = new ProgramHeaderTableSpecific(); \ - program_headers_.reset(phdrs); \ - if (!phdrs->Initialize( \ - memory_, ehdr_address_ + header.e_phoff, header.e_phnum)) { \ - return false; \ - } \ +bool ElfImageReader::GetDynamicArrayAddress(VMAddress* address) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + VMAddress dyn_segment_address; + VMSize dyn_segment_size; + if (!program_headers_.get()->GetDynamicSegment(&dyn_segment_address, + &dyn_segment_size)) { + LOG(ERROR) << "no dynamic segment"; + return false; + } + *address = dyn_segment_address + GetLoadBias(); + return true; +} + +VMAddress ElfImageReader::GetProgramHeaderTableAddress() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return ehdr_address_ + + (memory_.Is64Bit() ? header_64_.e_phoff : header_32_.e_phoff); +} + +bool ElfImageReader::InitializeProgramHeaders(bool verbose) { +#define INITIALIZE_PROGRAM_HEADERS(PhdrType, header) \ + do { \ + if (header.e_phentsize != sizeof(PhdrType)) { \ + LOG_IF(ERROR, verbose) << "unexpected phdr size"; \ + return false; \ + } \ + auto phdrs = new ProgramHeaderTableSpecific(); \ + program_headers_.reset(phdrs); \ + if (!phdrs->Initialize(memory_, \ + ehdr_address_ + header.e_phoff, \ + header.e_phnum, \ + verbose)) { \ + return false; \ + } \ } while (false); if (memory_.Is64Bit()) { diff --git a/snapshot/elf/elf_image_reader.h b/snapshot/elf/elf_image_reader.h index 7549f346..29f8b1eb 100644 --- a/snapshot/elf/elf_image_reader.h +++ b/snapshot/elf/elf_image_reader.h @@ -118,7 +118,13 @@ class ElfImageReader { //! \param[in] memory A memory reader for the remote process. //! \param[in] address The address in the remote process' address space where //! the ELF image is loaded. - bool Initialize(const ProcessMemoryRange& memory, VMAddress address); + //! \param[in] verbose `true` if this method should log error messages during + //! initialization. Setting this value to `false` will reduce the error + //! messages relating to verifying the ELF image, but may not suppress + //! logging entirely. + bool Initialize(const ProcessMemoryRange& memory, + VMAddress address, + bool verbose = true); //! \brief Returns the base address of the image's memory range. //! @@ -172,6 +178,16 @@ class ElfImageReader { //! \return `true` if the debug address was found. bool GetDebugAddress(VMAddress* debug); + //! \brief Determine the address of `PT_DYNAMIC` segment. + //! + //! \param[out] address The address of the array, valid if this method returns + //! `true`. + //! \return `true` on success. Otherwise `false` with a message logged. + bool GetDynamicArrayAddress(VMAddress* address); + + //! \brief Return the address of the program header table. + VMAddress GetProgramHeaderTableAddress(); + //! \brief Return a NoteReader for this image, which scans all PT_NOTE //! segments in the image. //! @@ -238,7 +254,7 @@ class ElfImageReader { template class ProgramHeaderTableSpecific; - bool InitializeProgramHeaders(); + bool InitializeProgramHeaders(bool verbose); bool InitializeDynamicArray(); bool InitializeDynamicSymbolTable(); bool GetAddressFromDynamicArray(uint64_t tag, bool log, VMAddress* address); diff --git a/snapshot/elf/elf_image_reader_test.cc b/snapshot/elf/elf_image_reader_test.cc index d64fd5c4..11fb1feb 100644 --- a/snapshot/elf/elf_image_reader_test.cc +++ b/snapshot/elf/elf_image_reader_test.cc @@ -103,10 +103,10 @@ void LocateExecutable(PtraceConnection* connection, ASSERT_TRUE(memory_map.Initialize(connection)); const MemoryMap::Mapping* phdr_mapping = memory_map.FindMapping(phdrs); ASSERT_TRUE(phdr_mapping); - const MemoryMap::Mapping* exe_mapping = - memory_map.FindFileMmapStart(*phdr_mapping); - ASSERT_TRUE(exe_mapping); - *elf_address = exe_mapping->range.Base(); + std::vector possible_mappings = + memory_map.FindFilePossibleMmapStarts(*phdr_mapping); + ASSERT_EQ(possible_mappings.size(), 1u); + *elf_address = possible_mappings[0]->range.Base(); } #endif // OS_FUCHSIA diff --git a/snapshot/linux/debug_rendezvous_test.cc b/snapshot/linux/debug_rendezvous_test.cc index f9920ab4..431e2bbe 100644 --- a/snapshot/linux/debug_rendezvous_test.cc +++ b/snapshot/linux/debug_rendezvous_test.cc @@ -74,9 +74,10 @@ void TestAgainstTarget(PtraceConnection* connection) { const MemoryMap::Mapping* phdr_mapping = mappings.FindMapping(phdrs); ASSERT_TRUE(phdr_mapping); - const MemoryMap::Mapping* exe_mapping = - mappings.FindFileMmapStart(*phdr_mapping); - LinuxVMAddress elf_address = exe_mapping->range.Base(); + std::vector exe_mappings = + mappings.FindFilePossibleMmapStarts(*phdr_mapping); + ASSERT_EQ(exe_mappings.size(), 1u); + LinuxVMAddress elf_address = exe_mappings[0]->range.Base(); ProcessMemoryLinux memory; ASSERT_TRUE(memory.Initialize(connection->GetProcessID())); @@ -142,9 +143,24 @@ void TestAgainstTarget(PtraceConnection* connection) { mappings.FindMapping(module.dynamic_array); ASSERT_TRUE(dyn_mapping); - const MemoryMap::Mapping* module_mapping = - mappings.FindFileMmapStart(*dyn_mapping); - ASSERT_TRUE(module_mapping); + std::vector possible_mappings = + mappings.FindFilePossibleMmapStarts(*dyn_mapping); + ASSERT_GE(possible_mappings.size(), 1u); + + std::unique_ptr module_reader; + const MemoryMap::Mapping* module_mapping = nullptr; + for (const auto mapping : possible_mappings) { + auto parsed_module = std::make_unique(); + VMAddress dynamic_address; + if (parsed_module->Initialize(range, mapping->range.Base()) && + parsed_module->GetDynamicArrayAddress(&dynamic_address) && + dynamic_address == module.dynamic_array) { + module_reader = std::move(parsed_module); + module_mapping = mapping; + break; + } + } + ASSERT_TRUE(module_reader.get()); #if defined(OS_ANDROID) EXPECT_FALSE(module.name.empty()); @@ -168,20 +184,17 @@ void TestAgainstTarget(PtraceConnection* connection) { module.name); #endif // OS_ANDROID - ElfImageReader module_reader; - ASSERT_TRUE(module_reader.Initialize(range, module_mapping->range.Base())); - // Android's loader stops setting its own load bias after Android 4.4.4 // (API 20) until Android 6.0 (API 23). if (is_android_loader && android_runtime_api > 20 && android_runtime_api < 23) { EXPECT_EQ(module.load_bias, 0); } else { - EXPECT_EQ(module.load_bias, module_reader.GetLoadBias()); + EXPECT_EQ(module.load_bias, module_reader->GetLoadBias()); } CheckedLinuxAddressRange module_range( - connection->Is64Bit(), module_reader.Address(), module_reader.Size()); + connection->Is64Bit(), module_reader->Address(), module_reader->Size()); EXPECT_TRUE(module_range.ContainsValue(module.dynamic_array)); } } diff --git a/snapshot/linux/process_reader_linux.cc b/snapshot/linux/process_reader_linux.cc index 038d5cef..33d4f92b 100644 --- a/snapshot/linux/process_reader_linux.cc +++ b/snapshot/linux/process_reader_linux.cc @@ -347,20 +347,45 @@ void ProcessReaderLinux::InitializeModules() { return; } - const MemoryMap::Mapping* exe_mapping; - if (!(exe_mapping = GetMemoryMap()->FindMapping(phdrs)) || - !(exe_mapping = GetMemoryMap()->FindFileMmapStart(*exe_mapping))) { - return; - } - ProcessMemoryRange range; if (!range.Initialize(Memory(), is_64_bit_)) { return; } - auto exe_reader = std::make_unique(); - if (!exe_reader->Initialize(range, exe_mapping->range.Base())) { - return; + // The strategy used for identifying loaded modules depends on ELF files + // conventionally loading their header and program headers into memory. + // Locating the correct module could fail if the headers aren't mapped, are + // mapped at an unexpected location, or if there are other mappings + // constructed to look like the ELF module being searched for. + const MemoryMap::Mapping* exe_mapping = nullptr; + std::unique_ptr exe_reader; + { + const MemoryMap::Mapping* phdr_mapping = memory_map_.FindMapping(phdrs); + if (!phdr_mapping) { + return; + } + + std::vector possible_mappings = + memory_map_.FindFilePossibleMmapStarts(*phdr_mapping); + for (auto riter = possible_mappings.rbegin(); + riter != possible_mappings.rend(); + ++riter) { + auto mapping = *riter; + auto parsed_exe = std::make_unique(); + if (parsed_exe->Initialize( + range, + mapping->range.Base(), + /* verbose= */ possible_mappings.size() == 1) && + parsed_exe->GetProgramHeaderTableAddress() == phdrs) { + exe_mapping = mapping; + exe_reader = std::move(parsed_exe); + break; + } + } + if (!exe_mapping) { + LOG(ERROR) << "no exe mappings " << possible_mappings.size(); + return; + } } LinuxVMAddress debug_address; @@ -386,19 +411,42 @@ void ProcessReaderLinux::InitializeModules() { aux.GetValue(AT_BASE, &loader_base); for (const DebugRendezvous::LinkEntry& entry : debug.Modules()) { - const MemoryMap::Mapping* mapping; - if (!(mapping = memory_map_.FindMapping(entry.dynamic_array)) || - !(mapping = memory_map_.FindFileMmapStart(*mapping))) { - continue; - } + const MemoryMap::Mapping* module_mapping = nullptr; + std::unique_ptr elf_reader; + { + const MemoryMap::Mapping* dyn_mapping = + memory_map_.FindMapping(entry.dynamic_array); + if (!dyn_mapping) { + continue; + } - auto elf_reader = std::make_unique(); - if (!elf_reader->Initialize(range, mapping->range.Base())) { - continue; + std::vector possible_mappings = + memory_map_.FindFilePossibleMmapStarts(*dyn_mapping); + for (auto riter = possible_mappings.rbegin(); + riter != possible_mappings.rend(); + ++riter) { + auto mapping = *riter; + auto parsed_module = std::make_unique(); + VMAddress dynamic_address; + if (parsed_module->Initialize( + range, + mapping->range.Base(), + /* verbose= */ possible_mappings.size() == 1) && + parsed_module->GetDynamicArrayAddress(&dynamic_address) && + dynamic_address == entry.dynamic_array) { + module_mapping = mapping; + elf_reader = std::move(parsed_module); + break; + } + } + if (!module_mapping) { + LOG(ERROR) << "no module mappings " << possible_mappings.size(); + continue; + } } Module module = {}; - module.name = !entry.name.empty() ? entry.name : mapping->name; + module.name = !entry.name.empty() ? entry.name : module_mapping->name; module.elf_reader = elf_reader.get(); module.type = loader_base && elf_reader->Address() == loader_base ? ModuleSnapshot::kModuleTypeDynamicLoader diff --git a/snapshot/linux/process_reader_linux_test.cc b/snapshot/linux/process_reader_linux_test.cc index 940114c6..d827f35d 100644 --- a/snapshot/linux/process_reader_linux_test.cc +++ b/snapshot/linux/process_reader_linux_test.cc @@ -14,6 +14,8 @@ #include "snapshot/linux/process_reader_linux.h" +#include +#include #include #include #include @@ -38,7 +40,11 @@ #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" @@ -507,7 +513,220 @@ void ExpectModulesFromSelf( #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.size(), 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()); @@ -515,15 +734,19 @@ TEST(ProcessReaderLinux, SelfModules) { ASSERT_TRUE(process_reader.Initialize(&connection)); ExpectModulesFromSelf(process_reader.Modules()); + ExpectTestModule(&process_reader, module_name); } class ChildModuleTest : public Multiprocess { public: - ChildModuleTest() : Multiprocess() {} + 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())); @@ -531,9 +754,20 @@ class ChildModuleTest : public Multiprocess { ASSERT_TRUE(process_reader.Initialize(&connection)); ExpectModulesFromSelf(process_reader.Modules()); + ExpectTestModule(&process_reader, module_name_); } - void MultiprocessChild() override { CheckedReadFileAtEOF(ReadPipeHandle()); } + void MultiprocessChild() override { + ScopedModuleHandle empty_test_module(LoadTestModule(module_name_)); + ASSERT_TRUE(empty_test_module.valid()); + + char c; + ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &c, sizeof(c))); + + CheckedReadFileAtEOF(ReadPipeHandle()); + } + + const std::string module_name_; DISALLOW_COPY_AND_ASSIGN(ChildModuleTest); }; diff --git a/test/scoped_module_handle.cc b/test/scoped_module_handle.cc index 3854222b..df246fcf 100644 --- a/test/scoped_module_handle.cc +++ b/test/scoped_module_handle.cc @@ -36,6 +36,11 @@ void ScopedModuleHandle::Impl::Close(ModuleHandle handle) { ScopedModuleHandle::ScopedModuleHandle(ModuleHandle handle) : handle_(handle) {} +ScopedModuleHandle::ScopedModuleHandle(ScopedModuleHandle&& other) + : handle_(other.handle_) { + other.handle_ = nullptr; +} + ScopedModuleHandle::~ScopedModuleHandle() { if (valid()) { Impl::Close(handle_); diff --git a/test/scoped_module_handle.h b/test/scoped_module_handle.h index 9909d1c8..bbab066d 100644 --- a/test/scoped_module_handle.h +++ b/test/scoped_module_handle.h @@ -57,6 +57,7 @@ class ScopedModuleHandle { using ModuleHandle = Impl::ModuleHandle; explicit ScopedModuleHandle(ModuleHandle handle); + ScopedModuleHandle(ScopedModuleHandle&& handle); ~ScopedModuleHandle(); //! \return The module handle being managed. diff --git a/util/linux/memory_map.cc b/util/linux/memory_map.cc index a274790e..345f63ca 100644 --- a/util/linux/memory_map.cc +++ b/util/linux/memory_map.cc @@ -296,40 +296,39 @@ const MemoryMap::Mapping* MemoryMap::FindMappingWithName( return nullptr; } -const MemoryMap::Mapping* MemoryMap::FindFileMmapStart( +std::vector MemoryMap::FindFilePossibleMmapStarts( const Mapping& mapping) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - size_t index = 0; - for (; index < mappings_.size(); ++index) { - if (mappings_[index].Equals(mapping)) { - break; - } - } - if (index >= mappings_.size()) { - LOG(ERROR) << "mapping not found"; - return nullptr; - } + std::vector possible_starts; // If the mapping is anonymous, as is for the VDSO, there is no mapped file to // find the start of, so just return the input mapping. if (mapping.device == 0 && mapping.inode == 0) { - return &mappings_[index]; + for (const auto& candidate : mappings_) { + if (mapping.Equals(candidate)) { + possible_starts.push_back(&candidate); + return possible_starts; + } + } + + LOG(ERROR) << "mapping not found"; + return std::vector(); } - do { - // There may by anonymous mappings or other files mapped into the holes, - // so check that the mapping uses the same file as the input, but keep - // searching if it doesn't. - if (mappings_[index].device == mapping.device && - mappings_[index].inode == mapping.inode && - mappings_[index].offset == 0) { - return &mappings_[index]; + for (const auto& candidate : mappings_) { + if (candidate.device == mapping.device && + candidate.inode == mapping.inode && + candidate.offset == 0) { + possible_starts.push_back(&candidate); } - } while (index--); + if (mapping.Equals(candidate)) { + return possible_starts; + } + } LOG(ERROR) << "mapping not found"; - return nullptr; + return std::vector(); } } // namespace crashpad diff --git a/util/linux/memory_map.h b/util/linux/memory_map.h index c8276ba6..af194e99 100644 --- a/util/linux/memory_map.h +++ b/util/linux/memory_map.h @@ -76,20 +76,27 @@ class MemoryMap { //! it was obtained from. const Mapping* FindMappingWithName(const std::string& name) const; - //! \brief Find the first Mapping in a series of mappings for the same file. + //! \brief Find Mappings that share a Mapping's file, mapped from offset 0. //! //! Executables and libaries are typically loaded into several mappings with - //! varying permissions for different segments. This method searches for the - //! mapping with the highest address at or below \a mapping, which maps the - //! same file as \a mapping from file offset 0. + //! varying permissions for different segments. Portions of an ELF file may + //! be mapped multiple times as part of loading the file, for example, when + //! initializing GNU_RELRO segments. This method searches for mappings at or + //! below \a mapping in memory that are mapped from the same file as \a + //! mapping from offset 0. //! - //! If \a mapping is not found, `nullptr` is returned. If \a mapping is found - //! but does not map a file, \a mapping is returned. + //! This method is intended to help identify the possible base address for + //! loaded modules, but it is the caller's responsibility to determine which + //! returned mapping is correct. + //! + //! If \a mapping does not refer to a valid mapping, an empty vector will be + //! returned and a message will be logged. If \a mapping is found but does not + //! map a file, \a mapping is returned in \a possible_starts. //! //! \param[in] mapping A Mapping whose series to find the start of. - //! \return The first Mapping in the series or `nullptr` on failure with a - //! message logged. - const Mapping* FindFileMmapStart(const Mapping& mapping) const; + //! \return a vector of the possible mapping starts. + std::vector FindFilePossibleMmapStarts( + const Mapping& mapping) const; private: std::vector mappings_; diff --git a/util/linux/memory_map_test.cc b/util/linux/memory_map_test.cc index a056589a..645a0b4d 100644 --- a/util/linux/memory_map_test.cc +++ b/util/linux/memory_map_test.cc @@ -355,8 +355,8 @@ TEST(MemoryMap, MapRunningChild) { // Expects first and third pages from mapping_start to refer to the same mapped // file. The second page should not. -void ExpectFindFileMmapStart(LinuxVMAddress mapping_start, - LinuxVMSize page_size) { +void ExpectFindFilePossibleMmapStarts(LinuxVMAddress mapping_start, + LinuxVMSize page_size) { FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); @@ -373,17 +373,27 @@ void ExpectFindFileMmapStart(LinuxVMAddress mapping_start, ASSERT_NE(mapping1, mapping2); ASSERT_NE(mapping2, mapping3); - EXPECT_EQ(map.FindFileMmapStart(*mapping1), mapping1); - EXPECT_EQ(map.FindFileMmapStart(*mapping2), mapping2); - EXPECT_EQ(map.FindFileMmapStart(*mapping3), mapping1); + std::vector mappings; + + mappings = map.FindFilePossibleMmapStarts(*mapping1); + ASSERT_EQ(mappings.size(), 1u); + EXPECT_EQ(mappings[0], mapping1); + + mappings = map.FindFilePossibleMmapStarts(*mapping2); + ASSERT_EQ(mappings.size(), 1u); + EXPECT_EQ(mappings[0], mapping2); + + mappings = map.FindFilePossibleMmapStarts(*mapping3); + ASSERT_EQ(mappings.size(), 1u); + EXPECT_EQ(mappings[0], mapping1); } -TEST(MemoryMap, FindFileMmapStart) { +TEST(MemoryMap, FindFilePossibleMmapStarts) { const size_t page_size = getpagesize(); ScopedTempDir temp_dir; - base::FilePath path = - temp_dir.path().Append(FILE_PATH_LITERAL("FindFileMmapStartTestFile")); + base::FilePath path = temp_dir.path().Append( + FILE_PATH_LITERAL("FindFilePossibleMmapStartsTestFile")); ScopedFileHandle handle; size_t file_length = page_size * 3; ASSERT_NO_FATAL_FAILURE(InitializeFile(path, file_length, &handle)); @@ -418,9 +428,19 @@ TEST(MemoryMap, FindFileMmapStart) { ASSERT_NE(mapping1, mapping2); ASSERT_NE(mapping2, mapping3); - EXPECT_EQ(map.FindFileMmapStart(*mapping1), mapping1); - EXPECT_EQ(map.FindFileMmapStart(*mapping2), mapping1); - EXPECT_EQ(map.FindFileMmapStart(*mapping3), mapping1); + std::vector mappings; + + mappings = map.FindFilePossibleMmapStarts(*mapping1); + ASSERT_EQ(mappings.size(), 1u); + EXPECT_EQ(mappings[0], mapping1); + + mappings = map.FindFilePossibleMmapStarts(*mapping2); + ASSERT_EQ(mappings.size(), 1u); + EXPECT_EQ(mappings[0], mapping1); + + mappings = map.FindFilePossibleMmapStarts(*mapping3); + ASSERT_EQ(mappings.size(), 1u); + EXPECT_EQ(mappings[0], mapping1); #if defined(ARCH_CPU_64_BITS) constexpr bool is_64_bit = true; @@ -429,7 +449,7 @@ TEST(MemoryMap, FindFileMmapStart) { #endif MemoryMap::Mapping bad_mapping; bad_mapping.range.SetRange(is_64_bit, 0, 1); - EXPECT_EQ(map.FindFileMmapStart(bad_mapping), nullptr); + EXPECT_EQ(map.FindFilePossibleMmapStarts(bad_mapping).size(), 0u); } // Make the second page an anonymous mapping @@ -449,12 +469,12 @@ TEST(MemoryMap, FindFileMmapStart) { MAP_PRIVATE | MAP_FIXED, handle.get(), page_size * 2)); - ExpectFindFileMmapStart(mapping_start, page_size); + ExpectFindFilePossibleMmapStarts(mapping_start, page_size); // Map the second page to another file. ScopedFileHandle handle2; - base::FilePath path2 = - temp_dir.path().Append(FILE_PATH_LITERAL("FindFileMmapStartTestFile2")); + base::FilePath path2 = temp_dir.path().Append( + FILE_PATH_LITERAL("FindFilePossibleMmapStartsTestFile2")); ASSERT_NO_FATAL_FAILURE(InitializeFile(path2, page_size, &handle2)); page2_mapping.ResetMmap(file_mapping.addr_as() + page_size, @@ -463,7 +483,106 @@ TEST(MemoryMap, FindFileMmapStart) { MAP_PRIVATE | MAP_FIXED, handle2.get(), 0); - ExpectFindFileMmapStart(mapping_start, page_size); + ExpectFindFilePossibleMmapStarts(mapping_start, page_size); +} + +TEST(MemoryMap, FindFilePossibleMmapStarts_MultipleStarts) { + ScopedTempDir temp_dir; + base::FilePath path = + temp_dir.path().Append(FILE_PATH_LITERAL("MultipleStartsTestFile")); + const size_t page_size = getpagesize(); + ScopedFileHandle handle; + ASSERT_NO_FATAL_FAILURE(InitializeFile(path, page_size * 2, &handle)); + + // Locate a sequence of pages to setup a test in. + char* seq_addr; + { + ScopedMmap whole_mapping; + ASSERT_TRUE(whole_mapping.ResetMmap( + nullptr, page_size * 8, PROT_READ, MAP_PRIVATE | MAP_ANON, -1, 0)); + seq_addr = whole_mapping.addr_as(); + } + + // Arrange file and anonymous mappings in the sequence. + ScopedMmap file_mapping0; + ASSERT_TRUE(file_mapping0.ResetMmap(seq_addr, + page_size, + PROT_READ, + MAP_PRIVATE | MAP_FIXED, + handle.get(), + page_size)); + + ScopedMmap file_mapping1; + ASSERT_TRUE(file_mapping1.ResetMmap(seq_addr + page_size, + page_size * 2, + PROT_READ, + MAP_PRIVATE | MAP_FIXED, + handle.get(), + 0)); + + ScopedMmap file_mapping2; + ASSERT_TRUE(file_mapping2.ResetMmap(seq_addr + page_size * 3, + page_size, + PROT_READ, + MAP_PRIVATE | MAP_FIXED, + handle.get(), + 0)); + + // Skip a page + + ScopedMmap file_mapping3; + ASSERT_TRUE(file_mapping3.ResetMmap(seq_addr + page_size * 5, + page_size, + PROT_READ, + MAP_PRIVATE | MAP_FIXED, + handle.get(), + 0)); + + ScopedMmap anon_mapping; + ASSERT_TRUE(anon_mapping.ResetMmap(seq_addr + page_size * 6, + page_size, + PROT_READ, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, + 0)); + + ScopedMmap file_mapping4; + ASSERT_TRUE(file_mapping4.ResetMmap(seq_addr + page_size * 7, + page_size, + PROT_READ, + MAP_PRIVATE | MAP_FIXED, + handle.get(), + 0)); + + FakePtraceConnection connection; + ASSERT_TRUE(connection.Initialize(getpid())); + MemoryMap map; + ASSERT_TRUE(map.Initialize(&connection)); + + auto mapping = map.FindMapping(file_mapping0.addr_as()); + ASSERT_TRUE(mapping); + auto possible_starts = map.FindFilePossibleMmapStarts(*mapping); + EXPECT_EQ(possible_starts.size(), 0u); + + mapping = map.FindMapping(file_mapping1.addr_as()); + ASSERT_TRUE(mapping); + possible_starts = map.FindFilePossibleMmapStarts(*mapping); + EXPECT_EQ(possible_starts.size(), 1u); + + mapping = map.FindMapping(file_mapping2.addr_as()); + ASSERT_TRUE(mapping); + possible_starts = map.FindFilePossibleMmapStarts(*mapping); + EXPECT_EQ(possible_starts.size(), 2u); + + mapping = map.FindMapping(file_mapping3.addr_as()); + ASSERT_TRUE(mapping); + possible_starts = map.FindFilePossibleMmapStarts(*mapping); + EXPECT_EQ(possible_starts.size(), 3u); + + mapping = map.FindMapping(file_mapping4.addr_as()); + ASSERT_TRUE(mapping); + possible_starts = map.FindFilePossibleMmapStarts(*mapping); + EXPECT_EQ(possible_starts.size(), 4u); } } // namespace