]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[build] Allow binaries to request a log partition from genfsimg
authorMichael Brown <mcb30@ipxe.org>
Thu, 26 Mar 2026 14:46:26 +0000 (14:46 +0000)
committerMichael Brown <mcb30@ipxe.org>
Thu, 26 Mar 2026 15:12:52 +0000 (15:12 +0000)
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 <mcb30@ipxe.org>
src/arch/x86/interface/pcbios/int13con.c
src/arch/x86/prefix/lkrnprefix.S
src/include/compiler.h
src/scripts/efi.lds
src/util/elf2efi.c
src/util/genfsimg

index 925228874fdaf6809ebf4d934d65dd16b35bf696..7372540b6029798aa9fd425d756fbd5070a9c5e3 100644 (file)
@@ -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" );
index 19d30141b95056bddce468ef4064bc311a8b776e..a2b188a2bdef39e2596d0861dd535ebfe3728f9c 100644 (file)
@@ -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
index f6d0aa67d082627c18012f3803ef33732452a9ce..13c1a1996a45b8da7d50e4c82def4e48da4b2c2b 100644 (file)
 
 /** @} */
 
+/**
+ * @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
  * @{
index d5b2d756796378fa2f4b115ed6455a12d8a6f54c..f7e6abf2890e6d2541938e0e574581aa1eda61b5 100644 (file)
@@ -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
index 9fd7c27ea5ea7a128634211588ea4728aa02f879..39f614d1b7f02362e95cfcdc0a9ebfafcea7346a 100644 (file)
@@ -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
 /** 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;
index 54e9d4354849299ff05f33f16d02b3b71bc6e3ef..dada6d2bd034804455ddb0d1b4e6bd521619e279 100755 (executable)
@@ -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}" <<EOF
 drive F:
   file="${FATIMG}"
   ${FATPART:+partition=}${FATPART}
+drive L:
+  file="${FATIMG}"
+  ${LOGPART:+partition=}${LOGPART}
 EOF
 export MTOOLSRC
 
@@ -244,6 +266,7 @@ export MTOOLSRC
 #
 LKRN=
 EFI=
+DISKLOG=
 for FILENAME ; do
     case "${FILENAME}" in
        *.lkrn)
@@ -308,6 +331,9 @@ for FILENAME ; do
        exit 1
     fi
     cp "${FILENAME}" "${DESTDIR}/${DESTFILE}"
+    if wants_disklog "${FILENAME}" ; then
+       DISKLOG=1
+    fi
 done
 
 # Configure ISO image, if applicable
@@ -371,6 +397,14 @@ if [ -n "${FATIMG}" ] ; then
        FATSERIAL=$(( SOURCE_DATE_EPOCH % 100000000 ))
        FATARGS="${FATARGS} -N ${FATSERIAL}"
     fi
+    if [ -n "${DISKLOG}" -a -n "${LOGPART}" ] ; then
+       LOGTYPE=0xe0
+       LOGCYLS=1
+       LOGSIZE=$(( LOGCYLS * FATALIGN ))
+       FATSIZE=$(( LOGSIZE + FATSIZE ))
+       LOGOFFS="${FATOFFS}"
+       FATOFFS="${LOGSIZE}"
+    fi
     touch "${FATIMG}"
     truncate -s 0 "${FATIMG}"
     truncate -s $(( FATSIZE * 512 )) "${FATIMG}"
@@ -380,6 +414,12 @@ if [ -n "${FATIMG}" ] ; then
                   -b "${FATOFFS}" F:
        mpartition -a F:
     fi
+    if [ -n "${DISKLOG}" -a -n "${LOGPART}" ] ; then
+       mpartition -c -t "${LOGCYLS}" -h "${FATHEADS}" -s "${FATSECTS}" \
+                  -b "${LOGOFFS}" -T "${LOGTYPE}" L:
+       printf "iPXE LOG\n\n" |
+           dd of="${FATIMG}" seek="${LOGOFFS}" conv=notrunc status=none
+    fi
     mformat -v iPXE ${FATARGS} F:
     mcopy -s "${FATDIR}"/* F:
     if [ "${BIOSDIR}" = "${FATDIR}" ] ; then