Skip to content

Commit

Permalink
Add basic UEFI loader to lk
Browse files Browse the repository at this point in the history
Thids adds a uefi_load <block dev name> command, which will
parse the PE header and do some basic validation checks.

Bug: 294283461
Test: uefi_load virtio0
Change-Id: I97393652526bda5be1b995e59647e239c64d31d6
  • Loading branch information
zhangxp1998 committed Jun 14, 2024
1 parent 4e9edd2 commit c750ed0
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 2 deletions.
54 changes: 54 additions & 0 deletions lib/uefi/defer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#ifndef __DEFER_HEADER_
#define __DEFER_HEADER_

// Macro for running a block of code before function exits.
// Example:
// DEFER {
// fclose(hc);
// hc = nullptr;
// };
// It works by creating a new local variable struct holding the lambda, the
// destructor of that struct will invoke the lambda.

// ScopeGuard ensures that the specified functor is executed no matter how the
// current scope exits.
template <typename F> class ScopeGuard {
public:
constexpr ScopeGuard(F &&f) : f_(static_cast<F &&>(f)) {}
constexpr ScopeGuard(ScopeGuard &&that) noexcept
: f_(that.f_), active_(that.active_) {
that.active_ = false;
}

template <typename Functor>
constexpr ScopeGuard(ScopeGuard<Functor> &&that)
: f_(that.f_), active_(that.active_) {
that.active_ = false;
}

~ScopeGuard() { f_(); }

ScopeGuard() = delete;
ScopeGuard(const ScopeGuard &) = delete;
void operator=(const ScopeGuard &) = delete;
void operator=(ScopeGuard &&that) = delete;

private:
template <typename Functor> friend class ScopeGuard;
F f_;
bool active_ = true;
};

constexpr struct {
template <typename F> constexpr auto operator<<(F &&f) const noexcept {
return ScopeGuard<F>(static_cast<F &&>(f));
}
} deferrer;

#define TOKENPASTE1(x, y) x##y
#define TOKENPASTE2(x, y) TOKENPASTE1(x, y)
#define DEFER \
auto TOKENPASTE2(_deferred_lambda_call, __COUNTER__) = deferrer \
<< [&]() mutable

#endif
208 changes: 208 additions & 0 deletions lib/uefi/pe.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
#ifndef __PE_HEADER_
#define __PE_HEADER_

#include <endian.h>
#include <sys/types.h>

static constexpr size_t IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;
static constexpr size_t IMAGE_SIZEOF_SHORT_NAME = 8;

static constexpr uint32_t kPEHeader = 0x4550;
struct IMAGE_NT_HEADERS64;

struct IMAGE_DOS_HEADER { // DOS .EXE header
u16 e_magic; // Magic number
u16 e_cblp; // Bytes on last page of file
u16 e_cp; // Pages in file
u16 e_crlc; // Relocations
u16 e_cparhdr; // Size of header in paragraphs
u16 e_minalloc; // Minimum extra paragraphs needed
u16 e_maxalloc; // Maximum extra paragraphs needed
u16 e_ss; // Initial (relative) SS value
u16 e_sp; // Initial SP value
u16 e_csum; // Checksum
u16 e_ip; // Initial IP value
u16 e_cs; // Initial (relative) CS value
u16 e_lfarlc; // File address of relocation table
u16 e_ovno; // Overlay number
u16 e_res[4]; // Reserved words
u16 e_oemid; // OEM identifier (for e_oeminfo)
u16 e_oeminfo; // OEM information; e_oemid specific
u16 e_res2[10]; // Reserved words
u32 e_lfanew; // File address of new exe header

constexpr bool CheckMagic() const { return LE32(e_magic) == 0x5A4D; }
IMAGE_NT_HEADERS64 *GetPEHeader() {
auto address = reinterpret_cast<char *>(this);
const auto pe_header =
reinterpret_cast<IMAGE_NT_HEADERS64 *>(address + e_lfanew);
return pe_header;
}
const IMAGE_NT_HEADERS64 *GetPEHeader() const {
auto address = reinterpret_cast<const char *>(this);
const auto pe_header =
reinterpret_cast<const IMAGE_NT_HEADERS64 *>(address + e_lfanew);
return pe_header;
}
} __attribute__((packed));

enum class ArchitectureType : u16 {
Unknown = 0x00,
ALPHAAXPOld = 0x183,
ALPHAAXP = 0x184,
ALPHAAXP64Bit = 0x284,
AM33 = 0x1D3,
AMD64 = 0x8664,
ARM = 0x1C0,
ARM64 = 0xAA64,
ARMNT = 0x1C4,
CLRPureMSIL = 0xC0EE,
EBC = 0xEBC,
I386 = 0x14C,
I860 = 0x14D,
IA64 = 0x200,
LOONGARCH32 = 0x6232,
LOONGARCH64 = 0x6264,
M32R = 0x9041,
MIPS16 = 0x266,
MIPSFPU = 0x366,
MIPSFPU16 = 0x466,
MOTOROLA68000 = 0x268,
POWERPC = 0x1F0,
POWERPCFP = 0x1F1,
POWERPC64 = 0x1F2,
R3000 = 0x162,
R4000 = 0x166,
R10000 = 0x168,
RISCV32 = 0x5032,
RISCV64 = 0x5064,
RISCV128 = 0x5128,
SH3 = 0x1A2,
SH3DSP = 0x1A3,
SH4 = 0x1A6,
SH5 = 0x1A8,
THUMB = 0x1C2,
WCEMIPSV2 = 0x169
};

struct IMAGE_FILE_HEADER {
u32 Signature;
ArchitectureType Machine;
u16 NumberOfSections;
u32 TimeDateStamp;
u32 PointerToSymbolTable;
u32 NumberOfSymbols;
u16 SizeOfOptionalHeader;
u16 Characteristics;
} __attribute__((packed));

struct IMAGE_DATA_DIRECTORY {
u32 VirtualAddress;
u32 Size;
} __attribute__((packed));

enum SubsystemType : u16 {
Unknown = 0x00,
Native = 0x01,
WindowsGUI = 0x02,
WindowsCUI = 0x03,
OS2CUI = 0x05,
POSIXCUI = 0x07,
Windows9xNative = 0x08,
WindowsCEGUI = 0x09,
EFIApplication = 0x0A,
EFIBootServiceDriver = 0x0B,
EFIRuntimeDriver = 0x0C,
EFIROM = 0x0D,
Xbox = 0x0E,
WindowsBootApplication = 0x10
};

constexpr const char *ToString(SubsystemType type) {
switch (type) {
case Native:
return "Native";
case WindowsGUI:
return "WindowsGUI";
case WindowsCUI:
return "WindowsCUI";
case OS2CUI:
return "OS2CUI";
case POSIXCUI:
return "POSIXCUI";
case Windows9xNative:
return "Windows9xNative";
case WindowsCEGUI:
return "WindowsCEGUI";
case EFIApplication:
return "EFIApplication";
case EFIBootServiceDriver:
return "EFIBootServiceDriver";
case EFIRuntimeDriver:
return "EFIRuntimeDriver";
case EFIROM:
return "EFIROM";
case Xbox:
return "Xbox";
case WindowsBootApplication:
return "WindowsBootApplication";
default:
return "Unknown";
}
}

struct IMAGE_OPTIONAL_HEADER64 {
u16 Magic;
u8 MajorLinkerVersion;
u8 MinorLinkerVersion;
u32 SizeOfCode;
u32 SizeOfInitializedData;
u32 SizeOfUninitializedData;
u32 AddressOfEntryPoint;
u32 BaseOfCode;
u64 ImageBase;
u32 SectionAlignment;
u32 FileAlignment;
u16 MajorOperatingSystemVersion;
u16 MinorOperatingSystemVersion;
u16 MajorImageVersion;
u16 MinorImageVersion;
u16 MajorSubsystemVersion;
u16 MinorSubsystemVersion;
u32 Win32VersionValue;
u32 SizeOfImage;
u32 SizeOfHeaders;
u32 CheckSum;
SubsystemType Subsystem;
u16 DllCharacteristics;
u64 SizeOfStackReserve;
u64 SizeOfStackCommit;
u64 SizeOfHeapReserve;
u64 SizeOfHeapCommit;
u32 LoaderFlags;
u32 NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} __attribute__((packed));

struct IMAGE_SECTION_HEADER {
char Name[IMAGE_SIZEOF_SHORT_NAME];
union {
u32 PhysicalAddress;
u32 VirtualSize;
} Misc;
u32 VirtualAddress;
u32 SizeOfRawData;
u32 PointerToRawData;
u32 PointerToRelocations;
u32 PointerToLinenumbers;
u16 NumberOfRelocations;
u16 NumberOfLinenumbers;
u32 Characteristics;
} __attribute__((packed));

struct IMAGE_NT_HEADERS64 {
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} __attribute__((packed));

#endif
8 changes: 8 additions & 0 deletions lib/uefi/rules.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
LOCAL_DIR := $(GET_LOCAL_DIR)

MODULE := $(LOCAL_DIR)

MODULE_SRCS += \
$(LOCAL_DIR)/uefi.cpp \

include make/module.mk
76 changes: 76 additions & 0 deletions lib/uefi/uefi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include "defer.h"
#include "pe.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 <string.h>

// ASCII "PE\x0\x0"
static constexpr uint32_t kPEHeader = 0x4550;

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); };
constexpr size_t kBlocKSize = 4096;

lk_time_t t = current_time();
uint8_t *address = (uint8_t *)malloc(kBlocKSize);
ssize_t err = bio_read(dev, (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 %u - %u\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)) {
printf("Unexpected size of optional header %d, expected %d\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");

return 0;
}

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);
3 changes: 2 additions & 1 deletion project/qemu-virt-arm64-test.mk
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# main project for qemu-aarch64
MODULES += \
app/shell
app/shell \
lib/uefi \

include project/virtual/test.mk
include project/virtual/fs.mk
Expand Down
2 changes: 1 addition & 1 deletion project/virtual/test.mk
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ MODULES += \
lib/cksum \
lib/debugcommands \
lib/unittest \
lib/version
lib/version \

# set a build system variable for other modules to include test code
# on their own.
Expand Down

0 comments on commit c750ed0

Please sign in to comment.