Step to UEFI (205)NT32下动态查看 Application(下)

前面提到了使用 Image Size 作为CPU Debug Break的触发条件,相比使用Image 的Size作为触发条件,使用 Image Name 作为触发条件要方便很多,每次只需要重新编译Application 然后运行之即可,因此这里研究如何实现。

首先要解决的是哪里取得Image Name。通过观察可以得知当我们运行 NT32 模拟器时,每次调用 EFI Application时候会在 Debug 窗口显示加载的Image 名称:

这个显示的功能位于 \MdeModulePkg\Core\Dxe\Image\Image.c如下函数中:

EFI_STATUS
CoreLoadPeImage (
  IN BOOLEAN                     BootPolicy,
  IN VOID                        *Pe32Handle,
  IN LOADED_IMAGE_PRIVATE_DATA   *Image,
  IN EFI_PHYSICAL_ADDRESS        DstBuffer    OPTIONAL,
  OUT EFI_PHYSICAL_ADDRESS       *EntryPoint  OPTIONAL,
  IN  UINT32                     Attribute
  ) 

具体代码如下:

    DEBUG ((DEBUG_INFO | DEBUG_LOAD,
           "Loading driver at 0x%11p EntryPoint=0x%11p ",
           (VOID *)(UINTN) Image->ImageContext.ImageAddress,
           FUNCTION_ENTRY_POINT (Image->ImageContext.EntryPoint)));

    //
    // Print Module Name by Pdb file path.
    // Windows and Unix style file path are all trimmed correctly.
    //
    if (Image->ImageContext.PdbPointer != NULL) {
      StartIndex = 0;
      for (Index = 0; Image->ImageContext.PdbPointer[Index] != 0; Index++) {
        if ((Image->ImageContext.PdbPointer[Index] == '\\') || (Image->ImageContext.PdbPointer[Index] == '/')) {
          StartIndex = Index + 1;
        }
      }
      //
      // Copy the PDB file name to our temporary string, and replace .pdb with .efi
      // The PDB file name is limited in the range of 0~255.
      // If the length is bigger than 255, trim the redudant characters to avoid overflow in array boundary.
      //
      for (Index = 0; Index < sizeof (EfiFileName) - 4; Index++) {
        EfiFileName[Index] = Image->ImageContext.PdbPointer[Index + StartIndex];
        if (EfiFileName[Index] == 0) {
          EfiFileName[Index] = '.';
        }
        if (EfiFileName[Index] == '.') {
          EfiFileName[Index + 1] = 'e';
          EfiFileName[Index + 2] = 'f';
          EfiFileName[Index + 3] = 'i';
          EfiFileName[Index + 4] = 0;
          break;
        }
      }

      if (Index == sizeof (EfiFileName) - 4) {
        EfiFileName[Index] = 0;
      }
      DEBUG ((DEBUG_INFO | DEBUG_LOAD, "%a", EfiFileName)); // &Image->ImageContext.PdbPointer[StartIndex]));
    }
    DEBUG ((DEBUG_INFO | DEBUG_LOAD, "\n"));

  DEBUG_CODE_END ();

简单的说,有些 Application包含了 PDB 信息的EFI 文件可以从Image中获得文件名称。

第一个关键位置在于 Image->ImageContext.PdbPointer,其中的ImageContext 定义如下:

   /// PeCoffLoader ImageContext
  PE_COFF_LOADER_IMAGE_CONTEXT  ImageContext;
  PE_COFF_LOADER_IMAGE_CONTEXT  结构体定义在 \MdePkg\Include\Library\PeCoffLib.h 文件中:
///
/// The context structure used while PE/COFF image is being loaded and relocated.
///
typedef struct {
…..省略…..
  ///
  /// Set by PeCoffLoaderLoadImage() to point to the PDB entry contained in the CodeView area.
  /// The PdbPointer points to the filename of the PDB file used for source-level debug of
  /// the image by a debugger.
  ///
  CHAR8                             *PdbPointer; 
…..省略…..
} PE_COFF_LOADER_IMAGE_CONTEXT;

在\MdePkg\Library\BasePeCoffGetEntryPointLib\PeCoffGetEntryPoint.c 有定义如下函数用来取得这个指针:

/**
  Returns a pointer to the PDB file name for a PE/COFF image that has been
  loaded into system memory with the PE/COFF Loader Library functions.

  Returns the PDB file name for the PE/COFF image specified by Pe32Data.  If
  the PE/COFF image specified by Pe32Data is not a valid, then NULL is
  returned.  If the PE/COFF image specified by Pe32Data does not contain a
  debug directory entry, then NULL is returned.  If the debug directory entry
  in the PE/COFF image specified by Pe32Data does not contain a PDB file name,
  then NULL is returned.
  If Pe32Data is NULL, then ASSERT().

  @param  Pe32Data   The pointer to the PE/COFF image that is loaded in system
                     memory.

  @return The PDB file name for the PE/COFF image specified by Pe32Data or NULL
          if it cannot be retrieved.

**/
VOID *
EFIAPI
PeCoffLoaderGetPdbPointer (
  IN VOID  *Pe32Data
  )

    if (Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
      //
      // Use PE32 offset get Debug Directory Entry
      //
      NumberOfRvaAndSizes = Hdr.Pe32->OptionalHeader.NumberOfRvaAndSizes;
      DirectoryEntry = (EFI_IMAGE_DATA_DIRECTORY *)&(Hdr.Pe32->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG]);
      DebugEntry     = (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY *) ((UINTN) Pe32Data + DirectoryEntry->VirtualAddress);

使用 SFF 分析,上面的位置是下面绿色框中 Data Directories[x] 中的EFI_IMAGE_DIRECTORY_ENTRY_DEBUG (该值为6),即右侧红色框中的值。可以看到在文件中的0x1AD0位置,大小为0x54。

继续使用 SFF 可以直接查看 Debug Directory 的内容:

直接查看 0x1B24 位置就可以看到信息:

PeCoffLoaderGetPdbPointer函数对应的代码上有一个扫描的动作,最终确定 PDB File Name:

  //
  // Scan the directory to find the debug entry.
  //
  for (DirCount = 0; DirCount < DirectoryEntry->Size; DirCount += sizeof (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY), DebugEntry++) {
    if (DebugEntry->Type == EFI_IMAGE_DEBUG_TYPE_CODEVIEW) {
      if (DebugEntry->SizeOfData > 0) {
        CodeViewEntryPointer = (VOID *) ((UINTN) DebugEntry->RVA + ((UINTN)Pe32Data) + (UINTN)TEImageAdjust);
        switch (* (UINT32 *) CodeViewEntryPointer) {
        case CODEVIEW_SIGNATURE_NB10:
          return (VOID *) ((CHAR8 *)CodeViewEntryPointer + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY));
        case CODEVIEW_SIGNATURE_RSDS:
          return (VOID *) ((CHAR8 *)CodeViewEntryPointer + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_RSDS_ENTRY));
        case CODEVIEW_SIGNATURE_MTOC:
          return (VOID *) ((CHAR8 *)CodeViewEntryPointer + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_MTOC_ENTRY));
        default:
          break;
        }
      }
    }
  }

根据上面的代码,最终代码如下,就是根据上面的代码取出PDB文件名,然后通过比较确定加载的Image是否触发BreakPoint。

EFI_STATUS
EFIAPI
CoreStartImage (
  IN EFI_HANDLE  ImageHandle,
  OUT UINTN      *ExitDataSize,
  OUT CHAR16     **ExitData  OPTIONAL
  )
……省略……
  SetJumpFlag = SetJump (Image->JumpContext);
  //
  // The initial call to SetJump() must always return 0.
  // Subsequent calls to LongJump() cause a non-zero value to be returned by SetJump().
  //
  if (SetJumpFlag == 0) {
    RegisterMemoryProfileImage (Image, (Image->ImageContext.ImageType == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION ? EFI_FV_FILETYPE_APPLICATION : EFI_FV_FILETYPE_DRIVER));
    //LABZDEBUG_Start
                UINTN Index;
                UINTN StartIndex;
                CHAR8 EfiFileName[256];
            //
            // Print Module Name by Pdb file path.
            // Windows and Unix style file path are all trimmed correctly.
            //
            if (Image->ImageContext.PdbPointer != NULL) {
              StartIndex = 0;
              for (Index = 0; Image->ImageContext.PdbPointer[Index] != 0; Index++) {
                if ((Image->ImageContext.PdbPointer[Index] == '\\') || (Image->ImageContext.PdbPointer[Index] == '/')) {
                  StartIndex = Index + 1;
                }
              }
              //
              // Copy the PDB file name to our temporary string, and replace .pdb with .efi
              // The PDB file name is limited in the range of 0~255.
              // If the length is bigger than 255, trim the redudant characters to avoid overflow in array boundary.
              //
              for (Index = 0; Index < sizeof (EfiFileName) - 4; Index++) {
                EfiFileName[Index] = Image->ImageContext.PdbPointer[Index + StartIndex];
                if (EfiFileName[Index] == 0) {
                  EfiFileName[Index] = '.';
                }
                if (EfiFileName[Index] == '.') {
                  EfiFileName[Index + 1] = 'e';
                  EfiFileName[Index + 2] = 'f';
                  EfiFileName[Index + 3] = 'i';
                  EfiFileName[Index + 4] = 0;
                  break;
                }
              }

              if (Index == sizeof (EfiFileName) - 4) {
                EfiFileName[Index] = 0;
              }
              DEBUG ((DEBUG_INFO , "%a\n", EfiFileName)); 
              if (AsciiStrCmp(EfiFileName,"Hello.efi")==0) {
                   CpuBreakpoint();
              }
            }
    //LABZDEBUG_End
    //
    // Call the image's entry point
    //
    Image->Started = TRUE;
Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);
……省略……

同样的方法可以用于判定当前的Image是什么文件上,简单的说就是向上,找到“MZ”头文件,然后再去查找 PDB 的信息从中确认文件名称。这样的方法对于使用 WinDBG/DCI 调试Windows 驱动同样有效。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注