Tutorial scripts
June 24, 2019 ยท View on GitHub
Enumerating processes being debugged
foreach (Process process in Process.All)
Console.WriteLine($"{process.Id}:{process.SystemId} {process.ExecutableName}");
Console.WriteLine($"Current process: {Process.Current.SystemId}");
Enumeratung all threads in current process being debugged
foreach (Thread thread in Thread.All)
Console.WriteLine($"{thread.Id}:{thread.SystemId}");
Console.WriteLine($"Current thread: {Thread.Current.Id}:{Thread.Current.SystemId}");
Enumerating all modules in current process being debugged
foreach (Module module in Module.All)
Console.WriteLine($"0x{module.Offset:X} {module.Name}");
List all functions on a call stack of the current thread
foreach (StackFrame frame in Thread.Current.StackTrace.Frames)
Console.WriteLine(frame.FunctionName);
Mappings of current process/thread shortcuts
Thread.All => Process.Current.Threads;
Module.All => Process.Current.Modules;
Thread.Current => Process.Current.CurrentThread;
StackTrace.Current => Thread.Current.StackTrace;
StackFrame.Current => StackTrace.Current.CurrentFrame;
Accessing local variables in scripts
VariableCollection locals = StackTrace.Current.Frames[0].Locals;
foreach (Variable l in locals)
Console.WriteLine(l.GetName());
dynamic myVar = locals["myVar"];
Console.WriteLine(myVar);
Accessing global variables
Variable globalVariable = Process.Current.GetGlobal("mymodule!globalVariable");
Variable staticClassVariable = Process.Current.GetGlobal("mymodule!Class::staticVariable");
You can also access through Modules in interactive mode:
dynamic globalVariable = Modules.mymodule.globalVariable;
dynamic staticClassVariable = Modules.mymodule.GetVariable("Class::staticVariable");
Accessing variable fields
Variable variable = Process.Current.GetGlobal("mymodule!globalVariable");
Variable field = variable.GetField("field");
Or if you use dynamic:
dynamic variable = Process.Current.GetGlobal("mymodule!globalVariable");
dynamic field = variable.field;
Getting variable type
CodeType type = myVariable.GetCodeType();
Casting variable to built-in type
Variable variable = Process.Current.GetGlobal("mymodule!globalVariable");
int intValue = (int)variable;
ulong ulongValue = (ulong)variable;
Casting variable to user type
Variable variable = Process.Current.GetGlobal("mymodule!globalVariable");
std.wstring s = variable.CastAs<std.wstring>();
Casting variable to code type given by string
Variable p = Process.Current.GetGlobal("mymodule!voidPointerVariable");
Console.WriteLine(p); // This will print address of void* pointer
Variable s = p.CastAs("wchar_t*");
Console.WriteLine(s); // This will print string value pointed by s
Getting array element of variable array
Variable a = Process.Current.GetGlobal("mymodule!intArrayVariable");
int i = (int)a.GetArrayElement(0);
Or by using dynamic:
dynamic a = Modules.mymodule.intArrayVariable;
int i = (int)a[0];
Debugging CLR applications
Most of the things will work automatically the same way as for native applications. When debugger (for example DbgEng.dll) doesn't know how to get CLR stack trace correctly, you can find ClrThread and get ClrStackTrace. There are also CLR specific classes that help with debugging CLR applications. Debugging CLR applications is done using ClrMD.
Note: ClrMD at this moment supports Windows PDBs only. If you want to debug CLR application that has Portable PDBs, you can use Pdb2Pdb tool to convert from Portable to Windows PDBs. Another option is to add <DebugType>Full</DebugType> to your CLR project file.
Enumerating CLR runtimes in the process
foreach (Runtime runtime in Process.Current.ClrRuntimes)
Console.WriteLine(runtime.Version);
Getting All user AppDomains in the runtime
foreach (AppDomain appDomain in runtime.AppDomains)
Console.WriteLine($"{appDomain.Id}: {appDomain.Name}");
Getting system and shared AppDomains
Console.WriteLine($"{runtime.SystemDomain.Id}: {runtime.SystemDomain.Name}");
Console.WriteLine($"{runtime.SharedDomain.Id}: {runtime.SharedDomain.Name}");
Getting all modules loaded in the AppDomain
foreach (Module module in appDomain.Modules)
Console.WriteLine($"0x{module.Address:X} {module.Name}");
Getting the heap and all objects in it
Heap heap = runtime.Heap;
foreach (Variable variable in heap.EnumerateObects())
Console.WriteLine($"({variable.GetCodeType()}) {variable.GetPointerAddress()}");
Getting generation sizes from the heap
for (int generation = 0; generation <= 3; generation++)
Console.WriteLine("{generation}: {heap.GetSizeByGeneration(generation)}");
Getting all managed threads from the runtime
foreach (ClrThread thread in runtime.Threads)
Console.WriteLine("{0}", thread.Id);
Getting managed call stack from the thread
ClrThread thread = Thread.Current.FindClrThread();
if (thread != null)
foreach (StackFrame frame in thread.ClrCallStack.Frames)
Console.WriteLine(frame.FunctionNameWithoutModule);
Getting last thrown exception in the thread
ClrThread thread = Thread.Current.FindClrThread();
ClrException exception = thread?.LastThrownException;
Console.WriteLine(exception?.Message);
Enumerating stack objects
ClrThread thread = Thread.Current.FindClrThread();
foreach (Variable variable in thread.EnumerateStackObjects())
Console.WriteLine("({variable.GetCodeType()}) {variable.GetPointerAddress()}");
Getting arguments and local variables from function on the call stack
It works the same way as for native debugging with an addition that if debugger doesn't recognize CLR call stack correctly, you can use ClrThread.ClrStackTrace to get correct one.
Thread thread = Thread.Current;
foreach (StackFrame frame in thread.StackTrace.Frames)
{
Console.WriteLine(frame.FunctionName);
foreach (Variable arg in frame.Arguments)
Console.WriteLine("argument '{arg.GetName()}': ({arg.GetCodeType()}) {arg.GetPointerAddress()}");
foreach (Variable local in frame.Locals)
Console.WriteLine("argument '{local.GetName()}': ({local.GetCodeType()}) {local.GetPointerAddress()}");
}
Getting static fields from the type
You can use the same mechanisms used in native debugging to read CLR static variables. If your application has multiple AppDomains, you can get static variable per AppDomain.
Module myModule = Modules.All.Single(m => m.Name = "MyModule");
CodeType codeType = CodeType.Create("MyType", myModule);
Variable sf = codeType.GetStaticField("MyStaticField"); // This will use current AppDomain for CLR applications
Variable sf2 = codeType.GetClrStaticField("MyStaticField", Process.Current.ClrRuntimes[0].AppDomains[1]);
Variable sf3 = myModule.GetVariable("MyType.MyStaticField");
Variable sf4 = Process.Current.GetGlobal("MyModule!MyType.MyStaticField");
Working with Variables returned from CLR application
You will walk through field, values the same way you do as with native Variables:
// Getting variable fields
Variable variable = Process.Current.GetGlobal("mymodule!MyType.singleton");
Variable field = variable.GetField("field");
// CodeType
CodeType type = myVariable.GetCodeType();
// Arrays
Variable a = Process.Current.GetGlobal("mymodule!MyType.intArrayVariable");
int i = (int)a.GetArrayElement(0);
// Strings
Variable s = Process.Current.GetGlobal("mymodule!MyType.stringVariable");
ClrString str = new ClrString(s);
Console.WriteLine(str.Text);
// Exceptions
Variable e = Process.Current.GetGlobal("mymodule!MyType.exceptionVariable");
ClrException exception = new ClrException(e);
Console.WriteLine(exception.Message);
Advanced scripting
Previous examples show that scripts don't have namespace and class definition inside. Here you will learn what else can be used in scripts.
How to use using in scripts
using C# statement must be located at the top of the script. It is supposed to be used as in any other C# file but its scope is for entire script file:
using System;
using System.Linq;
Importing files into scripts
Having multiple script files is preferable as script files should share common code. For example, having this helper script file, helper.csx:
using System;
void HelpMe(string text)
{
Console.WriteLine(text);
}
we can have sample script that is referencing that helper file, script.csx:
#load "helper.csx"
Console.Error.WriteLine("This is sample error");
HelpMe("It works!");
Referencing assemblies in scripts
By now, you have huge collection of common code and compiling scripts is not that fast any more, you should create .NET library (dll) and just reference it from the script:
#r "SharpDebug.CommonUserTypes.dll"
using std = SharpDebug.CommonUserTypes.NativeTypes.std;
var variable = Process.Current.GetGlobal("mymodule!globalVariable");
var s = variable.CastAs<std.wstring>();
Please note that CommonUserTypes library is already automatically added as a reference to all scripts.