|  | /* | 
|  | * 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 <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <windows.h> | 
|  |  | 
|  | #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; | 
|  | } | 
|  |  | 
|  |  |