VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
Persistable = 0 'NotPersistable
DataBindingBehavior = 0 'vbNone
DataSourceBehavior = 0 'vbNone
MTSTransactionMode = 0 'NotAnMTSObject
END
Attribute VB_Name = "clsLIMODS"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' John Robbins - June '99 Microsoft Systems Journal Bugslayer Column
' The LIMODS helper class for Visual Basic development.
' 0. Include this class file in your Visual Basic project. Create a global
' instance of this class in your project. (I would call it Trace)
' 1. Just call Trace.Trace to redirect everything through here.
' 2. LIMODS is only active using compiled Visual Basic. If you run this class
' under the Visual Basic IDE debugger, your trace statements will be sent
' through the Debug.Print regular trace statement.
Option Explicit
Private Declare Function LoadLibrary Lib "kernel32" _
Alias "Loadlibrarya" (ByVal lpLibFileName As String) As Long
Private Declare Sub OutputDebugString Lib "kernel32" _
Alias "OutputDebugStringA" (ByVal lpOutputString As String)
Private Declare Function GetModuleFileName Lib "kernel32" _
Alias "GetModuleFileNameA" (ByVal hModule As Long, _
ByVal lpFileName As String, ByVal nSize As Long) As Long
Private Declare Function GetModuleHandle Lib "kernel32" _
Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
Private m_IsInIDE As Boolean
#If LIMODS Then
Private Sub Class_Initialize()
' I need to check the module this class is running under. If it is
' running under VB6.EXE, then I just redirect everything to Debug.Trace.
Dim Str As String
Dim lRet As Long
Str = String$(255, vbNullChar)
lRet = GetModuleHandle(vbNullString)
lRet = GetModuleFileName(lRet, Str, 255)
lRet = InStr(1, UCase(Str), "VB6.EXE", vbTextCompare)
' Check VB5 too.
If (0 = lRet) Then
lRet = InStr(1, UCase(Str), "VB5.EXE", vbTextCompare)
End If
' If sRet is 0 then the main module is not the Visual Basic IDE so
' I can load LIMODSDLL.DLL.
If (0 = lRet) Then
LoadLibrary "LIMODSDLL.DLL"
m_IsInIDE = False
Else
m_IsInIDE = True
End If
End Sub
#End If
#If LIMODS Then
Public Sub Trace(sOut As Variant)
If (True = m_IsInIDE) Then
Debug.Print sOut
Else
Dim s As String
s = sOut
OutputDebugString s
End If
End Sub
#Else ' LIMODS is *not* conditionally defined.
Public Sub Trace(sOut As Variant)
End Sub
#End If
Figure 3
GenLIMODS.EXE Sample Address Ranges
Start
|
End
|
Source File
|
0x00401910
|
0x00401A9A
|
CommandLine.cpp
|
0x00401D10
|
0x00402FB8
|
GenLIMODS.cpp
|
0x00403510
|
0x00403841
|
..\include\SymbolEngine.h
|
0x00403870
|
0x00403B94
|
ResString.h
|
0x00403BA0
|
0x00403BBD
|
GenLIMODS.h
|
0x00403BD0
|
0x00403CF9
|
..\include\SymbolEngine.h
|
0x004047B0
|
0x004047CC
|
GenLIMODS.cpp
|
0x004071A0
|
0x00408598
|
LOMFile.cpp
|
0x00409D30
|
0x0040A5A2
|
ReadIgnoreFiles.cpp
|
0x0040C8E0
|
0x0040C98C
|
Verbose.cpp
|
Figure 4
A Sample .LOM File
[Module Info]
DateTimeStamp=36e1cad9
BaseAddress=400000
ModuleName=LIMODS.EXE
[Ranges]
RangeCount=16
Range0=004017C0,00401C7E,0,D:\Dev\Column\June99\SourceCode\LIMODS\About.cpp
Range1=00401EE0,00402303,0,D:\Dev\Column\June99\SourceCode\LIMODS\BigIcon.CPP
Range2=00402420,00402A36,0,D:\Dev\Column\June99\SourceCode\LIMODS\LIMODS.cpp
Range3=00402D30,004036F7,0,D:\Dev\Column\June99\SourceCode\LIMODS\LIMODSDoc.cpp
Range4=00404480,004047DD,0,D:\Dev\Column\June99\SourceCode\LIMODS\LIMODSOptions.cpp
Range5=00404920,004057F3,0,D:\Dev\Column\June99\SourceCode\LIMODS\LIMODSView.cpp
Range6=00405D40,00405D80,0,D:\Dev\Column\June99\SourceCode\LIMODS\LIMODSDoc.h
Range7=00406120,0040731C,0,D:\Dev\Column\June99\SourceCode\LIMODS\LOMFile.cpp
Range8=00408A50,00408E4F,0,D:\Dev\Column\June99\SourceCode\LIMODS\MainFrm.cpp
Range9=00408FC0,00409266,0,D:\Dev\Column\June99\SourceCode\LIMODS\OptionsDialog.cpp
Range10=00409A80,00409AD6,0,atonexit.c
Range11=00409B00,00409CE8,0,crtexe.c
Range12=00409D20,00409D35,0,intel\fp8.c
Range13=00409D40,00409D45,0,merr.c
Range14=00409D50,00409D53,0,dllargv.c
Range15=00409DF0,00409E90,0,appmodul.cpp
[Sources]
Source0=0,D:\Dev\Column\June99\SourceCode\LIMODS\About.cpp
Source1=0,D:\Dev\Column\June99\SourceCode\LIMODS\BigIcon.CPP
Source2=0,D:\Dev\Column\June99\SourceCode\LIMODS\LIMODS.cpp
Source3=0,D:\Dev\Column\June99\SourceCode\LIMODS\LIMODSDoc.cpp
Source4=0,D:\Dev\Column\June99\SourceCode\LIMODS\LIMODSOptions.cpp
Source5=0,D:\Dev\Column\June99\SourceCode\LIMODS\LIMODSView.cpp
Source6=0,D:\Dev\Column\June99\SourceCode\LIMODS\LIMODSDoc.h
Source7=0,D:\Dev\Column\June99\SourceCode\LIMODS\LOMFile.cpp
Source8=0,D:\Dev\Column\June99\SourceCode\LIMODS\MainFrm.cpp
Source9=0,D:\Dev\Column\June99\SourceCode\LIMODS\OptionsDialog.cpp
Source10=0,appmodul.cpp
Source11=0,atonexit.c
Source12=0,crtexe.c
Source13=0,intel\fp8.c
Source14=0,merr.c
Source15=0,dllargv.c
SourceCount=16
Figure 5
An Expanded __cdecl Hooking Function
VOID NAKEDDEF LIMODS_DiagOutputA( void ){
DWORD_PTR dwRet; //Holds the return address in order to find the caller.
// The saved ESI so VC6 debug builds work.
DWORD_PTR dwESI;
__asm PUSH EBP /* Set up the standard frame.*/
__asm MOV EBP, ESP
__asm SUB ESP, __LOCAL_SIZE /* Save room for the locals */
__asm MOV EAX, EBP /* EBP has the stack coming */
/* into the fn. in it. */
__asm ADD EAX, 4 /* Account for PUSH EBP */
__asm MOV EAX, [EAX] /* Get return address. */
__asm MOV [dwRet], EAX /* Save return address. */
__asm MOV [dwESI], ESI /* Save ESI so chkesp in dbg */
/* builds works. */
// Call the function that determines if this address is one to show.
// The return value is in EAX after this call and is checked below.
// A return of TRUE means call the trace function. A return of
// FALSE means skip the trace function.
CheckIfAddressIsOn( dwRet );
__asm MOV ESI, [dwESI] /* Restore ESI. */
__asm ADD ESP, __LOCAL_SIZE /* Take away local var space */
__asm MOV ESP, EBP /* Restore standard frame. */
__asm POP EBP
// Here's where the fun begins! The above 4 lines of asm code
// restored the stack to exactly what it looked like coming into
// this function so I am now prepared to call the real function.
// pReadDiagOutputA holds the real function address that I got
// during initialization.
__asm TEST EAX , EAX /* Test EAX for zero. */
__asm JZ lblDiagOutputA /* If zero, just return. */
__asm JMP pReadDiagOutputA /* Do it! THE JUMP WILL */
/* RETURN TO THE CALLER NOT */
/* TO THIS FUNCTION. */
lblDiagOutputA: \
/* Skipped the TRACE!, just */
__asm RET /* return to the caller. */
}
Figure 6
HookOrdinalExport
BOOL BUGSUTIL_DLLINTERFACE __stdcall HookOrdinalExport(HMODULE hModule,
LPCTSTR szImportMod, DWORD dwOrdinal, PROC pHookFunc, PROC *ppOrigAddr){
ASSERT (NULL != hModule);
ASSERT (FALSE == IsBadStringPtr(szImportMod, MAX_PATH));
ASSERT (0 != dwOrdinal);
ASSERT (FALSE == IsBadCodePtr(pHookFunc));
if((NULL == hModule)||(TRUE == IsBadStringPtr(szImportMod, MAX_PATH))||
(0 == dwOrdinal)||(TRUE == IsBadCodePtr(pHookFunc))){
SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);
return(FALSE);
}
if(NULL != ppOrigAddr){
ASSERT (FALSE == IsBadWritePtr(ppOrigAddr, sizeof(PROC)));
if(TRUE == IsBadWritePtr(ppOrigAddr, sizeof(PROC))){
SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);
return(FALSE);
}
}
// Find the import descriptor first.
PIMAGE_IMPORT_DESCRIPTOR pImportDesc =
GetNamedImportDescriptor(hModule, szImportMod);
if(NULL == pImportDesc){
// The requested module was not imported. This is not an error.
return(TRUE);
}
// Get the original thunk information for this DLL. I cannot use
// the thunk information stored in the pImportDesc->FirstThunk
// because the that is the array that the loader has already
// bashed to fix up all the imports. This pointer gives us access
// to the function names.
PIMAGE_THUNK_DATA pOrigThunk = MakePtr(PIMAGE_THUNK_DATA, hModule,
pImportDesc->OriginalFirstThunk);
// Get the array pointed to by the pImportDesc->FirstThunk. This is
// where I will do the actual bash.
PIMAGE_THUNK_DATA pRealThunk = MakePtr(PIMAGE_THUNK_DATA, hModule,
pImportDesc->FirstThunk);
// The flag is to be set from the thunk so make it easy to look up.
DWORD dwCompareOrdinal = IMAGE_ORDINAL_FLAG|dwOrdinal;
// Loop through and look for the one that matches the ordinal.
while ( NULL != pOrigThunk->u1.Function ){
// Only look at those that are imported by ordinal.
if(IMAGE_ORDINAL_FLAG==(pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG)){
// Is this the one?
if ( dwCompareOrdinal == pOrigThunk->u1.Ordinal){
// I found it. Now I need to change the protection to
// writeable before I do the blast. Note that I am now
// blasting into the real thunk area!
MEMORY_BASIC_INFORMATION mbi_thunk;
VirtualQuery(pRealThunk, &mbi_thunk,
sizeof(MEMORY_BASIC_INFORMATION));
VERIFY (VirtualProtect(mbi_thunk.BaseAddress,
mbi_thunk.RegionSize, PAGE_READWRITE,
&mbi_thunk.Protect));
// Save the original address if requested.
if(NULL != ppOrigAddr){
*ppOrigAddr = (PROC)pRealThunk->u1.Function;
}
// Do the actual hook.
pRealThunk->u1.Function = (PDWORD)pHookFunc;
DWORD dwOldProtect;
// Change the protection back to what it was before.
VERIFY (VirtualProtect(mbi_thunk.BaseAddress,
mbi_thunk.RegionSize, mbi_thunk.Protect,
&dwOldProtect));
SetLastError(ERROR_SUCCESS); // Life is good.
return(TRUE);
}
}
// Increment both tables.
pOrigThunk++ ;
pRealThunk++ ;
}
// Nothing was hooked. This is technically not an error. It just
// means that the module is imported but the one function is not.
SetLastError ( ERROR_SUCCESS ) ;
return ( FALSE ) ;
}
Figure 7
VectorWalk.cpp
#include<stdlib.h>
#include<time.h>
#include<iostream.h>
#include<vector>
using namespace std;
int vectorwalk(vector<int> &v){
int vsize = v.size();
for(int i=0; i<vsize; i++){
cout << "v[" << i << "] = " << v[i] << endl;
}
cout << endl;
return vsize;
}
main(){
vector<int> a;
int size;
srand( (unsigned)time( NULL ) );
do{
size = rand()/100;
}while((size != 0)&&(size > 50));
for(int i = 0; i<size; i++){
a.push_back(rand());
}
return 0;
}