Figure 1 Assembler from Nonoptimized Code
foo proc
401000: PUSH EBP
401001: MOV EBP,ESP
401003: PUSH EBX
401004: PUSH ESI
401005: PUSH EDI
401006: MOV EAX,DWORD PTR [EBP+08]
401009: ADD EAX,EAX
40100B: JMP 00401010
401010: POP EDI
401011: POP ESI
401012: POP EBX
401013: LEAVE
401014: RET
foo endp
main proc
401015: PUSH EBP
401016: MOV EBP,ESP
401018: PUSH EBX
401019: PUSH ESI
40101A: PUSH EDI
40101B: PUSH 07
40101D: CALL 00401000
401022: ADD ESP,04
401025: TEST EAX,EAX
401027: JE 0040103C
40102D: MOV EAX,00000001
401032: JMP 00401043
401037: JMP 00401043
40103C: XOR EAX,EAX
40103E: JMP 00401043
401043: POP EDI
401044: POP ESI
401045: POP EBX
401046: LEAVE
401047: RET
main endp
Figure 4 LIPO32.CPP
//==========================================
// LIPO32/LIPOSUCTION32 - Matt Pietrek 1996
// Microsoft Systems Journal, October 1996
// FILE: LIPO32.CPP
//==========================================
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#pragma hdrstop
#include "peexe2.h"
#include "dependencylist.h"
#include "lipo32output.h"
// Prototypes for the analysis routines
void LookForIncrementalLinking( PE_EXE2 & peFile );
void LookForUnoptimizedCode( PE_EXE2 & peFile );
void LookForDebugInfo( PE_EXE2 & peFile );
void LookForRelocations( PE_EXE2 & peFile );
void LookForCombinableSections( PE_EXE2 & peFile );
void LookForBSSSections( PE_EXE2 &peFile );
void LookForLoadAddressConflicts( PSTR pszFileName );
void LookForBoundImage( PE_EXE2 & peFile );
// Helper function prototypes
unsigned ScanForInt3s( PE_SECTION * pSection, unsigned minSequence );
unsigned ScanForJMPsToNoWhere( PE_SECTION * pSection );
PSTR GetDebugTypeName( unsigned type );
int SectSort( const void *a, const void *b );
int ModLoadSort( const void *a, const void *b );
void AnalyzeFile( PSTR pszFileName )
{
PE_EXE2 peFile( pszFileName ); // Create the PE_EXE2 object to be examined
char szBuffer[MAX_PATH*2];
OutputClearAllFields( ); // Clear the UI of previous file information
if ( !peFile.IsValid() ) // Make sure it's a PE EXE we're working with
{
wsprintf( szBuffer, "%s is not a PE file", pszFileName );
OutputError( szBuffer );
return;
}
if ( IMAGE_FILE_MACHINE_I386 != peFile.GetMachine() ) // 386 only!
{
wsprintf(szBuffer, "%s is not an Intel i386 executable", pszFileName );
OutputError( szBuffer );
return;
}
// Let the analysis begin!
OutputSetFileName( pszFileName );
LookForIncrementalLinking( peFile );
LookForUnoptimizedCode( peFile );
LookForDebugInfo( peFile );
LookForRelocations( peFile );
LookForCombinableSections( peFile );
LookForBSSSections( peFile );
LookForLoadAddressConflicts( pszFileName );
LookForBoundImage( peFile );
}
void LookForIncrementalLinking( PE_EXE2 & peFile )
{
// Iterate through each section...
for ( unsigned i=1; i <= peFile.GetNumberOfSections(); i++ )
{
PE_SECTION * pSection = peFile.GetSectionByIndex( i );
// If it's not a code section, don't go any further
if ( !pSection->IsCode() )
continue;
// Look for INT 3's, with a minimum sequence of 3 INT 3's before
// counting starts
unsigned cInt3s = ScanForInt3s( pSection, 3 );
if ( 0 == cInt3s )
continue;
// If INT 3's were found, send to the UI to display
char szName[10];
pSection->GetName( szName );
OutputAddIncrementalLinking( szName, pSection->GetIndex(), cInt3s );
}
}
void LookForUnoptimizedCode( PE_EXE2 & peFile )
{
// Iterate through each section...
for ( unsigned i=1; i <= peFile.GetNumberOfSections(); i++ )
{
PE_SECTION * pSection = peFile.GetSectionByIndex( i );
// If it's not a code section, don't go any further
if ( !pSection->IsCode() )
continue;
// Count how many bytes were taken up by JMPs to the next instruction
unsigned cStupidJmps = ScanForJMPsToNoWhere( pSection );
if ( cStupidJmps < 25 ) // Cut them a little slack (enough for 5 JMPs)
continue;
char szName[10];
pSection->GetName( szName );
OutputAddUnoptimizedCode( szName, pSection->GetIndex(), cStupidJmps );
}
}
void LookForDebugInfo( PE_EXE2 & peFile )
{
// Find out how many types of debug info are present. If none, we're done
unsigned cDebugTypes = peFile.GetDebugInfoTypeCount();
if ( !cDebugTypes )
return;
// Get a pointer to the debug directory
PIMAGE_DEBUG_DIRECTORY pDbgDir = peFile.GetDebugDirectory();
// Emit information on each type of debug information
for( unsigned i = 0; i < cDebugTypes; i++, pDbgDir++ )
{
OutputAddDebugInfo( GetDebugTypeName(pDbgDir->Type),
pDbgDir->SizeOfData );
}
}
void LookForRelocations( PE_EXE2 & peFile )
{
// Get the number of relocations from the EXE's data directory
DWORD cbRelocations =
peFile.GetDataDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_BASERELOC);
OutputSetRelocations( cbRelocations,
peFile.GetCharacteristics() & IMAGE_FILE_DLL );
}
// Data structure used by LookForCombinableSections
struct SECTION_ATTRIBUTES
{
DWORD Characteristics;
DWORD index;
};
void LookForCombinableSections( PE_EXE2 & peFile )
{
unsigned cSections = peFile.GetNumberOfSections();
// Allocate an array of SECTION_ATTRIBUTES structures to hold the index
// and flags for each section
SECTION_ATTRIBUTES * pAttrs = new SECTION_ATTRIBUTES[ cSections ];
// Iterate through each section, adding it to the SECTION_ATTRIBUTES array
unsigned i, cUsed;
for ( i=0, cUsed=0; i < cSections; i++ )
{
PE_SECTION * pSection = peFile.GetSectionByIndex( i+1 );
char szName[IMAGE_SIZEOF_SHORT_NAME+2];
// Don't put the .rsrc and .reloc sections into the array
pSection->GetName( szName );
if ( (0==strcmp(szName, ".reloc")) || (0==strcmp(szName, ".rsrc")) )
continue;
pAttrs[cUsed].index = i+1;
pAttrs[cUsed].Characteristics = pSection->GetCharacteristics();
cUsed++;
}
unsigned max_i; // )(*&)(^*%*$ "goto skips initialization" error!!!
// Make sure that we put at least 2 items into our pAttrs array. Otherwise
// it's not worth looking for combinable sections.
if ( cUsed <= 2 )
goto done_comparing;
// Sort the sections based on their attributes
qsort( pAttrs, cUsed, sizeof(SECTION_ATTRIBUTES), SectSort );
max_i = cUsed-1;
// Now go though the sorted SECTION_ATTRIBUTES array and find sequences
// of two or more sections with the same flags (characteristics)
for ( i=0; i < max_i; i++ )
{
// first, scan until we find two sections with the same flags
while ( pAttrs[i].Characteristics != pAttrs[i+1].Characteristics )
{
if ( ++i >= max_i )
goto done_comparing;
}
// If we get here, we've found two sections that have the same flags
// Now find the last section that has the same flags value
unsigned j = i+1;
while ( (pAttrs[j].Characteristics == pAttrs[j+1].Characteristics )
&& (j <= max_i) )
j++;
DWORD cCurrentPagesUsed = 0;
DWORD cBytesUsed = 0;
// Tell the UI that we're about to start emitting a list of
// combinable sections
OutputAddCombinableSectionsStart();
// Emit the information about each combinable section
while ( i <= j )
{
PE_SECTION * pSection = peFile.GetSectionByIndex(pAttrs[i].index);
char szName[IMAGE_SIZEOF_SHORT_NAME+2];
pSection->GetName( szName );
OutputAddCombinableSections( szName, pSection->GetAccessibleSize(),
pSection->GetCharacteristics() );
i++;
cCurrentPagesUsed += (pSection->GetMaxSizeInMem() + 0xFFF)/0x1000;
cBytesUsed += pSection->GetMaxSizeInMem();
}
// Tell the UI that we're done emitting sections. Also provide it
// with the before and after page counts
OutputAddCombinableSectionsEnd( cCurrentPagesUsed,
(cBytesUsed + 0xFFF) / 0x1000 );
}
done_comparing:
delete []pAttrs;
}
void LookForBSSSections( PE_EXE2 &peFile )
{
// Iterate through every section
for ( unsigned i=1; i <= peFile.GetNumberOfSections(); i++ )
{
PE_SECTION * pSection = peFile.GetSectionByIndex( i );
// If the section has the uninitialized data flag set, tattle on it
if (pSection->GetCharacteristics() & IMAGE_SCN_CNT_UNINITIALIZED_DATA)
{
char szName[10];
pSection->GetName( szName );
OutputBSSSection( szName, i );
}
}
}
// Data structure used by LookForLoadAddressConflicts
struct MODULE_LOAD_INFO
{
DWORD m_baseRVA; // RVA that module loads at
DWORD m_endRVA; // RVA that module ends at
PSTR m_pszFullName; // Full path to the module
};
typedef MODULE_LOAD_INFO * PMODULE_LOAD_INFO;
void LookForLoadAddressConflicts( PSTR pszFileName )
{
// Create a module dependency list
MODULE_DEPENDENCY_LIST depList( pszFileName );
unsigned cModules = depList.GetNumberOfModules();
if ( cModules <= 1 ) // At least 2 modules are needed for a conflict
return;
// Create an array of MODULE_LOAD_INFOs to store info about each module
// pMods is the start of the array, pModCurr is the current
// MODULE_LOAD_INFO that's being accessed
PMODULE_LOAD_INFO pMods = new MODULE_LOAD_INFO[ cModules ];
PMODULE_LOAD_INFO pModCurr = pMods;
// Get a pointer to the first module in the dependency list
PMODULE_FILE_INFO pModFile = depList.GetNextModule( NULL );
// While there are still modules in the list...
while ( pModFile )
{
// Make a PE_EXE object to access the module's information
PE_EXE peFile( pModFile->m_szFullName );
if ( !peFile.IsValid() )
break;
// Add the information about the module to the MODULE_LOAD_INFO
pModCurr->m_pszFullName = strdup( pModFile->m_szFullName );
pModCurr->m_baseRVA = peFile.GetImageBase();
pModCurr->m_endRVA = peFile.GetImageBase() + peFile.GetSizeOfImage();
// Round up the ending address to a multiple of 64K, since that's the
// granularity that the OS allocates
pModCurr->m_endRVA = (pModCurr->m_endRVA + 0xFFFF) & 0xFFFF0000;
// Prepare to process the next module
pModCurr++;
pModFile = depList.GetNextModule( pModFile );
}
// Sort the MODULE_LOAD_INFO array, based on the load address
qsort( pMods, cModules, sizeof(MODULE_LOAD_INFO), ModLoadSort );
unsigned i;
// Now iterate through the MODULE_LOAD_INFO array, looking for consecutive
// entries that have an overlap
pModCurr = pMods;
for ( i=1; i < cModules; i++, pModCurr++ )
{
// Yes, this looks wacky. But it works. If the end of the current
// module's range is after the start of the next module range, there's
// an overlap.
if ( pModCurr->m_endRVA > pModCurr[1].m_baseRVA )
{
OutputLoadAddressConflicts(
pModCurr->m_pszFullName,
pModCurr->m_baseRVA,
pModCurr->m_endRVA,
pModCurr[1].m_pszFullName,
pModCurr[1].m_baseRVA,
pModCurr[1].m_endRVA
);
}
}
// Clean up all the filename strings we strdup'ed earlier
pModCurr = pMods;
for ( i=0; i < cModules; i++, pModCurr++ )
free( pModCurr->m_pszFullName );
delete []pMods; // Delete the MODULE_LOAD_INFO array
}
void LookForBoundImage( PE_EXE2 & peFile )
{
// Look in the appropriate data directory slot in the executable. Note
// that in NT 3.5 and earlier, this field wasn't set by BIND.
BOOL fBound = (BOOL)
peFile.GetDataDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT);
OutputBindStatus( fBound );
}
//======================== Helper functions ================================
unsigned ScanForInt3s( PE_SECTION * pSection, unsigned minSequence )
{
// Scan the specified section, looking for as strings of INT 3s. The
// minimum length for a match is given by the minSequence parameter
DWORD len = pSection->GetAccessibleSize();
// Get a pointer to the start of the data
PBYTE pData = (PBYTE)pSection->GetPointer();
if ( minSequence > len )
return 0;
PBYTE pEnd = pData + len; // Get a pointer to the end of the data
DWORD count = 0;
while ( pData < pEnd ) // While still more data...
{
// use memchr to quickly find the first 0xCC byte
pData = (PBYTE)memchr( pData, 0xCC, pEnd - pData );
if ( !pData )
break;
DWORD cThisRun = 0;
// Count how many INT 3's are in this sequence.
while ( pData < pEnd )
{
if ( *pData++ != 0xCC )
break;
cThisRun++;
}
// If the minimum length was met, add it to our running total
if ( cThisRun >= minSequence )
count += cThisRun;
}
return count; // Return final total
}
unsigned ScanForJMPsToNoWhere( PE_SECTION * pSection )
{
DWORD len = pSection->GetAccessibleSize();
// Get pointer to the start of the data
PBYTE pData = (PBYTE)pSection->GetPointer();
if ( len < 6 )
return 0;
// Get pointer to the last possible byte where we could find a JMP
PBYTE pEnd = pData + len - 6;
DWORD count = 0;
// Scan for JMPs to the next instruction, followed by a MOV XXX,[XXX]
// or POP XXX instruction. These are the most common sequences produced
// by a compiler in non-optimizing mode
while ( pData < pEnd )
{
pData = (PBYTE)memchr( pData, 0xE9, pEnd - pData );
if ( !pData )
break;
pData++;
if ( 0 == *(PDWORD)pData ) // Is the relative offset == 0?
{
// It sure looks like a wasted JMP, but verify by looking
// at the next instruction to see if it's a common instruction.
// 0x8B = MOV, 0x58-0x5F = POP XXX
if ( pData[4] == 0x8B || ( 0x58 == (pData[4] & 0xF8) ) )
count += 5; // The length of the JMP 00000000
}
}
return count;
}
PSTR GetDebugTypeName( unsigned type )
{
// Given an IMAGE_DEBUG_TYPE_XXX value (see WINNT.H), return a static
// string describing what type of debug information it is.
switch ( type )
{
case IMAGE_DEBUG_TYPE_COFF: return "COFF";
case IMAGE_DEBUG_TYPE_CODEVIEW: return "CODEVIEW";
case IMAGE_DEBUG_TYPE_FPO: return "FPO";
case IMAGE_DEBUG_TYPE_MISC: return "MISC";
case IMAGE_DEBUG_TYPE_EXCEPTION: return "EXCEPTION";
case IMAGE_DEBUG_TYPE_FIXUP: return "FIXUP";
case IMAGE_DEBUG_TYPE_OMAP_TO_SRC: return "OMAP_TO_SRC";
case IMAGE_DEBUG_TYPE_OMAP_FROM_SRC: return "OMAP_FROM_SRC";
case 0: return "BORLAND";
default: return "UNKNOWN";
}
}
// Comparison routine used by the qsort call in LookForCombinableSections
int SectSort( const void *a, const void *b )
{ // First, sort by characteristics
DWORD x = ((SECTION_ATTRIBUTES*)a)->Characteristics
- ((SECTION_ATTRIBUTES*)b)->Characteristics;
if ( x )
return x;
// If the characteristics match, sort by indexes, with lower values first
x = ((SECTION_ATTRIBUTES*)a)->index - ((SECTION_ATTRIBUTES*)b)->index;
return x;
}
// Comparison routine used by the qsort call in LookForAddressConflicts
int ModLoadSort( const void *a, const void *b )
{ // Sort based on the base (load) RVA
DWORD first = ((PMODULE_LOAD_INFO)a)->m_baseRVA;
DWORD second = ((PMODULE_LOAD_INFO)b)->m_baseRVA;
if ( first == second )
return 0;
return (first > second) ? 1 : -1;
}