From d96381e90e5f1952ee6d7bf6e51daa2b344319af Mon Sep 17 00:00:00 2001 From: Kay Sievers Date: Sat, 24 Jan 2015 21:09:41 +0100 Subject: stub: execute EFI image with an embedded kernel, initrd, cmdline sections Locate the following embedded PE sections .linux - bzImage .inird - initrd cpio archive .cmdline - kernel command line and hand over control to the bzImage. --- .gitignore | 5 +- Makefile.am | 83 +++++++++++++++++++++--------- src/efi/graphics.c | 2 +- src/efi/gummiboot.c | 13 ++--- src/efi/linux.c | 128 +++++++++++++++++++++++++++++++++++++++++++++++ src/efi/linux.h | 23 +++++++++ src/efi/pefile.c | 4 +- src/efi/stub.c | 71 ++++++++++++++++++++++++++ src/efi/util.c | 8 +-- test/test-create-disk.sh | 7 ++- 10 files changed, 303 insertions(+), 41 deletions(-) create mode 100644 src/efi/linux.c create mode 100644 src/efi/linux.h create mode 100644 src/efi/stub.c diff --git a/.gitignore b/.gitignore index 047bb03..d482a5d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,11 @@ .dirstamp /gummiboot /gummiboot.so -/gummibootia32.efi /gummibootx64.efi +/gummibootia32.efi +/stub.so +/stubx64.efi +/stubia32.efi /test-disk Makefile diff --git a/Makefile.am b/Makefile.am index c4cda7f..528f83c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -74,20 +74,6 @@ CLEANFILES += man/gummiboot.8 # EFI compilation -- this part of the build system uses custom make rules and # bypasses regular automake to provide absolute control on compiler and linker # flags. -efi_loadername = gummiboot$(MACHINE_TYPE_NAME).efi -efi_sources = \ - src/efi/util.c \ - src/efi/console.c \ - src/efi/graphics.c \ - src/efi/pefile.c \ - src/efi/gummiboot.c - -efi_headers = \ - src/efi/util.h \ - src/efi/console.h \ - src/efi/graphics.h \ - src/efi/pefile.h - efi_cppflags = \ $(EFI_CPPFLAGS) \ -I$(top_builddir) -include config.h \ @@ -128,25 +114,74 @@ efi_ldflags = \ -L $(EFI_LIB_DIR) \ $(EFI_LDS_DIR)/crt0-efi-$(ARCH).o -efi_objects = $(addprefix $(top_builddir)/,$(efi_sources:.c=.o)) -efi_solib = $(top_builddir)/src/efi/gummiboot.so +# ------------------------------------------------------------------------------ +gummiboot_headers = \ + src/efi/util.h \ + src/efi/console.h \ + src/efi/graphics.h \ + src/efi/pefile.h + +gummiboot_sources = \ + src/efi/util.c \ + src/efi/console.c \ + src/efi/graphics.c \ + src/efi/pefile.c \ + src/efi/gummiboot.c + +gummiboot_objects = $(addprefix $(top_builddir)/,$(gummiboot_sources:.c=.o)) +gummiboot_solib = $(top_builddir)/src/efi/gummiboot.so +gummiboot = gummiboot$(MACHINE_TYPE_NAME).efi + +gummibootlib_DATA = $(gummiboot) +CLEANFILES += $(gummiboot_objects) $(gummiboot_solib) $(gummiboot) +EXTRA_DIST += $(gimmiboot_sources) $(gummiboot_headers) + +$(top_builddir)/src/efi/%.o: $(top_srcdir)/src/efi/%.c $(addprefix $(top_srcdir)/,$(gummiboot_headers)) + @$(MKDIR_P) $(top_builddir)/src/efi/ + $(AM_V_CC)$(EFI_CC) $(efi_cppflags) $(efi_cflags) -c $< -o $@ + +$(gummiboot_solib): $(gummiboot_objects) + $(AM_V_CCLD)$(LD) $(efi_ldflags) $(gummiboot_objects) \ + -o $@ -lefi -lgnuefi $(shell $(CC) -print-libgcc-file-name); \ + nm -D -u $@ | grep ' U ' && exit 1 || : +.DELETE_ON_ERROR: $(gummboot_solib) + +$(gummiboot): $(gummiboot_solib) + $(AM_V_GEN) objcopy -j .text -j .sdata -j .data -j .dynamic \ + -j .dynsym -j .rel -j .rela -j .reloc \ + --target=efi-app-$(ARCH) $< $@ # ------------------------------------------------------------------------------ -gummibootlib_DATA = $(efi_loadername) -CLEANFILES += $(efi_objects) $(efi_solib) $(efi_loadername) -EXTRA_DIST += $(efi_sources) $(efi_headers) +stub_headers = \ + src/efi/util.h \ + src/efi/pefile.h \ + src/efi/linux.h + +stub_sources = \ + src/efi/util.c \ + src/efi/pefile.c \ + src/efi/linux.c \ + src/efi/stub.c + +stub_objects = $(addprefix $(top_builddir)/,$(stub_sources:.c=.o)) +stub_solib = $(top_builddir)/src/efi/stub.so +stub = stub$(MACHINE_TYPE_NAME).efi + +gummibootlib_DATA += $(stub) +CLEANFILES += $(stub_objects) $(stub_solib) $(stub) +EXTRA_DIST += $(stub_sources) $(stub_headers) -$(top_builddir)/src/efi/%.o: $(top_srcdir)/src/efi/%.c $(addprefix $(top_srcdir)/,$(efi_headers)) +$(top_builddir)/src/efi/%.o: $(top_srcdir)/src/efi/%.c $(addprefix $(top_srcdir)/,$(stub_headers)) @$(MKDIR_P) $(top_builddir)/src/efi/ $(AM_V_CC)$(EFI_CC) $(efi_cppflags) $(efi_cflags) -c $< -o $@ -$(efi_solib): $(efi_objects) - $(AM_V_CCLD)$(LD) $(efi_ldflags) $(efi_objects) \ +$(stub_solib): $(stub_objects) + $(AM_V_CCLD)$(LD) $(efi_ldflags) $(stub_objects) \ -o $@ -lefi -lgnuefi $(shell $(CC) -print-libgcc-file-name); \ nm -D -u $@ | grep ' U ' && exit 1 || : -.DELETE_ON_ERROR: $(efi_solib) +.DELETE_ON_ERROR: $(gummboot_solib) -$(efi_loadername): $(efi_solib) +$(stub): $(stub_solib) $(AM_V_GEN) objcopy -j .text -j .sdata -j .data -j .dynamic \ -j .dynsym -j .rel -j .rela -j .reloc \ --target=efi-app-$(ARCH) $< $@ diff --git a/src/efi/graphics.c b/src/efi/graphics.c index 0810538..11305b8 100644 --- a/src/efi/graphics.c +++ b/src/efi/graphics.c @@ -208,7 +208,7 @@ EFI_STATUS bmp_parse_header(UINT8 *bmp, UINTN size, struct bmp_dib **ret_dib, return EFI_SUCCESS; } -static void pixel_blend(UINT32 *dst, const UINT32 source) { +static VOID pixel_blend(UINT32 *dst, const UINT32 source) { UINT32 alpha, src, src_rb, src_g, dst_rb, dst_g, rb, g; alpha = (source & 0xff); diff --git a/src/efi/gummiboot.c b/src/efi/gummiboot.c index f9da90d..ff1226d 100644 --- a/src/efi/gummiboot.c +++ b/src/efi/gummiboot.c @@ -32,6 +32,7 @@ #include "console.h" #include "graphics.h" #include "pefile.h" +#include "linux.h" #ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI #define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL @@ -64,7 +65,7 @@ typedef struct { CHAR16 *options; CHAR16 *splash; CHAR16 key; - EFI_STATUS (*call)(void); + EFI_STATUS (*call)(VOID); BOOLEAN no_autoselect; BOOLEAN non_unique; } ConfigEntry; @@ -85,7 +86,7 @@ typedef struct { CHAR16 *entries_auto; } Config; -static void cursor_left(UINTN *cursor, UINTN *first) +static VOID cursor_left(UINTN *cursor, UINTN *first) { if ((*cursor) > 0) (*cursor)--; @@ -93,7 +94,7 @@ static void cursor_left(UINTN *cursor, UINTN *first) (*first)--; } -static void cursor_right(UINTN *cursor, UINTN *first, UINTN x_max, UINTN len) +static VOID cursor_right(UINTN *cursor, UINTN *first, UINTN x_max, UINTN len) { if ((*cursor)+1 < x_max) (*cursor)++; @@ -1558,7 +1559,7 @@ static VOID config_title_generate(Config *config) { } } -static BOOLEAN config_entry_add_call(Config *config, CHAR16 *title, EFI_STATUS (*call)(void)) { +static BOOLEAN config_entry_add_call(Config *config, CHAR16 *title, EFI_STATUS (*call)(VOID)) { ConfigEntry *entry; entry = AllocateZeroPool(sizeof(ConfigEntry)); @@ -1766,7 +1767,7 @@ static EFI_STATUS image_start(EFI_HANDLE parent_image, const Config *config, con if (options) { EFI_LOADED_IMAGE *loaded_image; - err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (void **)&loaded_image, + err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(err)) { Print(L"Error getting LoadedImageProtocol handle: %r", err); @@ -1847,7 +1848,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { efivar_set(L"LoaderFirmwareType", s, FALSE); FreePool(s); - err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (void **)&loaded_image, + err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(err)) { Print(L"Error getting a LoadedImageProtocol handle: %r ", err); diff --git a/src/efi/linux.c b/src/efi/linux.c new file mode 100644 index 0000000..922a30f --- /dev/null +++ b/src/efi/linux.c @@ -0,0 +1,128 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * Copyright (C) 2015 Kay Sievers + */ + +#include +#include + +#include "util.h" +#include "linux.h" + +#define SETUP_MAGIC 0x53726448 /* "HdrS" */ +struct SetupHeader { + UINT8 boot_sector[0x01f1]; + UINT8 setup_secs; + UINT16 root_flags; + UINT32 sys_size; + UINT16 ram_size; + UINT16 video_mode; + UINT16 root_dev; + UINT16 signature; + UINT16 jump; + UINT32 header; + UINT16 version; + UINT16 su_switch; + UINT16 setup_seg; + UINT16 start_sys; + UINT16 kernel_ver; + UINT8 loader_id; + UINT8 load_flags; + UINT16 movesize; + UINT32 code32_start; + UINT32 ramdisk_start; + UINT32 ramdisk_len; + UINT32 bootsect_kludge; + UINT16 heap_end; + UINT8 ext_loader_ver; + UINT8 ext_loader_type; + UINT32 cmd_line_ptr; + UINT32 ramdisk_max; + UINT32 kernel_alignment; + UINT8 relocatable_kernel; + UINT8 min_alignment; + UINT16 xloadflags; + UINT32 cmdline_size; + UINT32 hardware_subarch; + UINT64 hardware_subarch_data; + UINT32 payload_offset; + UINT32 payload_length; + UINT64 setup_data; + UINT64 pref_address; + UINT32 init_size; + UINT32 handover_offset; +} __attribute__((packed)); + +#ifdef __x86_64__ +typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct SetupHeader *setup); +static inline VOID linux_efi_handover(EFI_HANDLE image, struct SetupHeader *setup) { + handover_f handover; + + asm volatile ("cli"); + handover = (handover_f)((UINTN)setup->code32_start + 512 + setup->handover_offset); + handover(image, ST, setup); +} +#else +typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct SetupHeader *setup) __attribute__((regparm(0))); +static inline VOID linux_efi_handover(EFI_HANDLE image, struct SetupHeader *setup) { + handover_f handover; + + handover = (handover_f)((UINTN)boot_setup->code32_start + setup->handover_offset); + handover(image, ST, setup); +} +#endif + +EFI_STATUS linux_exec(EFI_HANDLE *image, + CHAR8 *cmdline, UINTN linux_addr, + UINTN initrd_addr, UINTN initrd_size) { + struct SetupHeader *image_setup; + struct SetupHeader *boot_setup; + EFI_PHYSICAL_ADDRESS addr; + EFI_STATUS err; + + image_setup = (struct SetupHeader *)(linux_addr); + if (image_setup->signature != 0xAA55 || image_setup->header != SETUP_MAGIC) + return EFI_LOAD_ERROR; + + if (image_setup->version < 0x20b || !image_setup->relocatable_kernel) + return EFI_LOAD_ERROR; + + addr = 0x3fffffff; + err = uefi_call_wrapper(BS->AllocatePages, 4, AllocateMaxAddress, EfiLoaderData, + EFI_SIZE_TO_PAGES(0x4000), &addr); + if (EFI_ERROR(err)) + return err; + boot_setup = (struct SetupHeader *)addr; + ZeroMem(boot_setup, 0x4000); + CopyMem(boot_setup, image_setup, sizeof(struct SetupHeader)); + boot_setup->loader_id = 0xff; + + boot_setup->code32_start = (UINT32)linux_addr + (image_setup->setup_secs+1) * 512; + + if (cmdline) { + addr = 0xA0000; + err = uefi_call_wrapper(BS->AllocatePages, 4, AllocateMaxAddress, EfiLoaderData, + EFI_SIZE_TO_PAGES(strlena(cmdline) + 1), &addr); + if (EFI_ERROR(err)) + return err; + CopyMem((VOID *)addr, cmdline, strlena(cmdline) + 1); + boot_setup->cmd_line_ptr = (UINT32)addr; + } + + boot_setup->ramdisk_start = (UINT32)initrd_addr; + boot_setup->ramdisk_len = (UINT32)initrd_size; + + linux_efi_handover(image, boot_setup); + return EFI_LOAD_ERROR; +} diff --git a/src/efi/linux.h b/src/efi/linux.h new file mode 100644 index 0000000..020c728 --- /dev/null +++ b/src/efi/linux.h @@ -0,0 +1,23 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * Copyright (C) 2015 Kay Sievers + */ + +#ifndef __GUMMIBOOT_kernel_H +#define __GUMMIBOOT_kernel_H + +EFI_STATUS linux_exec(EFI_HANDLE *image, + CHAR8 *cmdline, UINTN linux_addr, + UINTN initrd_addr, UINTN initrd_size); +#endif diff --git a/src/efi/pefile.c b/src/efi/pefile.c index 6ac28da..5d750e0 100644 --- a/src/efi/pefile.c +++ b/src/efi/pefile.c @@ -11,8 +11,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * - * Copyright (C) 2012-2013 Kay Sievers - * Copyright (C) 2012 Harald Hoyer + * Copyright (C) 2015 Kay Sievers */ #include @@ -154,7 +153,6 @@ EFI_STATUS pefile_locate_sections(EFI_FILE *dir, CHAR16 *path, CHAR8 **sections, err = EFI_LOAD_ERROR; goto out; } - for (j = 0; sections[j]; j++) { if (strcmpa(sections[j], sect.Name) != 0) continue; diff --git a/src/efi/stub.c b/src/efi/stub.c new file mode 100644 index 0000000..c1be3d6 --- /dev/null +++ b/src/efi/stub.c @@ -0,0 +1,71 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/* This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * Copyright (C) 2015 Kay Sievers + */ + +#include +#include + +#include "util.h" +#include "pefile.h" +#include "linux.h" + +EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { + EFI_LOADED_IMAGE *loaded_image; + EFI_FILE *root_dir; + CHAR16 *loaded_image_path; + CHAR8 *sections[] = { + (UINT8 *)".cmdline", + (UINT8 *)".linux", + (UINT8 *)".initrd", + NULL + }; + UINTN addrs[3] = {}; + UINTN offs[3] = {}; + UINTN szs[3] = {}; + EFI_STATUS err; + + InitializeLib(image, sys_table); + + err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, + image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(err)) { + Print(L"Error getting a LoadedImageProtocol handle: %r ", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; + } + + root_dir = LibOpenRoot(loaded_image->DeviceHandle); + if (!root_dir) { + Print(L"Unable to open root directory: %r ", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return EFI_LOAD_ERROR; + } + + loaded_image_path = DevicePathToStr(loaded_image->FilePath); + + err = pefile_locate_sections(root_dir, loaded_image_path, sections, addrs, offs, szs); + if (EFI_ERROR(err)) { + Print(L"Unable to locate embedded .linux section: %r ", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; + } + + err = linux_exec(image, (CHAR8 *)"", + (UINTN)loaded_image->ImageBase + addrs[1], + (UINTN)loaded_image->ImageBase + addrs[2], szs[2]); + + Print(L"Execution of embedded linux image failed: %r\n", err); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return err; +} diff --git a/src/efi/util.c b/src/efi/util.c index 191c071..5678b50 100644 --- a/src/efi/util.c +++ b/src/efi/util.c @@ -28,13 +28,13 @@ static const EFI_GUID loader_guid = { 0x4a67b082, 0x0a4c, 0x41cf, {0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f} }; #ifdef __x86_64__ -UINT64 ticks_read(void) { +UINT64 ticks_read(VOID) { UINT64 a, d; __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d)); return (d << 32) | a; } #else -UINT64 ticks_read(void) { +UINT64 ticks_read(VOID) { UINT64 val; __asm__ volatile ("rdtsc" : "=A" (val)); return val; @@ -42,7 +42,7 @@ UINT64 ticks_read(void) { #endif /* count TSC ticks during a millisecond delay */ -UINT64 ticks_freq(void) { +UINT64 ticks_freq(VOID) { UINT64 ticks_start, ticks_end; ticks_start = ticks_read(); @@ -52,7 +52,7 @@ UINT64 ticks_freq(void) { return (ticks_end - ticks_start) * 1000; } -UINT64 time_usec(void) { +UINT64 time_usec(VOID) { UINT64 ticks; static UINT64 freq; diff --git a/test/test-create-disk.sh b/test/test-create-disk.sh index 3982223..9fdb3c3 100755 --- a/test/test-create-disk.sh +++ b/test/test-create-disk.sh @@ -19,8 +19,11 @@ cp test/splash.bmp mnt/EFI/gummiboot/ [ -e /boot/shellx64.efi ] && cp /boot/shellx64.efi mnt/ mkdir mnt/EFI/Linux -objcopy --add-section .osrel=/etc/os-release --change-section-vma .osrel=0x20000 \ - gummibootx64.efi mnt/EFI/Linux/Test-Linux.efi +objcopy \ + --add-section .osrel=/etc/os-release --change-section-vma .osrel=0x20000 \ + --add-section .linux=/boot/$(cat /etc/machine-id)/$(uname -r)/linux --change-section-vma .linux=0x30000 \ + --add-section .initrd=/boot/$(cat /etc/machine-id)/$(uname -r)/initrd --change-section-vma .initrd=0x3000000 \ + stubx64.efi mnt/EFI/Linux/linux-test.efi # install entries mkdir -p mnt/loader/entries -- cgit v1.2.3