771 lines
21 KiB
C++
771 lines
21 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// profile.cpp - Extension to dump profiling information.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Preprocessor material
|
|
|
|
// #define TEST 1
|
|
#ifdef TEST
|
|
|
|
#include <windows.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define ExtOut printf
|
|
|
|
#else
|
|
|
|
#include "singx86.h"
|
|
#define assert //
|
|
|
|
#endif
|
|
|
|
#define UNUSED(x) (x) = (x);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Forward declarations
|
|
|
|
extern "C" {
|
|
int OriginCompare(const void* a, const void* b);
|
|
int TargetCompare(const void* a, const void* b);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Global variables
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Utility methods
|
|
#ifdef TEST
|
|
|
|
static const char* fake_methods[] = { "foo", "bar", "puck", "po", "dizzy", "bazz", "escher", "ringo", "borix" };
|
|
static const size_t n_fake_methods = sizeof(fake_methods) / sizeof(fake_methods[0]);
|
|
|
|
ULONG64 MethodIp(ULONG64 ip)
|
|
{
|
|
return ip;
|
|
}
|
|
|
|
void GetMethodName(ULONG64 ip, PSTR buffer, ULONG bufferLength)
|
|
{
|
|
strncpy(buffer, fake_methods[ip], bufferLength);
|
|
}
|
|
|
|
#else // TEST
|
|
|
|
ULONG64 RoundIp(ULONG64 ip)
|
|
{
|
|
ULONG64 displacement;
|
|
if (g_ExtSymbols->GetNameByOffset(ip, NULL, 0, NULL, &displacement) == S_OK) {
|
|
return (ip - displacement);
|
|
}
|
|
return ip;
|
|
}
|
|
|
|
void GetMethodName(ULONG64 ip, PSTR buffer, ULONG bufferLength)
|
|
{
|
|
ULONG64 displacement;
|
|
ULONG bufferUsed = 0;
|
|
|
|
int status = g_ExtSymbols->GetNameByOffset(ip, buffer, bufferLength,
|
|
&bufferUsed, &displacement);
|
|
if (status != S_OK) {
|
|
_snprintf(buffer, bufferLength, "%I64x", ip);
|
|
return;
|
|
}
|
|
else if (displacement != 0) {
|
|
_snprintf(buffer + bufferUsed - 1, bufferLength - bufferUsed, "+0x%I64x", displacement);
|
|
}
|
|
}
|
|
#endif // TEST
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Target Node
|
|
|
|
struct TargetNode
|
|
{
|
|
ULONG64 jumpTarget;
|
|
int jumpCount;
|
|
|
|
struct TargetNode* next;
|
|
struct TargetNode* prev;
|
|
|
|
TargetNode(ULONG64 theJumpTarget = 0)
|
|
: jumpTarget(theJumpTarget), jumpCount(1)
|
|
{
|
|
next = this;
|
|
prev = this;
|
|
}
|
|
|
|
inline ULONG64 JumpTarget() const { return jumpTarget; }
|
|
inline int JumpCount() const { return jumpCount; }
|
|
|
|
static int CompareJumps(const TargetNode* a, const TargetNode* b)
|
|
{
|
|
if (a->jumpCount > b->jumpCount) {
|
|
return -1;
|
|
} else if (a->jumpCount < b->jumpCount) {
|
|
return +1;
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Originating Node
|
|
|
|
struct OriginNode
|
|
{
|
|
struct OriginNode* next;
|
|
struct OriginNode* prev;
|
|
|
|
private:
|
|
TargetNode sentinel;
|
|
ULONG64 jumpOrigin;
|
|
int jumpCount;
|
|
int nodeCount;
|
|
|
|
inline void Append(TargetNode* node)
|
|
{
|
|
node->next = sentinel.next;
|
|
node->next->prev = node;
|
|
node->prev = &sentinel;
|
|
sentinel.next = node;
|
|
nodeCount++;
|
|
}
|
|
|
|
public:
|
|
OriginNode(ULONG64 theJumpOrigin = 0) : sentinel(0)
|
|
{
|
|
jumpOrigin = theJumpOrigin;
|
|
jumpCount = 0;
|
|
nodeCount = 0;
|
|
|
|
this->next = this;
|
|
this->prev = this;
|
|
}
|
|
|
|
~OriginNode()
|
|
{
|
|
TargetNode* node = sentinel.next;
|
|
while (node != &sentinel) {
|
|
TargetNode* next = node->next;
|
|
delete node;
|
|
node = next;
|
|
}
|
|
}
|
|
|
|
void AddJump(ULONG64 theJumpTarget)
|
|
{
|
|
jumpCount++;
|
|
|
|
TargetNode* node = sentinel.next;
|
|
TargetNode* stop = &sentinel;
|
|
|
|
while (node != stop) {
|
|
if (node->jumpTarget == theJumpTarget) {
|
|
node->jumpCount++;
|
|
return;
|
|
}
|
|
node = node->next;
|
|
}
|
|
Append(new TargetNode(theJumpTarget));
|
|
}
|
|
|
|
inline int JumpCount() const
|
|
{
|
|
return jumpCount;
|
|
}
|
|
|
|
inline ULONG64 JumpOrigin() const { return jumpOrigin; }
|
|
|
|
TargetNode** CreateSortTable()
|
|
{
|
|
TargetNode** sortTable = new TargetNode* [nodeCount];
|
|
int insert = 0;
|
|
|
|
TargetNode* stop = &sentinel;
|
|
TargetNode* node = stop->next;
|
|
while (node != stop) {
|
|
sortTable[insert++] = node;
|
|
node = node->next;
|
|
}
|
|
assert(insert == nodeCount);
|
|
qsort(sortTable, nodeCount, sizeof(TargetNode*), TargetCompare);
|
|
return sortTable;
|
|
}
|
|
|
|
void ReleaseSortTable(TargetNode** sortTable)
|
|
{
|
|
delete [] sortTable;
|
|
}
|
|
|
|
void DisplayTargets(const char* preamble)
|
|
{
|
|
char name[512];
|
|
|
|
TargetNode** sortTable = CreateSortTable();
|
|
|
|
for (int i = 0; i < nodeCount; i++) {
|
|
TargetNode* node = sortTable[i];
|
|
GetMethodName(node->JumpTarget(), name, sizeof(name) - 1);
|
|
ExtOut("%s [%d] %s\n", preamble, node->JumpCount(), name);
|
|
}
|
|
ReleaseSortTable(sortTable);
|
|
}
|
|
|
|
static int CompareJumps(const OriginNode* a, const OriginNode* b)
|
|
{
|
|
if (a->jumpCount> b->jumpCount) {
|
|
return -1;
|
|
}
|
|
else if (a->jumpCount < b->jumpCount) {
|
|
return +1;
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Jump Table to store all IP jumps
|
|
|
|
class JumpTable
|
|
{
|
|
static const int hashBins = 32;
|
|
|
|
OriginNode hashTable[hashBins];
|
|
int totalJumps;
|
|
int nodeCount;
|
|
|
|
inline int Hash(ULONG64 theJumpOrigin)
|
|
{
|
|
return (int) ((theJumpOrigin ^
|
|
(theJumpOrigin >> 12) ^
|
|
(theJumpOrigin >> 24)
|
|
) % hashBins);
|
|
}
|
|
|
|
public:
|
|
|
|
JumpTable()
|
|
: nodeCount(0), totalJumps(0)
|
|
{
|
|
}
|
|
|
|
~JumpTable()
|
|
{
|
|
for (int i = 0; i < hashBins; i++) {
|
|
OriginNode* sentinel = &hashTable[i];
|
|
OriginNode* node = sentinel->next;
|
|
while (node != sentinel) {
|
|
OriginNode* next = node->next;
|
|
delete node;
|
|
node = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AddJump(ULONG64 theJumpOrigin, ULONG64 theJumpTarget)
|
|
{
|
|
int bin = Hash(theJumpOrigin);
|
|
|
|
OriginNode* sentinel = &hashTable[bin];
|
|
OriginNode* node = sentinel->next;
|
|
while (node != sentinel) {
|
|
if (node->JumpOrigin() == theJumpOrigin) {
|
|
node->AddJump(theJumpTarget);
|
|
totalJumps++;
|
|
return;
|
|
}
|
|
node = node->next;
|
|
}
|
|
|
|
node = new OriginNode(theJumpOrigin);
|
|
nodeCount++;
|
|
|
|
node->AddJump(theJumpTarget);
|
|
totalJumps++;
|
|
|
|
node->next = sentinel->next;
|
|
node->prev = sentinel;
|
|
node->next->prev = node;
|
|
sentinel->next = node;
|
|
}
|
|
|
|
OriginNode** CreateSortTable()
|
|
{
|
|
// Build table to sort
|
|
OriginNode** sortTable = new OriginNode* [nodeCount];
|
|
int insert = 0;
|
|
|
|
for (int i = 0; i < hashBins; i++)
|
|
{
|
|
OriginNode* sentinel = &hashTable[i];
|
|
OriginNode* node = sentinel->next;
|
|
while (node != sentinel) {
|
|
assert(node->JumpOrigin() < 0xffffffffULL);
|
|
sortTable[insert++] = node;
|
|
node = node->next;
|
|
}
|
|
}
|
|
qsort(sortTable, nodeCount, sizeof(OriginNode*), OriginCompare);
|
|
|
|
for (int i = 1; i < nodeCount; i++) {
|
|
assert(sortTable[i-1]->JumpCount() >= sortTable[i]->JumpCount());
|
|
}
|
|
|
|
return sortTable;
|
|
}
|
|
|
|
void ReleaseSortTable(OriginNode** sortTable)
|
|
{
|
|
delete [] sortTable;
|
|
}
|
|
|
|
void Display(const char* parentPreamble, const char* childPreamble)
|
|
{
|
|
OriginNode** sortTable = CreateSortTable();
|
|
char name[512];
|
|
|
|
int lastJumps = 0x7fffffff;
|
|
for (int i = 0; i < nodeCount; i++) {
|
|
OriginNode* node = sortTable[i];
|
|
int nodeJumps = node->JumpCount();
|
|
assert(nodeJumps <= lastJumps);
|
|
lastJumps = nodeJumps;
|
|
|
|
GetMethodName(node->JumpOrigin(), name, sizeof(name) - 1);
|
|
|
|
ExtOut("%d. %s %s [%d calls (%.2f%%)]\n",
|
|
i, parentPreamble, name, nodeJumps,
|
|
100.0 * (float)nodeJumps / (float) totalJumps);
|
|
node->DisplayTargets(childPreamble);
|
|
}
|
|
|
|
ReleaseSortTable(sortTable);
|
|
}
|
|
};
|
|
|
|
int OriginCompare(const void* pa, const void* pb)
|
|
{
|
|
return OriginNode::CompareJumps(*((const OriginNode**)pa),
|
|
*((const OriginNode**)pb));
|
|
}
|
|
|
|
int TargetCompare(const void* pa, const void* pb)
|
|
{
|
|
return TargetNode::CompareJumps(*(const TargetNode**)pa,
|
|
*(const TargetNode**)pb);
|
|
}
|
|
|
|
#ifdef TEST
|
|
|
|
int main()
|
|
{
|
|
JumpTable jt;
|
|
for (int calls = 0; calls < 150; calls++) {
|
|
int n = sizeof(fake_methods) / sizeof(fake_methods[0]);
|
|
int s = rand() % n_fake_methods;
|
|
int t = rand() % n_fake_methods;
|
|
jt.AddJump((ULONG64)s, (ULONG64)t);
|
|
}
|
|
jt.Display(" ", " >>");
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
struct SampleDescriptor
|
|
{
|
|
ULONG64 eip;
|
|
int count;
|
|
};
|
|
|
|
extern "C" {
|
|
static int eip_sort(const void* a, const void* b)
|
|
{
|
|
SampleDescriptor* sa = (SampleDescriptor*)a;
|
|
SampleDescriptor* sb = (SampleDescriptor*)b;
|
|
|
|
if (sa->eip > sb->eip)
|
|
return +1;
|
|
if (sa->eip < sb->eip)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int count_sort(const void* a, const void* b)
|
|
{
|
|
SampleDescriptor* sa = (SampleDescriptor*)a;
|
|
SampleDescriptor* sb = (SampleDescriptor*)b;
|
|
|
|
if (sa->count < sb->count)
|
|
return +1;
|
|
if (sa->count > sb->count)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
static HRESULT Usage()
|
|
{
|
|
ExtOut("Usage:\n"
|
|
" !sample {options}\n"
|
|
" !sample clear\n"
|
|
"Options:\n"
|
|
" -c : Print caller-callee information\n"
|
|
" -f : Print frequency distribution of methods\n"
|
|
" -k : Print callee-caller information\n"
|
|
" -l : Analyze call graph leafs only\n"
|
|
" -n count : Print count most recent sample stack traces\n"
|
|
" -t : Print sample stack traces\n"
|
|
" -x : Use exact IP values rather than rounding to method start\n"
|
|
);
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
static void
|
|
ClearLog()
|
|
{
|
|
ULONG64 address;
|
|
int newPcLength = 0;
|
|
if (g_ExtSymbols->GetOffsetByName("nt!Processor::pcLength", &address) == S_OK) {
|
|
g_ExtData->WriteVirtual(address, &newPcLength, 4, NULL);
|
|
ExtOut("Log cleared.\n");
|
|
return;
|
|
}
|
|
ExtOut("Failed: Could not locate log length variable.\n");
|
|
}
|
|
|
|
static ULONG
|
|
ScanBack(ULONG64 arrayBase,
|
|
ULONG arrayElements,
|
|
ULONG pointerSize,
|
|
ULONG pcHead,
|
|
ULONG pcLength,
|
|
ULONG traces)
|
|
{
|
|
// head points to start of next record, end of last record is zero
|
|
// so skip back 2 to get to the ending eip
|
|
ULONG pos = (pcHead + arrayElements - 2) % arrayElements;
|
|
ULONG wrap = pos;
|
|
|
|
while (traces != 0) {
|
|
ULONG64 element;
|
|
if (g_ExtData->ReadPointersVirtual(1, arrayBase + pos * pointerSize,
|
|
&element) != S_OK) {
|
|
ExtErr("Scan failed to read value.");
|
|
return pcLength;
|
|
}
|
|
|
|
if (element == 0) {
|
|
traces--;
|
|
}
|
|
|
|
pos = (pos + arrayElements - 1) % arrayElements;
|
|
if (pos == wrap)
|
|
return pcLength;
|
|
}
|
|
return (wrap + arrayElements - pos) % arrayElements;
|
|
}
|
|
|
|
EXT_DECL(sample) // Defines: PDEBUG_CLIENT Client, PCSTR args
|
|
{
|
|
JumpTable foreTable;
|
|
JumpTable backTable;
|
|
SampleDescriptor* statsValues = NULL;
|
|
|
|
EXT_ENTER(); // Defines: HRESULT status = S_OK;
|
|
|
|
ULONG pointerSize = (g_ExtControl->IsPointer64Bit() == S_OK) ? 8 : 4;
|
|
ULONG64 address = 0;
|
|
ULONG64 samples = 0;
|
|
ULONG type = 0;
|
|
ULONG subtype = 0;
|
|
ULONG64 module = 0;
|
|
|
|
bool doExactIps = false;
|
|
bool doCallerCallee = false;
|
|
bool doCalleeCaller = false;
|
|
bool doLeavesOnly = false;
|
|
bool doTraces = false;
|
|
bool doFrequencies = false;
|
|
int mostRecent = 0;
|
|
|
|
if (_strcmpi(args, "clear") == 0) {
|
|
ClearLog();
|
|
goto Exit;
|
|
}
|
|
|
|
while (*args != '\0') {
|
|
while (*args == ' ' || * args == '\t') {
|
|
args++;
|
|
}
|
|
|
|
if (*args != '-' && *args != '/') {
|
|
Usage();
|
|
EXT_CHECK(~S_OK);
|
|
}
|
|
args++;
|
|
switch (tolower(*args++)) {
|
|
case 'c':
|
|
doCallerCallee = true;
|
|
break;
|
|
case 'f':
|
|
doFrequencies = true;
|
|
break;
|
|
case 'k':
|
|
doCalleeCaller = true;
|
|
break;
|
|
case 'l':
|
|
doLeavesOnly = true;
|
|
break;
|
|
case 'n':
|
|
while ((*args == ' ' || *args == '\t') && *args != '\0') {
|
|
args++;
|
|
}
|
|
mostRecent = atoi(args);
|
|
while (*args != ' ' && *args != '\t' && *args != '\0') {
|
|
args++;
|
|
}
|
|
break;
|
|
case 't':
|
|
doTraces = true;
|
|
break;
|
|
case 'x':
|
|
doExactIps = true;
|
|
break;
|
|
default:
|
|
Usage();
|
|
EXT_CHECK(~S_OK);
|
|
}
|
|
}
|
|
|
|
if (doCallerCallee == false &&
|
|
doCalleeCaller == false &&
|
|
doTraces == false &&
|
|
doFrequencies == false) {
|
|
Usage();
|
|
EXT_CHECK(~S_OK);
|
|
}
|
|
|
|
EXT_CHECK(g_ExtSymbols->GetOffsetByName("nt!Processor::pcSamples", &address));
|
|
EXT_CHECK(g_ExtSymbols->GetOffsetTypeId(address, &type, &module));
|
|
EXT_CHECK(g_ExtData->ReadPointersVirtual(1, address, &samples));
|
|
|
|
int pcHead;
|
|
EXT_CHECK(g_ExtSymbols->GetOffsetByName("nt!Processor::pcHead", &address));
|
|
EXT_CHECK(g_ExtData->ReadVirtual(address, &pcHead, 4, NULL));
|
|
|
|
int pcLength;
|
|
EXT_CHECK(g_ExtSymbols->GetOffsetByName("nt!Processor::pcLength", &address));
|
|
EXT_CHECK(g_ExtData->ReadVirtual(address, &pcLength, 4, NULL));
|
|
|
|
CHAR name[512];
|
|
EXT_CHECK(g_ExtSymbols->GetTypeName(module, type, name, arrayof(name), NULL));
|
|
ExtVerb(" pcSamples type: %s\n", name);
|
|
|
|
int len = strlen(name);
|
|
if (len > 3 &&
|
|
name[len-3] == '[' &&
|
|
name[len-2] == ']' &&
|
|
name[len-1] == '*') {
|
|
name[len-3] = '\0';
|
|
|
|
EXT_CHECK(g_ExtSymbols->GetTypeId(module, name, &subtype));
|
|
EXT_CHECK(g_ExtSymbols->GetTypeName(module, subtype, name, arrayof(name), NULL));
|
|
ExtVerb(" pcSamples type: %s\n", name);
|
|
}
|
|
|
|
ULONG lengthOffset = 0;
|
|
EXT_CHECK(g_ExtSymbols->GetFieldOffset(module, type, "overall_length", &lengthOffset));
|
|
|
|
ULONG valuesOffset = 0;
|
|
EXT_CHECK(g_ExtSymbols->GetFieldOffset(module, type, "values", &valuesOffset));
|
|
|
|
ULONG maxLength = 0;
|
|
EXT_CHECK(g_ExtData->ReadVirtual(samples + lengthOffset,
|
|
&maxLength, sizeof(maxLength), NULL));
|
|
|
|
if (mostRecent != 0) {
|
|
pcLength = ScanBack(samples + valuesOffset, maxLength, pointerSize, pcHead, pcLength, mostRecent);
|
|
}
|
|
|
|
if (doTraces)
|
|
ExtOut("<Sample Round> <DeltaTicks> <interrupt> <eip ...>\n");
|
|
|
|
int pos = (pcHead + maxLength - pcLength) % maxLength;
|
|
|
|
// Allocate statistics array
|
|
statsValues = new SampleDescriptor [pcLength];
|
|
int statsCount = 0;
|
|
|
|
ExtOut("Starting analysis of sample log of %d entries.\n", pcLength);
|
|
if (doLeavesOnly)
|
|
ExtOut("Analyzing leaf nodes only.\n");
|
|
|
|
if (doTraces) {
|
|
ExtOut("==================================================================\n");
|
|
ExtOut("Raw call stacks");
|
|
}
|
|
|
|
while (pcLength > 0)
|
|
{
|
|
// Get Sample number
|
|
ULONG64 number;
|
|
EXT_CHECK(g_ExtData->ReadPointersVirtual(1, samples + valuesOffset + pos * pointerSize, &number));
|
|
pos = (pos + 1) % maxLength;
|
|
|
|
// Get ticks since last sample
|
|
ULONG64 deltaTicks;
|
|
EXT_CHECK(g_ExtData->ReadPointersVirtual(1, samples + valuesOffset + pos * pointerSize, &deltaTicks));
|
|
pos = (pos + 1) % maxLength;
|
|
|
|
// Get interrupt
|
|
ULONG64 interrupt;
|
|
EXT_CHECK(g_ExtData->ReadPointersVirtual(1, samples + valuesOffset + pos * pointerSize, &interrupt));
|
|
pos = (pos + 1) % maxLength;
|
|
|
|
pcLength -= 3;
|
|
|
|
if (doTraces) {
|
|
ExtOut(" %10d %10d %2x",
|
|
(int)number, (int)deltaTicks, (int)interrupt);
|
|
}
|
|
|
|
// Get instruction pointers
|
|
int doneFirst = 0;
|
|
ULONG64 lastIp = 0;
|
|
bool leafJumpDone = false;
|
|
bool leafIpDone = false;
|
|
do {
|
|
ULONG64 eip = 0;
|
|
EXT_CHECK(g_ExtData->ReadPointersVirtual(1, samples + valuesOffset + pos * pointerSize, &eip));
|
|
|
|
if (!doExactIps) {
|
|
eip = RoundIp(eip);
|
|
}
|
|
|
|
if ((leafIpDone == false || doLeavesOnly == false) && eip != 0) {
|
|
statsValues[statsCount].eip = eip;
|
|
statsValues[statsCount].count = 0;
|
|
statsCount++;
|
|
leafIpDone = true;
|
|
}
|
|
|
|
if (lastIp != 0 && eip != 0 &&
|
|
(leafJumpDone == false || doLeavesOnly == false)) {
|
|
foreTable.AddJump(lastIp, eip);
|
|
backTable.AddJump(eip, lastIp);
|
|
leafJumpDone = true;
|
|
}
|
|
|
|
lastIp = eip;
|
|
|
|
pos = (pos + 1) % maxLength;
|
|
pcLength -= 1;
|
|
|
|
if (eip == 0) {
|
|
break;
|
|
}
|
|
|
|
doneFirst |= 1;
|
|
|
|
if (doTraces == false)
|
|
continue;
|
|
|
|
if (g_ExtSymbols->GetNameByOffset(eip, (PSTR)&name, sizeof(name), NULL, NULL) == S_OK) {
|
|
if (doneFirst)
|
|
ExtOut(" <- %s", name);
|
|
else
|
|
ExtOut(" %s", name);
|
|
}
|
|
else {
|
|
if (doneFirst)
|
|
ExtOut(" <- %p", eip);
|
|
else
|
|
ExtOut(" %p", eip);
|
|
}
|
|
} while (pcLength > 0);
|
|
|
|
if (doTraces)
|
|
ExtOut("\n");
|
|
}
|
|
|
|
if (doFrequencies) {
|
|
ExtOut("==================================================================\n");
|
|
ExtOut("Raw instruction hits\n");
|
|
|
|
if (statsCount > 0) {
|
|
// Sort instruction pointers so we can count frequency of each unique
|
|
// value.
|
|
qsort(statsValues, statsCount, sizeof(SampleDescriptor), eip_sort);
|
|
|
|
int outIndex = 0;
|
|
ULONG64 lastIp = statsValues[0].eip;
|
|
for (int i = 0; i < statsCount; i++) {
|
|
if (statsValues[i].eip == lastIp) {
|
|
statsValues[outIndex].count++;
|
|
}
|
|
else {
|
|
outIndex++;
|
|
statsValues[outIndex].eip = statsValues[i].eip;
|
|
statsValues[outIndex].count = 1;
|
|
lastIp = statsValues[i].eip;
|
|
}
|
|
}
|
|
|
|
// Sort on frequency and display
|
|
qsort(statsValues, outIndex, sizeof(SampleDescriptor), count_sort);
|
|
ExtOut("Top of the pops has %d entries:\n", outIndex);
|
|
|
|
for (int i = 0; i < outIndex; i++) {
|
|
if (g_ExtSymbols->GetNameByOffset(statsValues[i].eip,
|
|
(PSTR)&name,
|
|
sizeof(name),
|
|
NULL, NULL) == S_OK) {
|
|
ExtOut("% 7d %s\n", statsValues[i].count, name);
|
|
}
|
|
else {
|
|
ExtOut("% 7d %p\n", statsValues[i].count, statsValues[i].eip);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (doCallerCallee) {
|
|
ExtOut("==================================================================\n");
|
|
ExtOut("Calls by target method to other methods:\n");
|
|
backTable.Display("Calls made by", " ");
|
|
}
|
|
|
|
if (doCalleeCaller) {
|
|
ExtOut("==================================================================\n");
|
|
ExtOut("Calls to target method by other methods.\n");
|
|
foreTable.Display("Calls made to", " ");
|
|
}
|
|
|
|
// EXT_LEAVE equivalent
|
|
Exit:
|
|
delete [] statsValues;
|
|
ExtRelease();
|
|
return status;
|
|
}
|
|
#endif // TEST
|