Visual Basic Profiler

User Guide Source Back to projects

Profiler.zip

The full source code. You'll need VB6 and VC++ 6 to build this. You'll also need MASM 6 if you wish to modify the assembler file.

How it works

Profiler.dll is deployed in MTS (Component Services) where it can get it hands on the middle-tier components of interest:

Block diagram

Profiler inspects every Visual Basic DLL in the host process. They're identified by the fact that they reference the Visual Basic runtime library (MSVBVM60.DLL) in the imports section of the PE header. Profiler uses the Windows symbolic debug API (dbghelp.dll) to enumerate symbols in each DLL and examines every procedure looking for the following standard prologues:

push  ebp
mov   ebp, esp
push  ecx
push  ecx
push  ebp
mov   ebp, esp
push  XX
push  ebp
mov   ebp, esp
sub   esp, XX

and the following epilogues:

pop   edi
pop   esi
pop   ebx
leave
ret
pop   edi
pop   esi
pop   ebx
leave
ret   XXXX
pop   edi
pop   esi
leave
ret   XXXX
xor   eax, eax
leave
ret   XXXX
mov   esp, ebp
pop   ebp
ret   XXXX

If a match is found, stubs are created and the original prologue and epilogue are overwritten with jumps to the stubs. The prologue stub ends by jumping back into the original procedure. The stub contains an extra call instruction which performs the logging e.g.

push  ebp
call  _Enter
mov   ebp, esp
push  ecx
push  ecx
jmp   ...

The epilogue stub begins with an extra call e.g.

call  _Leave
pop   edi
pop   esi
pop   ebx
leave
ret

Otherwise, the stubs are exact copies of the original code. This adds three jumps and two calls to every procedure plus the logging functions which run to 22 instructions each. This is negligible even compared to the simplest VB property get.

The stubs are located at a known offset within the logging data structure, enabling the logging functions to locate the procedure's counters relative to the stacked return address. CPU cycles are calculated using the Pentium rdtsc instruction which transfers an internal 64-bit cycle counter into EDX:EAX.

To maintain the exclusive counters, the profiler needs to keep track of the call stack for each thread. When an instrumented procedure is called, the profiler applies the necessary correction to the calling procedure's exclusive time. Although it handles threads to some extent, the profiler will not make allowances for context switches so it is best to profile one thing at a time!

Future enhancement

I think it is possible to attach to a process as a debugger and "inject" a DLL into that process. It would then be possible to profile any VB application, not just code hosted in MTS. Of course, you could add Instrument and Report buttons to your own form, effectively bypassing Profiler.EXE, and use the DLL directly, in your own application.