前面提到了使用 Image Size 作为CPU Debug Break的触发条件,相比使用Image 的Size作为触发条件,使用 Image Name 作为触发条件要方便很多,每次只需要重新编译Application 然后运行之即可,因此这里研究如何实现。
首先要解决的是哪里取得Image Name。通过观察可以得知当我们运行 NT32 模拟器时,每次调用 EFI Application时候会在 Debug 窗口显示加载的Image 名称:

这个显示的功能位于 \MdeModulePkg\Core\Dxe\Image\Image.c如下函数中:
1 2 3 4 5 6 7 8 9 | 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 ) |
具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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 定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /// 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 有定义如下函数用来取得这个指针:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /** 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // // 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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | 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 驱动同样有效。