//--------------------
// PROGRAM: PEDUMP
// FILE:    EXEDUMP.C
// AUTHOR:  Matt Pietrek - 1993
//--------------------
#include <windows.h>
#include <stdio.h>
#include "common.h"
#include "extrnvar.h"

typedef PIMAGE_COFF_SYMBOLS_HEADER PIMAGE_DEBUG_INFO;

PIMAGE_DEBUG_INFO PCOFFDebugInfo = 0;

char *SzDebugFormats[] = {
"UNKNOWN/BORLAND","COFF","CODEVIEW","FPO","MISC","EXCEPTION","FIXUP" };

//
// Dump the debug directory in a PE file.
//
void DumpDebugDirectory(DWORD base, PIMAGE_NT_HEADERS pNTHeader)
{
	PIMAGE_DEBUG_DIRECTORY debugDir;
	PIMAGE_SECTION_HEADER header;
	unsigned cDebugFormats, i;
	DWORD offsetInto_rdata;
	DWORD va_debug_dir;
	PSTR szDebugFormat;

	// This line was so long that we had to break it up
	va_debug_dir = pNTHeader->OptionalHeader.
						DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].
						VirtualAddress;
	if ( va_debug_dir == 0 )
		return;

	// If we found a .debug section, and the debug directory is at the
	// beginning of this section, it looks like a Borland file
	header = GetSectionHeader(".debug", pNTHeader);
	if ( header && (header->VirtualAddress == va_debug_dir) )
	{
		debugDir = (PIMAGE_DEBUG_DIRECTORY)(header->PointerToRawData+base);
		cDebugFormats = pNTHeader->OptionalHeader.
							DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size;
	}
	else	// Look for microsoft debug directory in the .rdata section
	{
		header = GetSectionHeader(".rdata", pNTHeader);
		if ( !header )
			return;

		// See if there's even any debug directories to speak of...
		cDebugFormats = pNTHeader->OptionalHeader.
							DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size
						/ sizeof(IMAGE_DEBUG_DIRECTORY);
		if ( cDebugFormats == 0 )
			return;
	
		offsetInto_rdata = va_debug_dir - header->VirtualAddress;
		debugDir = MakePtr(PIMAGE_DEBUG_DIRECTORY, base,
							header->PointerToRawData + offsetInto_rdata);
	}
    	
	printf(
	"Debug Formats in File\n"
    "  Type            Size     Address  FilePtr  Charactr TimeData Version\n"
    "  --------------- -------- -------- -------- -------- -------- --------\n"
	);
	
	for ( i=0; i < cDebugFormats; i++ )
	{
		szDebugFormat = (debugDir->Type <= 6)
						? SzDebugFormats[debugDir->Type] : "???";

		printf("  %-15s %08X %08X %08X %08X %08X %u.%02u\n",
			szDebugFormat, debugDir->SizeOfData, debugDir->AddressOfRawData,
			debugDir->PointerToRawData, debugDir->Characteristics,
			debugDir->TimeDateStamp, debugDir->MajorVersion,
			debugDir->MinorVersion);

		// If COFF debug info, save its address away for later.  We
		// do the check for "PointerToSymbolTable" because some files
		// have bogus values for the COFF header offset.
		if ( (debugDir->Type == IMAGE_DEBUG_TYPE_COFF) &&
		     pNTHeader->FileHeader.PointerToSymbolTable )
		{
			PCOFFDebugInfo =
				(PIMAGE_DEBUG_INFO)(base+ debugDir->PointerToRawData);
		}
		
		debugDir++;
	}
}

// Function prototype (necessary because two functions recurse)
void DumpResourceDirectory
(
	PIMAGE_RESOURCE_DIRECTORY resDir, DWORD resourceBase,
	DWORD level, DWORD resourceType
);

// The predefined resource types
char *SzResourceTypes[] = {
"???_0", "CURSOR", "BITMAP", "ICON", "MENU", "DIALOG", "STRING", "FONTDIR",
"FONT", "ACCELERATORS", "RCDATA", "MESSAGETABLE", "GROUP_CURSOR",
"???_13", "GROUP_ICON", "???_15", "VERSION"
};

// Get an ASCII string representing a resource type
void GetResourceTypeName(DWORD type, PSTR buffer, UINT cBytes)
{
	if ( type <= 16 )
		strncpy(buffer, SzResourceTypes[type], cBytes);
	else
		wsprintf(buffer, "%X", type);
}

//
// If a resource entry has a string name (rather than an ID), go find
// the string and convert it from unicode to ascii.
//
void GetResourceNameFromId
(
	DWORD id, DWORD resourceBase, PSTR buffer, UINT cBytes
)
{
	PIMAGE_RESOURCE_DIR_STRING_U prdsu;

	// If it's a regular ID, just format it.
	if ( !(id & IMAGE_RESOURCE_NAME_IS_STRING) )
	{
		wsprintf(buffer, "%X", id);
		return;
	}
	
	id &= 0x7FFFFFFF;
	prdsu = (PIMAGE_RESOURCE_DIR_STRING_U)(resourceBase + id);

	// prdsu->Length is the number of unicode characters
	WideCharToMultiByte(CP_ACP, 0, prdsu->NameString, prdsu->Length,
						buffer, cBytes,	0, 0);
	buffer[ min(cBytes-1,prdsu->Length) ] = 0;	// Null terminate it!!!
}

//
// Dump the information about one resource directory entry.  If the
// entry is for a subdirectory, call the directory dumping routine
// instead of printing information in this routine.
//
void DumpResourceEntry
(
	PIMAGE_RESOURCE_DIRECTORY_ENTRY resDirEntry,
	DWORD resourceBase,
	DWORD level
)
{
	UINT i;
	char nameBuffer[128];
		
	if ( resDirEntry->OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY )
	{
		DumpResourceDirectory( (PIMAGE_RESOURCE_DIRECTORY)
			((resDirEntry->OffsetToData & 0x7FFFFFFF) + resourceBase),
			resourceBase, level, resDirEntry->Name);
		return;
	}

	// Spit out the spacing for the level indentation
	for ( i=0; i < level; i++ )
		printf("    ");

	if ( resDirEntry->Name & IMAGE_RESOURCE_NAME_IS_STRING )
	{
		GetResourceNameFromId(resDirEntry->Name, resourceBase, nameBuffer,
							  sizeof(nameBuffer));
		printf("Name: %s  Offset: %08X\n",
			nameBuffer, resDirEntry->OffsetToData);
	}
	else
	{
		printf("ID: %08X  Offset: %08X\n",
				resDirEntry->Name, resDirEntry->OffsetToData);
	}
}

//
// Dump the information about one resource directory.
//
void DumpResourceDirectory
(
	PIMAGE_RESOURCE_DIRECTORY resDir,
	DWORD resourceBase,
	DWORD level,
	DWORD resourceType
)
{
	PIMAGE_RESOURCE_DIRECTORY_ENTRY resDirEntry;
	char szType[64];
	UINT i;

	// Spit out the spacing for the level indentation
	for ( i=0; i < level; i++ )
		printf("    ");

	// Level 1 resources are the resource types
	if ( level == 1 && !(resourceType & IMAGE_RESOURCE_NAME_IS_STRING) )
	{
		GetResourceTypeName( resourceType, szType, sizeof(szType) );
	}
	else	// Just print out the regular id or name
	{
		GetResourceNameFromId( resourceType, resourceBase, szType,
							   sizeof(szType) );
	}
	
	printf(
		"ResDir (%s) Named:%02X ID:%02X TimeDate:%08X Vers:%u.%02u Char:%X\n",
		szType,	resDir->NumberOfNamedEntries, resDir->NumberOfIdEntries,
		resDir->TimeDateStamp, resDir->MajorVersion,
		resDir->MinorVersion,resDir->Characteristics);

	resDirEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(resDir+1);
	
	for ( i=0; i < resDir->NumberOfNamedEntries; i++, resDirEntry++ )
		DumpResourceEntry(resDirEntry, resourceBase, level+1);

	for ( i=0; i < resDir->NumberOfIdEntries; i++, resDirEntry++ )
		DumpResourceEntry(resDirEntry, resourceBase, level+1);
}

//
// Top level routine called to dump out the entire resource hierarchy
//
void DumpResourceSection(DWORD base, PIMAGE_NT_HEADERS pNTHeader)
{
	PIMAGE_RESOURCE_DIRECTORY resDir;
	
	resDir = GetSectionPtr(".rsrc", pNTHeader, (DWORD)base);
	if ( !resDir )
		return;
	
 	printf("Resources\n");
	DumpResourceDirectory(resDir, (DWORD)resDir, 0, 0);
}

//
// Dump the imports table (the .idata section) of a PE file
//
void DumpImportsSection(DWORD base, PIMAGE_NT_HEADERS pNTHeader)
{
	PIMAGE_IMPORT_DESCRIPTOR importDesc;
	PIMAGE_SECTION_HEADER header;
	PIMAGE_THUNK_DATA thunk;
	PIMAGE_IMPORT_BY_NAME pOrdinalName;
	DWORD exportsStartRVA, exportsEndRVA;
	
	INT delta;

	header = GetSectionHeader(".idata", pNTHeader);
	if ( !header )
		return;
	importDesc = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, base,
						 header->PointerToRawData);
	delta = (INT)(header->VirtualAddress - header->PointerToRawData);
	
 	printf("Imports Table:\n");
	
	while ( 1 )
	{
		// See if we've reached an empty IMAGE_IMPORT_DESCRIPTOR
		if ( (importDesc->TimeDateStamp==0 ) && (importDesc->Name==0) )
			break;
		
		printf("  %s\n", (PBYTE)(importDesc->Name) - delta + base);

		printf("  Hint/Name Table: %08X\n", importDesc->Characteristics);
 		printf("  TimeDateStamp:   %08X\n", importDesc->TimeDateStamp);
 		printf("  ForwarderChain:  %08X\n", importDesc->ForwarderChain);
 		printf("  First thunk RVA: %08X\n", importDesc->FirstThunk);
	
		thunk = (PIMAGE_THUNK_DATA)importDesc->FirstThunk;
		thunk = (PIMAGE_THUNK_DATA)( (PBYTE)thunk - delta + base);
		
		// If the pointer that thunk points to is outside of the .idata
		// section, it looks like this file is "pre-fixed up" with regards
		// to the thunk table.  In this situation, we'll need to fall back
		// to the hint-name (aka, the "Characteristics") table.
		exportsStartRVA = header->VirtualAddress;
		exportsEndRVA= exportsStartRVA + header->SizeOfRawData;
		if ( (*(PDWORD)thunk <= exportsStartRVA) ||
			 (*(PDWORD)thunk >= exportsEndRVA) )
		{
			if ( importDesc->Characteristics == 0 )	// Borland doesn't have
				return;								// this table!!!
					
			thunk = (PIMAGE_THUNK_DATA)importDesc->Characteristics;
			if ( ((DWORD)thunk <= exportsStartRVA) ||
				 ((DWORD)thunk >= exportsEndRVA) )
				return;

			thunk = (PIMAGE_THUNK_DATA)( (PBYTE)thunk - delta + base);
		}

		printf("  Ordn  Name\n");
		
		while ( 1 )	// Loop forever (or until we break out)
		{
			if ( thunk->u1.AddressOfData == 0 )
				break;

			if ( thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG )
			{
				printf("  %4u\n", thunk->u1.Ordinal & 0xFFFF);
				break;
			}
			else
			{
				pOrdinalName = thunk->u1.AddressOfData;
				pOrdinalName = (PIMAGE_IMPORT_BY_NAME)
								((PBYTE)pOrdinalName - delta + base);
					
				printf("  %4u  %s\n", pOrdinalName->Hint, pOrdinalName->Name);
			}
			
			thunk++;			// Advance to next thunk
		}

		importDesc++;	// advance to next IMAGE_IMPORT_DESCRIPTOR
		printf("\n");
	}
}

//
// Dump the exports table (the .edata section) of a PE file
//
void DumpExportsSection(DWORD base, PIMAGE_NT_HEADERS pNTHeader)
{
	PIMAGE_EXPORT_DIRECTORY exportDir;
	PIMAGE_SECTION_HEADER header;
	INT delta; 
	PSTR filename;
	DWORD i;
	PDWORD functions;
	PWORD ordinals;
	PSTR *name;

	header = GetSectionHeader(".edata", pNTHeader);
	if ( !header )
		return;
	exportDir = MakePtr(PIMAGE_EXPORT_DIRECTORY, base,
						 header->PointerToRawData);
	delta = (INT)(header->VirtualAddress - header->PointerToRawData);
	
	filename = (PSTR)(exportDir->Name - delta + base);
		
 	printf("exports table:\n\n");
	printf("  Name:            %s\n", filename);
	printf("  Characteristics: %08X\n", exportDir->Characteristics);
	printf("  TimeDateStamp:   %08X\n", exportDir->TimeDateStamp);
	printf("  Version:         %u.%02u\n", exportDir->MajorVersion,
			exportDir->MinorVersion);
	printf("  Ordinal base:    %08X\n", exportDir->Base);
	printf("  # of functions:  %08X\n", exportDir->NumberOfFunctions);
	printf("  # of Names:      %08X\n", exportDir->NumberOfNames);
	
	functions = (PDWORD)((DWORD)exportDir->AddressOfFunctions - delta + base);
	ordinals = (PWORD)((DWORD)exportDir->AddressOfNameOrdinals - delta + base);
	name = (PSTR *)((DWORD)exportDir->AddressOfNames - delta + base);

	printf("\n  Entry Pt  Ordn  Name\n");
	for ( i=0; i < exportDir->NumberOfNames; i++ )
	{
		printf("  %08X  %4u  %s\n", *functions,
				*ordinals + exportDir->Base,
				(*name - delta + base));
		name++;			// Bump each pointer to the next array element
		ordinals++;
		functions++;
	}
}

// The names of the available base relocations
char *SzRelocTypes[] = {
"ABSOLUTE","HIGH","LOW","HIGHLOW","HIGHADJ","MIPS_JMPADDR",
"I860_BRADDR","I860_SPLIT" };

//
// Dump the base relocation table of a PE file
//
void DumpBaseRelocationsSection(DWORD base, PIMAGE_NT_HEADERS pNTHeader)
{
	PIMAGE_BASE_RELOCATION baseReloc;
	
	baseReloc = GetSectionPtr(".reloc", pNTHeader, base);
	if ( !baseReloc )
		return;

	printf("base relocations:\n\n");

	while ( baseReloc->SizeOfBlock != 0 )
	{
		unsigned i,cEntries;
		PWORD pEntry;
		char *szRelocType;
		WORD relocType;
		
		cEntries = (baseReloc->SizeOfBlock-sizeof(*baseReloc))/sizeof(WORD);
		pEntry = MakePtr( PWORD, baseReloc, sizeof(*baseReloc) );
		
		printf("Virtual Address: %08X  size: %08X\n",
				baseReloc->VirtualAddress, baseReloc->SizeOfBlock);
			
		for ( i=0; i < cEntries; i++ )
		{
			// Extract the top 4 bits of the relocation entry.  Turn those 4
			// bits into an appropriate descriptive string (szRelocType)
			relocType = (*pEntry & 0xF000) >> 12;
			szRelocType = relocType < 8 ? SzRelocTypes[relocType] : "unknown";
			
			printf("  %08X %s\n",
					(*pEntry & 0x0FFF) + baseReloc->VirtualAddress,
					szRelocType);
			pEntry++;	// Advance to next relocation entry
		}
		
		baseReloc = MakePtr( PIMAGE_BASE_RELOCATION, baseReloc,
							 baseReloc->SizeOfBlock);
	}
}

//
// Dump the COFF debug information header
//
void DumpCOFFHeader(PIMAGE_DEBUG_INFO pDbgInfo)
{
	printf("COFF Debug Info Header\n");
	printf("  NumberOfSymbols:      %08X\n", pDbgInfo->NumberOfSymbols);
	printf("  LvaToFirstSymbol:     %08X\n", pDbgInfo->LvaToFirstSymbol);
	printf("  NumberOfLinenumbers:  %08X\n", pDbgInfo->NumberOfLinenumbers);
	printf("  LvaToFirstLinenumber: %08X\n", pDbgInfo->LvaToFirstLinenumber);
	printf("  RvaToFirstByteOfCode: %08X\n", pDbgInfo->RvaToFirstByteOfCode);
	printf("  RvaToLastByteOfCode:  %08X\n", pDbgInfo->RvaToLastByteOfCode);
	printf("  RvaToFirstByteOfData: %08X\n", pDbgInfo->RvaToFirstByteOfData);
	printf("  RvaToLastByteOfData:  %08X\n", pDbgInfo->RvaToLastByteOfData);
}

//
// top level routine called from PEDUMP.C to dump the components of a PE file
//
void DumpExeFile( PIMAGE_DOS_HEADER dosHeader )
{
	PIMAGE_NT_HEADERS pNTHeader;
	DWORD base = (DWORD)dosHeader;
	
	pNTHeader = MakePtr( PIMAGE_NT_HEADERS, dosHeader,
								dosHeader->e_lfanew );

	// First, verify that the e_lfanew field gave us a reasonable
	// pointer, then verify the PE signature.
	if ( IsBadReadPtr(pNTHeader, sizeof(IMAGE_NT_HEADERS)) ||
	     pNTHeader->Signature != IMAGE_NT_SIGNATURE )
	{
		printf("Unhandled EXE type, or invalid .EXE\n");
		return;
	}
	
	DumpHeader((PIMAGE_FILE_HEADER)&pNTHeader->FileHeader);
	printf("\n");

	DumpOptionalHeader((PIMAGE_OPTIONAL_HEADER)&pNTHeader->OptionalHeader);
	printf("\n");

	DumpSectionTable( (PIMAGE_SECTION_HEADER)(pNTHeader+1),
						pNTHeader->FileHeader.NumberOfSections, TRUE);
	printf("\n");

	DumpDebugDirectory(base, pNTHeader);
	printf("\n");

 	DumpResourceSection(base, pNTHeader);
	printf("\n");

	DumpImportsSection(base, pNTHeader);
	printf("\n");
	
	DumpExportsSection(base, pNTHeader);
	printf("\n");

	if ( fShowRelocations )
	{
		DumpBaseRelocationsSection(base, pNTHeader);
		printf("\n");
	} 

	//
	// Initialize these vars here since we'll need them in DumpLineNumbers
	//
	PCOFFSymbolTable = MakePtr(PIMAGE_SYMBOL, base,
						pNTHeader->FileHeader.PointerToSymbolTable);
	COFFSymbolCount = pNTHeader->FileHeader.NumberOfSymbols;

	if ( fShowSymbolTable && PCOFFDebugInfo )
	{
		DumpCOFFHeader( PCOFFDebugInfo );
		printf("\n");
	}
	
	if ( fShowLineNumbers && PCOFFDebugInfo )
	{
		DumpLineNumbers( MakePtr(PIMAGE_LINENUMBER, PCOFFDebugInfo,
							PCOFFDebugInfo->LvaToFirstLinenumber),
							PCOFFDebugInfo->NumberOfLinenumbers);
		printf("\n");
	}

	if ( fShowSymbolTable )
	{
		if ( pNTHeader->FileHeader.NumberOfSymbols 
			&& pNTHeader->FileHeader.PointerToSymbolTable)
		{
			DumpSymbolTable(PCOFFSymbolTable, COFFSymbolCount);
			printf("\n");
		}
	}
	
	if ( fShowRawSectionData )
	{
		DumpRawSectionData( (PIMAGE_SECTION_HEADER)(pNTHeader+1),
							dosHeader,
							pNTHeader->FileHeader.NumberOfSections);
	}
}