UEFI News and Commentary

Monday, October 22, 2012

HOW TO: Disassembling the UEFI HII Database (Part 2)

In this series of articles, we are examining the UEFI HII database, a repository for all sorts of user-interface resources used by UEFI applications, including forms, strings, fonts, images and animations. UEFI firmware also provides a built-in API for displaying the forms and interactin the the user called the Form Browser2 protocol.

In part 1, we looked at the top-most layer of our disassembler, which retrieved all of the installed resources from the database. The resources are binary-encoded into variable-length data structures called Packages and then grouped together into Package Lists. Part 1 handled the Package Lists and now we will take a look at the Package structure and parsing code.

UEFI HII Packages

The encoding for UEFI HII Packages is deceptively simple: there is a header and then a body. The header describes what type of package it is and its size, in bytes.

typedef struct {
  UINT32  Length:24;
  UINT32  Type:8;
// UINT8  Data[...];
} EFI_HII_PACKAGE_HEADER;



So, likewise, the basic data structure for holding UEFI HII Packages is quite simple:

#define UEFI_HII_PACKAGE_SIGNATURE 0x5F504855 // "UHP_"
typedef struct _UEFI_HII_PACKAGE_P {
  SYS_OBJ Obj;                         


  UINT8 Type;                           // See EFI_HII_PACKAGE_x
  UEFI_HII_PACKAGE_LIST_P *PkgList;     // package list parent.
  SYS_OBJ *PkgData;                     // ptr to data specific to package type.
} UEFI_HII_PACKAGE_P;


typedef UEFI_HII_PACKAGE_P UefiHiiPackageP;
The Obj member provides the basic object-oriented functionality of the SysLib, including function pointers to initialization, emptying, copying and validity-checking functions.

The Type member matches the Type member of the IFR Package header. There are currently 8 types of packages as well as a GUIDed type for expansion and 32 reserved types for system vendors.

The PkgList member points to the structure that represents the package list which contains this package. If the package was parsed independently, as would happen when disassembling a file  containing only package binary-encoded data, this would be NULL.

The PkgData member points to an object that represents the contents of the package. The exact object depends on the Type member. So for forms, this is a pointer to a UEFI_HII_FORM_PACKAGE_P. By using the object pointer, we can handle copying and memory management without knowing the exact structure.
 

UefiHiiParsePkg()

In part 1, we saw that UefiHiiParsePkgList() walks through all packages in a package list and calls this function to handle actually parsing the package data and converting it into our structure. Essentially this is a factory function which creates a package object. It then uses the Type value to call another parsing function which processes the package body and records the pointer to the resulting data object.

EFI_STATUS
UefiHiiParsePkg (
  IN EFI_HII_PACKAGE_HEADER *Pkg,
  OUT UEFI_HII_PACKAGE *PHandle
  )
{
  EFI_STATUS s;
  UINT8 *PkgData;
  UINT32 PkgDataSize;
  UEFI_HII_PACKAGE_P *PkgP;

 
  if (Pkg == NULL || PHandle == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  if (Pkg->Length < sizeof (EFI_HII_PACKAGE_HEADER)) {
    return EFI_INVALID_PARAMETER;
  }

 
  ...
  PkgP = SysNew (UefiHiiPackageP);
  if (PkgP == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  *PHandle = (UEFI_HII_PACKAGE)PkgP;

 
  UefiHiiSetPkgType (*PHandle, (UINT8) Pkg->Type);
 
  PkgData = (UINT8 *)(Pkg + 1);
  PkgDataSize = Pkg->Length - sizeof (EFI_HII_PACKAGE_HEADER);
  switch (Pkg->Type) {

 
  case EFI_HII_PACKAGE_FORMS:
    s = UefiHiiParseFormPkg (
          (EFI_IFR_OP_HEADER *)PkgData,
          PkgDataSize,
          PkgP,
          &PkgP->PkgData
          );
    break;

 
  case EFI_HII_PACKAGE_STRINGS:
    s = UefiHiiParseStringPkg (
          (EFI_HII_STRING_PACKAGE_HDR *)Pkg,
          PkgDataSize,
          PkgP,
          &PkgP->PkgData
          );
    break;

 
  case EFI_HII_PACKAGE_SIMPLE_FONTS:
    s = UefiHiiParseSimpleFontPkg (
          (EFI_HII_SIMPLE_FONT_PACKAGE_HDR *)Pkg,
          PkgDataSize,
          PkgP,
          &PkgP->PkgData
          );
    break;


  case EFI_HII_PACKAGE_ANIMATIONS:
    s = UefiHiiParseAnimationPkg (
          (EFI_HII_ANIMATION_PACKAGE_HDR *)Pkg,
          PkgDataSize,
          PkgP,
          &PkgP->PkgData
          );
    break;


  case EFI_HII_PACKAGE_IMAGES:
    s = UefiHiiParseImagePkg (
          (EFI_HII_IMAGE_PACKAGE_HDR *)Pkg,
          PkgDataSize,
          PkgP,
          &PkgP->PkgData
          );
    break;

  case EFI_HII_PACKAGE_FONTS: 
    s = UefiHiiParseFontPkg (
         (EFI_HII_FONT_PACKAGE_HDR *)Pkg,
         PkgDataSize,
         PkgP,
         &PkgP->PkgData
         );

    break;
  case EFI_HII_PACKAGE_DEVICE_PATH:
    s = EFI_SUCCESS;
    break;

 
  case EFI_HII_PACKAGE_END:
    s = EFI_SUCCESS;
    break;

 
  default:
    s = EFI_INVALID_PARAMETER;
    break;
  }

 
  if (EFI_ERROR(s)) {
    SysDelete (PkgP);
  }
  return s;
}

Conclusion

I have described the actual format of string, font and image packages before, but never the forms. If you look carefully above, you can see that the FORMS package parsing function is slightly different than the others. The string, font, image and animation packages all start with fixed headers which are essentially supersets of the package header. The forms package does not. It uses the standard package header but then contains a series of variable length "opcodes". And what is IFR anyway? More on this next time, where we will go into depth into how forms are encoded.

No comments: