[PowerShell Script] Saving a Module from a .NET Method Call


This is my first script using the PowerDbg functions. It’s a good example of how to use PowerDbg to build your own scripts.


PowerDbgScriptSaveModule.ps1 is the PowerShell version of my Windbg script Save_Module.txt


 


Actually it does more than the previous version: it automatically saves the module that uses a specific method call. Then you just need to use .NET Reflector to decompile the code.


 


You can see this version is more powerful, easier to change, easier to improve, and easier to understand. Besides it has error handling!


 


When using PowerDbg scripts you can see the output from the PowerShell window; therefore, it’s easy to understand what the script is doing and the final result.


 


Remember, when using PowerDbg scripts, the WinDbg window has the row command processing and the PowerShell window has the output from the script. You should focus on the PowerShell window to get information that you may use in either reports or in e-mails to your customers. While other scripts may have copious amounts of information shown in the PowerShell window, this script doesn’t. It simply saves a module, so that’s why I chose it to be our first PowerDbg script.


 


Make sure to use the latest PowerDbg version.


 


The source code is actually indented. Copy and paste it to Word and save it as text file, then you can see the code using Notepad.


 


These are the screenshots:


 


 


 


 


.\PowerDbgScriptSaveModule “System.Threading.ExecutionContext.runTryCode” “c:\dumps”


 


 



 


 


 


 


 


This is the source code for PowerDbgScriptSaveModule.ps1:


 


 


 


 


 


 


 


########################################################################################################


# Script:      PowerDbgScriptSaveModule


#


# Parameters:  [string] <$methodName>


#              Name of the method from call stack.


#


#              [string] <$directory>


#              Directory where the module will be saved. Make sure this is a valid directory.


#              Example: “c:\module”


#


# Purpose:     Saves the module that has the method call provided as parameter into the directory provided as second


#              parameter. After that, you can use a tool like .NET Reflector to decompile the code.


#              This script makes the assumption that SOS.DLL extension is loaded.


#


# Changes History:


#


# Roberto Alexis Farah


# All my functions are provided “AS IS” with no warranties, and confer no rights.


########################################################################################################


param(


      [string] $methodName = $(throw “Error! You must provide the method name.”),


      [string] $directory  = $(throw “Error! You must provide the directory where the method will be saved.”)


     )


set-psdebugstrict


$ErrorActionPreference = “stop”


trap {“Error message: $_”}


 


write-Host “Extracting MethodDesc…”foreground Green –background Black


 


# If you want you can create code to load SOS.DLL extension if necessary.


# Here I’m assuming this extension was already loaded.


Send-PowerDbgCommand(“!name2ee *!$methodName”)


 


# Extracts output from previous command.


Parse-PowerDbgNAME2EE


 


write-Host “Done!”foreground Green –background Black


 


# Convert CSV file to Hash Table.


$output = @{}


$output = Convert-PowerDbgCSVtoHashTable


 


write-Host “MethodDesc = “ $output[“MethodDesc:”] –foreground Green –background Black


 


# If something goes wrong, stops execution and notifies user.


if($null -eq $output[“MethodDesc:”])


{


    throw “Couldn’t retrieve MethodDesc!”


}


 


write-Host “Extracting Module…”foreground Green –background Black


 


# Calls !dumpmd using the specific value we want.


Send-PowerDbgCommand(“!dumpmd “ + $output[“MethodDesc:”])


 


# Extracts output from previous command.


Parse-PowerDbgDUMPMD


 


write-Host “Done!”foreground Green –background Black


 


# Convert CSV file to Hash Table.


$output = @{}


$output = Convert-PowerDbgCSVtoHashTable


 


write-Host “Module = “ $output[“Module:”] –foreground Green –background Black


 


# If something goes wrong, stops execution and notifies user.


if($null -eq $output[“Module:”])


{


    throw “Couldn’t retrieve Module!”


}


 


write-Host “Extracting Metadata start address…”foreground Green –background Black


 


# Calls !dumpmodule using the module address.


Send-PowerDbgCommand(“!DumpModule “ + $output[“Module:”])


 


# Now we need to get the base address…


# To do that we need a specific field from the previous command.


# The fields from SOS.DLL have differences between .NET Framework 2.0 and 1.1


Parse-PowerDbgDUMPMODULE


 


# Convert CSV file to Hash Table.


$output = @{}


$output = Convert-PowerDbgCSVtoHashTable


 


write-Host “Done!”foreground Green –background Black


 


# We need to verify if the field is from SOS 2.0 or SOS 1.1


if($null -eq $output[“MetaData starts at”])


{


    write-Host “Metadata start address = “ $output[“MetaData start address:”] –foreground Green –background Black


}


else


{


    write-Host “Metadata start address = “ $output[“MetaData starts at”] –foreground Green –background Black


}


 


# If something goes wrong, stops execution and notifies user.


if(($null -eq $output[“MetaData starts at”]) –and ($null -eq $output[“MetaData start address:”]) )


{


    throw “Couldn’t retrieve MetaData start address!”


}


 


# We need to verify if the field is from SOS 2.0 or SOS 1.1


if($null -eq $output[“MetaData start address:”])


{


    Send-PowerDbgCommand(“!lmi “ +  $output[“MetaData starts at”])


}


else


{


    Send-PowerDbgCommand(“!lmi “ +  $output[“MetaData start address:”])


}


 


write-Host “Extracting base address…”foreground Green –background Black


 


# Yet again we need the output from the previous command.


Parse-PowerDbgLMI


 


# Convert CSV file to Hash Table.


$output = @{}


$output = Convert-PowerDbgCSVtoHashTable


 


write-Host “Done!”foreground Green –background Black


write-Host “Module to be saved = “ $output[“Image Name:”] –foreground Green –background Black


 


# If something goes wrong, stops execution and notifies user.


if($null -eq $output[“Image Name:”])


{


    throw “Couldn’t retrieve Image Name!”


}


 


write-Host “Saving module “ $output[“Image Name:”] ” into directory “ $directoryforeground Green –background Black


 


# Finally we save the module now that we have the name of the module and its base address.


Send-PowerDbgCommand(“!savemodule “ + $output[“Base Address:”] + ” “ + $directory + “\” + $output[“Image Name:“])


 


$hasCommandSucceeded = Has-PowerDbgCommandSucceeded


 


if($false -eq $hasCommandSucceeded)


{


    throw “Couldn’t save module into the provided path!”


}


 


write-Host “Done!”foreground Green –background Black


 


# Notifies user the script finished the execution.


Send-PowerDbgComment “PowerDbgScriptSaveModule was executed. See the PowerShell window for more information.”


 


 


 


 


 


 

Comments (10)

  1. nativecpp says:

    Hi Roberto,

    I have a general question on .Net and I thought you may have some ideas on how to solve this:

    Let say I have an app that I use try/catch to catch some system.exception such as division by zero. After catching this error, I could get the current stack frame from my app. Is there a way to dump the contents of all local vars (w/o var names) and params w/o using windbg (and SOS) and symbols.

    I tried to use reflection but seems there is not much I could do in reflection.

    If you could give me some ideas or link to some documents, I would appreciate that.

    Thanks

  2. Hi nativecpp,

    Without using Windbg and symbols I’d think about logging trace information.

    Within the catch block you could log the stack trace and frames using System.Diagnostics:

    http://msdn2.microsoft.com/en-us/library/system.diagnostics.stackframe.aspx

    http://msdn2.microsoft.com/en-us/library/system.diagnostics.stacktrace.aspx

    Besides your methods could save the content from the local variables or the most important local variables.

    Then when you check the log file or Event log you will have the stack trace and the most important local variables.

    It is a proactive approach using trace information: the application itself logs the stack from the exception and, as part of the execution flow, the important variables are always logged at different points of the same method call. When you analyze the log you will have the stack trace and the local variables from all stack frames in previous log entries.

    Thanks

  3. Attention! I just fixed two minor bugs:

    – The MetaData start address was not being displayed.

    – The !dumpmodule was replaced by !DumpModule because I had some problems with our internal SOS version.

    Please, make sure to use this updated version.

  4. nativecpp says:

    Hi Roberto,

    Thanks for the suggestopns. I did use stackframe to log as much info as possible, I think. But I would like to automatically dump at least params and some local vars (may be 1st nth ones)w/o having the catcher to do that.

    I am just wondering if there is a way to do dumping  if I have a stack frame and whether I can do follow those SOS commands ??

    Thanks

  5. Hi nativecpp,

    The local variables and parameters should be logged outside the catch block. Doing so you will end up having information from the local var and parameters aside of having an exception, like I mentioned before.

    Another approach: your application could create a dump when handling a specific exception. To do that it should use MiniDumpWriteDump API function from DBGHELP.DLL.

    http://msdn2.microsoft.com/en-us/library/ms680360.aspx

    http://msdn2.microsoft.com/en-us/library/bb204861.aspx

    The SOSEX extension helps to get the local variables: http://www.stevestechspot.com/SOSEXANewDebuggingExtensionForManagedCode.aspx

    Another approach: Within the catch handler you can use Trace.WriteLine(“WINDBGCMD: <execute commands here>”). To do that you need to use .ocommand WINDBGCMD from the debugger window. Using this approach you can attach the debugger to the application and when the exception happens the debugger recognizes the command string prefix WINDBGCMD and execute the commands you want. Doing that you would be able to see the local variables.

    Thanks

  6. Joe says:

    You scripts are amazing! I´m waiting for the next PowerDbg script.

    Keep doing this great work!

    Joe

  7. When helping my customers with scenarios in which the symptom is high CPU, I very often end up with only

  8. I’m very excited to present the new PowerDbg v5.0! There’s just one change, but it’s a HUGE change that

  9. So, here we go again. This is a minor version with a few new cmdlets. These new cmdlets are those that

  10. This version has two improvements and some scripts were changed to be compatible with this new version: