|  | // Copyright 2023 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <lib/ld/abi.h> | 
|  | #include <lib/ld/testing/test-elf-object.h> | 
|  |  | 
|  | #include <string_view> | 
|  |  | 
|  | #include "load-tests.h" | 
|  |  | 
|  | namespace ld::testing { | 
|  | namespace { | 
|  |  | 
|  | using namespace elfldltl::literals; | 
|  | using namespace std::literals; | 
|  |  | 
|  | std::vector<std::string> SplitLogLines(std::string_view log) { | 
|  | std::vector<std::string> log_lines; | 
|  | while (!log.empty()) { | 
|  | size_t n = log.find('\n'); | 
|  | EXPECT_NE(n, std::string_view::npos) << "last log line unterminated: " << log; | 
|  | if (n == std::string_view::npos) { | 
|  | break; | 
|  | } | 
|  | log_lines.emplace_back(log.substr(0, n)); | 
|  | log.remove_prefix(n + 1); | 
|  | } | 
|  | return log_lines; | 
|  | } | 
|  |  | 
|  | std::string TestModuleMarkup(const TestElfObject& module, size_t idx, std::string_view name) { | 
|  | return "{{{module:"s + std::to_string(idx) + ':' + std::string(name) + | 
|  | ":elf:" + std::string(module.build_id_hex) + "}}}"; | 
|  | } | 
|  |  | 
|  | // TODO(https://fxbug.dev/374053770): An apparent incremental build bug makes | 
|  | // this appear to be flaky. Re-enable this when the build bug is fixed. | 
|  | TYPED_TEST(LdLoadTests, DISABLED_SymbolizerMarkup) { | 
|  | if constexpr (!TestFixture::kCanCollectLog) { | 
|  | GTEST_SKIP() << "test requires log capture"; | 
|  | } | 
|  |  | 
|  | const std::string executable_name_string = | 
|  | "indirect-deps"s + std::string(TestFixture::kTestExecutableSuffix); | 
|  | const elfldltl::Soname<> executable_name{executable_name_string}; | 
|  |  | 
|  | const TestElfLoadSet* load_set = TestElfLoadSet::Get(executable_name); | 
|  | ASSERT_TRUE(load_set) << executable_name; | 
|  | TestElfLoadSet::SonameMap soname_map = load_set->MakeSonameMap(); | 
|  |  | 
|  | constexpr int64_t kReturnValue = 17; | 
|  |  | 
|  | ASSERT_NO_FATAL_FAILURE(this->Init({}, {"LD_DEBUG=1"})); | 
|  |  | 
|  | ASSERT_NO_FATAL_FAILURE(this->Needed({ | 
|  | "libindirect-deps-a.so", | 
|  | "libindirect-deps-b.so", | 
|  | "libindirect-deps-c.so", | 
|  | })); | 
|  |  | 
|  | ASSERT_NO_FATAL_FAILURE(this->Load("indirect-deps")); | 
|  |  | 
|  | EXPECT_EQ(this->Run(), kReturnValue); | 
|  |  | 
|  | // Collect the log and slice it into lines. | 
|  | const std::vector<std::string> log_lines = SplitLogLines(this->CollectLog()); | 
|  |  | 
|  | // There should be at least one module line and one mmap line per module. | 
|  | EXPECT_GE(log_lines.size(), 2u * 5u); | 
|  |  | 
|  | auto next_line = [left = std::span{log_lines}](bool skip_mmap = false) mutable  // | 
|  | -> std::optional<std::string_view> { | 
|  | std::string_view line; | 
|  | do { | 
|  | if (left.empty()) { | 
|  | return std::nullopt; | 
|  | } | 
|  | line = left.front(); | 
|  | EXPECT_FALSE(line.empty()); | 
|  | left = left.subspan(1); | 
|  | // If skip_mmap is true, we just saw a module line for a module whose | 
|  | // segment details we don't know, so just expect to see some number of | 
|  | // mmap lines for it before the next module we're looking for. | 
|  | } while (skip_mmap && line.starts_with("{{{mmap:"sv)); | 
|  | return line; | 
|  | }; | 
|  |  | 
|  | auto expect_mmap = [page_size = static_cast<size_t>(sysconf(_SC_PAGE_SIZE)), &next_line]( | 
|  | const TestElfObject& module, size_t idx) { | 
|  | for (const auto& phdr : module.load_segments) { | 
|  | const uint64_t vaddr = phdr.vaddr & -page_size; | 
|  | const uint64_t memsz = ((phdr.vaddr + phdr.memsz + page_size - 1) & -page_size) - vaddr; | 
|  | std::string flags; | 
|  | if (phdr.flags & elfldltl::Elf<>::Phdr::kRead) { | 
|  | flags += 'r'; | 
|  | } | 
|  | if (phdr.flags & elfldltl::Elf<>::Phdr::kWrite) { | 
|  | flags += 'w'; | 
|  | } | 
|  | if (phdr.flags & elfldltl::Elf<>::Phdr::kExecute) { | 
|  | flags += 'x'; | 
|  | } | 
|  | uint64_t mmap_memsz, mmap_file_vaddr; | 
|  | size_t mmap_modid; | 
|  | char mmap_flags[5]; | 
|  | std::optional<std::string_view> line = next_line(); | 
|  | ASSERT_TRUE(line) << "missing mmap line for " << module.soname << " after " | 
|  | << &phdr - module.load_segments.data() << " phdrs"; | 
|  | ASSERT_EQ(sscanf(std::string(*line).c_str(), | 
|  | "{{{mmap:%*x:%" PRIx64 ":load:%zi:%4[^:]:%" PRIx64 " }}}", &mmap_memsz, | 
|  | &mmap_modid, mmap_flags, &mmap_file_vaddr), | 
|  | 4) | 
|  | << *line << " for " << module.soname; | 
|  | EXPECT_EQ(mmap_modid, idx) << *line << " for " << module.soname; | 
|  | EXPECT_EQ(mmap_file_vaddr, vaddr) << *line << " for " << module.soname; | 
|  | EXPECT_EQ(mmap_memsz, memsz) << *line << " for " << module.soname; | 
|  | EXPECT_EQ(std::string(mmap_flags), flags) << *line << " for " << module.soname; | 
|  | } | 
|  | }; | 
|  |  | 
|  | auto expect_module = [load_set, &soname_map, &expect_mmap, &next_line]( | 
|  | elfldltl::Soname<> name, size_t idx, bool skip_mmap = false) { | 
|  | std::string_view modname = idx == 0 ? "<application>"sv : name.str(); | 
|  |  | 
|  | auto it = soname_map.end(); | 
|  | if (idx > 0) { | 
|  | it = soname_map.find(name); | 
|  | ASSERT_NE(it, soname_map.end()) << name; | 
|  | } | 
|  | const TestElfObject& module =  // Use the SONAME lookup unless idx is 0. | 
|  | idx > 0 ? it->second : load_set->main_module(); | 
|  | std::string markup = TestModuleMarkup(module, idx, modname); | 
|  |  | 
|  | std::optional<std::string_view> line = next_line(skip_mmap); | 
|  | EXPECT_THAT(line, ::testing::Optional(::testing::StrEq(markup))) | 
|  | << "Expected " << name << " (" << modname << ") [" << module.build_id_hex << "] at ID " | 
|  | << idx << " but got " << line.value_or("<EOF>"sv); | 
|  | expect_mmap(module, idx); | 
|  | }; | 
|  |  | 
|  | expect_module(executable_name, 0); | 
|  |  | 
|  | size_t idx = 1; | 
|  | bool skip_mmap_before_deps = false; | 
|  | if constexpr (TestFixture::kTestExecutableNeedsVdso) { | 
|  | // The vDSO will be the first dependency.  We don't know its build ID, | 
|  | // so just check the name. | 
|  | std::optional<std::string_view> line = next_line(); | 
|  | EXPECT_THAT(line, ::testing::Optional(::testing::StartsWith( | 
|  | "{{{module:1:"s + | 
|  | std::string{TestFixture::kTestExecutableNeedsVdso->str()} + ":elf:"s))) | 
|  | << line.value_or("<EOF>"sv); | 
|  | ++idx; | 
|  | // If we expected the vDSO module line, we expect mmap lines | 
|  | // for it too, though we don't know what they'll say. | 
|  | skip_mmap_before_deps = true; | 
|  | } | 
|  |  | 
|  | expect_module("libindirect-deps-a.so"_soname, idx++, skip_mmap_before_deps); | 
|  |  | 
|  | expect_module("libindirect-deps-b.so"_soname, idx++); | 
|  |  | 
|  | expect_module("libindirect-deps-c.so"_soname, idx++); | 
|  |  | 
|  | // None of these modules links against ld.so itself, and on non-Fuchsia none | 
|  | // links against the vDSO.  But those appear at the end of the list anyway. | 
|  | auto find_ld_module = soname_map.find(ld::abi::Abi<>::kSoname); | 
|  | ASSERT_NE(find_ld_module, soname_map.end()); | 
|  | const TestElfObject& ld_module = find_ld_module->second; | 
|  | std::optional<std::string_view> module_line = next_line(); | 
|  | ASSERT_THAT(module_line, ::testing::Optional( | 
|  | ::testing::StartsWith("{{{module:"s + std::to_string(idx) + ':'))); | 
|  | if (!TestFixture::kTestExecutableNeedsVdso && | 
|  | !module_line->starts_with("{{{module:"s + std::to_string(idx) + ':' + | 
|  | std::string{ld::abi::Abi<>::kSoname.str()})) { | 
|  | // This must be the vDSO.  The ld.so module will be after it.  We don't | 
|  | // know what the vDSO's mmap lines should look like, so just skip them all. | 
|  | ++idx; | 
|  | module_line = next_line(true); | 
|  | } | 
|  |  | 
|  | // Finally the ld.so module will appear. | 
|  | EXPECT_EQ(*module_line, TestModuleMarkup(ld_module, idx, ld::abi::Abi<>::kSoname.str())); | 
|  | expect_mmap(ld_module, idx); | 
|  |  | 
|  | // There should be no more log lines after that. | 
|  | std::optional<std::string_view> tail = next_line(); | 
|  | EXPECT_EQ(tail, std::nullopt) << *tail; | 
|  | } | 
|  |  | 
|  | TYPED_TEST(LdLoadTests, Backtrace) { | 
|  | if constexpr (std::is_same_v<ld::testing::LdStartupInProcessTests, TestFixture>) { | 
|  | GTEST_SKIP() << "test not supported in-process"; | 
|  | } | 
|  |  | 
|  | constexpr int64_t kReturnValue = 17; | 
|  |  | 
|  | ASSERT_NO_FATAL_FAILURE(this->Init()); | 
|  |  | 
|  | ASSERT_NO_FATAL_FAILURE(this->Load("backtrace")); | 
|  |  | 
|  | EXPECT_EQ(this->Run(), kReturnValue); | 
|  |  | 
|  | this->ExpectLog(""); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace ld::testing |