前面提到了使用 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 驱动同样有效。