// // cvtodbg.cpp // // Takes a PE file and a file containing CV info, and jams the CV info // into the PE file (trashing it). However, The Jammed PE File when // splitsym'ed gives a dbg file, which can be used for debugging. // // #undef UNICODE #include "windows.h" #include "imagehlp.h" #include "stdio.h" #include "stdlib.h" //////////////////////////////////////// // // Data // char szImageName[MAX_PATH]; char szCVName[MAX_PATH]; char szPdbName[MAX_PATH]; char szPdbCurrentPath[MAX_PATH]; HANDLE hFile = INVALID_HANDLE_VALUE; HANDLE hMappedFile = INVALID_HANDLE_VALUE; LPVOID pvImageBase = NULL; BOOL fVerbose = FALSE; BOOL fForce = FALSE; typedef struct NB10I // NB10 debug info { DWORD nb10; // NB10 DWORD off; // offset, always 0 DWORD sig; DWORD age; } NB10I; typedef struct cvinfo { NB10I nb10; char rgb[0x200 - sizeof(NB10I)]; } CVINFO; //////////////////////////////////////// // // Forward declarations // BOOL ParseArgs(int argc, WCHAR* argv[]); void UpdateCodeViewInfo(); void Usage(); void Message(const char* szFormat, ...); void Error(const char *sz, ...); void ErrorThrow(DWORD, const char *sz, ...); void Throw(DWORD); void MapImage(); void UnmapImage(BOOL fTouch); BOOL DebugDirectoryIsUseful(LPVOID, ULONG); void RecalculateChecksum(); ULONG FileSize(HANDLE); class FileMapping { public: FileMapping() : hFile(NULL), hMapping(NULL), pView(NULL) { } ~FileMapping() { Cleanup(); } void Cleanup() { if (pView != NULL) UnmapViewOfFile(pView); if (hMapping != NULL) CloseHandle(hMapping); if (hFile != NULL && hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); } bool Open(LPCTSTR szFile) { hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile != INVALID_HANDLE_VALUE) { hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hMapping != NULL) { pView = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); return true; } } Cleanup(); return false; } PVOID GetDataPtr() { return pView; } DWORD GetSize() { return GetFileSize(hFile, NULL); } bool IsValid() { return (pView != NULL); } private: HANDLE hFile; HANDLE hMapping; PVOID pView; }; //////////////////////////////////////// // // Code // void __cdecl wmain(int argc, WCHAR* argv[]) // Main entry point // { szPdbName[0] = 0; szPdbCurrentPath[0] = 0; if (ParseArgs(argc, argv)) { __try { UpdateCodeViewInfo(); } __except(EXCEPTION_EXECUTE_HANDLER) { // nothing, just don't propagate it higher to the user } } } // find the code view info; // if new info fits in old space, rewrite; else append new cv record and fix up // debug directory to point to the new record; append cv info to file. void UpdateCodeViewInfo() { PIMAGE_NT_HEADERS pntHeaders; ULONG cbWritten; MapImage(); FileMapping cvMapping; if (!cvMapping.Open(szCVName)) ErrorThrow(666, "Couldn't open CV file"); pntHeaders = ImageNtHeader(pvImageBase); if (pntHeaders) { if (pntHeaders->OptionalHeader.MajorLinkerVersion >= 3 || pntHeaders->OptionalHeader.MinorLinkerVersion >= 5) { // make it non vc generated image, we are trashing the binary anyway if ( pntHeaders->OptionalHeader.MajorLinkerVersion > 5) pntHeaders->OptionalHeader.MajorLinkerVersion = 5; // put dbg info back in if its already stripped. if (pntHeaders->FileHeader.Characteristics & IMAGE_FILE_DEBUG_STRIPPED) pntHeaders->FileHeader.Characteristics ^= IMAGE_FILE_DEBUG_STRIPPED; ULONG ibFileCvStart = FileSize(hFile); SetFilePointer(hFile, 0, NULL, FILE_END); while (ibFileCvStart & 7) // Align file length to 8-bytes. Slow, but clearly { // works! And for 7 bytes, who cares. BYTE zero = 0; WriteFile(hFile, &zero, 1, &cbWritten, NULL); ibFileCvStart++; } // Write out the CV info. WriteFile(hFile, cvMapping.GetDataPtr(), cvMapping.GetSize(), &cbWritten, NULL); // Make up a debug directory IMAGE_DEBUG_DIRECTORY dbgdirs[2]; dbgdirs[0].Characteristics = pntHeaders->FileHeader.Characteristics; dbgdirs[0].TimeDateStamp = pntHeaders->FileHeader.TimeDateStamp; dbgdirs[0].MajorVersion = 0; dbgdirs[0].MinorVersion = 0; dbgdirs[0].Type = IMAGE_DEBUG_TYPE_MISC; dbgdirs[0].SizeOfData = 0; dbgdirs[0].AddressOfRawData = ibFileCvStart; dbgdirs[0].PointerToRawData = ibFileCvStart; dbgdirs[1].Characteristics = pntHeaders->FileHeader.Characteristics; dbgdirs[1].TimeDateStamp = pntHeaders->FileHeader.TimeDateStamp; dbgdirs[1].MajorVersion = 0; dbgdirs[1].MinorVersion = 0; dbgdirs[1].Type = IMAGE_DEBUG_TYPE_CODEVIEW; dbgdirs[1].SizeOfData = cvMapping.GetSize(); dbgdirs[1].AddressOfRawData = ibFileCvStart; dbgdirs[1].PointerToRawData = ibFileCvStart; // Find the beginning of the first section and stick the debug directory there // (did we mention we're trashing the file?) IMAGE_SECTION_HEADER* pFirstSection = IMAGE_FIRST_SECTION(pntHeaders); memcpy((PBYTE)((DWORD)pvImageBase + pFirstSection->PointerToRawData), &dbgdirs, sizeof(dbgdirs)); pntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress = pFirstSection->VirtualAddress; pntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size = sizeof(dbgdirs); } } UnmapImage(TRUE); } void MapImage() // Map the image into memory for read-write. Caller MUST call // Unmapimage to clean up even on failure. { if (fForce) SetFileAttributesA(szImageName, FILE_ATTRIBUTE_NORMAL); hFile = CreateFileA( szImageName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL ); if (hFile == INVALID_HANDLE_VALUE) { ErrorThrow(GetLastError(), "unable to open '%s'\n", szImageName); } hMappedFile = CreateFileMapping( hFile, NULL, PAGE_READWRITE, 0, 0, NULL ); if (!hMappedFile) { ErrorThrow(GetLastError(), "un`able to create file mapping for '%s'\n", szImageName); } pvImageBase = MapViewOfFile(hMappedFile, FILE_MAP_WRITE, 0, 0, 0); if (!pvImageBase) { ErrorThrow(GetLastError(), "unable to map view of '%s'\n", szImageName); } } void UnmapImage(BOOL fTouch) // Clean up whatever MapImage does { if (pvImageBase) { FlushViewOfFile(pvImageBase, 0); UnmapViewOfFile(pvImageBase); pvImageBase = NULL; } if (hMappedFile != INVALID_HANDLE_VALUE) { CloseHandle(hMappedFile); hMappedFile = INVALID_HANDLE_VALUE; } if (hFile != INVALID_HANDLE_VALUE) { if (fTouch) { TouchFileTimes(hFile, NULL); } CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; } } BOOL ParseArgs(int argc, WCHAR* argv[]) // Parse the arguments and set our flags appropriately { WCHAR* wszString; WCHAR c; szImageName[0] = L'\0'; szCVName[0] = L'\0'; while (--argc) { wszString = *++argv; if (*wszString == L'/' || *wszString == L'-') { while ((c = *++wszString) != L'\0') { switch (towupper( c )) { case L'?': Usage(); return FALSE; case L'V': fVerbose = TRUE; break; default: Error("invalid switch - /%c\n", c ); Usage(); return FALSE; } } } else { if (szImageName[0] == L'\0') { wcstombs(szImageName, wszString, MAX_PATH); } else if (szCVName[0] == L'\0') { wcstombs(szCVName, wszString, MAX_PATH); } else { Error("too many files specified\n"); Usage(); return FALSE; } } } if (szImageName==NULL) { Error("no image name specified\n"); Usage(); return FALSE; } if (szCVName==NULL) { Error("no CV filename specified\n"); Usage(); return FALSE; } return TRUE; } void Usage() { fprintf(stderr, "Usage: cvtodbg [options] imageName cvFile\n" " [-?] display this message\n" " [-f] overwrite readonly files\n"); } void Message(const char* szFormat, ...) { va_list va; va_start(va, szFormat); fprintf (stdout, "resetpdb: "); vfprintf(stdout, szFormat, va); va_end(va); } void Error(const char* szFormat, ...) { va_list va; va_start(va, szFormat); fprintf (stderr, "resetpdb: error: "); vfprintf(stderr, szFormat, va); va_end(va); } void ErrorThrow(DWORD dw, const char* szFormat, ...) { va_list va; va_start(va, szFormat); fprintf (stderr, "resetpdb: error: "); vfprintf(stderr, szFormat, va); va_end(va); Throw(dw); } void Throw(DWORD dw) { RaiseException(dw, EXCEPTION_NONCONTINUABLE, 0, NULL); } BOOL DebugDirectoryIsUseful(LPVOID Pointer, ULONG Size) { return (Pointer != NULL) && (Size >= sizeof(IMAGE_DEBUG_DIRECTORY)) && ((Size % sizeof(IMAGE_DEBUG_DIRECTORY)) == 0); } ULONG FileSize(HANDLE h) // Answer the size of the file with this handle { BY_HANDLE_FILE_INFORMATION info; GetFileInformationByHandle(h, &info); return info.nFileSizeLow; }