Skip to content

Commit 35036ad

Browse files
committed
initial commit
0 parents  commit 35036ad

31 files changed

+1564
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
bin
2+
obj

Commands/Clr.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using DnExt.Helpers;
2+
using Microsoft.Diagnostics.Runtime;
3+
using System.Text;
4+
5+
namespace DnExt.Commands
6+
{
7+
public static partial class ClrCommands
8+
{
9+
internal static int ClrVersionIndex { get; private set; } = 0;
10+
11+
internal static ClrRuntime GetRuntime(this DataTarget dataTarget) =>
12+
dataTarget
13+
.ClrVersions[ClrVersionIndex]
14+
.CreateRuntime();
15+
16+
public static string ListClrVersions(this DataTarget dataTarget, string args)
17+
{
18+
var clrVersions = dataTarget.ClrVersions;
19+
var sbr = new StringBuilder();
20+
21+
for (var i = 0; i < clrVersions.Count; ++i)
22+
{
23+
sbr.AppendLine(OutputHelper.MakeDml($"!setclrversion {i + 1}", $"{i + 1}", $": {clrVersions[i].Version}"));
24+
}
25+
26+
return sbr.ToString();
27+
}
28+
29+
public static string SetClrVersion(this DataTarget dataTarget, string args)
30+
{
31+
var clrVersions = dataTarget.ClrVersions;
32+
33+
if (!int.TryParse(args.CleanArgs(), out var i) || i < 1 || i > clrVersions.Count)
34+
{
35+
return $"Invalid index: {args}";
36+
}
37+
38+
ClrVersionIndex = i - 1;
39+
40+
return string.Empty;
41+
}
42+
}
43+
}

Commands/Common.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using DnExt.Common;
2+
using System;
3+
4+
namespace DnExt.Commands
5+
{
6+
public static partial class ClrCommands
7+
{
8+
static ClrCommands()
9+
{
10+
DependencyResolver.RegisterAssemblyResolve();
11+
}
12+
}
13+
}

Commands/DataSet.cs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
using CommandLine;
2+
using DnExt.Helpers;
3+
using Microsoft.Diagnostics.Runtime;
4+
using RGiesecke.DllExport;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Globalization;
8+
using System.Linq;
9+
using System.Runtime.InteropServices;
10+
using System.Text;
11+
12+
namespace DnExt.Commands
13+
{
14+
public static partial class ClrCommands
15+
{
16+
class DumpDataTableOptions
17+
{
18+
[Option('n', "maxrowcount", Required = false, Default = 100, HelpText = "Maximum number of rows")]
19+
public int MaxRows { get; set; }
20+
[Value(0)]
21+
public string Address { get; set; }
22+
}
23+
24+
// todo: limit # of rows
25+
public static string DumpDataTable(this DataTarget dataTarget, string args)
26+
{
27+
if (args.ParseAsCommandLine<DumpDataTableOptions>() is var clo && !clo.IsValid)
28+
{
29+
return clo.Message;
30+
}
31+
32+
var rt = dataTarget.GetRuntime();
33+
var options = clo.Options;
34+
35+
if (!ulong.TryParse(options.Address, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address))
36+
{
37+
return "invalid address";
38+
}
39+
40+
if (rt.Heap.GetObjectType(address) is var ot && (!ot?.Name?.Equals("System.Data.DataTable") ?? true))
41+
{
42+
return $"invalid type: {ot.Name}";
43+
}
44+
45+
var o = rt.Heap.GetObject(address);
46+
var columnList = o.GetFieldFrom("columnCollection")
47+
.GetFieldFrom("_list")
48+
.GetFieldFrom("_items");
49+
var rowCount = o.GetField<long>("nextRowID") - 1;
50+
var columnArray = rt.Heap.GetObject(columnList.Address);
51+
var columnArrayType = columnArray.Type;
52+
var table = new List<List<string>>();
53+
54+
for (int i = 0; i < columnArray.Length; i++)
55+
{
56+
var colAddress = columnArrayType.GetArrayElementAddress(columnArray.Address, i);
57+
58+
if (!rt.Heap.ReadPointer(colAddress, out colAddress))
59+
{
60+
return $"Error reading address {colAddress:X}";
61+
}
62+
63+
if (colAddress == 0)
64+
{
65+
break;
66+
}
67+
68+
var col = rt.Heap.GetObject(colAddress);
69+
70+
if (col.Address == 0)
71+
{
72+
break;
73+
}
74+
75+
var colName = col.GetStringField("_columnName");
76+
var colData = new List<string> { colName };
77+
var colValues = col.GetFieldFrom("_storage")
78+
.GetFieldFrom("values");
79+
var colValueArray = rt.Heap.GetObject(colValues.Address);
80+
81+
for (int j = 0; j < Math.Min(rowCount, options.MaxRows); j++)
82+
{
83+
colData.Add(colValueArray.Type.GetArrayElementValue(colValueArray.Address, j)?.ToString());
84+
}
85+
86+
var maxLen = colData
87+
.Where(s => !string.IsNullOrEmpty(s))
88+
.Select(s => s.Length)
89+
.Max();
90+
91+
colData.Insert(1, new string('─', maxLen));
92+
93+
for (int j = 0; j < colData.Count; j++)
94+
{
95+
colData[j] = colData[j]?.PadLeft(maxLen);
96+
}
97+
98+
table.Add(colData);
99+
}
100+
101+
var sbr = new StringBuilder();
102+
103+
foreach (var l in table
104+
.SelectMany(inner => inner.Select((t, i) => new { Item = t, Index = i }))
105+
.GroupBy(i => i.Index, i => i.Item)
106+
.Select((g, i) => (List: g.ToList(), Index: i)))
107+
{
108+
sbr.AppendLine(string.Join(l.Index == 1 ? "┼" : "│", l.List));
109+
}
110+
111+
return sbr.ToString();
112+
}
113+
114+
public static string DumpDataSet(this DataTarget dataTarget, string args)
115+
{
116+
var rt = dataTarget.GetRuntime();
117+
118+
args = args.CleanArgs();
119+
120+
if (!ulong.TryParse(args, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address))
121+
{
122+
return "invalid address";
123+
}
124+
125+
if (rt.Heap.GetObjectType(address) is var ot && (!ot?.Name?.Equals("System.Data.DataSet") ?? true))
126+
{
127+
return $"invalid type: {ot?.Name ?? "<unk>"}";
128+
}
129+
130+
var tableList = rt.Heap.GetObject(address)
131+
.GetFieldFrom("tableCollection")
132+
.GetFieldFrom("_list")
133+
.GetFieldFrom("_items");
134+
var tableArray = rt.Heap.GetObject(tableList.Address);
135+
var tableArrayType = tableArray.Type;
136+
137+
var sbr = new StringBuilder();
138+
139+
for (int i = 0; i < tableArray.Length; i++)
140+
{
141+
var dtAddress = tableArrayType.GetArrayElementAddress(tableArray.Address, i);
142+
143+
if (rt.Heap.ReadPointer(dtAddress, out dtAddress))
144+
{
145+
var dt = rt.Heap.GetObject(dtAddress);
146+
147+
if (dt.Address > 0)
148+
{
149+
var a = dataTarget.FormatAddress(dtAddress);
150+
sbr.AppendLine(@$"{dt.GetStringField("tableName")}: {OutputHelper.MakeDml($"!dumpdatatable {a}", $"{a}")}");
151+
}
152+
}
153+
}
154+
155+
return sbr.ToString();
156+
}
157+
}
158+
}

Commands/Exceptions.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace DnExt.Commands
8+
{
9+
public static partial class ClrCommands
10+
{
11+
// dump exceptions with filtering
12+
}
13+
}

Commands/Heap.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using CommandLine;
2+
using DnExt.Commands.Utils;
3+
using DnExt.Helpers;
4+
using Microsoft.Diagnostics.Runtime;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
9+
namespace DnExt.Commands
10+
{
11+
public static partial class ClrCommands
12+
{
13+
class HeapStatOptions
14+
{
15+
[Option('m', "min", Required = false, Default = 1, HelpText = "Minimum number of occurences on heap")]
16+
public int MinOccurences { get; set; }
17+
[Option('n', "max", Required = false, Default = 100, HelpText = "Maximum number of occurences on heap")]
18+
public int MaxOccurences { get; set; }
19+
[Option('p', "pattern", Required = false, HelpText = "Filter pattern for type names")]
20+
public string FilterPattern { get; set; }
21+
[Option('r', "regex", Required = false, Default = false, HelpText = "Enable/disable regex filtering")]
22+
public bool IsRegex { get; set; }
23+
}
24+
25+
public static string HeapStat(this DataTarget dataTarget, string args)
26+
{
27+
if (args.ParseAsCommandLine<HeapStatOptions>() is var clo && !clo.IsValid)
28+
{
29+
return clo.Message;
30+
}
31+
32+
var rt = dataTarget.GetRuntime();
33+
var options = clo.Options;
34+
var m = new Matcher(options.IsRegex, options.FilterPattern);
35+
var objectDictionary = new Dictionary<ulong, int>();
36+
37+
foreach (var o in rt.Heap.EnumerateObjects())
38+
{
39+
if (m.IsMatch(o.Type.Name))
40+
{
41+
if (!objectDictionary.ContainsKey(o.Type.MethodTable))
42+
{
43+
objectDictionary.Add(o.Type.MethodTable, 0);
44+
}
45+
46+
objectDictionary[o.Type.MethodTable]++;
47+
}
48+
}
49+
50+
var mtList = objectDictionary
51+
.Where(kvp => kvp.Value >= options.MinOccurences && kvp.Value <= options.MaxOccurences)
52+
.OrderBy(kvp => kvp.Value)
53+
.ToList();
54+
var sbr = new StringBuilder();
55+
56+
foreach (var mt in mtList)
57+
{
58+
var a = dataTarget.FormatAddress(mt.Key);
59+
sbr.AppendLine($"{OutputHelper.MakeDml($"!dumpmt /d {a}", $"{a}")}: {rt.Heap.GetTypeByMethodTable(mt.Key).Name} ({mt.Value})");
60+
}
61+
62+
sbr.AppendLine($"Total {mtList.Count} types");
63+
64+
return sbr.ToString();
65+
}
66+
67+
// todo: dump with limiting # of obj
68+
public static string DumpHeap(this DataTarget dataTarget, string args)
69+
{
70+
return "hi";
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)