| // 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/elf/elf_image_reader.h" |
| |
| #include <dlfcn.h> |
| #include <link.h> |
| #include <unistd.h> |
| |
| #include "build/build_config.h" |
| #include "gtest/gtest.h" |
| #include "test/multiprocess_exec.h" |
| #include "test/process_type.h" |
| #include "test/scoped_module_handle.h" |
| #include "test/test_paths.h" |
| #include "util/file/file_io.h" |
| #include "util/misc/address_types.h" |
| #include "util/misc/elf_note_types.h" |
| #include "util/misc/from_pointer_cast.h" |
| #include "util/process/process_memory_native.h" |
| |
| #if BUILDFLAG(IS_FUCHSIA) |
| #include <lib/zx/process.h> |
| |
| #include "base/fuchsia/fuchsia_logging.h" |
| |
| #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) |
| |
| #include "test/linux/fake_ptrace_connection.h" |
| #include "util/linux/auxiliary_vector.h" |
| #include "util/linux/memory_map.h" |
| |
| #else |
| |
| #error Port. |
| |
| #endif // BUILDFLAG(IS_FUCHSIA) |
| |
| extern "C" { |
| __attribute__((visibility("default"))) void ElfImageReaderTestExportedSymbol() { |
| } |
| } // extern "C" |
| |
| namespace crashpad { |
| namespace test { |
| namespace { |
| |
| #if BUILDFLAG(IS_FUCHSIA) |
| |
| void LocateExecutable(const ProcessType& process, |
| ProcessMemory* memory, |
| VMAddress* elf_address) { |
| uintptr_t debug_address; |
| zx_status_t status = process->get_property( |
| ZX_PROP_PROCESS_DEBUG_ADDR, &debug_address, sizeof(debug_address)); |
| ASSERT_EQ(status, ZX_OK) |
| << "zx_object_get_property: ZX_PROP_PROCESS_DEBUG_ADDR"; |
| // Can be 0 if requested before the loader has loaded anything. |
| EXPECT_NE(debug_address, 0u); |
| |
| constexpr auto k_r_debug_map_offset = offsetof(r_debug, r_map); |
| uintptr_t map; |
| ASSERT_TRUE( |
| memory->Read(debug_address + k_r_debug_map_offset, sizeof(map), &map)) |
| << "read link_map"; |
| |
| constexpr auto k_link_map_addr_offset = offsetof(link_map, l_addr); |
| uintptr_t base; |
| ASSERT_TRUE(memory->Read(map + k_link_map_addr_offset, sizeof(base), &base)) |
| << "read base"; |
| |
| *elf_address = base; |
| } |
| |
| #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) |
| |
| void LocateExecutable(PtraceConnection* connection, |
| ProcessMemory* memory, |
| VMAddress* elf_address) { |
| AuxiliaryVector aux; |
| ASSERT_TRUE(aux.Initialize(connection)); |
| |
| VMAddress phdrs; |
| ASSERT_TRUE(aux.GetValue(AT_PHDR, &phdrs)); |
| |
| MemoryMap memory_map; |
| ASSERT_TRUE(memory_map.Initialize(connection)); |
| const MemoryMap::Mapping* phdr_mapping = memory_map.FindMapping(phdrs); |
| ASSERT_TRUE(phdr_mapping); |
| auto possible_mappings = memory_map.FindFilePossibleMmapStarts(*phdr_mapping); |
| ASSERT_EQ(possible_mappings->Count(), 1u); |
| *elf_address = possible_mappings->Next()->range.Base(); |
| } |
| |
| #endif // BUILDFLAG(IS_FUCHSIA) |
| |
| void ExpectSymbol(ElfImageReader* reader, |
| const std::string& symbol_name, |
| VMAddress expected_symbol_address) { |
| VMAddress symbol_address; |
| VMSize symbol_size; |
| ASSERT_TRUE( |
| reader->GetDynamicSymbol(symbol_name, &symbol_address, &symbol_size)); |
| EXPECT_EQ(symbol_address, expected_symbol_address); |
| |
| EXPECT_FALSE( |
| reader->GetDynamicSymbol("notasymbol", &symbol_address, &symbol_size)); |
| } |
| |
| void ReadThisExecutableInTarget(ProcessType process, |
| VMAddress exported_symbol_address) { |
| #if defined(ARCH_CPU_64_BITS) |
| constexpr bool am_64_bit = true; |
| #else |
| constexpr bool am_64_bit = false; |
| #endif // ARCH_CPU_64_BITS |
| |
| VMAddress elf_address; |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) |
| FakePtraceConnection connection; |
| ASSERT_TRUE(connection.Initialize(process)); |
| ProcessMemoryLinux memory(&connection); |
| LocateExecutable(&connection, &memory, &elf_address); |
| #elif BUILDFLAG(IS_FUCHSIA) |
| ProcessMemoryFuchsia memory; |
| ASSERT_TRUE(memory.Initialize(process)); |
| LocateExecutable(process, &memory, &elf_address); |
| #endif |
| ASSERT_NO_FATAL_FAILURE(); |
| |
| ProcessMemoryRange range; |
| ASSERT_TRUE(range.Initialize(&memory, am_64_bit)); |
| |
| ElfImageReader reader; |
| ASSERT_TRUE(reader.Initialize(range, elf_address)); |
| |
| ExpectSymbol( |
| &reader, "ElfImageReaderTestExportedSymbol", exported_symbol_address); |
| |
| ElfImageReader::NoteReader::Result result; |
| std::string note_name; |
| std::string note_desc; |
| ElfImageReader::NoteReader::NoteType note_type; |
| VMAddress desc_addr; |
| |
| std::unique_ptr<ElfImageReader::NoteReader> notes = reader.Notes(10000); |
| while ((result = notes->NextNote( |
| ¬e_name, ¬e_type, ¬e_desc, &desc_addr)) == |
| ElfImageReader::NoteReader::Result::kSuccess) { |
| } |
| EXPECT_EQ(result, ElfImageReader::NoteReader::Result::kNoMoreNotes); |
| |
| notes = reader.Notes(0); |
| EXPECT_EQ(notes->NextNote(¬e_name, ¬e_type, ¬e_desc, &desc_addr), |
| ElfImageReader::NoteReader::Result::kNoMoreNotes); |
| |
| // Find the note defined in elf_image_reader_test_note.S. |
| constexpr uint32_t kCrashpadNoteDesc = 42; |
| notes = reader.NotesWithNameAndType( |
| CRASHPAD_ELF_NOTE_NAME, CRASHPAD_ELF_NOTE_TYPE_SNAPSHOT_TEST, 10000); |
| ASSERT_EQ(notes->NextNote(¬e_name, ¬e_type, ¬e_desc, &desc_addr), |
| ElfImageReader::NoteReader::Result::kSuccess); |
| EXPECT_EQ(note_name, CRASHPAD_ELF_NOTE_NAME); |
| EXPECT_EQ(note_type, |
| implicit_cast<unsigned int>(CRASHPAD_ELF_NOTE_TYPE_SNAPSHOT_TEST)); |
| EXPECT_EQ(note_desc.size(), sizeof(kCrashpadNoteDesc)); |
| EXPECT_EQ(*reinterpret_cast<decltype(kCrashpadNoteDesc)*>(¬e_desc[0]), |
| kCrashpadNoteDesc); |
| |
| EXPECT_EQ(notes->NextNote(¬e_name, ¬e_type, ¬e_desc, &desc_addr), |
| ElfImageReader::NoteReader::Result::kNoMoreNotes); |
| } |
| |
| void ReadLibcInTarget(ProcessType process, |
| VMAddress elf_address, |
| VMAddress getpid_address) { |
| #if defined(ARCH_CPU_64_BITS) |
| constexpr bool am_64_bit = true; |
| #else |
| constexpr bool am_64_bit = false; |
| #endif // ARCH_CPU_64_BITS |
| |
| #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| FakePtraceConnection connection; |
| ASSERT_TRUE(connection.Initialize(process)); |
| ProcessMemoryLinux memory(&connection); |
| #else |
| ProcessMemoryNative memory; |
| ASSERT_TRUE(memory.Initialize(process)); |
| #endif |
| |
| ProcessMemoryRange range; |
| ASSERT_TRUE(range.Initialize(&memory, am_64_bit)); |
| |
| ElfImageReader reader; |
| ASSERT_TRUE(reader.Initialize(range, elf_address)); |
| |
| ExpectSymbol(&reader, "getpid", getpid_address); |
| } |
| |
| TEST(ElfImageReader, MainExecutableSelf) { |
| ReadThisExecutableInTarget( |
| GetSelfProcess(), |
| FromPointerCast<VMAddress>(ElfImageReaderTestExportedSymbol)); |
| } |
| |
| CRASHPAD_CHILD_TEST_MAIN(ReadExecutableChild) { |
| VMAddress exported_symbol_address = |
| FromPointerCast<VMAddress>(ElfImageReaderTestExportedSymbol); |
| CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput), |
| &exported_symbol_address, |
| sizeof(exported_symbol_address)); |
| CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); |
| return 0; |
| } |
| |
| class ReadExecutableChildTest : public MultiprocessExec { |
| public: |
| ReadExecutableChildTest() : MultiprocessExec() {} |
| |
| private: |
| void MultiprocessParent() { |
| // This read serves two purposes -- on Fuchsia, the loader may have not |
| // filled in debug address as soon as the child process handle is valid, so |
| // this causes a wait at least until the main() of the child, at which point |
| // it will always be valid. Secondarily, the address of the symbol to be |
| // looked up needs to be communicated. |
| VMAddress exported_symbol_address; |
| CheckedReadFileExactly(ReadPipeHandle(), |
| &exported_symbol_address, |
| sizeof(exported_symbol_address)); |
| ReadThisExecutableInTarget(ChildProcess(), exported_symbol_address); |
| } |
| }; |
| |
| TEST(ElfImageReader, MainExecutableChild) { |
| ReadExecutableChildTest test; |
| test.SetChildTestMainFunction("ReadExecutableChild"); |
| test.Run(); |
| } |
| |
| TEST(ElfImageReader, OneModuleSelf) { |
| Dl_info info; |
| ASSERT_TRUE(dladdr(reinterpret_cast<void*>(getpid), &info)) << "dladdr:" |
| << dlerror(); |
| VMAddress elf_address = FromPointerCast<VMAddress>(info.dli_fbase); |
| ReadLibcInTarget( |
| GetSelfProcess(), elf_address, FromPointerCast<VMAddress>(getpid)); |
| } |
| |
| CRASHPAD_CHILD_TEST_MAIN(ReadLibcChild) { |
| // Get the address of libc (by using getpid() as a representative member), |
| // and also the address of getpid() itself, and write them to the parent, so |
| // it can validate reading this information back out. |
| Dl_info info; |
| EXPECT_TRUE(dladdr(reinterpret_cast<void*>(getpid), &info)) |
| << "dladdr:" << dlerror(); |
| VMAddress elf_address = FromPointerCast<VMAddress>(info.dli_fbase); |
| VMAddress getpid_address = FromPointerCast<VMAddress>(getpid); |
| |
| CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput), |
| &elf_address, |
| sizeof(elf_address)); |
| CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput), |
| &getpid_address, |
| sizeof(getpid_address)); |
| CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput)); |
| return 0; |
| } |
| |
| class ReadLibcChildTest : public MultiprocessExec { |
| public: |
| ReadLibcChildTest() : MultiprocessExec() {} |
| ~ReadLibcChildTest() {} |
| |
| private: |
| void MultiprocessParent() { |
| VMAddress elf_address, getpid_address; |
| CheckedReadFileExactly(ReadPipeHandle(), &elf_address, sizeof(elf_address)); |
| CheckedReadFileExactly( |
| ReadPipeHandle(), &getpid_address, sizeof(getpid_address)); |
| ReadLibcInTarget(ChildProcess(), elf_address, getpid_address); |
| } |
| }; |
| |
| TEST(ElfImageReader, OneModuleChild) { |
| ReadLibcChildTest test; |
| test.SetChildTestMainFunction("ReadLibcChild"); |
| test.Run(); |
| } |
| |
| #if BUILDFLAG(IS_FUCHSIA) |
| |
| // crashpad_snapshot_test_both_dt_hash_styles is specially built and forced to |
| // include both .hash and .gnu.hash sections. Linux, Android, and Fuchsia have |
| // different defaults for which of these sections should be included; this test |
| // confirms that we get the same count from both sections. |
| // |
| // TODO(scottmg): Investigation in https://crrev.com/c/876879 resulted in |
| // realizing that ld.bfd does not emit a .gnu.hash that is very useful for this |
| // purpose when there's 0 exported entries in the module. This is not likely to |
| // be too important, as there's little need to look up non-exported symbols. |
| // However, it makes this test not work on Linux, where the default build uses |
| // ld.bfd. On Fuchsia, the only linker in use is lld, and it generates the |
| // expected .gnu.hash. So, for now, this test is only run on Fuchsia, not Linux. |
| // |
| // TODO(scottmg): Separately, the location of the ELF on Android needs some |
| // work, and then the test could also be enabled there. |
| TEST(ElfImageReader, DtHashAndDtGnuHashMatch) { |
| base::FilePath module_path = |
| TestPaths::BuildArtifact(FILE_PATH_LITERAL("snapshot"), |
| FILE_PATH_LITERAL("both_dt_hash_styles"), |
| TestPaths::FileType::kLoadableModule); |
| // TODO(scottmg): Remove this when upstream Fuchsia bug ZX-1619 is resolved. |
| // See also explanation in build/run_tests.py for Fuchsia .so files. |
| module_path = module_path.BaseName(); |
| ScopedModuleHandle module( |
| dlopen(module_path.value().c_str(), RTLD_LAZY | RTLD_LOCAL)); |
| ASSERT_TRUE(module.valid()) << "dlopen " << module_path.value() << ": " |
| << dlerror(); |
| |
| #if defined(ARCH_CPU_64_BITS) |
| constexpr bool am_64_bit = true; |
| #else |
| constexpr bool am_64_bit = false; |
| #endif // ARCH_CPU_64_BITS |
| |
| ProcessMemoryNative memory; |
| ASSERT_TRUE(memory.Initialize(GetSelfProcess())); |
| ProcessMemoryRange range; |
| ASSERT_TRUE(range.Initialize(&memory, am_64_bit)); |
| |
| struct link_map* lm = reinterpret_cast<struct link_map*>(module.get()); |
| |
| ElfImageReader reader; |
| ASSERT_TRUE(reader.Initialize(range, lm->l_addr)); |
| |
| VMSize from_dt_hash; |
| ASSERT_TRUE(reader.GetNumberOfSymbolEntriesFromDtHash(&from_dt_hash)); |
| |
| VMSize from_dt_gnu_hash; |
| ASSERT_TRUE(reader.GetNumberOfSymbolEntriesFromDtGnuHash(&from_dt_gnu_hash)); |
| |
| EXPECT_EQ(from_dt_hash, from_dt_gnu_hash); |
| } |
| |
| #endif // BUILDFLAG(IS_FUCHSIA) |
| |
| } // namespace |
| } // namespace test |
| } // namespace crashpad |