[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 <mcb30@ipxe.org>
This commit is contained in:
Michael Brown 2026-03-26 14:46:26 +00:00
parent e2e9e68f30
commit 646d4b0a09
6 changed files with 180 additions and 0 deletions

View 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" );

View 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

View File

@ -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
* @{

View 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

View 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
@ -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;

View File

@ -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