211 lines
7.3 KiB
C++
211 lines
7.3 KiB
C++
/*
|
|
* Copyright (C) 2024 The Android Open Source Project
|
|
*
|
|
* 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 "uefi/uefi.h"
|
|
|
|
#include <lib/bio.h>
|
|
#include <lib/heap.h>
|
|
#include <lk/console_cmd.h>
|
|
#include <lk/debug.h>
|
|
#include <lk/err.h>
|
|
#include <lk/trace.h>
|
|
#include <platform.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <uefi/boot_service.h>
|
|
#include <uefi/protocols/simple_text_output_protocol.h>
|
|
#include <uefi/runtime_service.h>
|
|
#include <uefi/system_table.h>
|
|
|
|
#include "boot_service_provider.h"
|
|
#include "configuration_table.h"
|
|
#include "defer.h"
|
|
#include "memory_protocols.h"
|
|
#include "pe.h"
|
|
#include "relocation.h"
|
|
#include "runtime_service_provider.h"
|
|
#include "switch_stack.h"
|
|
#include "text_protocol.h"
|
|
#include "uefi_platform.h"
|
|
|
|
namespace {
|
|
|
|
constexpr auto EFI_SYSTEM_TABLE_SIGNATURE =
|
|
static_cast<u64>(0x5453595320494249ULL);
|
|
|
|
// ASCII "PE\x0\x0"
|
|
|
|
using EfiEntry = int (*)(void *, struct EfiSystemTable *);
|
|
|
|
template <typename T>
|
|
void fill(T *data, size_t skip, uint8_t begin = 0) {
|
|
auto ptr = reinterpret_cast<char *>(data);
|
|
for (size_t i = 0; i < sizeof(T); i++) {
|
|
if (i < skip) {
|
|
continue;
|
|
}
|
|
ptr[i] = begin++;
|
|
}
|
|
}
|
|
|
|
int load_sections_and_execute(bdev_t *dev,
|
|
const IMAGE_NT_HEADERS64 *pe_header) {
|
|
const auto file_header = &pe_header->FileHeader;
|
|
const auto optional_header = &pe_header->OptionalHeader;
|
|
const auto sections = file_header->NumberOfSections;
|
|
const auto section_header = reinterpret_cast<const IMAGE_SECTION_HEADER *>(
|
|
reinterpret_cast<const char *>(pe_header) + sizeof(IMAGE_FILE_HEADER) +
|
|
file_header->SizeOfOptionalHeader);
|
|
if (sections <= 0) {
|
|
printf("This PE file does not have any sections, unsupported.\n");
|
|
return -8;
|
|
}
|
|
for (size_t i = 0; i < sections; i++) {
|
|
if (section_header[i].NumberOfRelocations != 0) {
|
|
printf("Section %s requires relocation, which is not supported.\n",
|
|
section_header[i].Name);
|
|
return -6;
|
|
}
|
|
}
|
|
const auto &last_section = section_header[sections - 1];
|
|
const auto virtual_size =
|
|
last_section.VirtualAddress + last_section.Misc.VirtualSize;
|
|
const auto image_base = reinterpret_cast<char *>(
|
|
alloc_page(reinterpret_cast<void *>(optional_header->ImageBase),
|
|
virtual_size, 21 /* Kernel requires 2MB alignment */));
|
|
if (image_base == nullptr) {
|
|
return -7;
|
|
}
|
|
memset(image_base, 0, virtual_size);
|
|
bio_read(dev, image_base, 0, section_header[0].PointerToRawData);
|
|
|
|
for (size_t i = 0; i < sections; i++) {
|
|
const auto §ion = section_header[i];
|
|
bio_read(dev, image_base + section.VirtualAddress, section.PointerToRawData,
|
|
section.SizeOfRawData);
|
|
}
|
|
printf("Relocating image from 0x%llx to %p\n", optional_header->ImageBase,
|
|
image_base);
|
|
relocate_image(image_base);
|
|
auto entry = reinterpret_cast<int (*)(void *, void *)>(
|
|
image_base + optional_header->AddressOfEntryPoint);
|
|
printf("Entry function located at %p\n", entry);
|
|
|
|
EfiSystemTable &table = *static_cast<EfiSystemTable *>(alloc_page(PAGE_SIZE));
|
|
memset(&table, 0, sizeof(EfiSystemTable));
|
|
EfiBootService boot_service{};
|
|
EfiRuntimeService runtime_service{};
|
|
fill(&runtime_service, 0);
|
|
fill(&boot_service, 0);
|
|
setup_runtime_service_table(&runtime_service);
|
|
setup_boot_service_table(&boot_service);
|
|
table.runtime_service = &runtime_service;
|
|
table.boot_services = &boot_service;
|
|
table.header.signature = EFI_SYSTEM_TABLE_SIGNATURE;
|
|
table.header.revision = 2 << 16;
|
|
EfiSimpleTextOutputProtocol console_out = get_text_output_protocol();
|
|
table.con_out = &console_out;
|
|
table.configuration_table =
|
|
reinterpret_cast<EfiConfigurationTable *>(alloc_page(PAGE_SIZE));
|
|
memset(table.configuration_table, 0, PAGE_SIZE);
|
|
setup_configuration_table(&table);
|
|
auto status = platform_setup_system_table(&table);
|
|
if (status != SUCCESS) {
|
|
printf("platform_setup_system_table failed: %lu\n", status);
|
|
return -static_cast<int>(status);
|
|
}
|
|
|
|
constexpr size_t kStackSize = 8 * 1024ul * 1024;
|
|
auto stack = reinterpret_cast<char *>(alloc_page(kStackSize, 23));
|
|
memset(stack, 0, kStackSize);
|
|
printf("Calling kernel with stack [%p, %p]\n", stack, stack + kStackSize - 1);
|
|
return call_with_stack(stack + kStackSize, entry, image_base, &table);
|
|
}
|
|
|
|
int cmd_uefi_load(int argc, const console_cmd_args *argv) {
|
|
if (argc != 2) {
|
|
printf("Usage: %s <name of block device to load from>\n", argv[0].str);
|
|
return 1;
|
|
}
|
|
load_pe_file(argv[1].str);
|
|
return 0;
|
|
}
|
|
|
|
STATIC_COMMAND_START
|
|
STATIC_COMMAND("uefi_load", "load UEFI application and run it", &cmd_uefi_load)
|
|
STATIC_COMMAND_END(uefi);
|
|
|
|
} // namespace
|
|
|
|
int load_pe_file(const char *blkdev) {
|
|
bdev_t *dev = bio_open(blkdev);
|
|
if (!dev) {
|
|
printf("error opening block device %s\n", blkdev);
|
|
return -1;
|
|
}
|
|
DEFER {
|
|
bio_close(dev);
|
|
dev = nullptr;
|
|
};
|
|
constexpr size_t kBlocKSize = 4096;
|
|
|
|
lk_time_t t = current_time();
|
|
uint8_t *address = static_cast<uint8_t *>(malloc(kBlocKSize));
|
|
ssize_t err = bio_read(dev, static_cast<void *>(address), 0, kBlocKSize);
|
|
t = current_time() - t;
|
|
dprintf(INFO, "bio_read returns %d, took %u msecs (%d bytes/sec)\n", (int)err,
|
|
(uint)t, (uint32_t)((uint64_t)err * 1000 / t));
|
|
|
|
const auto dos_header = reinterpret_cast<const IMAGE_DOS_HEADER *>(address);
|
|
if (!dos_header->CheckMagic()) {
|
|
printf("DOS Magic check failed %x\n", dos_header->e_magic);
|
|
return -2;
|
|
}
|
|
if (dos_header->e_lfanew > kBlocKSize - sizeof(IMAGE_FILE_HEADER)) {
|
|
printf(
|
|
"Invalid PE header offset %d exceeds maximum read size of %zu - %zu\n",
|
|
dos_header->e_lfanew, kBlocKSize, sizeof(IMAGE_FILE_HEADER));
|
|
return -3;
|
|
}
|
|
const auto pe_header = dos_header->GetPEHeader();
|
|
const auto file_header = &pe_header->FileHeader;
|
|
if (LE32(file_header->Signature) != kPEHeader) {
|
|
printf("COFF Magic check failed %x\n", LE32(file_header->Signature));
|
|
return -4;
|
|
}
|
|
printf("PE header machine type: %x\n",
|
|
static_cast<int>(file_header->Machine));
|
|
if (file_header->SizeOfOptionalHeader > sizeof(IMAGE_OPTIONAL_HEADER64) ||
|
|
file_header->SizeOfOptionalHeader <
|
|
sizeof(IMAGE_OPTIONAL_HEADER64) -
|
|
sizeof(IMAGE_OPTIONAL_HEADER64::DataDirectory)) {
|
|
printf("Unexpected size of optional header %d, expected %zu\n",
|
|
file_header->SizeOfOptionalHeader, sizeof(IMAGE_OPTIONAL_HEADER64));
|
|
return -5;
|
|
}
|
|
const auto optional_header = &pe_header->OptionalHeader;
|
|
if (optional_header->Subsystem != SubsystemType::EFIApplication) {
|
|
printf("Unsupported Subsystem type: %d %s\n", optional_header->Subsystem,
|
|
ToString(optional_header->Subsystem));
|
|
}
|
|
printf("Valid UEFI application found.\n");
|
|
auto ret = load_sections_and_execute(dev, pe_header);
|
|
printf("UEFI Application return code: %d\n", ret);
|
|
return ret;
|
|
}
|