Debugging Uniquely Identifying a Module's Build

It is common practice to embed a version resource (VS_VERSIONINFO) into PE images such as DLL and EXE files. While this resource mainly serves informational purposes, the version information is occasionaly used to perform certain checks, such as verifying the module’s suitability for a particular purpose.

Under certain circumstances, however, this versioning information may be too imprecise: Versions are not necessarily incremented after each build, so it is possible that two copies of a module carry the same versioning information, yet differ significantly in their implementation. In such situations, identifying the actual build of the module might become neccessary.

The most common, but by no means the only situation in which this applies in practice concerns debugging – to identify the PDB file exactly matching a given module, the debugger must be able to recognize the specific build of a module. It thus does not come as a surprise that all images for which debugging information has been generated contain a dedicated identifier for this purpose: The CodeView signature GUID.

Summarizing what Oleg Starodumov has covered in more detail, cl, when directed to generate a PDB file, implicitly creates this GUID and, along with the path to the PDB file, embeds this data into the PE image. For current versions, the relevant structure used to encode this information is CV_INFO_PDB70, which seems to have been documented once, but not any more:

    typedef struct _CV_INFO_PDB70
    {
      ULONG CvSignature;
      GUID Signature;
      ULONG Age;
      UCHAR PdbFileName[ ANYSIZE_ARRAY ];
    } CV_INFO_PDB70, *PCV_INFO_PDB70;
    

In order to be able to locate the structure within the PE image, a directory entry of type IMAGE_DEBUG_TYPE_CODEVIEW is written to the image’s debug directory. The following code listing demonstrates how to obtain the signature GUID of an image:

    #define PtrFromRva( base, rva ) ( ( ( PUCHAR ) base ) + rva )
    
    static PIMAGE_DATA_DIRECTORY GetDebugDataDirectory(
      __in ULONG_PTR LoadAddress
      )
    {
      PIMAGE_DOS_HEADER DosHeader = 
        ( PIMAGE_DOS_HEADER ) ( PVOID ) LoadAddress;
      PIMAGE_NT_HEADERS NtHeader = ( PIMAGE_NT_HEADERS ) 
        PtrFromRva( DosHeader, DosHeader->e_lfanew );
      ASSERT ( IMAGE_NT_SIGNATURE == NtHeader->Signature );
    
      return &NtHeader->OptionalHeader.DataDirectory
          [ IMAGE_DIRECTORY_ENTRY_DEBUG ];
    }
    
    NTSTATUS GetDebugGuid(
      __in ULONG_PTR ModuleBaseAddress,
      __out GUID *Guid
      )
    {
      PIMAGE_DATA_DIRECTORY DebugDataDirectory;
      PIMAGE_DEBUG_DIRECTORY DebugHeaders;
      ULONG Index;
      ULONG NumberOfDebugDirs;
      ULONG_PTR ModuleBaseAddress;
      NTSTATUS Status;
    
      DebugDataDirectory  = DebugDataDirectory( ModuleBaseAddress );
      DebugHeaders    = ( PIMAGE_DEBUG_DIRECTORY ) PtrFromRva( 
        ModuleBaseAddress, 
        DebugDataDirectory->VirtualAddress );
    
      ASSERT( ( DebugDataDirectory->Size % sizeof( IMAGE_DEBUG_DIRECTORY ) ) == 0 );
      NumberOfDebugDirs = DebugDataDirectory->Size / sizeof( IMAGE_DEBUG_DIRECTORY );
    
      //
      // Lookup CodeView record.
      //
      for ( Index = 0; Index < NumberOfDebugDirs; Index++ )
      {
        PCV_INFO_PDB70 CvInfo;
        if ( DebugHeaders[ Index ].Type != IMAGE_DEBUG_TYPE_CODEVIEW )
        {
          continue;
        }
    
        CvInfo = ( PCV_INFO_PDB70 ) PtrFromRva( 
          ModuleBaseAddress, 
          DebugHeaders[ Index ].AddressOfRawData );
    
        if ( CvInfo->CvSignature != 'SDSR' )
        {
          //
          // Weird, old PDB format maybe.
          //
          return STATUS_xxx_UNRECOGNIZED_CV_HEADER;
        }
    
      *Guid = CvInfo->Signature;
        return STATUS_SUCCESS;  
      }
    
      return STATUS_xxx_CV_GUID_LOOKUP_FAILED;
    }
    

Any opinions expressed on this blog are Johannes' own. Refer to the respective vendor’s product documentation for authoritative information.
« Back to home