/// <summary> /// Looks up the debug signature information in the EXE. Returns true and sets the parameters if it is found. /// If 'first' is true then the first entry is returned, otherwise (by default) the last entry is used /// (this is what debuggers do today). Thus NGEN images put the IL PDB last (which means debuggers /// pick up that one), but we can set it to 'first' if we want the NGEN PDB. /// </summary> public bool GetPdbSignature(out string pdbName, out Guid pdbGuid, out int pdbAge, bool first = false) { pdbName = null; pdbGuid = Guid.Empty; pdbAge = 0; bool ret = false; if (Header == null) { return(false); } if (Header.DebugDirectory.VirtualAddress != 0) { PEBuffer buff = AllocBuff(); IMAGE_DEBUG_DIRECTORY *debugEntries = (IMAGE_DEBUG_DIRECTORY *)FetchRVA((int)Header.DebugDirectory.VirtualAddress, (int)Header.DebugDirectory.Size, buff); if (Header.DebugDirectory.Size % sizeof(IMAGE_DEBUG_DIRECTORY) != 0) { return(false); } int debugCount = (int)Header.DebugDirectory.Size / sizeof(IMAGE_DEBUG_DIRECTORY); for (int i = 0; i < debugCount; i++) { if (debugEntries[i].Type == IMAGE_DEBUG_TYPE.CODEVIEW) { PEBuffer stringBuff = AllocBuff(); int ptr = _virt ? debugEntries[i].AddressOfRawData : debugEntries[i].PointerToRawData; CV_INFO_PDB70 *info = (CV_INFO_PDB70 *)stringBuff.Fetch(ptr, debugEntries[i].SizeOfData); if (info->CvSignature == CV_INFO_PDB70.PDB70CvSignature) { // If there are several this picks the last one. pdbGuid = info->Signature; pdbAge = info->Age; pdbName = info->PdbFileName; ret = true; if (first) { break; } } FreeBuff(stringBuff); } } FreeBuff(buff); } return(ret); }
/// <summary> /// Simple PE Parser, That Extracts The PDB Location From a PE File, which also checks for PE File Validity. /// </summary> /// <param name="path"></param> public PEFile(string path) { if (String.IsNullOrEmpty(path)) { throw new ArgumentNullException("path"); } FileInfo peContainer = new FileInfo(path); if (!peContainer.Exists) { throw new FileNotFoundException(path); } m_fileInfo = peContainer; try { m_fileVersionInfo = FileVersionInfo.GetVersionInfo(path); } catch (Exception exc) { Debug.WriteLine(exc.ToString()); m_fileVersionInfo = null; } using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)) { using (BinaryReader br = new BinaryReader(fs)) { // Find the address of the PE Header try { br.BaseStream.Seek(PESignatureOffsetLoc, SeekOrigin.Begin); // BugFix For Bug Number 677 // After the MS DOS stub, at the file offset specified at offset 0x3c, // We must read 4 bytes to located the position of the PE Header In The Executable File // is a 4-byte signature that identifies the file as a PE format image file. // This signature is “PE\0\0” (the letters “P” and “E” followed by two null bytes). // m_PESignatureOffset = br.ReadByte(); // Thx to Microsofts mdb source code in pdb2xml this was wrongly implemented. // Should i trust their code :) // Better Writing The Code Like In Pdb Extractor myself seems to be the right way :) m_PESignatureOffset = br.ReadUInt32(); br.BaseStream.Seek(m_PESignatureOffset, SeekOrigin.Begin); // The PE Signature like defined // in Microsoft Portable Executable and Common Object File Format Specification v8.0 if (br.ReadByte() != 'P') { throw new FileLoadException("PE Signature corrupt"); } if (br.ReadByte() != 'E') { throw new FileLoadException("PE Signature corrupt"); } if (br.ReadByte() != '\0') { throw new FileLoadException("PE Signature corrupt"); } if (br.ReadByte() != '\0') { throw new FileLoadException("PE Signature corrupt"); } } catch (EndOfStreamException) { throw new FileLoadException("Read past end of file"); } } } m_ExePath = path; byte[] Data = System.IO.File.ReadAllBytes(path); //This section is unsafe so faster access is achieved :) unsafe { //First Check The Length If it is lesser then dos header + image headers if (peContainer.Length < (sizeof(IMAGE_DOS_HEADER)) + sizeof(IMAGE_NT_HEADERS32)) throw new FileLoadException("Invalid PE File"); fixed(byte *p_Data = Data) { // Get the first 64 bytes and turn it into a IMAGE_DOS_HEADER IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *)p_Data; //Read Image Nt Headers IMAGE_NT_HEADERS64 *inhs64 = (IMAGE_NT_HEADERS64 *)(idh->lfanew + p_Data); //Check The PE00 signature if (inhs64->Signature != PEMAGIC) { throw new FileLoadException("PE Signature corrupt"); } // The calculated values that are different depending on // 32 vs. 64-bit. int SizeOfOptionalHeader = 0; uint rva = 0; uint DebugDirSize = 0; ushort NumberOfSections = 0; if (inhs64->OptionalHeader.Magic == PE32) { // Get all the 32-bit offsets. IMAGE_NT_HEADERS32 *inhs32 = (IMAGE_NT_HEADERS32 *)(idh->lfanew + p_Data); SizeOfOptionalHeader = (int) inhs32->FileHeader.SizeOfOptionalHeader; //Debug Section Is Always In 7th Member Of The Optional Headers Data Directory rva = inhs32->OptionalHeader.DataDirectory7.VirtualAddress; DebugDirSize = inhs32->OptionalHeader.DataDirectory7.Size; NumberOfSections = inhs32->FileHeader.NumberOfSections; } else if (inhs64->OptionalHeader.Magic == PE32PLUS) { SizeOfOptionalHeader = (int) inhs64->FileHeader.SizeOfOptionalHeader; //Debug Section Is Always In 7th Member Of The Optional Headers Data Directory rva = inhs64->OptionalHeader.DataDirectory7.VirtualAddress; DebugDirSize = inhs64->OptionalHeader.DataDirectory7.Size; NumberOfSections = inhs64->FileHeader.NumberOfSections; } //No Debug Section Found So Exit. if ((rva == 0) || (DebugDirSize == 0)) { throw new NoDebugSectionException( ); } //Find out the data section Int32 dataSectionsOffset = (idh->lfanew) + 4 + sizeof(IMAGE_FILE_HEADER) + (int)SizeOfOptionalHeader; bool found = false; //Loop through the debug sections, enumerate whole sections try to locate the type 2(CODEVIEW) section //with magical header 0x53445352 For PDB 7.0 Entries , my code won't support PDB V2.0 Entries , because none //of the .Net Assemblies Use It. for (int i = 0; i < NumberOfSections; i++) { PESectionHeader *myHeader = (PESectionHeader *)(dataSectionsOffset + p_Data); uint sectionSize = myHeader->VirtualSize; if (sectionSize == 0) { sectionSize = myHeader->RawDataSize; } if ((rva >= myHeader->VirtualAddress) && (rva < myHeader->VirtualAddress + sectionSize)) { found = true; } if (found) { found = false; int diff = (int)(myHeader->VirtualAddress - myHeader->RawDataAddress); UInt32 fileOffset = rva - (uint)diff; _IMAGE_DEBUG_DIRECTORY *debugDirectory = (_IMAGE_DEBUG_DIRECTORY *)(fileOffset + p_Data); int NumEntries = (int)(DebugDirSize / sizeof(_IMAGE_DEBUG_DIRECTORY)); for (int ix = 1; ix <= NumEntries; ix++, debugDirectory++) { if (debugDirectory->Type == 2) { UInt32 cvSignature = *(UInt32 *)(p_Data + debugDirectory->PointerToRawData); if (cvSignature == 0x53445352) { CV_INFO_PDB70 *pCvInfo = (CV_INFO_PDB70 *)(p_Data + debugDirectory->PointerToRawData); string hexAge = pCvInfo->Age.ToString("x2"); if (hexAge != "0") { hexAge = (hexAge.StartsWith("0") ? hexAge.Substring(1) : hexAge); } else { hexAge = "0"; } m_pdbAge = pCvInfo->Age.ToString(); string finalHex = String.Empty; byte[] firstHeader = BitConverter.GetBytes(pCvInfo->firstPart); byte[] secondHeader = BitConverter.GetBytes(pCvInfo->secondPart); byte[] thirdHeader = BitConverter.GetBytes(pCvInfo->thirdPart); byte[] fourthHeader = BitConverter.GetBytes(pCvInfo->fourthPart); byte[] finalGuid = new byte[16]; //Make The Byte Order Correct So We Can Construct The Right Guid Out Of It //Guid Buildup Begin finalGuid[0] = firstHeader[3]; finalGuid[1] = firstHeader[2]; finalGuid[2] = firstHeader[1]; finalGuid[3] = firstHeader[0]; //c section finalGuid[4] = secondHeader[5 - 4]; finalGuid[5] = secondHeader[4 - 4]; //d relocation finalGuid[6] = secondHeader[7 - 4]; finalGuid[7] = secondHeader[6 - 4]; for (int xx = 8; xx < 12; xx++) { finalGuid[xx] = thirdHeader[xx - 8]; } for (int x = 12; x < 16; x++) { finalGuid[x] = fourthHeader[x - 12]; } //Guid Buildup End //Get The Original Guid m_pdbGuid = new Guid(finalGuid); finalHex = Utility.ByteArrayToHex(finalGuid); m_pdbVersion = finalHex.ToUpperInvariant() + hexAge.ToUpperInvariant(); //Locate The Pdb Name Entry, it is a null terminated string. uint stringBeginLocation = debugDirectory->PointerToRawData + 24; byte stringLocator = *(p_Data + stringBeginLocation); System.Text.StringBuilder resultBuilder = new System.Text.StringBuilder(); MemoryStream stringHolder = new MemoryStream(); while (stringLocator != 0) { stringHolder.WriteByte(stringLocator); //resultBuilder.Append(Encoding.ASCII.GetString(new byte[]{stringLocator})); stringBeginLocation++; stringLocator = *(p_Data + stringBeginLocation); } ; // Buildup The String And Return It. // We assume always that it is ascii encoded. m_pdbFullName = Encoding.ASCII.GetString(stringHolder.ToArray()); //resultBuilder.ToString(); return; } } } } dataSectionsOffset += sizeof(PESectionHeader); } throw new NoPdbSectionException(); } } }