Skip to content

Commit

Permalink
SafeWaitHandle search, non-handle resource are named as such and incl…
Browse files Browse the repository at this point in the history
…uded in dot output.

* Event handles are matched with SafeWaitHandle CLR objects if possible.
* Non-handle resources are named correctly as Address, Delay or hWnd
* Non-handle resources are included in dot graph output.
  • Loading branch information
krk committed Oct 23, 2015
1 parent 52c0e10 commit 3a3d718
Show file tree
Hide file tree
Showing 13 changed files with 648 additions and 19 deletions.
59 changes: 49 additions & 10 deletions cosos/Extension.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Implements Extension class that provides entry points to the debugging engine.
#include <iostream>
#include <fstream>
#include <memory>
#include <cstdio>

#include "AddressCommandParser.h"
#include "EEHeapCommandParser.h"
Expand All @@ -47,6 +48,8 @@ Implements Extension class that provides entry points to the debugging engine.
#include "DbgEngLogger.h"
#include "HtraceCommandParser.h"
#include "DbgEngMemoryReader.h"
#include "DumpHeapCommandParser.h"
#include "SafeWaitHandleParser.h"

//----------------------------------------------------------------------------
//
Expand Down Expand Up @@ -104,9 +107,9 @@ HRESULT EXT_CLASS::Initialize()
DebugControl->GetWindbgExtensionApis64(&ExtensionApis);

#if _DEBUG
dprintf("COSOS v0.2.2 (%s) - Cousin of Son of Strike (DEBUG build) loaded.\n", __TIMESTAMP__);
dprintf("COSOS v0.2.3 (%s) - Cousin of Son of Strike (DEBUG build) loaded.\n", __TIMESTAMP__);
#else
dprintf("COSOS v0.2.2 (%s) - Cousin of Son of Strike loaded.\n", __TIMESTAMP__);
dprintf("COSOS v0.2.3 (%s) - Cousin of Son of Strike loaded.\n", __TIMESTAMP__);
#endif

DebugControl->Release();
Expand Down Expand Up @@ -291,9 +294,9 @@ EXT_COMMAND(waitingforobjects,
auto wap = WaitApiStackParser(memory_reader, logger);

auto handles = std::vector<std::pair<unsigned long, unsigned long>>();
auto waited_upon_addresses = std::vector<std::pair<unsigned long, unsigned long>>();
auto waited_upon_others = std::vector<std::tuple<unsigned long, unsigned long, std::string>>();

wap.GetHandlesAndAddresses(stackTracesOutput, handles, waited_upon_addresses);
wap.GetHandlesAndAddresses(stackTracesOutput, handles, waited_upon_others);

// Save wait graph.
bool save_graph = this->HasArg("dot");
Expand All @@ -314,17 +317,38 @@ EXT_COMMAND(waitingforobjects,
dot_file << "digraph {\n";
}

// TODO need to find MethodTable of SafeWaitHandle and pass it to DumpHeap.
// Find SafeWaitHandle objects in the heap.
auto dumpheap = DumpHeapCommandParser(executor, logger);
auto dumpheap_output = dumpheap.execute("Microsoft.Win32.SafeHandles.SafeWaitHandle");

auto swh_parser = SafeWaitHandleParser(memory_reader, logger);
auto swh_output = swh_parser.execute(dumpheap_output);

auto handle_address = swh_output.get_handle_addresses();

// WaitOnAddress and waiting on handles are exclusive.
for (auto waited_upon_address : waited_upon_addresses)
for (auto waited_upon_value : waited_upon_others)
{
this->Dml("<?dml?><exec cmd=\"dd %x L1\">%x</exec> Address:\n", waited_upon_address.second, waited_upon_address.second);
this->Dml("\t<?dml?><exec cmd=\"~~[%x]s\">%x</exec>\n", waited_upon_address.first, waited_upon_address.first);
this->Dml("<?dml?><exec cmd=\"dd %x L1\">%x</exec> %s:\n", std::get<1>(waited_upon_value), std::get<1>(waited_upon_value), std::get<2>(waited_upon_value).c_str());
this->Dml("\t<?dml?><exec cmd=\"~~[%x]s\">%x</exec>\n", std::get<0>(waited_upon_value), std::get<0>(waited_upon_value));

if (save_graph)
{
dot_file << std::hex << "\"" << std::get<0>(waited_upon_value) << "\" [shape=cds]" << std::endl;
dot_file << "\"" << std::get<0>(waited_upon_value) << "\" -> \"" << std::hex << std::get<1>(waited_upon_value) << " " << std::get<2>(waited_upon_value).c_str() << "\" [label = \"is waiting\"]" << std::endl;
}
}

HtraceCommandParser htraceCommandParser(executor, logger);

auto is_htrace_enabled = htraceCommandParser.is_enabled();

if (!is_htrace_enabled)
{
dprintf("htrace is not enabled.\n");
}

for (auto thread_handle : handles)
{
HandleCommandParser handleCommandParser(executor, logger);
Expand All @@ -335,6 +359,21 @@ EXT_COMMAND(waitingforobjects,

if (prev_handle != thread_handle.second)
{
std::string handle_address_dml = "";

if (handle_address != nullptr)
{
auto address = handle_address->find(thread_handle.second);

if (address != handle_address->end())
{
char buf[100];
memset(buf, 0, 100);
_snprintf(buf, 100, " object <exec cmd=\"!do %x\">%x</exec>", address->second, address->second);
handle_address_dml = std::string(buf);
}
}

if (is_htrace_enabled)
{
/* Find last opener. */
Expand All @@ -346,7 +385,7 @@ EXT_COMMAND(waitingforobjects,
{
auto last_opener_thread = htrace_output.get_thread_id();

this->Dml("<?dml?><exec cmd=\"!handle %x f\">%x</exec> %s last opened by <?dml?><exec cmd=\"~~[%x]s\">%x</exec>:\n", thread_handle.second, thread_handle.second, handle_type.c_str(), last_opener_thread, last_opener_thread);
this->Dml("<?dml?><exec cmd=\"!handle %x f\">%x</exec> %s%s last opened by <?dml?><exec cmd=\"~~[%x]s\">%x</exec>:\n", thread_handle.second, thread_handle.second, handle_type.c_str(), handle_address_dml, last_opener_thread, last_opener_thread);

if (save_graph)
{
Expand All @@ -356,12 +395,12 @@ EXT_COMMAND(waitingforobjects,
}
else
{
this->Dml("<?dml?><exec cmd=\"!handle %x f\">%x</exec> %s:\n", thread_handle.second, thread_handle.second, handle_type.c_str());
this->Dml("<?dml?><exec cmd=\"!handle %x f\">%x</exec> %s%s:\n", thread_handle.second, thread_handle.second, handle_type.c_str(), handle_address_dml.c_str());
}
}
else
{
this->Dml("<?dml?><exec cmd=\"!handle %x f\">%x</exec> %s:\n", thread_handle.second, thread_handle.second, handle_type.c_str());
this->Dml("<?dml?><exec cmd=\"!handle %x f\">%x</exec> %s%s:\n", thread_handle.second, thread_handle.second, handle_type.c_str(), handle_address_dml.c_str());
}
}

Expand Down
35 changes: 29 additions & 6 deletions cosos/WaitApiStackParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Implements WaitApiStackParser for stack trace outputs calling kernel wait APIs.
#include <algorithm>
#include "WaitApiStackParser.h"

const int OBJECT_COUNT_1 = 101;

std::map<std::string, std::pair<unsigned long, unsigned long>> WaitApiStackParser::_symbol_object = {
/* symbol_name, count_arg_number, address_arg_number */
{ "ntdll!NtWaitForSingleObject", std::make_pair(1, KernelObjectDescriptor::ADDRESS_IS_IMMEDIATE) },
Expand All @@ -40,13 +42,30 @@ std::map<std::string, std::pair<unsigned long, unsigned long>> WaitApiStackParse
{ "ntdll!NtWaitForWorkViaWorkerFactory", std::make_pair(1, KernelObjectDescriptor::ADDRESS_IS_IMMEDIATE) },
{ "ntdll!NtSignalAndWaitForSingleObject", std::make_pair(2, KernelObjectDescriptor::ADDRESS_IS_IMMEDIATE) },
{ "ntdll!NtWaitForAlertByThreadId", std::make_pair(1, KernelObjectDescriptor::ADDRESS_IS_IMMEDIATE) },
{ "ntdll!NtRemoveIoCompletion", std::make_pair(1, KernelObjectDescriptor::ADDRESS_IS_IMMEDIATE) },
{ "ntdll!NtDelayExecution", std::make_pair(2, OBJECT_COUNT_1) },
{ "user32!NtUserMessageCall", std::make_pair(1, KernelObjectDescriptor::ADDRESS_IS_IMMEDIATE) },

// https://msdn.microsoft.com/en-us/library/windows/desktop/ms687069(v=vs.85).aspx
};

bool is_handle(std::string symbol_name)
{
return symbol_name != "ntdll!NtWaitForAlertByThreadId";
return symbol_name != "ntdll!NtWaitForAlertByThreadId"
&& symbol_name != "ntdll!NtDelayExecution"
&& symbol_name != "user32!NtUserMessageCall";
}

std::string WaitApiStackParser::get_name(const std::string& symbol_name)
{
if (symbol_name == "ntdll!NtWaitForAlertByThreadId")
return "Address";
else if (symbol_name == "ntdll!NtDelayExecution")
return "Delay";
else if (symbol_name == "user32!NtUserMessageCall")
return "hWnd";
else
return "Handle";
}

/**
Expand Down Expand Up @@ -154,6 +173,9 @@ KernelObjectDescriptor WaitApiStackParser::ParseObjectDescriptor(const PartialSt
case 3:
count = stackFrame.arg3;
break;
case OBJECT_COUNT_1:
count = 1;
break;
default:
count = KernelObjectDescriptor::VALUE_NOT_FOUND;
break;
Expand All @@ -162,6 +184,7 @@ KernelObjectDescriptor WaitApiStackParser::ParseObjectDescriptor(const PartialSt
auto ret = KernelObjectDescriptor(value, count);

ret.set_handle(is_handle(stackFrame.symbol_name));
ret.set_name(get_name(stackFrame.symbol_name));

return ret;
}
Expand Down Expand Up @@ -257,13 +280,13 @@ Parses objectDescriptors and fills handles and addresses vectors.
/// <param name="objectDescriptors">The object descriptors.</param>
/// <param name="handles">The handles.</param>
/// <param name="addresses">The addresses.</param>
void WaitApiStackParser::GetHandlesAndAddresses(const std::vector<const KernelObjectDescriptor>* objectDescriptors, std::vector<std::pair<unsigned long, unsigned long>>& handles, std::vector<std::pair<unsigned long, unsigned long>>& addresses)
void WaitApiStackParser::GetHandlesAndAddresses(const std::vector<const KernelObjectDescriptor>* objectDescriptors, std::vector<std::pair<unsigned long, unsigned long>>& handles, std::vector<std::tuple<unsigned long, unsigned long, std::string>>& others)
{
for (auto descriptor : *objectDescriptors)
{
if (!descriptor.is_handle())
{
addresses.push_back(std::make_pair(descriptor.get_thread_id(), descriptor.get_value()));
others.push_back(std::make_tuple(descriptor.get_thread_id(), descriptor.get_value(), descriptor.get_name()));

continue;
}
Expand Down Expand Up @@ -313,7 +336,7 @@ void WaitApiStackParser::GetHandlesAndAddresses(const std::vector<const KernelOb
}

std::sort(handles.begin(), handles.end(), [](std::pair<unsigned long, unsigned long> a, std::pair<unsigned long, unsigned long> b){ return a.second < b.second; });
std::sort(addresses.begin(), addresses.end(), [](std::pair<unsigned long, unsigned long> a, std::pair<unsigned long, unsigned long> b){ return a.second < b.second; });
std::sort(others.begin(), others.end(), [](std::tuple<unsigned long, unsigned long, std::string> a, std::tuple<unsigned long, unsigned long, std::string> b){ return std::get<1>(a) < std::get<1>(b); });
}

/**
Expand All @@ -323,11 +346,11 @@ Parses objectDescriptors and fills handles and addresses vectors.
\param handles Parsed handles.
\param addresses Parsed addresses.
*/
void WaitApiStackParser::GetHandlesAndAddresses(const std::string& command_output, std::vector<std::pair<unsigned long, unsigned long>>& handles, std::vector<std::pair<unsigned long, unsigned long>>& addresses)
void WaitApiStackParser::GetHandlesAndAddresses(const std::string& command_output, std::vector<std::pair<unsigned long, unsigned long>>& handles, std::vector<std::tuple<unsigned long, unsigned long, std::string>>& others)
{
auto objectDescriptors = Parse(command_output);

GetHandlesAndAddresses(objectDescriptors, handles, addresses);
GetHandlesAndAddresses(objectDescriptors, handles, others);

delete objectDescriptors;
}
9 changes: 6 additions & 3 deletions cosos/WaitApiStackParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class KernelObjectDescriptor
unsigned long _value;
unsigned long _count;
bool _is_handle = true;
std::string _name;

public:
static const unsigned long ADDRESS_IS_IMMEDIATE = -1;
Expand All @@ -96,10 +97,12 @@ class KernelObjectDescriptor
unsigned long get_thread_id(){ return _thread_id; }
unsigned long get_value(){ return _value; }
unsigned long get_count(){ return _count; }
std::string get_name(){ return _name; }
unsigned long is_handle(){ return _is_handle; }

void set_thread_id(unsigned long thread_id){ _thread_id = thread_id; }
void set_handle(unsigned long is_handle){ _is_handle = is_handle; }
void set_name(const std::string& name){ _name = name; }
bool is_value_address(){ return _count != ADDRESS_IS_IMMEDIATE; }
};

Expand All @@ -120,11 +123,11 @@ class WaitApiStackParser
KernelObjectDescriptor ParseObjectDescriptor(const PartialStackFrame& stackFrame);
PartialStackFrame ParseStackFrame(const std::string& line);
std::vector<const KernelObjectDescriptor>* Parse(const std::string& lines);

void GetHandlesAndAddresses(const std::vector<const KernelObjectDescriptor>* objectDescriptors, std::vector<std::pair<unsigned long, unsigned long>>& handles, std::vector<std::pair<unsigned long, unsigned long>>& addresses);
std::string get_name(const std::string& symbol_name);
void GetHandlesAndAddresses(const std::vector<const KernelObjectDescriptor>* objectDescriptors, std::vector<std::pair<unsigned long, unsigned long>>& handles, std::vector<std::tuple<unsigned long, unsigned long, std::string>>& others);

public:
void GetHandlesAndAddresses(const std::string& command_output, std::vector<std::pair<unsigned long, unsigned long>>& handles, std::vector<std::pair<unsigned long, unsigned long>>& addresses);
void GetHandlesAndAddresses(const std::string& command_output, std::vector<std::pair<unsigned long, unsigned long>>& handles, std::vector<std::tuple<unsigned long, unsigned long, std::string>>& others);

WaitApiStackParser(IMemoryReader *memory_reader, ILogger *logger)
: _memory_reader(memory_reader), _logger(logger)
Expand Down
8 changes: 8 additions & 0 deletions dbgenginterface/dbgenginterface.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClInclude Include="inc\DumpHeapCommandParser.h" />
<ClInclude Include="inc\DumpHeapCommandOutput.h" />
<ClInclude Include="inc\AddressCommandOutput.h" />
<ClInclude Include="inc\AddressCommandParser.h" />
<ClInclude Include="inc\EEHeapCommandOutput.h" />
Expand All @@ -28,9 +30,15 @@
<ClInclude Include="inc\IMemoryReader.h" />
<ClInclude Include="inc\MemoryRange.h" />
<ClInclude Include="inc\MemoryRangeAnalyzer.h" />
<ClInclude Include="inc\SafeWaitHandleOutput.h" />
<ClInclude Include="inc\SafeWaitHandleParser.h" />
<ClInclude Include="src\ILogger.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\DumpHeapCommandParser.cpp" />
<ClCompile Include="src\DumpHeapCommandOutput.cpp" />
<ClCompile Include="src\SafeWaitHandleOutput.cpp" />
<ClCompile Include="src\SafeWaitHandleParser.cpp" />
<ClCompile Include="src\AddressCommandOutput.cpp" />
<ClCompile Include="src\AddressCommandParser.cpp" />
<ClCompile Include="src\EEHeapCommandOutput.cpp" />
Expand Down
24 changes: 24 additions & 0 deletions dbgenginterface/dbgenginterface.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@
<ClInclude Include="inc\EEHeapCommandOutput.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="inc\DumpHeapCommandOutput.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="inc\DumpHeapCommandParser.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="inc\SafeWaitHandleOutput.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="inc\SafeWaitHandleParser.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\HandleCommandOutput.cpp">
Expand Down Expand Up @@ -89,5 +101,17 @@
<ClCompile Include="src\EEHeapCommandOutput.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\DumpHeapCommandOutput.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\DumpHeapCommandParser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\SafeWaitHandleOutput.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\SafeWaitHandleParser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>
65 changes: 65 additions & 0 deletions dbgenginterface/inc/DumpHeapCommandOutput.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) 2015 Kerem KAT
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files(the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Do not hesisate to contact me about usage of the code or to make comments
// about the code. Your feedback will be appreciated.
//
// http://dissipatedheat.com/
// http://github.com/krk/

/**
\file DumpHeapOutput.h
Defines the DumpHeapCommandOutput class.
*/

#ifndef __DUMPHEAPCOMMANDOUTPUT_H__

#define __DUMPHEAPCOMMANDOUTPUT_H__

#include <vector>

/**
\class DumpHeapCommandOutput
Represents output of the !dumpheap command.
*/
class DumpHeapCommandOutput
{
private:
std::vector<unsigned long> *_addresses = nullptr;

public:
DumpHeapCommandOutput()
{

}

DumpHeapCommandOutput(std::vector<unsigned long> *addresses)
: _addresses(addresses)
{

}

std::vector<unsigned long>* get_addresses() const;

bool has_addresses() const { return _addresses != nullptr&& _addresses->size() > 0; }
};

#endif // #ifndef __DUMPHEAPCOMMANDOUTPUT_H__
Loading

0 comments on commit 3a3d718

Please sign in to comment.