blob: 4292ea99f9e51f37e829c62e279d229f7dd96efd [file] [log] [blame] [edit]
// 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 "ld-startup-in-process-tests-posix.h"
#include <lib/elfldltl/layout.h>
#include <lib/ld/abi.h>
#include <lib/stdcompat/span.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <numeric>
#include <gtest/gtest.h>
#include "../posix.h"
#include "ld-load-tests-base.h"
#include "test-chdir-guard.h"
namespace ld::testing {
namespace {
constexpr std::string_view kLibprefix = LD_STARTUP_TEST_LIBPREFIX;
constexpr size_t kStackSize = 64 << 10;
// This is actually defined in assembly code with internal linkage.
// It simply switches to the new SP and then calls the entry point.
// When that code returns, this just restores the old SP and also returns.
extern "C" int64_t CallOnStack(uintptr_t entry, void* sp);
__asm__(
R"""(
.pushsection .text.CallOnStack, "ax", %progbits
.type CallOnstack, %function
CallOnStack:
.cfi_startproc
)"""
#if defined(__aarch64__)
R"""(
stp x29, x30, [sp, #-16]!
.cfi_adjust_cfa_offset 16
mov x29, sp
.cfi_def_cfa_register x29
mov sp, x1
blr x0
mov sp, x29
.cfi_def_cfa_register sp
ldp x29, x30, [sp], #16
.cfi_adjust_cfa_offset -16
ret
)"""
#elif defined(__x86_64__)
// Note this stores our return address below the SP and then jumps, because
// a call would move the SP. The posix-startup.S entry point code expects
// the StartupStack at the SP, not a return address. Note this saves and
// restores %rbx so that the entry point code can clobber it.
// TODO(mcgrathr): For now, it then returns at the end, popping the stack.
R"""(
push %rbp
.cfi_adjust_cfa_offset 8
mov %rsp, %rbp
.cfi_def_cfa_register %rbp
.cfi_offset %rbp, -8*2
push %rbx
.cfi_offset %rbx, -8*3
lea 0f(%rip), %rax
mov %rsi, %rsp
mov %rax, -8(%rsp)
jmp *%rdi
0:mov %rbp, %rsp
.cfi_def_cfa_register %rsp
mov -8(%rsp), %rbx
.cfi_same_value %rbx
pop %rbp
.cfi_same_value %rbp
.cfi_adjust_cfa_offset -8
ret
)"""
#else
#error "unsupported machine"
#endif
R"""(
.cfi_endproc
.size CallOnStack, . - CallOnStack
.popsection
)""");
} // namespace
const std::string kLdStartupName = std::string(kLibprefix) + std::string(ld::abi::kInterp);
struct LdStartupInProcessTests::AuxvBlock {
ld::Auxv vdso = {
static_cast<uintptr_t>(ld::AuxvTag::kSysinfoEhdr),
getauxval(static_cast<uintptr_t>(ld::AuxvTag::kSysinfoEhdr)),
};
ld::Auxv pagesz = {
static_cast<uintptr_t>(ld::AuxvTag::kPagesz),
static_cast<uintptr_t>(sysconf(_SC_PAGE_SIZE)),
};
ld::Auxv phdr = {static_cast<uintptr_t>(ld::AuxvTag::kPhdr)};
ld::Auxv phent = {
static_cast<uintptr_t>(ld::AuxvTag::kPhent),
sizeof(elfldltl::Elf<>::Phdr),
};
ld::Auxv phnum = {static_cast<uintptr_t>(ld::AuxvTag::kPhnum)};
ld::Auxv entry = {static_cast<uintptr_t>(ld::AuxvTag::kEntry)};
const ld::Auxv null = {static_cast<uintptr_t>(ld::AuxvTag::kNull)};
};
void LdStartupInProcessTests::Init(std::initializer_list<std::string_view> args) {
ASSERT_NO_FATAL_FAILURE(AllocateStack());
ASSERT_NO_FATAL_FAILURE(PopulateStack(args, {}));
}
void LdStartupInProcessTests::Load(std::string_view executable_name) {
ASSERT_TRUE(auxv_); // Init must have been called already.
// First load the dynamic linker.
std::optional<LoadResult> result;
ASSERT_NO_FATAL_FAILURE(Load(kLdStartupName, result));
// Stash the dynamic linker's entry point.
entry_ = result->entry + result->loader.load_bias();
// Save the loader object so it gets destroyed when the test fixture is
// destroyed destroyed. That will clean up the mappings it made. (This
// doesn't do anything about any mappings that were made by the loaded code
// at Run(), but so it goes.)
loader_ = std::move(result->loader);
// Now load the executable.
ASSERT_NO_FATAL_FAILURE(Load(InProcessTestExecutable(executable_name), result));
// Set AT_PHDR and AT_PHNUM for where the phdrs were loaded.
cpp20::span phdrs = result->phdrs.get();
// This non-template lambda gets called with the vaddr, offset, and filesz of
// each segment. It's called by the generic lambda passed to VisitSegments.
auto on_segment = [load_bias = result->loader.load_bias(), phoff = result->phoff(),
phdrs_size_bytes = phdrs.size_bytes(),
this](uintptr_t vaddr, uintptr_t offset, size_t filesz) {
if (offset <= phoff && phoff - offset < filesz &&
filesz - (phoff - offset) >= phdrs_size_bytes) {
auxv_->phdr.back() = phoff - offset + vaddr + load_bias;
return false;
}
return true;
};
result->info.VisitSegments([on_segment](const auto& segment) {
return on_segment(segment.vaddr(), segment.offset(), segment.filesz());
});
ASSERT_NE(auxv_->phdr.back(), 0u);
auxv_->phnum.back() = phdrs.size();
// Set AT_ENTRY to the executable's entry point.
auxv_->entry.back() = result->entry + result->loader.load_bias();
// Save the second Loader object to keep the mappings alive.
exec_loader_ = std::move(result->loader);
}
int64_t LdStartupInProcessTests::Run() {
// Move into the directory where ld.so.1 and all the files are so that they
// can be loaded by simple relative file names. For now, the POSIX version
// of the dynamic linker uses the plain SONAME as a relative filename.
TestChdirGuard in_test_lib_dir;
return CallOnStack(entry_, sp_);
}
LdStartupInProcessTests::~LdStartupInProcessTests() {
if (stack_) {
munmap(stack_, kStackSize * 2);
}
}
void LdStartupInProcessTests::AllocateStack() {
// Allocate a stack and a guard region below it.
void* ptr = mmap(nullptr, kStackSize * 2, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
ASSERT_TRUE(ptr) << "mmap: " << strerror(errno);
stack_ = ptr;
// Protect the guard region below the stack.
EXPECT_EQ(mprotect(stack_, kStackSize, PROT_NONE), 0) << strerror(errno);
}
void LdStartupInProcessTests::PopulateStack(std::initializer_list<std::string_view> argv,
std::initializer_list<std::string_view> envp) {
// Figure out the total size of string data to write.
constexpr auto string_size = [](size_t total, std::string_view str) {
return total + str.size() + 1;
};
const size_t strings =
std::accumulate(argv.begin(), argv.end(),
std::accumulate(envp.begin(), envp.end(), 0, string_size), string_size);
// Compute the total number of pointers to write (after the argc word).
size_t ptrs = argv.size() + 1 + envp.size() + 1;
// The stack must fit all that plus the auxv block.
ASSERT_LT(strings + 15 + ((1 + ptrs) * sizeof(uintptr_t)) + sizeof(AuxvBlock), kStackSize);
// Start at the top of the stack, and place the strings.
std::byte* sp = static_cast<std::byte*>(stack_) + (kStackSize * 2);
sp -= strings;
cpp20::span string_space{reinterpret_cast<char*>(sp), strings};
// Adjust down so everything will be aligned.
const size_t strings_and_ptrs = strings + ((1 + ptrs) * sizeof(uintptr_t));
sp -= ((strings_and_ptrs + 15) & -size_t{16}) - strings_and_ptrs;
// Next, leave space for the auxv block, which can be filled in later.
static_assert(sizeof(AuxvBlock) % 16 == 0);
sp -= sizeof(AuxvBlock);
auxv_ = new (sp) AuxvBlock;
// Finally, the argc and pointers form what's seen right at the SP.
sp -= (1 + ptrs) * sizeof(uintptr_t);
ld::StartupStack* startup = new (sp) ld::StartupStack{.argc = argv.size()};
cpp20::span string_ptrs{startup->argv, ptrs};
// Now copy the strings and write the pointers to them.
for (auto list : {argv, envp}) {
for (std::string_view str : list) {
string_ptrs.front() = string_space.data();
string_ptrs = string_ptrs.subspan(1);
string_space = string_space.subspan(str.copy(string_space.data(), string_space.size()));
string_space.front() = '\0';
string_space = string_space.subspan(1);
}
string_ptrs.front() = nullptr;
string_ptrs = string_ptrs.subspan(1);
}
ASSERT_TRUE(string_ptrs.empty());
ASSERT_TRUE(string_space.empty());
ASSERT_EQ(reinterpret_cast<uintptr_t>(sp) % 16, 0u);
sp_ = sp;
}
// The loaded code is just writing to STDERR_FILENO in the same process.
// There's no way to install e.g. a pipe end as STDERR_FILENO for the loaded
// code without also hijacking stderr for the test harness itself, which seems
// a bit dodgy even if the original file descriptor were saved and dup2'd back
// after the test succeeds. In the long run, most cases where the real dynamic
// linker would emit any diagnostics are when it would then crash the process,
// so those cases will only get tested via spawning a new process, not
// in-process tests.
void LdStartupInProcessTests::ExpectLog(std::string_view expected_log) {
// No log capture, so this must be used only in tests that expect no output.
EXPECT_EQ(expected_log, "");
}
} // namespace ld::testing