/* * Functions to read parts of a .DBG file into their respective struct's * * Copyright 2000 John R. Sheets */ /* * .DBG File Layout: * * IMAGE_SEPARATE_DEBUG_HEADER * IMAGE_SECTION_HEADER[] * IMAGE_DEBUG_DIRECTORY[] * OMFSignature * debug data (typical example) * - IMAGE_DEBUG_TYPE_MISC * - IMAGE_DEBUG_TYPE_FPO * - IMAGE_DEBUG_TYPE_CODEVIEW * OMFDirHeader * OMFDirEntry[] */ /* * Descriptions: * * (hdr) IMAGE_SEPARATE_DEBUG_HEADER - .DBG-specific file header; holds info that * applies to the file as a whole, including # of COFF sections, file offsets, etc. * (hdr) IMAGE_SECTION_HEADER - list of COFF sections copied verbatim from .EXE; * although this directory contains file offsets, these offsets are meaningless * in the context of the .DBG file, because only the section headers are copied * to the .DBG file...not the binary data it points to. * (hdr) IMAGE_DEBUG_DIRECTORY - list of different formats of debug info contained in file * (see IMAGE_DEBUG_TYPE_* descriptions below); tells where each section starts * (hdr) OMFSignature (CV) - Contains "NBxx" signature, plus file offset telling how far * into the IMAGE_DEBUG_TYPE_CODEVIEW section the OMFDirHeader and OMFDirEntry's sit * (data) IMAGE_DEBUG_TYPE_MISC - usually holds name of original .EXE file * (data) IMAGE_DEBUG_TYPE_FPO - Frame Pointer Optimization data; used for dealing with * optimized stack frames (optional) * (data) IMAGE_DEBUG_TYPE_CODEVIEW - *** THE GOOD STUFF *** * This block of data contains all the symbol tables, line number info, etc., * that the Visual C++ debugger needs. * (hdr) OMFDirHeader (CV) - * (hdr) OMFDirEntry (CV) - list of subsections within CodeView debug data section */ /* * The .DBG file typically has three arrays of directory entries, which tell * the OS or debugger where in the file to look for the actual data * * IMAGE_SECTION_HEADER - number of entries determined by: * (IMAGE_SEPARATE_DEBUG_HEADER.NumberOfSections) * * IMAGE_DEBUG_DIRECTORY - number of entries determined by: * (IMAGE_SEPARATE_DEBUG_HEADER.DebugDirectorySize / sizeof (IMAGE_DEBUG_DIRECTORY)) * * OMFDirEntry - number of entries determined by: * (OMFDirHeader.cDir) */ #include #include #include #include #include "cvdump.h" extern DWORD g_dwStartOfCodeView; /* * Extract a generic block of data from debugfile (pass in fileoffset == -1 * to avoid the fseek()). */ int ReadChunk (FILE *debugfile, void *dest, int length, int fileoffset) { size_t bytes_read; if (fileoffset >= 0) fseek (debugfile, fileoffset, SEEK_SET); bytes_read = fread (dest, 1, length, debugfile); if (bytes_read < length) { printf ("ERROR: Only able to read %d bytes of %d-byte chunk!\n", bytes_read, length); return FALSE; } return TRUE; } /* * Scan the next two bytes of a file, and see if they correspond to a file * header signature. Don't forget to put the file pointer back where we * found it... */ CVHeaderType GetHeaderType (FILE *debugfile) { WORD hdrtype; CVHeaderType ret = CV_NONE; int oldpos = ftell (debugfile); #ifdef VERBOSE printf (" *** Current file position = %lx\n", ftell (debugfile)); #endif if (!ReadChunk (debugfile, &hdrtype, sizeof (WORD), -1)) { fseek (debugfile, oldpos, SEEK_SET); return CV_NONE; } if (hdrtype == 0x5A4D) /* "MZ" */ ret = CV_DOS; else if (hdrtype == 0x4550) /* "PE" */ ret = CV_NT; else if (hdrtype == 0x4944) /* "DI" */ ret = CV_DBG; fseek (debugfile, oldpos, SEEK_SET); #ifdef VERBOSE printf ("Returning header type = %d [0x%x]\n", ret, hdrtype); printf (" *** Current file position = %lx\n", ftell (debugfile)); #endif return ret; } /* * Extract the DOS file headers from an executable */ int ReadDOSFileHeader (FILE *debugfile, IMAGE_DOS_HEADER *doshdr) { size_t bytes_read; bytes_read = fread (doshdr, 1, sizeof (IMAGE_DOS_HEADER), debugfile); if (bytes_read < sizeof (IMAGE_DOS_HEADER)) { printf ("ERROR: Only able to read %d bytes of %d-byte DOS file header!\n", bytes_read, sizeof (IMAGE_DOS_HEADER)); return FALSE; } /* Skip over stub data, if present */ if (doshdr->e_lfanew) fseek (debugfile, doshdr->e_lfanew, SEEK_SET); return TRUE; } /* * Extract the DOS and NT file headers from an executable */ int ReadPEFileHeader (FILE *debugfile, IMAGE_NT_HEADERS *nthdr) { size_t bytes_read; bytes_read = fread (nthdr, 1, sizeof (IMAGE_NT_HEADERS), debugfile); if (bytes_read < sizeof (IMAGE_NT_HEADERS)) { printf ("ERROR: Only able to read %d bytes of %d-byte NT file header!\n", bytes_read, sizeof (IMAGE_NT_HEADERS)); return FALSE; } return TRUE; } /* * Extract the DBG file header from debugfile */ int ReadDBGFileHeader (FILE *debugfile, IMAGE_SEPARATE_DEBUG_HEADER *dbghdr) { size_t bytes_read; bytes_read = fread (dbghdr, 1, sizeof (IMAGE_SEPARATE_DEBUG_HEADER), debugfile); if (bytes_read < sizeof (IMAGE_SEPARATE_DEBUG_HEADER)) { printf ("ERROR: Only able to read %d bytes of %d-byte DBG file header!\n", bytes_read, sizeof (IMAGE_SEPARATE_DEBUG_HEADER)); return FALSE; } return TRUE; } /* * Extract all of the file's COFF section headers into an array of * IMAGE_SECTION_HEADER's. These COFF sections don't really apply to * the .DBG file directly (they contain file offsets into the .EXE file * which don't correspond to anything in the .DBG file). They are * copied verbatim into this .DBG file to help make the debugging process * more robust. By referencing these COFF section headers, the debugger * can still function in the absence of the original .EXE file! * * NOTE: Do not bother pre-allocating memory. This function will * allocate it for you. Don't forget to free() it when you're done, * though. */ int ReadSectionHeaders (FILE *debugfile, int numsects, IMAGE_SECTION_HEADER **secthdrs) { size_t bytes_read; /* Need a double-pointer so we can change the destination of the pointer * and return the new allocation back to the caller. */ *secthdrs = calloc (numsects, sizeof (IMAGE_SECTION_HEADER)); bytes_read = fread (*secthdrs, sizeof (IMAGE_SECTION_HEADER), numsects, debugfile); if (bytes_read < numsects) { printf ("ERROR while reading COFF headers: Only able to " "read %d headers out of %d desired!\n", bytes_read, sizeof (IMAGE_SECTION_HEADER)); return FALSE; } return TRUE; } /* * Load in the debug directory table. This directory describes the various * blocks of debug data that reside at the end of the file (after the COFF * sections), including FPO data, COFF-style debug info, and the CodeView * we are *really* after. */ int ReadDebugDir (FILE *debugfile, int numdirs, IMAGE_DEBUG_DIRECTORY **debugdirs) { size_t bytes_read; /* Need a double-pointer so we can change the destination of the pointer * and return the new allocation back to the caller. */ *debugdirs = calloc (numdirs, sizeof (IMAGE_DEBUG_DIRECTORY)); bytes_read = fread (*debugdirs, sizeof (IMAGE_DEBUG_DIRECTORY), numdirs, debugfile); if (bytes_read < numdirs) { printf ("ERROR while reading Debug Directory: Only able to " "read %d headers out of %d desired!\n", bytes_read, numdirs); return FALSE; } return TRUE; } /* * Load in the CodeView-style headers inside the CodeView debug section. * The 'sig' and 'dirhdr' parameters must point to already-allocated * data structures. */ int ReadCodeViewHeader (FILE *debugfile, OMFSignature *sig, OMFDirHeader *dirhdr) { size_t bytes_read; bytes_read = fread (sig, 1, sizeof (OMFSignature), debugfile); if (bytes_read < sizeof (OMFSignature)) { printf ("ERROR while reading CodeView Header Signature: Only " "able to read %d bytes out of %d desired!\n", bytes_read, sizeof (OMFSignature)); return FALSE; } /* Must perform a massive jump, almost to the end of the file, to find the * CodeView Directory Header (OMFDirHeader), which is immediately followed * by the array of entries (OMFDirEntry). We calculate the jump based on * the beginning of the CodeView debug section (from the CodeView entry in * the IMAGE_DEBUG_DIRECTORY array), with the added offset from OMGSignature. */ fseek (debugfile, sig->filepos + g_dwStartOfCodeView, SEEK_SET); bytes_read = fread (dirhdr, 1, sizeof (OMFDirHeader), debugfile); if (bytes_read < sizeof (OMFDirHeader)) { printf ("ERROR while reading CodeView Directory Header: Only " "able to read %d bytes out of %d desired!\n", bytes_read, sizeof (OMFDirHeader)); return FALSE; } /* File pointer is now at first OMGDirEntry, so we can begin reading those now, * with an immediate call to ReadCodeViewDirectory (). */ return TRUE; } /* * Load in the CodeView directory entries, which each point to a CodeView * subsection (e.g. sstModules, sstGlobalPub). The number of entries in * this table is determined by OMFDirEntry.cDir. * * Strangely enough, this particular section comes immediately *after* * the debug data (as opposed to immediately *before* the data as is the * standard with the COFF headers). */ int ReadCodeViewDirectory (FILE *debugfile, int entrynum, OMFDirEntry **entries) { size_t bytes_read; /* Need a double-pointer so we can change the destination of the pointer * and return the new allocation back to the caller. */ /* printf ("Allocating space for %d entries\n", entrynum); */ *entries = calloc (entrynum, sizeof (OMFDirEntry)); /* printf ("Allocated memory at %p (%p)\n", *entries, entries); */ bytes_read = fread (*entries, sizeof (OMFDirEntry), entrynum, debugfile); if (bytes_read < entrynum) { printf ("ERROR while reading CodeView Debug Directories: Only " "able to read %d entries out of %d desired!\n", bytes_read, entrynum); return FALSE; } return TRUE; } /* * Load in the data contents of all CodeView sstModule sub-sections in the file (likely a * large array, as there is one sub-section for every code module... > 100 modules is normal). * 'entrynum' should hold the total number of CV sub-sections, not the number of sstModule * subsections. The function will ignore anything that isn't a sstModule. * * NOTE: 'debugfile' must already be pointing to the correct location. */ int ReadModuleData (FILE *debugfile, int entrynum, OMFDirEntry *entries, int *module_count, OMFModuleFull **modules) { int i; int segnum; size_t bytes_read; OMFSegDesc *segarray; char namelen; OMFModuleFull *module; int pad; /* How much of the OMFModuleFull struct can we pull directly from the file? * (Kind of a hack, but not much else we can do...the 'SegInfo' and 'Name' * fields will hold memory pointers, not the actual data from the file.) */ int module_bytes = (sizeof (unsigned short) * 3) + (sizeof (char) * 2); if (entries == NULL) return FALSE; /* Find out how many sstModule sub-sections we have in 'entries' */ *module_count = 0; for (i = 0; i < entrynum; i++) { if (entries[i].SubSection == sstModule) (*module_count)++; } /* Need a double-pointer so we can change the destination of the pointer * and return the new allocation back to the caller. */ *modules = calloc (*module_count, sizeof (OMFModuleFull)); for (i = 0; i < *module_count; i++) { /* Convenience pointer to current module */ module = &(*modules)[i]; /* Must extract each OMFModuleFull separately from file, because the 'SegInfo' * and 'Name' fields also require separate allocations; the data for these * fields is interspersed in the file, between OMFModuleFull blocks. */ bytes_read = fread (module, sizeof (char), module_bytes, debugfile); if (bytes_read < module_bytes) { printf ("ERROR while reading CodeView Module Sub-section Data: " "Only able to read %d bytes from entry %d!\n", bytes_read, i); return FALSE; } /* Allocate space for, and grab the entire 'SegInfo' array. */ segnum = module->cSeg; segarray = calloc (segnum, sizeof (OMFSegDesc)); bytes_read = fread (segarray, sizeof (OMFSegDesc), segnum, debugfile); if (bytes_read < segnum) { printf ("ERROR while reading CodeView Module SegInfo Data: " "Only able to read %d segments from module %d!\n", bytes_read, i); return FALSE; } module->SegInfo = segarray; /* Allocate space for the (length-prefixed) 'Name' field. */ bytes_read = fread (&namelen, sizeof (char), 1, debugfile); if (bytes_read < 1) { printf ("ERROR while reading CodeView Module Name length!\n"); return FALSE; } /* Read 'Name' field from file. 'Name' must be aligned on a 4-byte * boundary, so we must do a little extra math on the string length. * (NOTE: Must include namelen byte in total padding length, too.) */ pad = ((namelen + 1) % 4); if (pad) namelen += (4 - pad); module->Name = calloc (namelen + 1, sizeof (char)); bytes_read = fread (module->Name, sizeof (char), namelen, debugfile); if (bytes_read < namelen) { printf ("ERROR while reading CodeView Module Name: " "Only able to read %d chars from module %d!\n", bytes_read, i); return FALSE; } /* printf ("%s\n", module->Name); */ } #ifdef VERBOSE printf ("Done reading %d modules\n", *module_count); #endif return TRUE; }