Hex-Rays' blog

Igor’s Tip of the Week #119: Force call type – Hex Rays

Written by Igor Skochinsky | Dec 15, 2022

When dealing with compile binary code, the decompiler lacks information present in the source code, such as function prototypes and so must guess it or rely on the information provided by the user (where its interactive features come handy).

One especially tricky situation is indirect calls: without exact information about the destination of the call, the decompiler can only try to analyze registers or stack slots initialized before the call and try to deduce the potential function prototype this way. For example, check this snippet from a UEFI module:

For several indirect calls involving qword_21D40, the decompiler had to guess the arguments and add casts.

If we analyze the module from the entry point, we can find the place where the variable is initialized and figure out that it is, in fact, the standard UEFI global variable gBS of the type EFI_BOOT_SERVICES *:

EFI_STATUS __fastcall UefiBootServicesTableLibConstructor(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
  gImageHandle = ImageHandle;
  if ( DebugAssertEnabled() && !gImageHandle )
    DebugAssert(
      "u:\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
      0x33ui64,
      "gImageHandle != ((void *) 0)");
  gST = SystemTable;
  if ( DebugAssertEnabled() && !gST )
    DebugAssert(
      "u:\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
      0x39ui64,
      "gST != ((void *) 0)");
  // gBS was qword_21D40	  
  gBS = SystemTable->BootServices;
  if ( DebugAssertEnabled() && !gBS )
    DebugAssert(
      "u:\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
      0x3Fui64,
      "gBS != ((void *) 0)");
  return 0i64;
}

After renaming and changing the type of the global variable, the original function is slightly improved thanks to the type information from the standard UEFI type library:

Even though the decompiler now has prototypes of function pointers such as LocateDevicePath (shown in the pop-up) or FreePool, it has to add casts because the arguments which are passed to the calls do not match the prototype. To tell the decompiler to rely on the type information instead of guessing the arguments, use the command Force call type from the context menu:

When running the command on the indirect calls, the decompiler also uses the type information to update the types of the arguments (except those already set by the user), making the pseudocode much cleaner:

See also: Hex-Rays interactive operation: Force call type