blob: a46e7640e2ca8e4411c2bf3ebeb662006121449c [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "trampoline-boot.h"
#include <lib/arch/x86/standard-segments.h>
#include <lib/arch/zbi-boot.h>
#include <lib/memalloc/allocator.h>
#include <cstddef>
#include <cstring>
#include <ktl/byte.h>
#include <phys/symbolize.h>
// This describes the "trampoline" area that is set up in some memory that's
// safely out of the way: not part of this shim's own image, which might be
// overwritten, and not part of the fixed-position kernel load image or reserve
// memory, not part of the kernel image being relocated, and not part of the
// data ZBI image. Trampoline::size() bytes must be allocated in the safe
// place and then it must be constructed with new (ptr) (Trampoline::size())
// before Boot() is finally called.
class TrampolineBoot::Trampoline {
public:
explicit Trampoline(size_t space) {
ZX_ASSERT(space >= size());
const zbitl::ByteView code = TrampolineCode();
memcpy(code_, code.data(), code.size());
}
static size_t size() { return offsetof(Trampoline, code_) + TrampolineCode().size(); }
[[noreturn]] void Boot(const zircon_kernel_t* kernel, uint32_t kernel_size, void* zbi) {
TrampolineArgs args = {
.dst = kFixedLoadAddress,
.src = reinterpret_cast<uintptr_t>(kernel),
.count = kernel_size,
.entry = static_cast<uintptr_t>(kernel->data_kernel.entry),
.zbi = reinterpret_cast<uintptr_t>(zbi),
};
args.SetDirection();
ZX_ASSERT(args.entry == kernel->data_kernel.entry);
arch::ZbiBootRaw(reinterpret_cast<uintptr_t>(code_), &args);
}
private:
// This packs up the arguments for the trampoline code, which are pretty much
// the operands for REP MOVSB plus the entry point and data ZBI addresses.
struct TrampolineArgs {
// When the addresses overlap, the copying can be done backwards and so the
// direction flag is set for REP MOVSB and the starting pointers are at the
// laste byte rather than the first.
void SetDirection() {
backwards = dst > src && dst - src < count;
if (backwards) {
dst += count - 1;
src += count - 1;
}
}
uint64_t dst;
uint64_t src;
uint64_t count;
uint64_t entry;
uint64_t zbi;
bool backwards;
};
[[gnu::const]] static zbitl::ByteView TrampolineCode() {
// This tiny bit of code will be copied someplace out of the way. Then it
// will be entered with %rsi pointing at TrampolineArgs, which can be on
// the stack since it's read immediately. Since this code is safely out of
// the way, it can perform a copy that might clobber this boot shim's own
// code, data, bss, and stack. After the copy, it jumps directly to the
// fixed-address ZBI kernel's entry point and %rsi points to the data ZBI.
//
// First the code loads the backwards flag into %al, the entry address into
// %rbx, and the ZBI address into %rdx. Then it loads the registers used
// by REP MOVSB (%rcx, %rdi, and %rsi). It then tests the %al flag to set
// the Direction flag (STD) for backwards mode. Then REP MOVSB does the
// copy, whether forwards or backwards. After that, the SP and FP are
// cleared, the D flag is cleared again and interrupts disabled for good
// measure, before finally moving the ZBI pointer into place (%rsi) and
// jumping to the entry point (%rbx).
const std::byte* code;
size_t size;
__asm__(R"""(
.code64
.pushsection .rodata.trampoline, "a?", %%progbits
0:
mov %c[backwards](%%rsi), %%al
mov %c[entry](%%rsi), %%rbx
mov %c[count](%%rsi), %%rcx
mov %c[zbi](%%rsi), %%rdx
mov %c[dst](%%rsi), %%rdi
mov %c[src](%%rsi), %%rsi
testb %%al, %%al
jz 1f
std
1:
rep movsb
xor %%esp, %%esp
xor %%ebp, %%ebp
cld
cli
mov %%rdx, %%rsi
jmp *%%rbx
2:
.popsection
)"""
#ifdef __i386__
R"""(
.code32
mov $0b, %[code]
mov $(2b - 0b), %[size]
)"""
#else
R"""(
lea 0b(%%rip), %[code]
mov $(2b - 0b), %[size]
)"""
#endif
: [code] "=r"(code), [size] "=r"(size)
: [backwards] "i"(offsetof(TrampolineArgs, backwards)), //
[dst] "i"(offsetof(TrampolineArgs, dst)), //
[src] "i"(offsetof(TrampolineArgs, src)), //
[count] "i"(offsetof(TrampolineArgs, count)), //
[zbi] "i"(offsetof(TrampolineArgs, zbi)), //
[entry] "i"(offsetof(TrampolineArgs, entry)));
return {code, size};
}
arch::X86StandardSegments segments_;
ktl::byte code_[];
};
fitx::result<BootZbi::Error> TrampolineBoot::Load(uint32_t extra_data_capacity) {
if (KernelHeader()->entry < kFixedLoadAddress) {
// New-style position-independent kernel.
return BootZbi::Load(extra_data_capacity);
}
// Now we know how much space the kernel image needs.
// Reserve it at the fixed load address.
auto& alloc = Allocation::GetAllocator();
ZX_ASSERT(alloc.RemoveRange(kFixedLoadAddress, KernelMemorySize()).is_ok());
// The trampoline needs someplace safely neither in the kernel image, nor in
// the data ZBI image, nor in this shim's own image since that might overlap
// the fixed-address target region. It's tiny, so just extend the extra data
// capacity to cover it and use the few bytes just after the data ZBI. The
// space is safely allocated in our present reckoning so it's disjoint from
// the data and kernel image memory and from this shim's own image, but as
// soon as we boot into the new kernel it will be reclaimable memory.
auto result = BootZbi::Load(extra_data_capacity + Trampoline::size(), kFixedLoadAddress);
if (result.is_error()) {
return result.take_error();
}
auto extra_space = DataZbi().storage().subspan(DataZbi().size_bytes());
auto trampoline = extra_space.subspan(extra_data_capacity);
trampoline_ = new (trampoline.data()) Trampoline(trampoline.size());
return fitx::ok();
}
[[noreturn]] void TrampolineBoot::Boot() {
ZX_ASSERT(!MustRelocateDataZbi());
uintptr_t entry = static_cast<uintptr_t>(KernelEntryAddress());
ZX_ASSERT(entry == KernelEntryAddress());
uintptr_t zbi = static_cast<uintptr_t>(DataLoadAddress());
ZX_ASSERT(zbi == DataLoadAddress());
uintptr_t kernel_first = static_cast<uintptr_t>(KernelLoadAddress());
uintptr_t kernel_last = static_cast<uintptr_t>(KernelLoadAddress() + KernelLoadSize() - 1);
ZX_ASSERT(kernel_first == KernelLoadAddress());
ZX_ASSERT(kernel_last == KernelLoadAddress() + KernelLoadSize() - 1);
uintptr_t kernel_size = static_cast<uintptr_t>(KernelLoadSize());
ZX_ASSERT(kernel_size == KernelLoadSize());
uintptr_t fixed_first = static_cast<uintptr_t>(kFixedLoadAddress);
uintptr_t fixed_last = static_cast<uintptr_t>(kFixedLoadAddress + KernelLoadSize() - 1);
ZX_ASSERT(fixed_first == kFixedLoadAddress);
ZX_ASSERT(fixed_last == kFixedLoadAddress + KernelLoadSize() - 1);
if (!trampoline_) {
// This is a new-style position-independent kernel. Boot it where it is.
BootZbi::Boot();
}
LogAddresses();
LogFixedAddresses();
LogBoot(KernelEntryAddress());
trampoline_->Boot(KernelImage(), KernelLoadSize(), DataZbi().storage().data());
}
// This output lines up with what BootZbi::LogAddresses() prints.
void TrampolineBoot::LogFixedAddresses() const {
#define ADDR "0x%016" PRIx64
const auto& name = Symbolize::kProgramName_;
const uint64_t kernel = kFixedLoadAddress;
const uint64_t bss = kernel + KernelLoadSize();
const uint64_t end = kernel + KernelMemorySize();
printf("%s: Relocated @ [" ADDR ", " ADDR ")\n", name, kernel, bss);
printf("%s: BSS @ [" ADDR ", " ADDR ")\n", name, bss, end);
}