From: Igor Bagdamyan <37334640+En3Tho@users.noreply.github.com> Date: Sun, 18 Jun 2023 15:53:20 +0000 (+0300) Subject: Added dumpexceptions command (#3986) X-Git-Tag: accepted/tizen/unified/riscv/20231226.055542~38^2^2~159 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=6deaeb6544a577957f251b6bc179d4ec9499dbff;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Added dumpexceptions command (#3986) Please look at this. Left some questions inside the code. Mainly about ux/printing stuff. Should messages be printed in multiple rows just for convenience? Or alternatively should message be printed in another row? It's not like a really long message can be shown anyway I guess. But at least more chars can be shown. This way StackTrace can be partially shown too. 0262a3816710 7ffc556d2da0 System.InvalidOperationException Message: The Settings Storage This computer was corrupted upon loading from XML Settings Stor ... (more chars)? StackTrace: little bit of stacktrace? For tests should I somehow validate that all output lines except for header and the last "Total" one contain "Exception" is the name? P.S. Look at screenshots below first --- diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpExceptionsCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpExceptionsCommand.cs new file mode 100644 index 000000000..4f57cf389 --- /dev/null +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpExceptionsCommand.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.Diagnostics.DebugServices; +using Microsoft.Diagnostics.ExtensionCommands.Output; +using Microsoft.Diagnostics.Runtime; + +#nullable enable + +namespace Microsoft.Diagnostics.ExtensionCommands +{ + [Command(Name = "dumpexceptions", Help = "Displays a list of all managed exceptions.")] + public class DumpExceptionsCommand : CommandBase + { + [ServiceImport] + public ClrRuntime Runtime { get; set; } = null!; + + [ServiceImport] + public LiveObjectService LiveObjects { get; set; } = null!; + + [Option(Name = "-live")] + public bool Live { get; set; } + + [Option(Name = "-dead")] + public bool Dead{ get; set; } + + [Option(Name = "-gen")] + public string? Generation { get; set; } + + [Option(Name = "-type")] + public string? Type { get; set; } + + public override void Invoke() + { + HeapWithFilters filteredHeap = ParseArguments(); + + IEnumerable exceptionObjects = + filteredHeap.EnumerateFilteredObjects(Console.CancellationToken) + .Where(obj => obj.IsException); + + if (Live) + { + exceptionObjects = exceptionObjects.Where(obj => LiveObjects.IsLive(obj)); + } + + if (Dead) + { + exceptionObjects = exceptionObjects.Where(obj => !LiveObjects.IsLive(obj)); + } + + if (!string.IsNullOrWhiteSpace(Type)) + { + string type = Type!; + exceptionObjects = exceptionObjects.Where(obj => obj.Type!.Name!.IndexOf(type, StringComparison.OrdinalIgnoreCase) >= 0); + } + + PrintExceptions(exceptionObjects); + } + + private void PrintExceptions(IEnumerable exceptionObjects) + { + Table output = new(Console, ColumnKind.Pointer, ColumnKind.Pointer, ColumnKind.TypeName); + output.WriteHeader("Address", "MethodTable", "Message", "Name"); + + int totalExceptions = 0; + foreach (ClrObject exceptionObject in exceptionObjects) + { + totalExceptions++; + + ClrException clrException = exceptionObject.AsException()!; + output.WriteRow(exceptionObject.Address, exceptionObject.Type!.MethodTable, exceptionObject.Type!.Name); + + Console.Write(" Message: "); + Console.WriteLine(clrException.Message ?? ""); + + ImmutableArray stackTrace = clrException.StackTrace; + if (stackTrace.Length > 0) + { + Console.Write(" StackFrame: "); + Console.WriteLine(stackTrace[0].ToString()); + } + } + + Console.WriteLine(); + Console.WriteLine($" Total: {totalExceptions} objects"); + } + + private HeapWithFilters ParseArguments() + { + HeapWithFilters filteredHeap = new(Runtime.Heap); + + if (Live && Dead) + { + Live = false; + Dead = false; + } + + // TODO: CR: this is the same as in dumpheap. Where to put this? + if (!string.IsNullOrWhiteSpace(Generation)) + { + Generation generation = Generation!.ToLowerInvariant() switch + { + "gen0" => Diagnostics.Runtime.Generation.Generation0, + "gen1" => Diagnostics.Runtime.Generation.Generation1, + "gen2" => Diagnostics.Runtime.Generation.Generation2, + "loh" or "large" => Diagnostics.Runtime.Generation.Large, + "poh" or "pinned" => Diagnostics.Runtime.Generation.Pinned, + "foh" or "frozen" => Diagnostics.Runtime.Generation.Frozen, + _ => throw new ArgumentException($"Unknown generation: {Generation}. Only gen0, gen1, gen2, loh (large), poh (pinned) and foh (frozen) are supported") + }; + + filteredHeap.Generation = generation; + } + + return filteredHeap; + } + } +}