boot: Add EFISTUB and refactor boot protocols

This commit is contained in:
2026-10-27 22:50:43 +01:00
parent 6827dc309e
commit 196543c3d3
40 changed files with 1923 additions and 208 deletions
+249
View File
@@ -0,0 +1,249 @@
// SPDX-License-Identifier: ISC
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "efi_arch.h"
typedef uint64_t efi_status;
typedef void* efi_handle;
typedef uint16_t char16;
constexpr efi_status EFI_SUCCESS = 0;
constexpr efi_status EFI_ERROR = 1ull << 63;
constexpr efi_status EFI_LOAD_ERROR = EFI_ERROR | 1;
constexpr efi_status EFI_INVALID_PARAMETER = EFI_ERROR | 2;
constexpr efi_status EFI_UNSUPPORTED = EFI_ERROR | 3;
constexpr efi_status EFI_BUFFER_TOO_SMALL = EFI_ERROR | 5;
constexpr efi_status EFI_OUT_OF_RESOURCES = EFI_ERROR | 9;
constexpr efi_status EFI_NOT_FOUND = EFI_ERROR | 14;
struct efi_guid {
uint32_t data1;
uint16_t data2;
uint16_t data3;
uint8_t data4[8];
};
enum efi_memory_type : uint32_t {
EFI_RESERVED_MEMORY_TYPE,
EFI_LOADER_CODE,
EFI_LOADER_DATA,
EFI_BOOT_SERVICES_CODE,
EFI_BOOT_SERVICES_DATA,
EFI_RUNTIME_SERVICES_CODE,
EFI_RUNTIME_SERVICES_DATA,
EFI_CONVENTIONAL_MEMORY,
EFI_UNUSABLE_MEMORY,
EFI_ACPI_RECLAIM_MEMORY,
EFI_ACPI_MEMORY_NVS,
EFI_MEMORY_MAPPED_IO,
EFI_MEMORY_MAPPED_IO_PORT_SPACE,
EFI_PAL_CODE,
EFI_PERSISTENT_MEMORY,
EFI_UNACCEPTED_MEMORY,
};
enum efi_allocate_type : uint32_t {
EFI_ALLOCATE_ANY_PAGES,
EFI_ALLOCATE_MAX_ADDRESS,
EFI_ALLOCATE_ADDRESS,
};
enum efi_locate_search_type : uint32_t {
EFI_LOCATE_ALL_HANDLES,
EFI_LOCATE_BY_REGISTER_NOTIFY,
EFI_LOCATE_BY_PROTOCOL,
};
struct efi_memory_descriptor {
uint32_t type;
uint64_t physical_start;
uint64_t virtual_start;
uint64_t number_of_pages;
uint64_t attribute;
};
struct efi_table_header {
uint64_t signature;
uint32_t revision;
uint32_t header_size;
uint32_t crc32;
uint32_t reserved;
};
struct efi_simple_text_output_protocol {
void* reset;
efi_status(EFIAPI* output_string)(struct efi_simple_text_output_protocol* self, const char16* string);
};
struct efi_boot_services {
struct efi_table_header hdr;
void* raise_tpl;
void* restore_tpl;
efi_status(EFIAPI* allocate_pages)(
enum efi_allocate_type type,
enum efi_memory_type memory_type,
size_t pages,
uint64_t* memory
);
efi_status(EFIAPI* free_pages)(uint64_t memory, size_t pages);
efi_status(EFIAPI* get_memory_map)(
size_t* map_size,
struct efi_memory_descriptor* map,
size_t* map_key,
size_t* descriptor_size,
uint32_t* descriptor_version
);
efi_status(EFIAPI* allocate_pool)(enum efi_memory_type pool_type, size_t size, void** buffer);
efi_status(EFIAPI* free_pool)(void* buffer);
void* create_event;
void* set_timer;
void* wait_for_event;
void* signal_event;
void* close_event;
void* check_event;
void* install_protocol_interface;
void* reinstall_protocol_interface;
void* uninstall_protocol_interface;
efi_status(EFIAPI* handle_protocol)(efi_handle handle, const struct efi_guid* protocol, void** interface);
void* reserved;
void* register_protocol_notify;
void* locate_handle;
void* locate_device_path;
void* install_configuration_table;
void* load_image;
void* start_image;
void* exit;
void* unload_image;
efi_status(EFIAPI* exit_boot_services)(efi_handle image_handle, size_t map_key);
void* get_next_monotonic_count;
void* stall;
void* set_watchdog_timer;
void* connect_controller;
void* disconnect_controller;
void* open_protocol;
void* close_protocol;
void* open_protocol_information;
void* protocols_per_handle;
efi_status(EFIAPI* locate_handle_buffer)(
enum efi_locate_search_type search_type,
const struct efi_guid* protocol,
void* search_key,
size_t* no_handles,
efi_handle** buffer
);
efi_status(EFIAPI* locate_protocol)(const struct efi_guid* protocol, void* registration, void** interface);
};
struct efi_configuration_table {
struct efi_guid vendor_guid;
void* vendor_table;
};
struct efi_system_table {
struct efi_table_header hdr;
char16* firmware_vendor;
uint32_t firmware_revision;
efi_handle console_in_handle;
void* con_in;
efi_handle console_out_handle;
struct efi_simple_text_output_protocol* con_out;
efi_handle standard_error_handle;
void* std_err;
void* runtime_services;
struct efi_boot_services* boot_services;
size_t number_of_table_entries;
struct efi_configuration_table* configuration_table;
};
struct efi_loaded_image_protocol {
uint32_t revision;
efi_handle parent_handle;
struct efi_system_table* system_table;
efi_handle device_handle;
void* file_path;
void* reserved;
uint32_t load_options_size;
void* load_options;
void* image_base;
uint64_t image_size;
enum efi_memory_type image_code_type;
enum efi_memory_type image_data_type;
void* unload;
};
constexpr uint64_t EFI_FILE_MODE_READ = 1;
struct efi_file_protocol {
uint64_t revision;
efi_status(EFIAPI* open)(
struct efi_file_protocol* self,
struct efi_file_protocol** new_handle,
const char16* file_name,
uint64_t open_mode,
uint64_t attributes
);
efi_status(EFIAPI* close)(struct efi_file_protocol* self);
void* delete;
efi_status(EFIAPI* read)(struct efi_file_protocol* self, size_t* buffer_size, void* buffer);
void* write;
efi_status(EFIAPI* get_position)(struct efi_file_protocol* self, uint64_t* position);
efi_status(EFIAPI* set_position)(struct efi_file_protocol* self, uint64_t position);
};
struct efi_simple_file_system_protocol {
uint64_t revision;
efi_status(EFIAPI* open_volume)(struct efi_simple_file_system_protocol* self, struct efi_file_protocol** root);
};
enum efi_pixel_format : uint32_t {
EFI_PIXEL_RGBX_8,
EFI_PIXEL_BGRX_8,
EFI_PIXEL_BIT_MASK,
EFI_PIXEL_BLT_ONLY,
};
struct efi_pixel_bitmask {
uint32_t red;
uint32_t green;
uint32_t blue;
uint32_t reserved;
};
struct efi_gop_mode_info {
uint32_t version;
uint32_t horizontal_resolution;
uint32_t vertical_resolution;
enum efi_pixel_format pixel_format;
struct efi_pixel_bitmask pixel_information;
uint32_t pixels_per_scan_line;
};
struct efi_gop_mode {
uint32_t max_mode;
uint32_t mode;
struct efi_gop_mode_info* info;
size_t size_of_info;
uint64_t frame_buffer_base;
size_t frame_buffer_size;
};
struct efi_graphics_output_protocol {
void* query_mode;
void* set_mode;
void* blt;
struct efi_gop_mode* mode;
};
static const struct efi_guid EFI_LOADED_IMAGE_PROTOCOL_GUID =
{0x5b1b'31a1, 0x9562, 0x11d2, {0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}};
static const struct efi_guid EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID =
{0x964e'5b22, 0x6459, 0x11d2, {0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}};
static const struct efi_guid EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID =
{0x9042'a9de, 0x23dc, 0x4a38, {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a}};
static const struct efi_guid EFI_ACPI_20_TABLE_GUID =
{0x8868'e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81}};
static const struct efi_guid EFI_ACPI_10_TABLE_GUID =
{0xeb9d'2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d}};
+97
View File
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: ISC
#include <zinnia/compiler.h>
#include <stddef.h>
#include <stdint.h>
#include "efi_arch.h"
#include "pe.h"
extern const char __pe_code_size[];
extern const char __pe_data_sizes[];
extern const char __pe_entry[];
extern const char __pe_image_sizes[];
extern const char __pe_base_relocs[];
extern const char __pe_text_virtual[];
extern const char __pe_text_raw[];
extern const char __pe_reloc_virtual[];
extern const char __pe_reloc_raw[];
extern const char __pe_rodata_virtual[];
extern const char __pe_rodata_raw[];
extern const char __pe_data_virtual[];
extern const char __pe_data_raw[];
struct efistub_pe_header {
struct pe_dos_header dos;
uint32_t signature;
struct pe_coff_header coff;
struct pe_optional_header64 optional;
struct pe_section_header sections[4];
};
static_assert(offsetof(struct efistub_pe_header, optional) % 8 == 0);
static_assert(offsetof(struct efistub_pe_header, sections) % 8 == 0);
[[__used, __section(".header")]]
const struct efistub_pe_header efistub_pe_header = {
.dos =
{
.e_magic = PE_DOS_MAGIC,
.e_lfanew = offsetof(struct efistub_pe_header, signature),
},
.signature = PE_SIGNATURE,
.coff =
{
.machine = EFI_ARCH_PE_MACHINE,
.number_of_sections = 4,
.size_of_optional_header = sizeof(struct pe_optional_header64),
.characteristics = PE_FILE_EXECUTABLE_IMAGE | PE_FILE_LINE_NUMS_STRIPPED | PE_FILE_DEBUG_STRIPPED,
},
.optional =
{
.magic_and_code_size = (uintptr_t)__pe_code_size + PE_OPTIONAL_MAGIC_PE32_PLUS,
.data_sizes = (uintptr_t)__pe_data_sizes,
.entry_and_code_base = (uintptr_t)__pe_entry,
.section_alignment = 0x1000,
.file_alignment = 0x1000,
.image_and_header_sizes = (uintptr_t)__pe_image_sizes,
.subsystem = PE_SUBSYSTEM_EFI_APPLICATION,
.dll_characteristics = PE_DLL_NX_COMPAT,
.number_of_rva_and_sizes = PE_NUM_DATA_DIRECTORIES,
.data_directories =
{
[PE_DIRECTORY_BASE_RELOC] = {.layout = (uintptr_t)__pe_base_relocs},
},
},
.sections = {
{
.name = ".text",
.virtual_layout = (uintptr_t)__pe_text_virtual,
.raw_layout = (uintptr_t)__pe_text_raw,
.characteristics = PE_SCN_CNT_CODE | PE_SCN_MEM_EXECUTE | PE_SCN_MEM_READ,
},
{
.name = ".reloc",
.virtual_layout = (uintptr_t)__pe_reloc_virtual,
.raw_layout = (uintptr_t)__pe_reloc_raw,
.characteristics = PE_SCN_CNT_INITIALIZED_DATA | PE_SCN_MEM_DISCARDABLE | PE_SCN_MEM_READ,
},
{
.name = ".rdata",
.virtual_layout = (uintptr_t)__pe_rodata_virtual,
.raw_layout = (uintptr_t)__pe_rodata_raw,
.characteristics = PE_SCN_CNT_INITIALIZED_DATA | PE_SCN_MEM_READ,
},
{
.name = ".data",
.virtual_layout = (uintptr_t)__pe_data_virtual,
.raw_layout = (uintptr_t)__pe_data_raw,
.characteristics = PE_SCN_CNT_INITIALIZED_DATA | PE_SCN_MEM_READ | PE_SCN_MEM_WRITE,
},
},
};
[[__used, __section(".reloc")]]
const struct pe_base_reloc_block efistub_base_relocs = {
.page_rva = 0,
.block_size = sizeof(struct pe_base_reloc_block),
};
+325
View File
@@ -0,0 +1,325 @@
// SPDX-License-Identifier: ISC
#include <string.h>
#include "stub.h"
constexpr size_t MEM_MAP_SLACK = 64;
struct efi_boot_services* bs;
bool loader_alloc(enum phys_mem_usage usage, size_t size, uint64_t* out) {
enum efi_memory_type efi_type = EFI_LOADER_DATA;
if (usage == PHYS_STATIC)
efi_type = MEM_STATIC;
else if (usage == PHYS_RECLAIMABLE)
efi_type = MEM_RECLAIMABLE;
const size_t pages = ALIGN_UP(size, PAGE_SIZE) / PAGE_SIZE;
if (bs->allocate_pages(EFI_ALLOCATE_ANY_PAGES, efi_type, pages, out))
return false;
memset((void*)*out, 0, pages * PAGE_SIZE);
return true;
}
void* loader_ptr(uint64_t phys) {
return (void*)phys;
}
static efi_status open_file_on(efi_handle handle, const char16* path, struct efi_file_protocol** out) {
struct efi_simple_file_system_protocol* fs;
efi_status status = bs->handle_protocol(handle, &EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID, (void**)&fs);
if (status)
return status;
struct efi_file_protocol* root;
status = fs->open_volume(fs, &root);
if (status)
return status;
status = root->open(root, out, path, EFI_FILE_MODE_READ, 0);
if (status && path[0] == '\\')
status = root->open(root, out, path + 1, EFI_FILE_MODE_READ, 0);
root->close(root);
return status;
}
static efi_status read_file(struct efi_file_protocol* file, enum phys_mem_usage usage, uint64_t* base, uint64_t* size) {
uint64_t total;
uint64_t done = 0;
efi_status status = file->set_position(file, UINT64_MAX);
if (!status)
status = file->get_position(file, &total);
if (!status)
status = file->set_position(file, 0);
if (!status && !loader_alloc(usage, total ? total : 1, base))
status = EFI_OUT_OF_RESOURCES;
while (!status && done < total) {
size_t chunk = total - done;
status = file->read(file, &chunk, (void*)(*base + done));
if (!chunk)
break;
done += chunk;
}
file->close(file);
*size = done;
return status;
}
static void color_from_mask(uint32_t mask, struct boot_color* out) {
if (!mask)
return;
out->shift = __builtin_ctz(mask);
out->size = __builtin_ctzll(~(uint64_t)(mask >> out->shift));
}
static enum phys_mem_usage convert_mem_type(uint32_t type) {
switch (type) {
case EFI_LOADER_CODE:
case EFI_LOADER_DATA:
case EFI_BOOT_SERVICES_CODE:
case EFI_BOOT_SERVICES_DATA:
case EFI_CONVENTIONAL_MEMORY:
return PHYS_USABLE;
case EFI_ACPI_RECLAIM_MEMORY:
case MEM_RECLAIMABLE:
return PHYS_RECLAIMABLE;
case MEM_STATIC:
return PHYS_STATIC;
default:
return PHYS_RESERVED;
}
}
efi_status efi_main(efi_handle image_handle, struct efi_system_table* st) {
bs = st->boot_services;
// Check if we support this configuration.
if (!arch_check_cpu())
return EFI_UNSUPPORTED;
struct efi_loaded_image_protocol* image;
efi_status status = bs->handle_protocol(image_handle, &EFI_LOADED_IMAGE_PROTOCOL_GUID, (void**)&image);
if (status)
return status;
struct kernel_image kernel;
if (!loader_load_kernel(&kernel, "EFISTUB"))
return EFI_LOAD_ERROR;
struct boot_info* info = kernel.info;
// Reserve room for the memory map.
size_t map_size = 0;
size_t map_key;
size_t desc_size;
uint32_t desc_version;
status = bs->get_memory_map(&map_size, nullptr, &map_key, &desc_size, &desc_version);
if (status != EFI_BUFFER_TOO_SMALL)
return status;
const size_t map_capacity = map_size + MEM_MAP_SLACK * desc_size;
uint64_t map;
if (!loader_alloc(PHYS_USABLE, map_capacity, &map))
return EFI_OUT_OF_RESOURCES;
uint64_t mem;
if (!loader_alloc(PHYS_USABLE, map_capacity / desc_size * sizeof(struct phys_mem), &mem))
return EFI_OUT_OF_RESOURCES;
// Use the load options as the command line.
const char16* opts = image->load_options;
const size_t opts_len = image->load_options_size / sizeof(char16);
size_t cmdline_len = 0;
for (size_t i = 0; opts && i < opts_len && opts[i]; i++) {
if (opts[i] < 0x20 || opts[i] > 0x7e || cmdline_len + 1 >= sizeof(info->cmdline)) {
cmdline_len = 0;
break;
}
info->cmdline[cmdline_len++] = (char)opts[i];
}
info->cmdline[cmdline_len] = 0;
// Without load options, fall back to a command line file on the boot device.
struct efi_file_protocol* cmdline_file;
if (!cmdline_len && !open_file_on(image->device_handle, u"\\EFI\\zinnia\\cmdline", &cmdline_file)) {
uint64_t base;
uint64_t size;
status = read_file(cmdline_file, PHYS_USABLE, &base, &size);
if (status)
return status;
const char* text = (const char*)base;
while (cmdline_len < size && text[cmdline_len] && text[cmdline_len] != '\n' && text[cmdline_len] != '\r') {
if (text[cmdline_len] < 0x20 || text[cmdline_len] > 0x7e)
return EFI_INVALID_PARAMETER;
if (cmdline_len + 1 >= sizeof(info->cmdline))
return EFI_BUFFER_TOO_SMALL;
info->cmdline[cmdline_len] = text[cmdline_len];
cmdline_len++;
}
while (cmdline_len && info->cmdline[cmdline_len - 1] == ' ')
cmdline_len--;
info->cmdline[cmdline_len] = 0;
}
// Load every initrd= file.
const char* cmdline = info->cmdline;
for (size_t i = 0; cmdline[i];) {
if (cmdline[i] == ' ') {
i++;
continue;
}
const size_t start = i;
bool quoted = false;
while (cmdline[i] && (quoted || cmdline[i] != ' ')) {
if (cmdline[i] == '"')
quoted = !quoted;
i++;
}
const char* token = cmdline + start;
const size_t len = i - start;
if (len <= 7 || memcmp(token, "initrd=", 7))
continue;
const size_t path_len = len - 7;
if (path_len >= BOOT_PATH_MAX)
return EFI_INVALID_PARAMETER;
char path[BOOT_PATH_MAX];
char16 wide[BOOT_PATH_MAX];
for (size_t j = 0; j < path_len; j++) {
path[j] = token[7 + j];
wide[j] = path[j] == '/' ? '\\' : (char16)path[j];
}
path[path_len] = 0;
wide[path_len] = 0;
// Try the boot device first, then all other file systems.
struct efi_file_protocol* file;
status = open_file_on(image->device_handle, wide, &file);
size_t num_handles;
efi_handle* handles;
if (status &&
!bs->locate_handle_buffer(
EFI_LOCATE_BY_PROTOCOL,
&EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID,
nullptr,
&num_handles,
&handles
)) {
for (size_t j = 0; j < num_handles && status; j++) {
if (handles[j] != image->device_handle)
status = open_file_on(handles[j], wide, &file);
}
bs->free_pool(handles);
}
if (status)
return status;
uint64_t base;
uint64_t size;
status = read_file(file, PHYS_STATIC, &base, &size);
if (status)
return status;
if (!loader_add_file(&kernel, path, base, size))
return EFI_OUT_OF_RESOURCES;
}
// Framebuffer
struct efi_graphics_output_protocol* gop;
if (!bs->locate_protocol(&EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID, nullptr, (void**)&gop) && gop && gop->mode &&
gop->mode->info) {
const struct efi_gop_mode_info* mode = gop->mode->info;
struct boot_framebuffer* fb = &info->fb;
switch (mode->pixel_format) {
case EFI_PIXEL_RGBX_8:
fb->bytes_per_pixel = 4;
fb->red = (struct boot_color){0, 8};
fb->green = (struct boot_color){8, 8};
fb->blue = (struct boot_color){16, 8};
break;
case EFI_PIXEL_BGRX_8:
fb->bytes_per_pixel = 4;
fb->red = (struct boot_color){16, 8};
fb->green = (struct boot_color){8, 8};
fb->blue = (struct boot_color){0, 8};
break;
case EFI_PIXEL_BIT_MASK: {
const struct efi_pixel_bitmask* m = &mode->pixel_information;
const uint32_t all = m->red | m->green | m->blue | m->reserved;
if (!all)
break;
fb->bytes_per_pixel = (32 - __builtin_clz(all) + 7) / 8;
color_from_mask(m->red, &fb->red);
color_from_mask(m->green, &fb->green);
color_from_mask(m->blue, &fb->blue);
break;
}
default:
break;
}
if (fb->bytes_per_pixel) {
fb->base = gop->mode->frame_buffer_base;
fb->width = mode->horizontal_resolution;
fb->height = mode->vertical_resolution;
fb->pitch = mode->pixels_per_scan_line * fb->bytes_per_pixel;
}
}
// Find the RSDP.
for (size_t i = 0; i < st->number_of_table_entries; i++) {
const struct efi_configuration_table* table = &st->configuration_table[i];
if (!memcmp(&table->vendor_guid, &EFI_ACPI_20_TABLE_GUID, sizeof(struct efi_guid))) {
info->rsdp = (uint64_t)table->vendor_table;
break;
}
if (!memcmp(&table->vendor_guid, &EFI_ACPI_10_TABLE_GUID, sizeof(struct efi_guid)))
info->rsdp = (uint64_t)table->vendor_table;
}
// Map everything up to the highest non-reserved address.
map_size = map_capacity;
status = bs->get_memory_map(&map_size, (struct efi_memory_descriptor*)map, &map_key, &desc_size, &desc_version);
if (status)
return status;
uint64_t top = 0;
for (size_t off = 0; off + desc_size <= map_size; off += desc_size) {
const struct efi_memory_descriptor* desc = (const struct efi_memory_descriptor*)(map + off);
if (convert_mem_type(desc->type) != PHYS_RESERVED)
top = MAX(top, desc->physical_start + desc->number_of_pages * PAGE_SIZE);
}
uint64_t root;
if (!loader_page_tables(&kernel, top, (uint64_t)arch_enter_kernel, &root))
return EFI_OUT_OF_RESOURCES;
// Exit boot services.
for (size_t attempt = 0;; attempt++) {
map_size = map_capacity;
status = bs->get_memory_map(&map_size, (struct efi_memory_descriptor*)map, &map_key, &desc_size, &desc_version);
if (status)
return status;
status = bs->exit_boot_services(image_handle, map_key);
if (!status)
break;
if (attempt == 8)
return status;
}
// Hand the final memory map to the kernel.
struct phys_mem* mem_map = (struct phys_mem*)mem;
size_t num_mem = 0;
for (size_t off = 0; off + desc_size <= map_size; off += desc_size) {
const struct efi_memory_descriptor* desc = (const struct efi_memory_descriptor*)(map + off);
mem_map[num_mem++] = (struct phys_mem){
.address = desc->physical_start,
.length = desc->number_of_pages * PAGE_SIZE,
.usage = convert_mem_type(desc->type),
};
}
loader_set_mem_map(&kernel, mem_map, num_mem);
arch_enter_kernel(root, kernel.entry);
}
+42
View File
@@ -0,0 +1,42 @@
efistub_sources = files(
'header.c',
'main.c',
'reloc.c',
)
efistub_lds = meson.current_source_dir() / 'stub.ld'
subdir(target_machine.cpu_family())
efistub_elf = executable(
'efistub.elf',
sources: [loader_sources, efistub_sources, loader_kernel],
include_directories: [
common_includes,
loader_includes,
include_directories('.', target_machine.cpu_family()),
],
c_args: loader_c_args,
link_args: [
'-nostdlib',
'-Wl,-T,' + efistub_lds,
'-Wl,--no-dynamic-linker',
'-Wl,-z,text',
'-Wl,-z,norelro',
'-Wl,-z,noexecstack',
'-Wl,-z,max-page-size=0x1000',
'-Wl,--build-id=none',
],
link_depends: efistub_lds,
pie: true,
)
zinnia_efi = custom_target(
'zinnia.efi',
input: efistub_elf,
output: 'zinnia.efi',
command: [objcopy, '-O', 'binary', '@INPUT@', '@OUTPUT@'],
build_by_default: true,
install: true,
install_dir: zinnia_bootdir,
)
+164
View File
@@ -0,0 +1,164 @@
// SPDX-License-Identifier: ISC
#pragma once
#include <stddef.h>
#include <stdint.h>
constexpr uint16_t PE_DOS_MAGIC = 0x5a4d;
constexpr uint32_t PE_SIGNATURE = 0x0000'4550;
constexpr uint16_t PE_OPTIONAL_MAGIC_PE32_PLUS = 0x020b;
constexpr uint16_t PE_SUBSYSTEM_EFI_APPLICATION = 10;
constexpr size_t PE_NUM_DATA_DIRECTORIES = 16;
constexpr size_t PE_DIRECTORY_BASE_RELOC = 5;
enum pe_file_characteristics : uint16_t {
PE_FILE_EXECUTABLE_IMAGE = 0x0002,
PE_FILE_LINE_NUMS_STRIPPED = 0x0004,
PE_FILE_DEBUG_STRIPPED = 0x0200,
};
enum pe_dll_characteristics : uint16_t {
PE_DLL_NX_COMPAT = 0x0100,
};
enum pe_section_characteristics : uint32_t {
PE_SCN_CNT_CODE = 0x0000'0020,
PE_SCN_CNT_INITIALIZED_DATA = 0x0000'0040,
PE_SCN_MEM_DISCARDABLE = 0x0200'0000,
PE_SCN_MEM_EXECUTE = 0x2000'0000,
PE_SCN_MEM_READ = 0x4000'0000,
PE_SCN_MEM_WRITE = 0x8000'0000,
};
struct pe_dos_header {
uint16_t e_magic;
uint16_t e_cblp;
uint16_t e_cp;
uint16_t e_crlc;
uint16_t e_cparhdr;
uint16_t e_minalloc;
uint16_t e_maxalloc;
uint16_t e_ss;
uint16_t e_sp;
uint16_t e_csum;
uint16_t e_ip;
uint16_t e_cs;
uint16_t e_lfarlc;
uint16_t e_ovno;
uint16_t e_res[4];
uint16_t e_oemid;
uint16_t e_oeminfo;
uint16_t e_res2[10];
uint32_t e_lfanew;
};
struct pe_coff_header {
uint16_t machine;
uint16_t number_of_sections;
uint32_t time_date_stamp;
uint32_t pointer_to_symbol_table;
uint32_t number_of_symbols;
uint16_t size_of_optional_header;
uint16_t characteristics;
};
struct pe_data_directory {
union {
struct {
uint32_t virtual_address;
uint32_t size;
};
uint64_t layout;
};
};
struct pe_optional_header64 {
union {
struct {
uint16_t magic;
uint8_t major_linker_version;
uint8_t minor_linker_version;
uint32_t size_of_code;
};
uint64_t magic_and_code_size;
};
union {
struct {
uint32_t size_of_initialized_data;
uint32_t size_of_uninitialized_data;
};
uint64_t data_sizes;
};
union {
struct {
uint32_t address_of_entry_point;
uint32_t base_of_code;
};
uint64_t entry_and_code_base;
};
uint64_t image_base;
uint32_t section_alignment;
uint32_t file_alignment;
uint16_t major_operating_system_version;
uint16_t minor_operating_system_version;
uint16_t major_image_version;
uint16_t minor_image_version;
uint16_t major_subsystem_version;
uint16_t minor_subsystem_version;
uint32_t win32_version_value;
union {
struct {
uint32_t size_of_image;
uint32_t size_of_headers;
};
uint64_t image_and_header_sizes;
};
uint32_t checksum;
uint16_t subsystem;
uint16_t dll_characteristics;
uint64_t size_of_stack_reserve;
uint64_t size_of_stack_commit;
uint64_t size_of_heap_reserve;
uint64_t size_of_heap_commit;
uint32_t loader_flags;
uint32_t number_of_rva_and_sizes;
struct pe_data_directory data_directories[PE_NUM_DATA_DIRECTORIES];
};
struct pe_section_header {
char name[8];
union {
struct {
uint32_t virtual_size;
uint32_t virtual_address;
};
uint64_t virtual_layout;
};
union {
struct {
uint32_t size_of_raw_data;
uint32_t pointer_to_raw_data;
};
uint64_t raw_layout;
};
uint32_t pointer_to_relocations;
uint32_t pointer_to_line_numbers;
uint16_t number_of_relocations;
uint16_t number_of_line_numbers;
uint32_t characteristics;
};
struct pe_base_reloc_block {
uint32_t page_rva;
uint32_t block_size;
uint16_t entries[2];
};
static_assert(sizeof(struct pe_dos_header) == 64);
static_assert(sizeof(struct pe_coff_header) == 20);
static_assert(sizeof(struct pe_optional_header64) == 240);
static_assert(offsetof(struct pe_optional_header64, size_of_image) == 56);
static_assert(offsetof(struct pe_optional_header64, data_directories) == 112);
static_assert(sizeof(struct pe_section_header) == 40);
static_assert(sizeof(struct pe_base_reloc_block) == 12);
+61
View File
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: ISC
#include <bits/elf.h>
#include <stddef.h>
#include <stdint.h>
#include "efi.h"
efi_status efistub_relocate(uintptr_t base, const elf64_dyn_t* dynamic) {
uintptr_t rela = 0;
size_t rela_size = 0;
size_t rela_ent = sizeof(elf64_rela_t);
uintptr_t relr = 0;
size_t relr_size = 0;
for (const elf64_dyn_t* dyn = dynamic; dyn->d_tag != DT_NULL; dyn++) {
switch (dyn->d_tag) {
case DT_RELA:
rela = base + dyn->d_un.d_val;
break;
case DT_RELASZ:
rela_size = dyn->d_un.d_val;
break;
case DT_RELAENT:
rela_ent = dyn->d_un.d_val;
break;
case DT_RELR:
relr = base + dyn->d_un.d_val;
break;
case DT_RELRSZ:
relr_size = dyn->d_un.d_val;
break;
}
}
for (size_t off = 0; rela && off + rela_ent <= rela_size; off += rela_ent) {
const elf64_rela_t* r = (const elf64_rela_t*)(rela + off);
const uint32_t type = ELF64_R_TYPE(r->r_info);
if (type == R_NONE)
continue;
if (type != R_RELATIVE)
return EFI_LOAD_ERROR;
*(uint64_t*)(base + r->r_offset) = base + r->r_addend;
}
uint64_t* where = nullptr;
for (size_t off = 0; relr && off + sizeof(uint64_t) <= relr_size; off += sizeof(uint64_t)) {
uint64_t entry = *(const uint64_t*)(relr + off);
if (!(entry & 1)) {
where = (uint64_t*)(base + entry);
*where++ += base;
continue;
}
for (size_t i = 0; (entry >>= 1) != 0; i++) {
if (entry & 1)
where[i] += base;
}
where += 63;
}
return EFI_SUCCESS;
}
+13
View File
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: ISC
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "efi.h"
#include "loader.h"
constexpr enum efi_memory_type MEM_STATIC = (enum efi_memory_type)0x8000'0000;
constexpr enum efi_memory_type MEM_RECLAIMABLE = (enum efi_memory_type)0x8000'0001;
extern struct efi_boot_services* bs;
+100
View File
@@ -0,0 +1,100 @@
ENTRY(_start)
PHDRS
{
text PT_LOAD FLAGS(5);
rodata PT_LOAD FLAGS(4);
data PT_LOAD FLAGS(6);
dynamic PT_DYNAMIC FLAGS(6);
}
SECTIONS
{
. = 0;
.header : {
__image_base = .;
KEEP(*(.header))
} :text
. = ALIGN(0x1000);
.text : {
__text_start = .;
*(.text .text.*)
. = ALIGN(0x1000);
__text_end = .;
} :text
.reloc : {
__reloc_start = .;
KEEP(*(.reloc))
__reloc_data_end = .;
. = ALIGN(0x1000);
__reloc_end = .;
} :rodata
.rodata : {
__rodata_start = .;
*(.rodata .rodata.*)
. = ALIGN(16);
__kernel_start = .;
KEEP(*(.kernel))
__kernel_end = .;
} :rodata
.hash : { *(.hash) } :rodata
.gnu.hash : { *(.gnu.hash) } :rodata
.dynsym : { *(.dynsym) } :rodata
.dynstr : { *(.dynstr) } :rodata
.rela.dyn : { *(.rela.*) } :rodata
.relr.dyn : { *(.relr.dyn) } :rodata
.rodata.end : {
. = ALIGN(0x1000);
__rodata_end = .;
} :rodata
.dynamic : {
__data_start = .;
*(.dynamic)
} :data :dynamic
.data : {
*(.data .data.*)
*(.got .got.*)
. = ALIGN(0x1000);
__data_raw_end = .;
} :data
.bss (NOLOAD) : {
__bss_start = .;
*(.bss .bss.*)
*(COMMON)
. = ALIGN(0x1000);
__bss_end = .;
} :data
__image_end = .;
__pe_code_size = ABSOLUTE((__text_end - __text_start) << 32);
__pe_data_sizes = ABSOLUTE(((__reloc_end - __reloc_start) + (__rodata_end - __rodata_start) +
(__data_raw_end - __data_start)) | ((__bss_end - __bss_start) << 32));
__pe_entry = ABSOLUTE(_start | (__text_start << 32));
__pe_image_sizes = ABSOLUTE(__image_end | (__text_start << 32));
__pe_base_relocs = ABSOLUTE(__reloc_start | ((__reloc_data_end - __reloc_start) << 32));
__pe_text_virtual = ABSOLUTE((__text_end - __text_start) | (__text_start << 32));
__pe_text_raw = ABSOLUTE((__text_end - __text_start) | (__text_start << 32));
__pe_reloc_virtual = ABSOLUTE((__reloc_data_end - __reloc_start) | (__reloc_start << 32));
__pe_reloc_raw = ABSOLUTE((__reloc_end - __reloc_start) | (__reloc_start << 32));
__pe_rodata_virtual = ABSOLUTE((__rodata_end - __rodata_start) | (__rodata_start << 32));
__pe_rodata_raw = ABSOLUTE((__rodata_end - __rodata_start) | (__rodata_start << 32));
__pe_data_virtual = ABSOLUTE((__bss_end - __data_start) | (__data_start << 32));
__pe_data_raw = ABSOLUTE((__data_raw_end - __data_start) | (__data_start << 32));
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.note*)
*(.interp)
}
}
+9
View File
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: ISC
#pragma once
#include <stdint.h>
#define EFIAPI __attribute__((ms_abi))
constexpr uint16_t EFI_ARCH_PE_MACHINE = 0x8664;
+48
View File
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: ISC
.intel_syntax noprefix
.text
.globl _start
.type _start, @function
_start:
// Align the stack pointer
push rbp
mov rbp, rsp
push rdi
push rsi
push rbx
push r12
and rsp, -16
cld
mov rbx, rcx
mov r12, rdx
// Clear .bss
lea rdi, [rip + __bss_start]
lea rcx, [rip + __bss_end]
sub rcx, rdi
xor eax, eax
rep stosb
// Self-relocate
lea rdi, [rip + __image_base]
lea rsi, [rip + _DYNAMIC]
call efistub_relocate
test rax, rax
jnz 1f
mov rdi, rbx
mov rsi, r12
call efi_main
1:
lea rsp, [rbp - 32]
pop r12
pop rbx
pop rsi
pop rdi
pop rbp
ret
.size _start, . - _start
+1
View File
@@ -0,0 +1 @@
efistub_sources += files('entry.S')