Step to UEFI (264)代码读取一个 FFS

最近有一个读取 FFS 的需求,具体来说就是在 DXE 阶段需要读取指定的 FFS 文件的内容。针对这个需求,使用QEMU 进行了一番研究。

OVMF 中继承了 Shell,读取这个的动作和我们需求非常类似,于是在 QEMU 的Log中搜索可以看到如下字样:

[Bds]Booting EFI Internal Shell
[Bds] Expand Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(7C04A583-9E3E-4F1C-AD65-E05268D0B4D1) -> Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(7C04A583-9E3E-4F1C-AD65-E05268D0B4D1)
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 6C14040
Loading driver at 0x000068AE000 EntryPoint=0x000068AE4E8 Shell.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 70F9418

对应在代码中位于\mdemodulepkg\library\uefibootmanagerlib\BmLoadOption.c

DEBUG ((DEBUG_INFO, "[Bds] Expand "));
BmPrintDp (FilePath);
DEBUG ((DEBUG_INFO, " -> "));
BmPrintDp (CurFullPath);
DEBUG ((DEBUG_INFO, "\n"));

经过追踪可以看到具体工作的是位于 \mdepkg\library\dxeserviceslib\DxeServicesLib.c的如下函数:

/**
  Get the image file buffer data and buffer size by its device path.
 
  Access the file either from a firmware volume, from a file system interface,
  or from the load file interface.
 
  Allocate memory to store the found image. The caller is responsible to free memory.
 
  If FilePath is NULL, then NULL is returned.
  If FileSize is NULL, then NULL is returned.
  If AuthenticationStatus is NULL, then NULL is returned.
 
  @param[in]       BootPolicy           Policy for Open Image File.If TRUE, indicates
                                        that the request originates from the boot
                                        manager, and that the boot manager is
                                        attempting to load FilePath as a boot
                                        selection. If FALSE, then FilePath must
                                        match an exact file to be loaded.
  @param[in]       FilePath             The pointer to the device path of the file
                                        that is abstracted to the file buffer.
  @param[out]      FileSize             The pointer to the size of the abstracted
                                        file buffer.
  @param[out]      AuthenticationStatus Pointer to the authentication status.
 
  @retval NULL   FilePath is NULL, or FileSize is NULL, or AuthenticationStatus is NULL, or the file can't be found.
  @retval other  The abstracted file buffer. The caller is responsible to free memory.
**/
VOID *
EFIAPI
GetFileBufferByFilePath (
  IN BOOLEAN                           BootPolicy,
  IN CONST EFI_DEVICE_PATH_PROTOCOL    *FilePath,
  OUT      UINTN                       *FileSize,
  OUT UINT32                           *AuthenticationStatus
  )

进一步研究,上述函数通过 EFI_FIRMWARE_VOLUME2_PROTOCOL 来完成读取。这个 Protocol 在 PI Spec (并不是 UEFI Spec)中有描述:

进一步追踪,可以看到是通过FwVol->ReadSection() 将Shell读取出来的:

SectionType = EFI_SECTION_PE32;
ImageBuffer = NULL;
Status = FwVol->ReadSection (
                  FwVol,
                  FvNameGuid,
                  SectionType,
                  0,
                  (VOID **)&ImageBuffer,
                  &ImageBufferSize,
                  AuthenticationStatus
                  );

ReadSection() 原型定义如下:

/**
  Locates the requested section within a file and returns it in a buffer.
 
  ReadSection() is used to retrieve a specific section from a file
  within a firmware volume. The section returned is determined
  using a depth-first, left-to-right search algorithm through all
  sections found in the specified file. The output buffer is specified by a double indirection
  of the Buffer parameter. The input value of Buffer is used to
  determine if the output buffer is caller allocated or is
  dynamically allocated by ReadSection(). If the input value of
  Buffer!=NULL, it indicates that the output buffer is caller
  allocated. In this case, the input value of *BufferSize
  indicates the size of the caller-allocated output buffer. If
  the output buffer is not large enough to contain the entire
  requested output, it is filled up to the point that the output
  buffer is exhausted and EFI_WARN_BUFFER_TOO_SMALL is returned,
  and then BufferSize is returned with the size that is required
  to successfully complete the read. All other
  output parameters are returned with valid values. If the input
  value of *Buffer==NULL, it indicates the output buffer is to
  be allocated by ReadSection(). In this case, ReadSection()
  will allocate an appropriately sized buffer from boot services
  pool memory, which will be returned in *Buffer. The size of
  the new buffer is returned in *BufferSize and all other output
  parameters are returned with valid values. ReadSection() is
  callable only from TPL_NOTIFY and below. Behavior of
  ReadSection() at any EFI_TPL above TPL_NOTIFY is
  undefined.
 
  @param  This                Indicates the EFI_FIRMWARE_VOLUME2_PROTOCOL instance.
 
  @param NameGuid             Pointer to an EFI_GUID, which indicates the
                              file name from which the requested section
                              will be read.
 
  @param SectionType          Indicates the section type to return.
                              SectionType in conjunction with
                              SectionInstance indicates which section to
                              return.
 
  @param SectionInstance      Indicates which instance of sections
                              with a type of SectionType to return.
                              SectionType in conjunction with
                              SectionInstance indicates which
                              section to return. SectionInstance is
                              zero based.
 
  @param Buffer               Pointer to a pointer to a buffer in which the
                              section contents are returned, not including
                              the section header.
 
  @param BufferSize           Pointer to a caller-allocated UINTN. It
                              indicates the size of the memory
                              represented by Buffer.
 
  @param AuthenticationStatus Pointer to a caller-allocated
                              UINT32 in which the authentication
                              status is returned.
 
 
  @retval EFI_SUCCESS   The call completed successfully.
 
  @retval EFI_WARN_BUFFER_TOO_SMALL   The caller-allocated
                                      buffer is too small to
                                      contain the requested
                                      output. The buffer is
                                      filled and the output is
                                      truncated.
 
  @retval EFI_OUT_OF_RESOURCES  An allocation failure occurred.
 
  @retval EFI_NOT_FOUND   The requested file was not found in
                          the firmware volume. EFI_NOT_FOUND The
                          requested section was not found in the
                          specified file.
 
  @retval EFI_DEVICE_ERROR  A hardware error occurred when
                            attempting to access the firmware
                            volume.
 
  @retval EFI_ACCESS_DENIED The firmware volume is configured to
                            disallow reads. EFI_PROTOCOL_ERROR
                            The requested section was not found,
                            but the file could not be fully
                            parsed because a required
                            GUIDED_SECTION_EXTRACTION_PROTOCOL
                            was not found. It is possible the
                            requested section exists within the
                            file and could be successfully
                            extracted once the required
                            GUIDED_SECTION_EXTRACTION_PROTOCOL
                            is published.
 
**/
typedef
EFI_STATUS
(EFIAPI * EFI_FV_READ_SECTION)(
  IN CONST  EFI_FIRMWARE_VOLUME2_PROTOCOL *This,
  IN CONST  EFI_GUID                      *NameGuid,
  IN        EFI_SECTION_TYPE              SectionType,
  IN        UINTN                         SectionInstance,
  IN OUT    VOID                          **Buffer,
  IN OUT    UINTN                         *BufferSize,
  OUT       UINT32                        *AuthenticationStatus
);

在 OVMF 中实际动作是 \mdemodulepkg\core\dxe\fwvol\FwVolRead.c 中的FvReadFileSection()函数完成的(通过输出 Log 的方法可以确认):

/**
  Locates a section in a given FFS File and
  copies it to the supplied buffer (not including section header).
 
  @param  This                       Indicates the calling context.
  @param  NameGuid                   Pointer to an EFI_GUID, which is the
                                     filename.
  @param  SectionType                Indicates the section type to return.
  @param  SectionInstance            Indicates which instance of sections with a
                                     type of SectionType to return.
  @param  Buffer                     Buffer is a pointer to pointer to a buffer
                                     in which the file or section contents or are
                                     returned.
  @param  BufferSize                 BufferSize is a pointer to caller allocated
                                     UINTN.
  @param  AuthenticationStatus       AuthenticationStatus is a pointer to a
                                     caller allocated UINT32 in which the
                                     authentication status is returned.
 
  @retval EFI_SUCCESS                Successfully read the file section into
                                     buffer.
  @retval EFI_WARN_BUFFER_TOO_SMALL  Buffer too small.
  @retval EFI_NOT_FOUND              Section not found.
  @retval EFI_DEVICE_ERROR           Device error.
  @retval EFI_ACCESS_DENIED          Could not read.
  @retval EFI_INVALID_PARAMETER      Invalid parameter.
 
**/
EFI_STATUS
EFIAPI
FvReadFileSection (
  IN CONST  EFI_FIRMWARE_VOLUME2_PROTOCOL  *This,
  IN CONST  EFI_GUID                       *NameGuid,
  IN        EFI_SECTION_TYPE               SectionType,
  IN        UINTN                          SectionInstance,
  IN OUT    VOID                           **Buffer,
  IN OUT    UINTN                          *BufferSize,
  OUT       UI

运行结果如下:

oading driver at 0x00007AC4000 EntryPoint=0x00007AC4494 PcRtc.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 7097A98
ProtectUefiImageCommon - 0x7097740
  - 0x0000000007AC4000 - 0x0000000000005300
  Image - c:\buildbs\stable202108\Build\OvmfX64\DEBUG_VS2015x86\X64\PcAtChipsetPkg\PcatRealTimeClockRuntimeDxe\PcatRealTimeClockRuntimeDxe\DEBUG\PcRtc.pdb
!!!!!!!!  ProtectUefiImageCommon - Section Alignment(0x20) is incorrect  !!!!!!!!
!!!!!!!!  Image - c:\buildbs\stable202108\Build\OvmfX64\DEBUG_VS2015x86\X64\PcAtChipsetPkg\PcatRealTimeClockRuntimeDxe\PcatRealTimeClockRuntimeDxe\DEBUG\PcRtc.pdb  !!!!!!!!
[EFI_FIRMWARE_VOLUME2_PROTOCOL Success 944416]
4D 5A 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
A5 A0 A5 C0 A5 D8 A5 F0 A5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
InstallProtocolInterface: 27CFAC87-46CC-11D4-9A38-0090273FC14D 0

使用工具可以看到 Shell.efi 的大小是 94416Bytes,读取到Buffer 中的内容也是 Shell 的内容。

总结:BIOS 工程师可以通过 EFI_FIRMWARE_VOLUME2_PROTOCOL 来获取指定 FFS 的内容。

一个让你轻松配置ESP32 WIFI的库

ESP32 带有 WIFI 功能,而众所周知要想让一个设备连接WIFI AP 需要告知设备对应 AP 的名称和密码,简单实验的话,可以直接在代码中写死这两个参数,但这种情况下烧写之后设备只能在固定的环境下使用。https://github.com/tzapu/wifimanager 这个项目可以解决上述问题。先说一下这个东西如何使用:

  1. 编译下载Arduino\libraries\WiFiManager-master\examples\OnDemand下面的代码
  2. 上电运行之后短接 Pin0
  3. 用手机查找OnDemandAP 这个 AP
  4. 连接之后自动打开下面的界面

5.选择 Configure WIFI 会显示当前能搜索到的WIFI AP名称,选择你要的

6.输入对应的密码设备即可连接

我在 DFRobot的 FireBeetle 上实验(需要注意他自带的WIFI库太老,运行期AP 无法启动,需要用ESP32 Arduino库中的 FireBeetle),工作正常。

Step to UEFI (263)实验:直接修改QEMU的启动顺序

当我们使用如下 Command 启动 QEMU 的时候,能够将 d:\tmp 目录映射为 fs0:

.\qemu-system-x86_64.exe -bios ovmf.fd  -hda fat:rw:d:\tmp\

之后,可以在这个目录中看到一个名为 NvVars 的文件。这次实验的目标是直接修改 NvVars 文件来实现调整启动顺序。例如:启动之后在 Setup 中可以看到如下4个启动设备:

1.进入Shell 后使读取变量的命令,将变量保存在 Result.txt 文件中:

Dmpstore >> result.txt

2.在 Debug Log 中可以看到如下字样,就是说 OVMF 在启动过程中会读取NvVars这个文件作为变量:

InstallProtocolInterface: 964E5B22-6459-11D2-8E39-00A0C969723B 6B6F030
Installed Fat filesystem on 6C14698
FsAccess.c: LoadNvVarsFromFs
FSOpen: Open 'NvVars' Success
FsAccess.c: Read 10590 bytes from NV Variables file

3.进一步分析,读取动作来自 ovmfpkg\library\nvvarsfilelib\FsAccess.c 文件中的LoadNvVarsFromFs() 函数

/**
  Loads the non-volatile variables from the NvVars file on the
  given file system.
 
  @param[in]  FsHandle - Handle for a gEfiSimpleFileSystemProtocolGuid instance
 
  @return     EFI_STATUS based on the success or failure of load operation
 
**/
EFI_STATUS
LoadNvVarsFromFs (
  EFI_HANDLE                            FsHandle
  )

4.具体读取动作在ReadNvVarsFile() 函数中:

/**
  Reads the contents of the NvVars file on the file system
 
  @param[in]  FsHandle - Handle for a gEfiSimpleFileSystemProtocolGuid instance
 
  @return     EFI_STATUS based on the success or failure of the file read
 
**/
EFI_STATUS
ReadNvVarsFile (
  IN  EFI_HANDLE            FsHandle
  )

读取内容

FileContents = FileHandleReadToNewBuffer (File, FileSize);
if (FileContents == NULL) {
  FileHandleClose (File);
  return EFI_UNSUPPORTED;
}

5.读取之后使用 IterateVariablesInBuffer() 函数进行解析

/**
  Iterates through the variables in the buffer, and calls a callback
  function for each variable found.
 
  @param[in]  CallbackFunction - Function called for each variable instance
  @param[in]  Context - Passed to each call of CallbackFunction
  @param[in]  Buffer - Buffer containing serialized variables
  @param[in]  MaxSize - Size of Buffer in bytes
 
  @return     EFI_STATUS based on the success or failure of the operation
 
**/
STATIC
EFI_STATUS
IterateVariablesInBuffer (
  IN VARIABLE_SERIALIZATION_ITERATION_CALLBACK  CallbackFunction,
  IN VOID                                       *CallbackContext,
  IN VOID                                       *Buffer,
  IN UINTN                                      MaxSize
  )

上面是一些基本的研究,下面尝试直接修改。在Dump 的变量文件Result.txt 中,可以看到 BootOrder 变量:

00000080: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  00000090: 00 00 00 00 7F FF 04 00-4E AC 08 81 11 9F 59 4D  *........N.....YM*
  000000A0: 85 0E E2 1A 52 2C 59 B2-                         *....R,Y.*
Variable NV+RT+BS 'EFIGlobalVariable:BootOrder' DataSize = 0x0A
  00000000: 00 00 01 00 02 00 03 00-04 00                    *..........*
Variable NV+RT+BS 'EFIGlobalVariable:Key0002' DataSize = 0x0E
  00000000: 00 00 00 40 3C 4A 2D 14-05 00 11 00 00 00        *...@<J-.......*

对应在 NvVars 文件中位于下面几行:

这里,将 00 00 01 00 02 00 03 00-04 00 修改为 00 00 04 00 02 00 03 00-01 00,下次再启动 QEMU 会会先启动到网卡上了。   

Step to UEFI (262)给 QEMU 的 BIOS 增加 BootMenu快捷键

在实验中,我发现 QEMU 启动的虚拟机并不支持调出 BootMenu,为此研究一下如何修改BIOS 添加这个功能。

前面的实验中,我们在启动项目中插入了自定义的 Application,主要代码在BdsPlatform.c 中。继续查看这个文件,可以看到分配快捷键的代码:

VOID
PlatformRegisterOptionsAndKeys (
  VOID
  )
{
  EFI_STATUS                   Status;
  EFI_INPUT_KEY                Enter;
  EFI_INPUT_KEY                F2;
  EFI_INPUT_KEY                Esc;
  EFI_BOOT_MANAGER_LOAD_OPTION BootOption;
 
  //
  // Register ENTER as CONTINUE key
  //
  Enter.ScanCode    = SCAN_NULL;
  Enter.UnicodeChar = CHAR_CARRIAGE_RETURN;
  Status = EfiBootManagerRegisterContinueKeyOption (0, &Enter, NULL);
  ASSERT_EFI_ERROR (Status);
 
  //
  // Map F2 to Boot Manager Menu
  //
  F2.ScanCode     = SCAN_F2;
  F2.UnicodeChar  = CHAR_NULL;
  Esc.ScanCode    = SCAN_ESC;
  Esc.UnicodeChar = CHAR_NULL;
  Status = EfiBootManagerGetBootManagerMenu (&BootOption);
  ASSERT_EFI_ERROR (Status);
  Status = EfiBootManagerAddKeyOptionVariable (
             NULL, (UINT16) BootOption.OptionNumber, 0, &F2, NULL
             );
  ASSERT (Status == EFI_SUCCESS || Status == EFI_ALREADY_STARTED);
  Status = EfiBootManagerAddKeyOptionVariable (
             NULL, (UINT16) BootOption.OptionNumber, 0, &Esc, NULL
             );
  ASSERT (Status == EFI_SUCCESS || Status == EFI_ALREADY_STARTED);
}

和EmulatorPkg 中的PlatformBdsRegisterStaticBootOptions() 函数进行对比(代码位于 \EmulatorPkg\Library\PlatformBmLib\PlatformBm.c中),可以看到后者注册了多个快捷键对应着不同的功能:

/**
  Register the static boot options.
**/
VOID
PlatformBdsRegisterStaticBootOptions (
  VOID
  )
{
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL  Black;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL  White;
  EFI_INPUT_KEY                  Enter;
  EFI_INPUT_KEY                  F2;
  EFI_INPUT_KEY                  F7;
  EFI_BOOT_MANAGER_LOAD_OPTION   BootOption;
  UINTN                          OptionNumber;
 
  Black.Blue = Black.Green = Black.Red = Black.Reserved = 0;
  White.Blue = White.Green = White.Red = White.Reserved = 0xFF;
 
  //
  // Register ENTER as CONTINUE key
  //
  Enter.ScanCode    = SCAN_NULL;
  Enter.UnicodeChar = CHAR_CARRIAGE_RETURN;
  EfiBootManagerRegisterContinueKeyOption (0, &Enter, NULL);
  //
  // Map F2 to Boot Manager Menu
  //
  F2.ScanCode    = SCAN_F2;
  F2.UnicodeChar = CHAR_NULL;
  EfiBootManagerGetBootManagerMenu (&BootOption);
  EfiBootManagerAddKeyOptionVariable (NULL, (UINT16) BootOption.OptionNumber, 0, &F2, NULL);
 
  //
  // 3. Boot Device List menu
  //
  F7.ScanCode     = SCAN_F7;
  F7.UnicodeChar  = CHAR_NULL;
  OptionNumber    = GetBootManagerMenuAppOption ();
  EfiBootManagerAddKeyOptionVariable (NULL, (UINT16)OptionNumber, 0, &F7, NULL);
 
  PrintXY (10, 10, &White, &Black, L"F2    to enter Setup.                              ");
  PrintXY (10, 30, &White, &Black, L"F7    to enter Boot Manager Menu.");
  PrintXY (10, 50, &White, &Black, L"Enter to boot directly.");
}

最终修改\OvmfPkg\Library\PlatformBootManagerLib\BdsPlatform.c 代码如下:

//LABZ_Start
EFI_GUID mBootMenuFile = {
  0xEEC25BDC, 0x67F2, 0x4D95, { 0xB1, 0xD5, 0xF8, 0x1B, 0x20, 0x39, 0xD1, 0x1D }
};
 
 
/**
  Check if it's a Device Path pointing to BootManagerMenuApp.
 
  @param  DevicePath     Input device path.
 
  @retval TRUE   The device path is BootManagerMenuApp File Device Path.
  @retval FALSE  The device path is NOT BootManagerMenuApp File Device Path.
**/
BOOLEAN
IsBootManagerMenuAppFilePath (
  EFI_DEVICE_PATH_PROTOCOL     *DevicePath
)
{
  EFI_HANDLE                      FvHandle;
  VOID                            *NameGuid;
  EFI_STATUS                      Status;
 
  Status = gBS->LocateDevicePath (&gEfiFirmwareVolume2ProtocolGuid, &DevicePath, &FvHandle);
  if (!EFI_ERROR (Status)) {
    NameGuid = EfiGetNameGuidFromFwVolDevicePathNode ((CONST MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *) DevicePath);
    if (NameGuid != NULL) {
      return CompareGuid (NameGuid, &mBootMenuFile);
    }
  }
 
  return FALSE;
}
 
EFI_DEVICE_PATH *
FvFilePath (
  EFI_GUID                     *FileGuid
  )
{
 
  EFI_STATUS                         Status;
  EFI_LOADED_IMAGE_PROTOCOL          *LoadedImage;
  MEDIA_FW_VOL_FILEPATH_DEVICE_PATH  FileNode;
 
  EfiInitializeFwVolDevicepathNode (&FileNode, FileGuid);
 
  Status = gBS->HandleProtocol (
                  gImageHandle,
                  &gEfiLoadedImageProtocolGuid,
                  (VOID **) &LoadedImage
                  );
  ASSERT_EFI_ERROR (Status);
  return AppendDevicePathNode (
           DevicePathFromHandle (LoadedImage->DeviceHandle),
           (EFI_DEVICE_PATH_PROTOCOL *) &FileNode
           );
}
 
/**
  Create one boot option for BootManagerMenuApp.
 
  @param  FileGuid          Input file guid for the BootManagerMenuApp.
  @param  Description       Description of the BootManagerMenuApp boot option.
  @param  Position          Position of the new load option to put in the ****Order variable.
  @param  IsBootCategory    Whether this is a boot category.
 
 
  @retval OptionNumber      Return the option number info.
 
**/
UINTN
RegisterBootManagerMenuAppBootOption (
  EFI_GUID                         *FileGuid,
  CHAR16                           *Description,
  UINTN                            Position,
  BOOLEAN                          IsBootCategory
  )
{
  EFI_STATUS                       Status;
  EFI_BOOT_MANAGER_LOAD_OPTION     NewOption;
  EFI_DEVICE_PATH_PROTOCOL         *DevicePath;
  UINTN                            OptionNumber;
 
  DevicePath = FvFilePath (FileGuid);
  Status = EfiBootManagerInitializeLoadOption (
             &NewOption,
             LoadOptionNumberUnassigned,
             LoadOptionTypeBoot,
             IsBootCategory ? LOAD_OPTION_ACTIVE : LOAD_OPTION_CATEGORY_APP,
             Description,
             DevicePath,
             NULL,
             0
             );
  ASSERT_EFI_ERROR (Status);
  FreePool (DevicePath);
 
  Status = EfiBootManagerAddLoadOptionVariable (&NewOption, Position);
  ASSERT_EFI_ERROR (Status);
 
  OptionNumber = NewOption.OptionNumber;
 
  EfiBootManagerFreeLoadOption (&NewOption);
 
  return OptionNumber;
}
/**
  Return the boot option number to the BootManagerMenuApp.
 
  If not found it in the current boot option, create a new one.
 
  @retval OptionNumber   Return the boot option number to the BootManagerMenuApp.
 
**/
UINTN
GetBootManagerMenuAppOption (
  VOID
  )
{
  UINTN                        BootOptionCount;
  EFI_BOOT_MANAGER_LOAD_OPTION *BootOptions;
  UINTN                        Index;
  UINTN                        OptionNumber;
 
  OptionNumber = 0;
 
  BootOptions = EfiBootManagerGetLoadOptions (&BootOptionCount, LoadOptionTypeBoot);
 
  for (Index = 0; Index < BootOptionCount; Index++) {
    if (IsBootManagerMenuAppFilePath (BootOptions[Index].FilePath)) {
      OptionNumber = BootOptions[Index].OptionNumber;
      break;
    }
  }
 
  EfiBootManagerFreeLoadOptions (BootOptions, BootOptionCount);
 
  if (Index >= BootOptionCount) {
    //
    // If not found the BootManagerMenuApp, create it.
    //
    OptionNumber = (UINT16) RegisterBootManagerMenuAppBootOption (&mBootMenuFile, L"UEFI BootManagerMenuApp", (UINTN) -1, FALSE);
  }
 
  return OptionNumber;
}
//LABZ_End
VOID
PlatformRegisterOptionsAndKeys (
  VOID
  )
{
  EFI_STATUS                   Status;
  EFI_INPUT_KEY                Enter;
  EFI_INPUT_KEY                F2;
  EFI_INPUT_KEY                F7;  
  EFI_INPUT_KEY                Esc;
  EFI_BOOT_MANAGER_LOAD_OPTION BootOption;
  UINTN                        OptionNumber;
  //
  // Register ENTER as CONTINUE key
  //
  Enter.ScanCode    = SCAN_NULL;
  Enter.UnicodeChar = CHAR_CARRIAGE_RETURN;
  Status = EfiBootManagerRegisterContinueKeyOption (0, &Enter, NULL);
  ASSERT_EFI_ERROR (Status);
 
  //
  // Map F2 to Boot Manager Menu
  //
  F2.ScanCode     = SCAN_F2;
  F2.UnicodeChar  = CHAR_NULL;
  Esc.ScanCode    = SCAN_ESC;
  Esc.UnicodeChar = CHAR_NULL;
  Status = EfiBootManagerGetBootManagerMenu (&BootOption);
  ASSERT_EFI_ERROR (Status);
  Status = EfiBootManagerAddKeyOptionVariable (
             NULL, (UINT16) BootOption.OptionNumber, 0, &F2, NULL
             );
  ASSERT (Status == EFI_SUCCESS || Status == EFI_ALREADY_STARTED);
  Status = EfiBootManagerAddKeyOptionVariable (
             NULL, (UINT16) BootOption.OptionNumber, 0, &Esc, NULL
             );
  ASSERT (Status == EFI_SUCCESS || Status == EFI_ALREADY_STARTED);
   
  //LABZ_Start
  //
  // 3. Boot Device List menu
  //
  F7.ScanCode     = SCAN_F7;
  F7.UnicodeChar  = CHAR_NULL;
  OptionNumber    = GetBootManagerMenuAppOption ();
  EfiBootManagerAddKeyOptionVariable (NULL, (UINT16)OptionNumber, 0, &F7, NULL);  
  //LABZ_End
}

此外,在 \OvmfPkg\OvmfPkgX64.dsc 中添加BootManagerMenuApp.inf如下:

    <PcdsFixedAtBuild>
      gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0xFF
      gEfiShellPkgTokenSpaceGuid.PcdShellLibAutoInitialize|FALSE
      gEfiMdePkgTokenSpaceGuid.PcdUefiLibMaxPrintBufferSize|8000
  }
 
  MdeModulePkg/Application/BootManagerMenuApp/BootManagerMenuApp.inf
 
!if $(SECURE_BOOT_ENABLE) == TRUE
  SecurityPkg/VariableAuthenticated/SecureBootConfigDxe/SecureBootConfigDxe.inf
  OvmfPkg/EnrollDefaultKeys/EnrollDefaultKeys.inf
!endif

\OvmfPkg\OvmfPkgX64.fdf修改如下:

!if $(TOOL_CHAIN_TAG) != "XCODE5"
INF  ShellPkg/DynamicCommand/TftpDynamicCommand/TftpDynamicCommand.inf
INF  ShellPkg/DynamicCommand/HttpDynamicCommand/HttpDynamicCommand.inf
INF  OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.inf
!endif
INF  ShellPkg/Application/Shell/Shell.inf
 
INF  MdeModulePkg/Application/BootManagerMenuApp/BootManagerMenuApp.inf
 
INF MdeModulePkg/Logo/LogoDxe.inf
 
#
# Network modules
#
!if $(E1000_ENABLE)

可以看到整体和之前的“BootMenu插入自己的Application”并无多大差别,只是多了使用EfiBootManagerAddKeyOptionVariable() 函数将  Application 和快捷键关联起来的。

运行结果:

NASM Assert 一个错误的方法

有时候为了确定代码是否编译到,我们需要在代码中添加让编译器报错的指令,对于 MASM 来说是 .ERR。在 NASM 中是”%error”【参考1】。编写例子如下:

segment code
.start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,stacktop
 
%error "assert error here" 
 
mov ax,0x0600
mov bh,0x43
xor cx,cx
mov dh,24
mov dl,79
int 0x10   
 
mov dx,hello
mov ah,9
int 0x21   
 
mov ax,0x4c00
int 0x21  
 
segment data
 
hello : db 'hello,world',13,10,'$'
 
segment stack stack
 
resb 64
 
stacktop:

编译时会自动报错如下:

此外,从我的实验来看,在 BIOS 代码中的 NASM 代码中随意插入字符并不能使得编译过程报错停止下来,所以如果有需要务必使用上述方法。

参考:

  1. https://www.nasm.us/doc/nasmdoc4.html#section-4.9

4.9 Reporting User-Defined Errors: %error, %warning, %fatal

The preprocessor directive %error will cause NASM to report an error if it occurs in assembled code. So if other users are going to try to assemble your source files, you can ensure that they define the right macros by means of code like this:

%ifdef F1

    ; do some setup

%elifdef F2

    ; do some different setup

%else

    %error “Neither F1 nor F2 was defined.”

%endif

Then any user who fails to understand the way your code is supposed to be assembled will be quickly warned of their mistake, rather than having to wait until the program crashes on being run and then not knowing what went wrong.

Similarly, %warning issues a warning, but allows assembly to continue:

%ifdef F1

    ; do some setup

%elifdef F2

    ; do some different setup

%else

    %warning “Neither F1 nor F2 was defined, assuming F1.”

    %define F1

%endif

%error and %warning are issued only on the final assembly pass. This makes them safe to use in conjunction with tests that depend on symbol values.

%fatal terminates assembly immediately, regardless of pass. This is useful when there is no point in continuing the assembly further, and doing so is likely just going to cause a spew of confusing error messages.

It is optional for the message string after %error, %warning or %fatal to be quoted. If it is not, then single-line macros are expanded in it, which can be used to display more information to the user. For example:

%if foo > 64

    %assign foo_over foo-64

    %error foo is foo_over bytes too large

%endif

Windows 全自动安装

之前介绍过的通过WinPE启动,通过软件将安装好的硬盘制作成镜像,然后当有需要时直接恢复的方法。但是更多时候我们出于测试的目的,需要安装最新版本的 Pure OS​。 这次介绍的全自动安装也就是无人值守模式安装,就是说只需要启动到安装盘上,Windows会自动完成包括分区格式化在内的全部工作。这是Windows自带的功能,只需要将特定的autounattend.xml 放置在启动盘的根目录下即可。具体做法如下:

1. 在 https://schneegans.de/windows/unattend-generator/ 根据需要生成 autounattend.xml 文件;

2.对于有要求配置 WIFI 的Windows可以在XML 文件中加入

<OOBE>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
</OOBE>

3.将 autounattend.xml 文件放置在安装盘的根目录下,启动安装即可。

这里放一个我制作好的:

<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
  <settings pass="offlineServicing" />
  <settings pass="windowsPE">
    <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
      <SetupUILanguage>
        <UILanguage>en-US</UILanguage>
      </SetupUILanguage>
      <InputLocale>0409:00000409</InputLocale>
      <SystemLocale>en-US</SystemLocale>
      <UILanguage>en-US</UILanguage>
      <UserLocale>en-US</UserLocale>
    </component>
    <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
      <DiskConfiguration>
        <WillShowUI>OnError</WillShowUI>
        <Disk wcm:action="add">
          <CreatePartitions>
            <!-- EFI system partition -->
            <CreatePartition wcm:action="add">
              <Order>1</Order>
              <Type>EFI</Type>
              <Size>100</Size>
            </CreatePartition>
            <!-- Microsoft reserved partition (MSR) -->
            <CreatePartition wcm:action="add">
              <Order>2</Order>
              <Type>MSR</Type>
              <Size>16</Size>
            </CreatePartition>
            <!-- Windows partition -->
            <CreatePartition wcm:action="add">
              <Order>3</Order>
              <Type>Primary</Type>
              <Extend>true</Extend>
            </CreatePartition>
          </CreatePartitions>
          <ModifyPartitions>
            <!-- EFI system partition -->
            <ModifyPartition wcm:action="add">
              <Order>1</Order>
              <PartitionID>1</PartitionID>
              <Label>System</Label>
              <Format>FAT32</Format>
            </ModifyPartition>
            <!-- Microsoft reserved partition (MSR) -->
            <ModifyPartition wcm:action="add">
              <Order>2</Order>
              <PartitionID>2</PartitionID>
            </ModifyPartition>
            <!-- Windows partition -->
            <ModifyPartition wcm:action="add">
              <Order>3</Order>
              <PartitionID>3</PartitionID>
              <Label>Windows</Label>
              <Format>NTFS</Format>
              <Letter>C</Letter>
            </ModifyPartition>
          </ModifyPartitions>
          <DiskID>0</DiskID>
          <WillWipeDisk>true</WillWipeDisk>
        </Disk>
      </DiskConfiguration>
      <ImageInstall>
        <OSImage>
          <InstallTo>
            <DiskID>0</DiskID>
            <PartitionID>3</PartitionID>
          </InstallTo>
        </OSImage>
      </ImageInstall>
      <UserData>
        <ProductKey>
          <Key>VK7JG-NPHTM-C97JM-9MPGT-3V66T</Key>
        </ProductKey>
        <AcceptEula>true</AcceptEula>
      </UserData>
    </component>
  </settings>
  <settings pass="generalize" />
  <settings pass="specialize" />
  <settings pass="auditSystem" />
  <settings pass="auditUser" />
  <settings pass="oobeSystem">
    <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
      <InputLocale>0409:00000409</InputLocale>
      <SystemLocale>en-US</SystemLocale>
      <UILanguage>en-US</UILanguage>
      <UserLocale>en-US</UserLocale>
    </component>
    <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
      <UserAccounts>
        <AdministratorPassword>
          <Value>password</Value>
          <PlainText>true</PlainText>
        </AdministratorPassword>
      </UserAccounts>
      <AutoLogon>
        <Username>Administrator</Username>
        <Enabled>true</Enabled>
        <LogonCount>1</LogonCount>
        <Password>
          <Value>password</Value>
          <PlainText>true</PlainText>
        </Password>
      </AutoLogon>
      <OOBE>
        <ProtectYourPC>3</ProtectYourPC>
        <HideEULAPage>true</HideEULAPage>
        <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
      </OOBE>
    </component>
  </settings>
</unattend>

安装后对于有一些 Windows 会要求输入密码,对应的 Administrator 密码为 password

虚拟机中测试视频可以在B站看到 ,在VirtualBox上安装 22616.1.220502-1800

本文提到的 autounattend.xml 下载

ESP32 创建图片 WebServer

这次介绍一个比较好玩的:通过 ESP32 创建一个 WebServer ,通过浏览器访问的时候自动播放预先定义好的图片序列。

制作方法:

1.创建一个 640×480的图片,放上彩色文字,便于观察,保存为 JPG 格式

2.旋转文字,再保存成另外一个文件

3.重复上述动作我们能获得4个文件

4.使用 bin2c 工具将四个文件转化为4个C语言h文件

5.文件头定义中做一点调整,图片大小命名为  picN_size, 内容命名为 picN 这种

6.接下来就可以编写我们的代码了

#include "esp_camera.h"
#include <WiFi.h>
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_http_server.h"
#include "pic\ph0.h"
#include "pic\ph1.h"
#include "pic\ph2.h"
#include "pic\ph3.h"
 
// 这里改成你自己的 WIFI 名称和密码
const char* ssid = "YOUWIFI";
const char* password = "PASSWORD";
 
#define PART_BOUNDARY "123456789000000000000987654321"
 
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
 
httpd_handle_t stream_httpd = NULL;
 
static esp_err_t stream_handler(httpd_req_t *req) {
  esp_err_t res = ESP_OK;
  size_t _jpg_buf_len = 0;
  uint8_t * _jpg_buf = NULL;
  char * part_buf[64];
   
  static int index=0;
   
  res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
  if (res != ESP_OK) {
    return res;
  }
 
  while (true) {
    // 这里设定一个循环,轮流发送PIC0-4
    if (index==0) {
      _jpg_buf_len = pic0_size;
      _jpg_buf = (uint8_t *)pic0; 
    } else if (index==1) {
      _jpg_buf_len = pic1_size;
      _jpg_buf = (uint8_t *)pic1; 
    } else if (index==2) {
      _jpg_buf_len = pic2_size;
      _jpg_buf = (uint8_t *)pic2; 
    } else if (index==3) {
      _jpg_buf_len = pic3_size;
      _jpg_buf = (uint8_t *)pic3; 
    } 
    // 如果发送完PIC3 接来发送PIC0
    index=(index+1)%4;
 
    if (res == ESP_OK) {
      size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
      res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
    }
    if (res == ESP_OK) {
      res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
    }
    if (res == ESP_OK) {
      res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
    }
    if (res != ESP_OK) {
      break;
    }
    Serial.printf("MJPG: %uB\n",(uint32_t)(_jpg_buf_len));
  }
  return res;
}
 
void startPicServer() {
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;
 
  httpd_uri_t index_uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = stream_handler,
    .user_ctx  = NULL
  };
 
  if (httpd_start(&stream_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(stream_httpd, &index_uri);
  }
}
 
void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
 
  Serial.begin(115200);
  Serial.setDebugOutput(false);
 
  // Wi-Fi connection
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
 
  Serial.print("Camera Stream Ready! Go to: http://");
  Serial.print(WiFi.localIP());
 
  //启动
  startPicServer();
}
 
void loop() {
  delay(1);
}

7.运行之后串口监视器会输出当前 WebServer 的 IP ,将地址在浏览器中打开即可看到结果:

ESP32PICWebServer下载

Step to UEFI (260)Section Alignment(0x20) is not 4K

查看 Log 的过程中,发现在 DXE 阶段有错误报出,本文针对这个问题进行了一点研究。

首先,遇到的错误信息如下:

InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 7543E98
ProtectUefiImageCommon - 0x7543B40
  - 0x0000000007105000 - 0x0000000000005E60
InstallProtocolInterface: AA0E8BC1-DABC-46B0-A844-37B8169B2BEA 710A9C0
Loading driver 4B28E4C7-FF36-4E10-93CF-A82159E777C5
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 7543140
!!!!!!!!  InsertImageRecord - Section Alignment(0x20) is not 4K  !!!!!!!!
!!!!!!!!  Image - c:\buildbs\stable202108org\Build\OvmfX64\DEBUG_VS2015x86\X64\MdeModulePkg\Universal\ResetSystemRuntimeDxe\ResetSystemRuntimeDxe\DEBUG\ResetSystemRuntimeDxe.pdb  !!!!!!!!
Loading driver at 0x00007AE1000 EntryPoint=0x00007AE14D4 ResetSystemRuntimeDxe.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 7543418
ProtectUefiImageCommon - 0x7543140
  - 0x0000000007AE1000 - 0x00000000000041A0

接下来定位。输出错误的代码位于 \mdemodulepkg\core\dxe\misc\MemoryAttributesTable.c 中的InsertImageRecord() 函数中:

/**
  Insert image record.
 
  @param  RuntimeImage    Runtime image information
**/
VOID
InsertImageRecord (
  IN EFI_RUNTIME_IMAGE_ENTRY  *RuntimeImage
  )
……………
  //
  // Get SectionAlignment
  //
  if (Hdr.Pe32->OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
    SectionAlignment  = Hdr.Pe32->OptionalHeader.SectionAlignment;
  } else {
    SectionAlignment  = Hdr.Pe32Plus->OptionalHeader.SectionAlignment;
  }
  SetMemoryAttributesTableSectionAlignment (SectionAlignment);
  if ((SectionAlignment & (RUNTIME_PAGE_ALLOCATION_GRANULARITY - 1)) != 0) {
    DEBUG ((DEBUG_WARN, "!!!!!!!!  InsertImageRecord - Section Alignment(0x%x) is not %dK  !!!!!!!!\n",
      SectionAlignment, RUNTIME_PAGE_ALLOCATION_GRANULARITY >> 10));
    PdbPointer = PeCoffLoaderGetPdbPointer ((VOID*) (UINTN) ImageAddress);
    if (PdbPointer != NULL) {
      DEBUG ((DEBUG_WARN, "!!!!!!!!  Image - %a  !!!!!!!!\n", PdbPointer));
    }
    goto Finish;
  }
…………

在\MdePkg\Include\X64\ProcessorBind.h 有如下定义:

///
/// Page allocation granularity for x64
///
#define DEFAULT_PAGE_ALLOCATION_GRANULARITY   (0x1000)
#define RUNTIME_PAGE_ALLOCATION_GRANULARITY   (0x1000)

可以看到,SectionAlignment 来自 Hdr.Pe32Plus->OptionalHeader.SectionAlignment,使用 CFF 查看 ResetSystemRuntimeDxe.efi 这个文件,注意下图中的2个位置。上述代码首先检查ImageType(Subsystem) 是否为EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER,如果是再继续检查SectionAlignment是否为4K,如果不是 4K 对齐那么就进行报错。

ImageType的定义在\MdeModulePkg\Universal\ResetSystemRuntimeDxe\ResetSystemRuntimeDxe.inf 文件中:

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = ResetSystemRuntimeDxe
  MODULE_UNI_FILE                = ResetSystemRuntimeDxe.uni
  FILE_GUID                      = 4B28E4C7-FF36-4e10-93CF-A82159E777C5
  MODULE_TYPE                    = DXE_RUNTIME_DRIVER
  VERSION_STRING                 = 1.0
 
  ENTRY_POINT                    = InitializeResetSystem

搜索所有 MODULE_TYPE 为 DXE_RUNTIME_DRIVER 的 Module 发现在运行期都有这样的问题。

确定了问题,找到了问题点,接下来就开始分析“为什么出现不满足条件”。

检查\Build\OvmfX64\DEBUG_VS2015x86\X64\MdeModulePkg\Universal\ResetSystemRuntimeDxe\ResetSystemRuntimeDxe\Makefile 文件有下面一行:

DLINK_FLAGS = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG
(注意 /ALIGN:32 参数)

尝试修改上面为 /ALIGN:0x1000 修改之后再次编译(需要同样的环境,进入 Debug  目录直接运行 NMAKE,这种方法在之前的实验中有介绍过),生成的EFI如下:

我们需要修改EFI 的生成方式。在\Conf\tools_def.txt 可以看到默认的编译参数:

 DEBUG_VS2015x86_X64_DLINK_FLAGS  = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG
RELEASE_VS2015x86_X64_DLINK_FLAGS  = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /IGNORE:4254 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /MERGE:.rdata=.data
NOOPT_VS2015x86_X64_DLINK_FLAGS    = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG

这里我们使用的是DEBUG版,所以需要修改为下面这样(/ALIGN:0x1000)

DEBUG_VS2015x86_X64_DLINK_FLAGS  = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:0x1000 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG

修改之后重新编译OVMF,得到的ResetSystemRuntimeDxe.efi 和前面手工修改的结果相同。但是这样修改会导致编译最后一步无法通过:

Generating FVMAIN_COMPACT FV
Generating PEIFV FV
##### ['GenFv', '-a', 'c:\\buildbs\\stable202108org\\Build\\OvmfX64\\DEBUG_VS2015x86\\FV\\Ffs\\PEIFV.inf', '-o', 'c:\\buildbs\\stable202108org\\Build\\OvmfX64\\DEBUG_VS2015x86\\FV\\PEIFV.Fv', '-i', 'c:\\buildbs\\stable202108org\\Build\\OvmfX64\\DEBUG_VS2015x86\\FV\\PEIFV.inf']
Return Value = 2
GenFv: ERROR 3000: Invalid
  PE image Section-Alignment and File-Alignment do not match : c:\buildbs\stable202108org\Build\OvmfX64\DEBUG_VS2015x86\FV\Ffs\52C05B14-0B98-496c-BC3B-04B50211D680PeiCore\52C05B14-0B98-496c-BC3B-04B50211D680.ffs.
GenFv: ERROR 3000: Invalid
  Could not rebase c:\buildbs\stable202108org\Build\OvmfX64\DEBUG_VS2015x86\FV\Ffs\52C05B14-0B98-496c-BC3B-04B50211D680PeiCore\52C05B14-0B98-496c-BC3B-04B50211D680.ffs.
 
build.py...
 : error 7000: Failed to generate FV
build.py...
 : error 7000: Failed to execute command

看起来是因为 PE头中 SectionAlignment和FileAlignment 不同导致的,在【参考1】介绍了一个设置 FileAlignment 的参数。同样的,将这个参数加入到 tools_def.txt 文件中:

  DEBUG_VS2015x86_X64_DLINK_FLAGS  = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:0x1000 /FILEALIGN:0x1000 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG
(注意  /FILEALIGN:0x1000 )

再次编译可以得到OVMF.FD ,使用QEMU 运行,Log 中就没有这个错误了。

这样的修改虽然能够解决问题,但是我们并不了解 InsertImageRecord() 函数的用途,后面有机会再继续进行分析。

参考:

1.https://blog.csdn.net/bagboy_taobao_com/article/details/7295575/

Step to UEFI (261)BootMenu插入自己的Application

在QEMU 的Boot Manager/Menu 中可以看到”EFI Internal Shell” 这个选项,选中之后会跳转到其中执行 Shell.efi,这次就尝试编代码在其中插入能够启动到自己Application的选项。

首先我们研究一下这个选项是如何插入的。在\ovmfpkg\library\platformbootmanagerlib\BdsPlatform.c 的 PlatformBootManagerAfterConsole()函数中有:

//
// Perform some platform specific connect sequence
//
PlatformBdsConnectSequence ();
 
EfiBootManagerRefreshAllBootOption ();
 
//
// Register UEFI Shell
//
PlatformRegisterFvBootOption (
  &gUefiShellFileGuid, L"EFI Internal Shell", LOAD_OPTION_ACTIVE
  );
 
RemoveStaleFvFileOptions ();
SetBootOrderFromQemu ();

其中gUefiShellFileGuid 的定义在 \ShellPkg\ShellPkg.dec

# FILE_GUID as defined in ShellPkg/Application/Shell/Shell.inf
gUefiShellFileGuid              = {0x7c04a583, 0x9e3e, 0x4f1c, {0xad, 0x65, 0xe0, 0x52, 0x68, 0xd0, 0xb4, 0xd1}

也可以在\ShellPkg\Application\Shell\Shell.inf 中看到

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = Shell
  FILE_GUID                      = 7C04A583-9E3E-4f1c-AD65-E05268D0B4D1 # gUefiShellFileGuid
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain

使用工具可以看到:

接下来我们着手设计一个自己的 Application 用于验证,需要特别注意的是,这个程序不能使用 SHELL 的任何Protocol和Service,因为调用它的时候没有 Shell。代码非常简单:

1. BootTest.c 如下

#include  <Uefi.h>
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
        for (int i=0;i<1000;i++) {
                SystemTable->ConOut->OutputString(
                        SystemTable->ConOut,
                        L"www.lab-z.com\n");
        }
 
   
  return(0);
}

2. BootTest.inf如下:

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = BootTest
  FILE_GUID                      = a912f198-7f0e-4803-b908-b757b806ec89
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = UefiMain
 
#
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#
 
[Sources]
  BootTest.c
 
[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec
 
[LibraryClasses]
  UefiLib
  UefiApplicationEntryPoint
  DebugLib

EDK2 202108 已经没有 AppPkg , 所以我们直接放在 ShellPkg 中。接下来修改代码如下:

1.\OvmfPkg\OvmfPkgX64.dsc 中,插入 INF 文件

    <PcdsFixedAtBuild>
      gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0xFF
      gEfiShellPkgTokenSpaceGuid.PcdShellLibAutoInitialize|FALSE
      gEfiMdePkgTokenSpaceGuid.PcdUefiLibMaxPrintBufferSize|8000
  }
 
  ShellPkg/Application/BootTest/BootTest.inf
 
!if $(SECURE_BOOT_ENABLE) == TRUE
  SecurityPkg/VariableAuthenticated/SecureBootConfigDxe/SecureBootConfigDxe.inf
  OvmfPkg/EnrollDefaultKeys/EnrollDefaultKeys.inf
!endif

2.\OvmfPkg\OvmfPkgX64.fdf 中插入

!if $(TOOL_CHAIN_TAG) != "XCODE5"
INF  ShellPkg/DynamicCommand/TftpDynamicCommand/TftpDynamicCommand.inf
INF  ShellPkg/DynamicCommand/HttpDynamicCommand/HttpDynamicCommand.inf
INF  OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.inf
!endif
INF  ShellPkg/Application/Shell/Shell.inf
INF  ShellPkg/Application/BootTest/BootTest.inf
 
INF MdeModulePkg/Logo/LogoDxe.inf
 
#
# Network modules
#
!if $(E1000_ENABLE)
  FILE DRIVER = 5D695E11-9B3F-4b83-B25F-4A8D5D69BE07 {
    SECTION PE32 = Intel3.5/EFIX64/E3522X2.EFI
  }

3. \OvmfPkg\Library\PlatformBootManagerLib\BdsPlatform.c 文件做插入 BootMenu的动作

定义一个 GUID,来自 BootTest.inf 中的FILE_GUID        

EFI_GUID  gBootTest = {
    0xa912f198, 0x7f0e, 0x4803,
    { 0xb9, 0x08, 0xb7, 0x57, 0xb8, 0x06, 0xec, 0x89 } };

插入动作:

//
// Perform some platform specific connect sequence
//
PlatformBdsConnectSequence ();
 
EfiBootManagerRefreshAllBootOption ();
DEBUG((DEBUG_INFO, "LABZTest_Start\n"));
//
// Register a Test Application
//
PlatformRegisterFvBootOption(
    &gBootTest, L"LABZTest", LOAD_OPTION_ACTIVE
);
DEBUG((DEBUG_INFO, "LABZTest_End\n"));
//
// Register UEFI Shell
//
PlatformRegisterFvBootOption (
  &gUefiShellFileGuid, L"EFI Internal Shell", LOAD_OPTION_ACTIVE
  );

重新编译之后,可以用工具看到我们插入的 Application:

运行结果:

可以看到 BootMenu 中出现了我们设定的启动选项。

本文提到的用于测试的 BootTest:

BootTest下载