blob: 7748c7021f0b892fb885f51312db48cc07bf323f [file] [log] [blame]
// 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/elfldltl/vmo.h>
#include <lib/elfldltl/zircon.h>
#include <lib/llvm-profdata/llvm-profdata.h>
#include <lib/trivial-allocator/new.h>
#include <lib/trivial-allocator/zircon.h>
#include <lib/zx/channel.h>
#include <lib/zx/eventpair.h>
#include <lib/zx/vmo.h>
#include <zircon/compiler.h>
#include <zircon/syscalls.h>
#include <optional>
#include <string_view>
#include <utility>
#include "allocator.h"
#include "bootstrap.h"
#include "startup-diagnostics.h"
#include "zircon.h"
namespace ld {
namespace {
using VmoFile = elfldltl::VmoFile<Diagnostics>;
using SystemPageAllocator = trivial_allocator::ZirconVmar;
auto MakeStartupSystemPageAllocator(StartupData& startup) {
return SystemPageAllocator{startup.vmar};
}
auto MakeStartupScratchAllocator(SystemPageAllocator system) {
return MakeScratchAllocator(system);
}
using ScratchAllocator = decltype(MakeStartupScratchAllocator(SystemPageAllocator{}));
auto MakeStartupInitialExecAllocator(SystemPageAllocator system) {
return MakeInitialExecAllocator(system);
}
using InitialExecAllocator = decltype(MakeStartupInitialExecAllocator(SystemPageAllocator{}));
struct LoadExecutableResult : public StartupLoadResult {
StartupModule* module = nullptr;
};
LoadExecutableResult LoadExecutable(Diagnostics& diag, StartupData& startup,
ScratchAllocator& scratch, InitialExecAllocator& initial_exec,
zx::vmo vmo) {
LoadExecutableResult result = {
.module = StartupModule::New(diag, scratch, abi::Abi<>::kExecutableName, startup.vmar),
};
if (!vmo) [[unlikely]] {
diag.SystemError("no executable VMO in bootstrap message");
} else {
elfldltl::UnownedVmoFile file{vmo.borrow(), diag};
Elf::size_type max_tls_modid = 0;
static_cast<StartupLoadResult&>(result) =
result.module->Load(diag, initial_exec, file, 0, max_tls_modid);
assert(max_tls_modid <= 1);
}
return result;
}
[[maybe_unused]] void ProtectData(Diagnostics& diag, size_t page_size, zx::vmar self) {
auto [data_start, data_size] = DataBounds(page_size);
zx_status_t status = self.protect(ZX_VM_PERM_READ, data_start, data_size);
if (status != ZX_OK) [[unlikely]] {
diag.SystemError("cannot protect dynamic linker data pages", elfldltl::ZirconError{status});
}
}
zx::eventpair PublishProfdata(Diagnostics& diag, zx::unowned_vmar vmar,
cpp20::span<const std::byte> build_id) {
#if HAVE_LLVM_PROFDATA
auto error = [&diag](zx_status_t status, auto&&... args) -> zx::eventpair {
diag.SystemError(std::forward<decltype(args)>(args)..., elfldltl::ZirconError{status});
return {};
};
LlvmProfdata profdata;
profdata.Init(build_id);
const size_t size = profdata.size_bytes();
if (size != 0) {
// Make a VMO and map it in to hold the profdata.
zx::vmo vmo;
zx_status_t status = zx::vmo::create(size, 0, &vmo);
if (status != ZX_OK) {
return error(status, "cannot create llvm-profdata VMO of ", size, " bytes");
}
uintptr_t ptr;
status = vmar->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, vmo, 0, size, &ptr);
if (status != ZX_OK) {
return error(status, "cannot map llvm-profdata VMO of ", size, " bytes");
}
cpp20::span vmo_data{reinterpret_cast<std::byte*>(ptr), size};
// Now fill the VMO and redirect the instrumentation to update its data.
auto live_data = profdata.WriteFixedData(vmo_data);
profdata.CopyLiveData(live_data);
LlvmProfdata::UseLiveData(live_data);
// At this point the instrumentation will no longer touch the data segment.
zx::eventpair local_token, remote_token;
status = zx::eventpair::create(0, &local_token, &remote_token);
if (status != ZX_OK) {
return error(status, "zx_eventpair_create");
}
// TODO(https://fxbug.dev/42080826): Send the VMO and remote_token in a
// fuchsia.debugdata.Publisher/Publish message to ... somewhere.
return local_token;
}
#endif
return {};
}
} // namespace
// This is returned to the _start assembly code, which hands off to the
// returned entry point with the given argument register and the stack unwound
// to the starting conditions when _start was called.
struct StartLdResult {
uintptr_t arg, entry;
};
extern "C" StartLdResult StartLd(zx_handle_t handle, void* vdso) {
// First thing, bootstrap our own dynamic linking against ourselves and the
// vDSO. For this, nothing should go wrong so use a diagnostics object that
// crashes the process at the first error. Before linking against the vDSO
// is completed successfully, there's no way to make a system call to get an
// error out anyway.
auto bootstrap_diag = elfldltl::TrapDiagnostics();
BootstrapModule vdso_module = BootstrapVdsoModule(bootstrap_diag, vdso);
BootstrapModule self_module = BootstrapSelfModule(bootstrap_diag, vdso_module.module);
// Only now can we make the system call to discover the page size.
const size_t page_size = zx_system_get_page_size();
CompleteBootstrapModule(vdso_module.module, page_size);
CompleteBootstrapModule(self_module.module, page_size);
// Read the bootstrap message.
zx::channel bootstrap{std::exchange(handle, {})};
StartupData startup = ReadBootstrap(bootstrap.borrow());
// Now that things are bootstrapped, set up the main diagnostics object.
Diagnostics diag{startup};
// Start publishing profiling data in an instrumented build. Before this,
// the instrumentation is updating counters in the data segment. After this,
// it's updating a VMO mapped elsewhere. That VMO remains mapped after
// startup just to avoid bothering with code to unmap it since that code and
// the rest of the return path would have to be uninstrumented. When the
// returned handle is closed by going out of scope at the end of startup,
// this will signal the data receiver that the VMO's data is ready. It's
// still possible for either the last bit of instrumented code in the startup
// path, or just stray pointer writes in the process after startup will
// modify it, but will be ignored or will be tolerable noise in the data.
auto profdata = PublishProfdata(diag, startup.vmar.borrow(), self_module.module.build_id);
// Set up the allocators. These objects hold zx::unowned_vmar copies but do
// not own the VMAR handle.
auto system_page_allocator = MakeStartupSystemPageAllocator(startup);
auto scratch = MakeStartupScratchAllocator(system_page_allocator);
auto initial_exec = MakeStartupInitialExecAllocator(system_page_allocator);
// TODO(https://fxbug.dev/42084623): We should be making an ldsvc.Config call
// here to get the correct shared objects.
// Load the main executable.
LoadExecutableResult main =
LoadExecutable(diag, startup, scratch, initial_exec, std::move(startup.executable_vmo));
auto get_vmo_file = [&diag,
&startup](const elfldltl::Soname<>& soname) -> std::optional<VmoFile> {
if (zx::vmo vmo = startup.GetLibraryVmo(diag, soname.c_str())) {
return VmoFile{std::move(vmo), diag};
}
return {};
};
StartupModule::LinkModules(diag, scratch, initial_exec, main.module, get_vmo_file,
{vdso_module, self_module}, main.needed_count, startup.vmar);
// Bail out before relocation if there were any loading errors.
CheckErrors(diag);
if constexpr (kProtectData) {
// Now that startup is completed, protect not only the RELRO, but also all
// the data and bss. Then drop that VMAR handle so the protections cannot
// be changed again.
ProtectData(diag, page_size, std::move(startup.self_vmar));
}
// Bail out before handoff if any errors have been detected.
CheckErrors(diag);
return {.arg = bootstrap.release(), .entry = main.entry};
}
} // namespace ld