Introduction
Part 1 - What is a DbgEng Extension?
Part 2 - A Use Case & the Problem Setup
Part 3 - A Crash Course on Object Layout
Now that we know how to solve our problem conceptually, we can put pen
to paper. Metaphorically speaking, I suppose. As I said in the last
post, our strategy will be to search memory for INT_PTRs matching the
MethodTable for our ArbitraryType. When we find matches, we'll perform
some further validation to reduce the likelihood of false positives.
It's not unreasonable to assume that if we're successful in writing an
extension to look at this class, we might want to do something like it
again in the future. So let's define an interface for commands that
search through memory.
// ------------------------------------------------------
// SearchCommand.h
// Defines the interface for extension commands
// that search through memory.
//
#pragma once
class SearchCommand
{
public:
// Called whenever the search pattern is encountered
// at the provided offset. The method should return
// true if the offset is a hit.
virtual bool HandleMatch(ULONG64 offset)=0;
// Called when the search is finished. The parameter
// will contain the total number of matches found.
virtual void ShowResults(int totalHits)=0;
};
This should give us some flexibility later on if we need it. We can
also abstract the process of searching through memory. The DbgEng API
that is available to us is the
IDebugDataSpaces interface. This defines a
SearchVirtual function, which we'll use to scan for the ArbitraryType's MethodTable. This is its definition.
HRESULT
IDebugDataSpaces::SearchVirtual(
IN ULONG64 Offset
IN ULONG64 Length
IN PVOID Pattern
IN ULONG PatternSize
IN ULONG PatternGranularity
OUT PULONG64 MatchOffset
);
To make our algorithm generic, we'll add a templated function to our extension class.
// ------------------------------------------------------
// dmext.h
//
#pragma once
#include "engextcpp.hpp"
#include "searchcommand.h"
class EXT_CLASS : public ExtExtension
{
protected:
// Does a range search for the pattern, keeps track of the hits, and
// calls methods matching the SearchCommand interface on an instance
// of the search_command parameter.
//
template<class search_command>
inline void Search(ULONG64 pattern, ULONG64 start, ULONG64 end)
{
if( start > end )
{
Err("The start cannot be after the end.\n");
return;
}
search_command sc;
Out("Searching %08I64x to %08I64x.\n", start, end);
HRESULT hr = 0;
int hits = 0;
ULONG64 offs = start;
do
{
hr = m_Data->SearchVirtual(offs,
end - offs,
&pattern,
this->m_PtrSize,
1,
&offs);
if( hr == S_OK )
{
if( sc.HandleMatch(offs) )
{
++hits;
}
// Search again, starting at the the next
// pointer-sized location.
offs += m_PtrSize;
}
}
while( hr == S_OK );
sc.ShowResults(hits);
}
// Shortcut for an x86 without the /3GB switch.
template<class search_command>
inline void Search(ULONG64 pattern)
{
this->Search<search_command>(pattern, 0, 0x7fffffff);
}
public:
EXT_CLASS();
EXT_COMMAND_METHOD(atstat);
};
I also added a shortcut function that searches all of the virtual
memory that is available to user mode. This function assumes that we're
debugging on an Intel x86 machine, and that the process is not
LARGEADDRESSAWARE--that is to say, it can't make use of more that 2
gigabytes of virtual memory.
(A brief aside: although I'm using ULONG64 addresses and other
conventions, I'm making no sincere attempt to ensure that this
extension will work properly with a 64-bit debuggee. That much should be
obvious from my last shortcut. Where I can, I will try to make life
easy for someone writing a port.)
We now have enough framework to implement the skeleton of our extension
command. For now, we'll just spit out addresses when we think we have a
match.
// ------------------------------------------------------
// atstat.cpp
//
#include "stdafx.h"
#include "dmext.h"
class AtStatCmd : public SearchCommand
{
public:
virtual void ShowResults(int totalHits);
virtual bool HandleMatch(ULONG64 offset);
AtStatCmd();
};
AtStatCmd::AtStatCmd()
{
g_Ext->Out("Searching for ArbitraryTypes...\n");
g_Ext->Out("--------------------------------------------\n");
}
bool AtStatCmd::HandleMatch(ULONG64 offset)
{
g_Ext->Out("%08I64x\n", offset);
return true;
}
void AtStatCmd::ShowResults(int totalHits)
{
g_Ext->Out("--------------------------------------------\n");
g_Ext->Out("Found %d total instances.\n\n", totalHits);
}
EXT_COMMAND(atstat,
"Displays statistics about ArbitraryType instances in memory.",
"{;e;The MethodTable for SampleApp.ArbitraryType.}")
{
ULONG64 mt = this->GetUnnamedArgU64(0);
this->Search<AtStatCmd>(mt);
}
The !atstat command is defined using the EngExtCpp framework's macro;
this will automatically parse any parameters and provide debugger help.
Look in the engextcpp.hpp header for the definition of this macro--I'm
not even going to try to explain it here.
As you can see, I've simplified matters by assuming that we can
just retrieve the MethodTable for our type by some other means and
provide it to the extension.
Here's some output of what we have finished so far:
0:000> .load C:\src\samples\dmext\dmext\objfre_wnet_x86\i386\dmext ;
0:000> .load C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\SOS
0:000> !name2ee sampleapp!SampleApp.ArbitraryType
Module: 00912c14 (SampleApp.exe)
Token: 0x02000003
MethodTable: 009131b0
EEClass: 00911410
Name: SampleApp.ArbitraryType
0:000> !atstat 00912c14
Searching for ArbitraryTypes...
--------------------------------------------
Searching 00000000 to 7fffffff.
0012f560
0012f72c
00167628
0016832c
001685f4
0016860c
00168624
0016abec
0016ae28
009101c0
009101e8
009111f8
00911268
009112d0
00911414
00911478
00913058
00913118
009131c4
00c316b8
--------------------------------------------
Found 20 total instances.
In the next post, we'll work on trimming down false positives and
accomplishing what we set out to do: showing statistics about the
"colors" of the ArbitraryTypes.