From 646d4b0a09168669c883c249a3f027a9af357854 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 26 Mar 2026 14:46:26 +0000 Subject: [PATCH] [build] Allow binaries to request a log partition from genfsimg For UEFI, the USB disk image is constructed from the built EFI binary (e.g. bin-x86_64-efi/ipxe.efi) by genfsimg, which does not itself have any way to access the build configuration. We therefore need a way to annotate the binary such that genfsimg can determine whether or not to include a log partition within the USB disk image. The "OEM ID" and "OEM information" fields within the PE header can be used for this, since they are easily accessed and serve no other purpose. We define bit 0 of "OEM information" as a flag indicating that a log partition should be included. If this bit is set, genfsimg will create a log partition with a layout matching that of the BIOS build (i.e. using partition 3 and at an offset of 16kB from the start of the disk). The PE header is constructed by elf2efi.c, which takes as an input the linked ELF form of the binary. We use an ELF .note section to allow any linked-in object to communicate the log partition request through to elf2efi.c, which then populates the OEM information field accordingly. We choose to use the same field locations within the BIOS bzImage header, since this allows genfsimg to use the same logic for both BIOS and UEFI binaries. In a BIOS build, there is no external processing equivalent to elf2efi.c, and so we construct the field value directly using absolute symbols and explicit relocation records. (Note that the bzImage header is relevant only when using genfsimg to construct a combined BIOS/UEFI image. In the common case of building a BIOS-only image such as bin/ipxe.usb, the partition table is manually constructed by usbdisk.S and genfsimg is not involved.) Signed-off-by: Michael Brown --- src/arch/x86/interface/pcbios/int13con.c | 4 ++ src/arch/x86/prefix/lkrnprefix.S | 31 +++++++++++ src/include/compiler.h | 25 +++++++++ src/scripts/efi.lds | 10 ++++ src/util/elf2efi.c | 70 ++++++++++++++++++++++++ src/util/genfsimg | 40 ++++++++++++++ 6 files changed, 180 insertions(+) diff --git a/src/arch/x86/interface/pcbios/int13con.c b/src/arch/x86/interface/pcbios/int13con.c index 925228874..7372540b6 100644 --- a/src/arch/x86/interface/pcbios/int13con.c +++ b/src/arch/x86/interface/pcbios/int13con.c @@ -298,3 +298,7 @@ struct console_driver int13con __console_driver = { .disabled = CONSOLE_DISABLED, .usage = CONSOLE_INT13, }; + +/* Request a log partition from genfsimg */ +__asm__ ( ".globl ipxe_oem_info_disklog\n\t" + ".equ ipxe_oem_info_disklog, 0x0001\n\t" ); diff --git a/src/arch/x86/prefix/lkrnprefix.S b/src/arch/x86/prefix/lkrnprefix.S index 19d30141b..a2b188a2b 100644 --- a/src/arch/x86/prefix/lkrnprefix.S +++ b/src/arch/x86/prefix/lkrnprefix.S @@ -11,6 +11,37 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ) .globl _lkrn_start _lkrn_start: +/***************************************************************************** + * + * OEM information + * + * For UEFI, we (ab)use the PE format OEM information field to + * communicate information to external processing utilities such as + * genfsimg. + * + * Since these fields lie in an unused location in a bzImage kernel + * header (and since hybrid PE/bzImage is already a well-known file + * format used for dual-mode BIOS/UEFI kernels), we choose to use the + * same field locations for BIOS. + * + */ + + .org 0x24 +oem_id: + .word 0x18ae +oem_info: + .word 0 + +#if __x86_64__ +#define R_X86_16 R_X86_64_16 +#else +#define R_X86_16 R_386_16 +#endif + + /* Allow linked-in objects to request a log partition from genfsimg */ + .weak ipxe_oem_info_disklog + .reloc oem_info, R_X86_16, ipxe_oem_info_disklog + /***************************************************************************** * * Kernel header diff --git a/src/include/compiler.h b/src/include/compiler.h index f6d0aa67d..13c1a1996 100644 --- a/src/include/compiler.h +++ b/src/include/compiler.h @@ -166,6 +166,31 @@ /** @} */ +/** + * @defgroup ipxenote Macros to provide ELF notes + * @{ + */ + +/* Construct an iPXE-specific ELF note */ +#define IPXE_NOTE( type ) \ + __asm__ ( ".section \".note.ipxe\", \"\", " \ + _S2 ( ASM_TCHAR ) "note\n\t" \ + /* Owner name length */ \ + ".long 4\n\t" \ + /* Content length */ \ + ".long 0\n\t" \ + /* Type */ \ + ".long " _S2 ( _C2 ( IPXE_NOTE_, type ) ) \ + "\n\t" \ + /* Owner name */ \ + ".ascii \"iPXE\"\n\t" \ + ".previous\n\t" ) + +/** Build will use a disk-based console log, if present */ +#define IPXE_NOTE_DISKLOG 0x18aed109 + +/** @} */ + /** * @defgroup objmacros Macros to provide or require explicit objects * @{ diff --git a/src/scripts/efi.lds b/src/scripts/efi.lds index d5b2d7567..f7e6abf28 100644 --- a/src/scripts/efi.lds +++ b/src/scripts/efi.lds @@ -119,6 +119,16 @@ SECTIONS { } _assert = ASSERT ( ( _weak == _eweak ), ".weak is non-zero length" ); + /* + * iPXE-specific note sections + * + */ + + .note : { + *(.note.ipxe) + *(.note.ipxe.*) + } + /* * Dispose of the comment and note sections to make the link map * easier to read diff --git a/src/util/elf2efi.c b/src/util/elf2efi.c index 9fd7c27ea..39f614d1b 100644 --- a/src/util/elf2efi.c +++ b/src/util/elf2efi.c @@ -56,6 +56,7 @@ #define Elf_Ehdr Elf32_Ehdr #define Elf_Phdr Elf32_Phdr #define Elf_Shdr Elf32_Shdr +#define Elf_Nhdr Elf32_Nhdr #define Elf_Sym Elf32_Sym #define Elf_Addr Elf32_Addr #define Elf_Rel Elf32_Rel @@ -72,6 +73,7 @@ #define Elf_Ehdr Elf64_Ehdr #define Elf_Phdr Elf64_Phdr #define Elf_Shdr Elf64_Shdr +#define Elf_Nhdr Elf64_Nhdr #define Elf_Sym Elf64_Sym #define Elf_Addr Elf64_Addr #define Elf_Rel Elf64_Rel @@ -243,6 +245,15 @@ /** Number of data directory entries */ #define NUMBER_OF_DIRECTORY_ENTRIES 8 +/** Build requested a console log partition */ +#define IPXE_NOTE_DISKLOG 0x18aed109 + +/** OEM identifier used for iPXE */ +#define IPXE_OEM_ID 0x18ae + +/** OEM information: console log partition requested */ +#define IPXE_OEM_INFO_DISKLOG 0x0001 + struct elf_file { void *data; size_t len; @@ -272,6 +283,7 @@ struct pe_header { static struct pe_header efi_pe_header = { .dos = { .e_magic = EFI_IMAGE_DOS_SIGNATURE, + .e_oemid = IPXE_OEM_ID, .e_lfanew = offsetof ( typeof ( efi_pe_header ), nt ), }, .nt = { @@ -315,6 +327,8 @@ struct options { unsigned int subsystem; /** Create hybrid BIOS/UEFI binary */ int hybrid; + /** Use disk-based console log */ + int disklog; }; /** @@ -998,6 +1012,53 @@ static void process_relocs ( struct elf_file *elf, const Elf_Shdr *shdr, } } +/** + * Process note records + * + * @v elf ELF file + * @v shdr ELF section header + * @v opts Options + */ +static void process_notes ( struct elf_file *elf, const Elf_Shdr *shdr, + struct options *opts ) { + const struct { + Elf_Nhdr hdr; + char name[4]; + } __attribute__ (( packed )) *note; + static const char name[4] = { 'i', 'P', 'X', 'E' }; + size_t remaining; + + /* Process each note */ + note = ( elf->data + shdr->sh_offset ); + remaining = shdr->sh_size; + while ( remaining ) { + + /* Sanity check */ + if ( ( remaining < sizeof ( *note ) ) || + ( note->hdr.n_namesz != sizeof ( name ) ) || + ( note->hdr.n_descsz != 0 ) || + ( memcmp ( note->name, name, sizeof ( name ) ) != 0 ) ) { + eprintf ( "Invalid iPXE note\n" ); + exit ( 1 ); + } + + /* Handle note type */ + switch ( note->hdr.n_type ) { + case IPXE_NOTE_DISKLOG: + opts->disklog = 1; + break; + default: + eprintf ( "Unrecognised iPXE note type %#x\n", + note->hdr.n_type ); + exit ( 1 ); + } + + /* Move to next note */ + note = ( ( ( void * ) note ) + sizeof ( *note ) ); + remaining -= sizeof ( *note ); + } +} + /** * Create relocations section * @@ -1273,12 +1334,21 @@ static void elf2pe ( const char *elf_name, const char *pe_name, /* Process .rela relocations */ process_relocs ( &elf, shdr, sizeof ( Elf_Rela ), &pe_reltab, opts ); + + } else if ( shdr->sh_type == SHT_NOTE ) { + + /* Process .note records */ + process_notes ( &elf, shdr, opts ); } } /* Update image base address */ update_image_base ( &pe_header, pe_sections, pe_reltab ); + /* Update OEM information */ + if ( opts->disklog ) + pe_header.dos.e_oeminfo |= IPXE_OEM_INFO_DISKLOG; + /* Create the .reloc section */ *(next_pe_section) = create_reloc_section ( &pe_header, pe_reltab ); next_pe_section = &(*next_pe_section)->next; diff --git a/src/util/genfsimg b/src/util/genfsimg index 54e9d4354..dada6d2bd 100755 --- a/src/util/genfsimg +++ b/src/util/genfsimg @@ -97,6 +97,22 @@ efi_boot_arch() { esac } +# Check if binary wants a log partition +# +wants_disklog() { + local FILENAME + local OEMID + local OEMINFO + local FLAG + + FILENAME="${1}" + + OEMID=$(get_word "${FILENAME}" 0x24) + OEMINFO=$(get_word "${FILENAME}" 0x26) + FLAG=$(( OEMINFO & 0x0001 )) + [ "${OEMID}" = "18ae" -a "${FLAG}" -ne "0" ] +} + # Find syslinux file # find_syslinux_file() { @@ -214,6 +230,7 @@ case "${OUTFILE}" in BIOSDIR="${ISODIR}" SYSLINUXCFG="${ISODIR}/isolinux.cfg" FATPART= + LOGPART= ;; *.sdsk) ISOIMG= @@ -221,6 +238,7 @@ case "${OUTFILE}" in BIOSDIR="${FATDIR}" SYSLINUXCFG="${FATDIR}/syslinux.cfg" FATPART= + LOGPART= ;; *) ISOIMG= @@ -228,6 +246,7 @@ case "${OUTFILE}" in BIOSDIR="${FATDIR}" SYSLINUXCFG="${FATDIR}/syslinux.cfg" FATPART="4" + LOGPART="3" ;; esac @@ -237,6 +256,9 @@ cat >"${MTOOLSRC}" <