UEFI News and Commentary

Friday, October 19, 2012

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

This article is the first in a series that talks about UEFI's Human Interface Infrastructure (HII) database. The HII Database is the repository for all sorts of user-interface related information in a platform, including forms, strings, bitmaps, fonts and keyboard layouts. Within UEFI, these resources are used primarily to present configuration information to a user. One example is the setup application, common to most PC BIOS firmware implementations. But that is not all. UEFI also uses these resources to implement the Driver Health and User Identification infrastructure. And, of course, our applications can use these.

I'm going to show you the HII database by using the source code to a tool (hiidd) that parses the HII database contents into structures and then displays the information. This tool is not merely a disassembler, but also a foundation for further UEFI tools. It uses some of the SysLib that I described previously.

main()

So let's jump in to main:

int

EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  int ret;

 
  ret = 0;
  InitCmdLine();

  ret = ParseCmdLine (Argc, Argv);
  if (ret != 0) {
    goto error;
  }

  if (gPackageListFromHiiDatabase) {

    verbosePhase("Read package lists from HII database.\n");
    ret = ReadHiiDatabase();
  } else if (gPackageListFromFiles) {
    verbosePhase("Read package lists from files.\n");
    ret = ReadPackageListsFromFiles();
  } else if (gPackageFromFiles) {
    verbosePhase("Read pacakges from files.\n");
    ret = ReadPackagesFromFiles();
  }

  DumpPackageLists();
error:

  ShutCmdLine();
  return ret;
}
 

Pretty standard. We parse the command-line, read the HII database either from UEFI or from a file and then dump it out. The tool supports the following command-line options: 
  • -hiidb - Read the package lists from the HII Database on the machine.
  • -packagelist file-name - Read the package list from the file file-name. The file has the package list format as described in the UEFI specification.
  • -package file-name - Read in an individual package from the file file-name. The file has the package fromat as described in the UEFI specification.
  • -verbose, -v1, -v2, -v3 - Turn on the level of informational output provided by the tool. 1 = phases, 2 = actions, 3 = the kitchen sink. There are three functions: verbosePhase(), verboseAction() and verboseInfo() which will only display the string if the verbosity level is set to the corresponding level.

ReadHiiDatabase()

 Now let's take a look at the main HII Database parsing code:

int
ReadHiiDatabase (void)
{
  EFI_STATUS s;
  EFI_HII_PACKAGE_LIST_HEADER *PackageLists;
  EFI_HII_PACKAGE_LIST_HEADER *PackageList;
  UINTN PackageListSize;
  int PackageListIndex;
  SYS_STRA guid;
  int ret;
  UEFI_HII_PACKAGE_LIST PLHandle;


  SysStrAInit (&guid);

  //
  // All of the package lists are exported into one big buffer.
  //
  s = UefiHiiExportPackageListsA (
        NULL,
        &PackageLists,
        &PackageListSize
        );
  if (EFI_ERROR (s)) {
    ret = 1;
    goto exit;
  }


  PackageListIndex = 0;
  PackageList = PackageLists;
  while (PackageListSize > sizeof (EFI_HII_PACKAGE_LIST_HEADER)) {

    verboseInfo ("Package List #%d\n",
                 PackageListIndex
                 );
    verboseInfo ("  Offset: 0x%08x\n",
                 (UINT32)((UINT8 *)PackageList - (UINT8 *)PackageLists));

    SysStrAFromGuid (&guid, &PackageList->PackageListGuid);
    verboseInfo ("  GUID:   %s\n", SysStrAGetData(&guid));

    if (PackageList->PackageLength > PackageListSize) {
      printf ("error: package list extends beyond end of buffer. "

              "%d bytes in package list. %d bytes in buffer.\n",
              PackageList->PackageLength,
              PackageListSize
              );
      ret = 1;
      goto exit;
    }

    s = UefiHiiParsePkgList (
          PackageList,
          &PLHandle
          );
    if (EFI_ERROR(s)) {
      printf ("error: unable to parse package list.\n");
      ret = 1;
      goto exit;
    }

    if (!SysArrayAppend (&gPackageLists, &PLHandle)) {
      printf ("fatal: out of memory.\n");
      exit (1);
    }


    PackageListIndex++;
    PackageListSize -= PackageList->PackageLength;
    PackageList =

      (EFI_HII_PACKAGE_LIST_HEADER *)
      ((UINT8*)PackageList + PackageList->PackageLength);
  }

  if (PackageListSize != 0) {
    printf("error: HII database array of package lists did not "

           "end on an even boundary.\n");
    ret = 1;
    goto exit;
  }

  ret = 0;
exit:
  SysStrAEmpty (&guid);
  return ret;
}

In this section, we use the library function UefiHiiExportPackageListsA to grab all of the HII package lists from the database into one big buffer. More details on that function later. Then the function UefiHiiParsePkgList() runs through the data, creates a package list handle for each package list found and adds that handle to an array. Each handle is associated with a single package list.

I use a handle here to abstract the relationship between the application and the actual data structures inside the parsing library. This allows me to refactor the code later without messing up the apps that depend on it.

Package Lists are really just big containers for zero or more Packages, identified by a GUID. The GUID is just an identifier that allows the user to uniquely identify a package list in the database. Packages, in turn, are containers for all sorts of interesting things, like Forms, Strings, Fonts, Images, Animations, Keyboard Layouts or OEM data. I've talked about some of these before in the early days of my blog.

UefiHiiParsePkgList()

Inside the UefiHiiParsePkgList(), each handle is actually a pointer to a private data structure constructed by the library:

typedef struct _UEFI_HII_PACKAGE_LIST_P {
  SYS_OBJ Obj;                     // standard object structure. must be first.


  EFI_GUID Id;                     // package list identifier.
  SYS_LIST_O Packages;             // list of packages in package list.
} UEFI_HII_PACKAGE_LIST_P;

#define UEFI_HII_PACKAGE_LIST_SIGNATURE 0x4C504855 // "UHPL"

typedef UEFI_HII_PACKAGE_LIST_P UefiHiiPackageListP;
This structure uses the same object model (SysObj) from my library, which means every instance contains function pointers to instances of Init(), Empty(), Copy(), IsValid() and Dump() as well as a signature so that we can validate object pointers. The member 'Packages' is an Object List container where each list entry is an Object. So when the List container is emptied, all the memory will be freed automatically.

So, each package list is parsed by this function, and a single object of type UEFI_HII_PACKAGE_LIST_P is created to represent it. The pointer is typecast into the package list handle type and returned. 

Here's the actual code:

EFI_STATUS
UefiHiiParsePkgList (
  IN EFI_HII_PACKAGE_LIST_HEADER *PkgList,
  OUT UEFI_HII_PACKAGE_LIST *PLHandle
  )
{
  EFI_STATUS s;
  EFI_HII_PACKAGE_HEADER *Pkg;
  UINT32 PkgListSize;
  UEFI_HII_PACKAGE PHandle;
  UINT32 PkgIndex;


  if (PkgList == NULL || PLHandle == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  if (PkgList->PackageLength < sizeof (EFI_HII_PACKAGE_LIST_HEADER)) {
    return EFI_INVALID_PARAMETER;
  }


  ...

  *PLHandle = (UEFI_HII_PACKAGE_LIST) SysNew (UefiHiiPackageListP);
  if (*PLHandle == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  memcpy (
   &((UEFI_HII_PACKAGE_LIST_P *)(*PLHandle))->Id,
   &PkgList->PackageListGuid,
   sizeof (EFI_GUID)
   );

  PkgIndex = 0;
  PkgListSize = PkgList->PackageLength - sizeof (EFI_HII_PACKAGE_LIST_HEADER);
  Pkg = (EFI_HII_PACKAGE_HEADER *)(PkgList + 1);
  while (PkgListSize >= sizeof (EFI_HII_PACKAGE_HEADER)) {
    ...
   

    if (Pkg->Length > PkgListSize) {
      return EFI_VOLUME_CORRUPTED;
    }

    s = UefiHiiParsePkg (
          Pkg,
          &PHandle
          );
    if (EFI_ERROR(s)) {
      return s;
    }

   
    s = UefiHiiSetPkgPkgList (PHandle, *PLHandle);
    if (EFI_ERROR (s)) {
      return s;
    }


    PkgIndex++;
    PkgListSize -= Pkg->Length;
    Pkg = (EFI_HII_PACKAGE_HEADER *)((UINT8 *)Pkg + Pkg->Length);
  }

  if (PkgListSize != 0) {
    return EFI_VOLUME_CORRUPTED;
  }

  return EFI_SUCCESS;
}

After the package list header is the body of the package list, which consists of a series of variable-length package structures. Each fo the package structures contains is own length, so it is trivial to find the first one, parse it, mark it as belong to this package list (using UefiHiiSetPkgPkgList()) and then advancing the pointer to the next one. There is some error checking code to make sure we don't advance past the end of the buffer and that the end of the last package aligns exactly with the end of the package list body. The main work of parsing the individual packages is done using the function UefiHiiParsePkg().

Conclusion

At this point, we're just scratching the surface of the HII Database. Next week, we'll dive a little deeper into parsing the packages, introduce the UEFI_HII_PACKAGE_P object, and peek into what it means to parse the forms (IFR).

Extra Stuff: UefiHiiExportPackageListsA()

You don't need to read this part unless you are curious how my SysLib's HII database functions actually relate to the functions described in chapter 30 of the UEFI specification. This time, we talked about the function UefiHiiExportPackageListsA(), which acts as a handy wrapper for the HII Database function ExportPackageLists(). It takes care of the memory allocate details for you.

EFI_STATUS
UefiHiiExportPackageListsA (
  IN EFI_HII_HANDLE Handle,
  OUT EFI_HII_PACKAGE_LIST_HEADER **PackageList,
  OUT UINTN *PackageListSize
  )
{
  EFI_STATUS s;


  if (EFI_ERROR (_UefiHiiDatabaseProtocol())) {
    return EFI_NOT_FOUND;
  }


  if (PackageList == NULL || PackageListSize == NULL) {
    return EFI_INVALID_PARAMETER;
  }


  *PackageListSize = 0;
  s = gHiiDb->ExportPackageLists (
                gHiiDb,
                Handle,
                PackageListSize,
                *PackageList
                );

  if (!EFI_ERROR (s)) {
    return s;
  } else if (s != EFI_BUFFER_TOO_SMALL) {
    return s;
  }


  *PackageList = (EFI_HII_PACKAGE_LIST_HEADER *)malloc (*PackageListSize);
  if (*PackageList == NULL) {
    return EFI_BUFFER_TOO_SMALL;
  }

  s = gHiiDb->ExportPackageLists (
                gHiiDb,
                Handle,
                PackageListSize,
                *PackageList
                );
  return s;
}


The function ExportPackageLists() is from the HII Database protocol and takes all of the package lists from the HII Database and puts them in a big buffer. This is done with two calls. The first call returns how much size is actually needed. The second call actually gets the data.
 

No comments: