diff --git a/.vsts-dotnet.yml b/.vsts-dotnet.yml index fdcf40215c..e85cd8b011 100644 --- a/.vsts-dotnet.yml +++ b/.vsts-dotnet.yml @@ -215,7 +215,7 @@ phases: - agent.os -equals Windows_NT variables: _PublishBlobFeedUrl: https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json - _SOSNETCorePath: $(Build.SourcesDirectory)/artifacts/bin/SOS.NETCore/Release/netcoreapp2.0/publish + _SOSNETCorePath: $(Build.SourcesDirectory)/artifacts/bin/SOS.NETCore/Release/netstandard2.0/publish _TeamName: DotNetCore steps: @@ -433,7 +433,7 @@ phases: displayName: Publish TestHelpers to MyGet dotnet-buildtools feed inputs: command: custom - arguments: 'push $(Build.SourcesDirectory)\artifacts\packages\Release\NonShipping\Microsoft.Diagnostic.TestHelpers.*.nupkg -ApiKey $(dotnetfeed-storage-access-key-1) -Source $(_PublishBlobFeedUrl)' + arguments: 'push $(Build.SourcesDirectory)\artifacts\packages\Release\Shipping\Microsoft.Diagnostic.TestHelpers.*.nupkg -ApiKey $(dotnetfeed-storage-access-key-1) -Source $(_PublishBlobFeedUrl)' condition: and(succeeded(), eq(variables['PushPackages'], 'true')) - task: PublishBuildArtifacts@1 diff --git a/NuGet.Config b/NuGet.Config index 04240c3996..1832c0de6e 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,4 +1,4 @@ - + diff --git a/diagnostics.sln b/diagnostics.sln index 03b8f94734..fa4e6d875b 100644 --- a/diagnostics.sln +++ b/diagnostics.sln @@ -39,6 +39,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SOS.InstallHelper", "src\SO EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-sos", "src\Tools\dotnet-sos\dotnet-sos.csproj", "{41351955-16D5-48D7-AF4C-AF25F5FB2E78}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SOS.Hosting", "src\SOS\SOS.Hosting\SOS.Hosting.csproj", "{ED27F39F-DF5C-4E22-87E0-EC5B5873B503}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostic.Repl", "src\Microsoft.Diagnostic.Repl\Microsoft.Diagnostic.Repl.csproj", "{90CF2633-58F0-44EE-943B-D70207455F20}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Checked|Any CPU = Checked|Any CPU @@ -593,6 +597,86 @@ Global {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.RelWithDebInfo|x86.Build.0 = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Checked|Any CPU.Build.0 = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Checked|ARM.ActiveCfg = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Checked|ARM.Build.0 = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Checked|ARM64.ActiveCfg = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Checked|ARM64.Build.0 = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Checked|x64.ActiveCfg = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Checked|x64.Build.0 = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Checked|x86.ActiveCfg = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Checked|x86.Build.0 = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Debug|ARM.ActiveCfg = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Debug|ARM.Build.0 = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Debug|ARM64.Build.0 = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Debug|x64.ActiveCfg = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Debug|x64.Build.0 = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Debug|x86.ActiveCfg = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Debug|x86.Build.0 = Debug|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Release|Any CPU.Build.0 = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Release|ARM.ActiveCfg = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Release|ARM.Build.0 = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Release|ARM64.ActiveCfg = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Release|ARM64.Build.0 = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Release|x64.ActiveCfg = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Release|x64.Build.0 = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Release|x86.ActiveCfg = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.Release|x86.Build.0 = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503}.RelWithDebInfo|x86.Build.0 = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|Any CPU.Build.0 = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|ARM.ActiveCfg = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|ARM.Build.0 = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|ARM64.ActiveCfg = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|ARM64.Build.0 = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|x64.ActiveCfg = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|x64.Build.0 = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|x86.ActiveCfg = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Checked|x86.Build.0 = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|ARM.ActiveCfg = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|ARM.Build.0 = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|ARM64.Build.0 = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|x64.ActiveCfg = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|x64.Build.0 = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|x86.ActiveCfg = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Debug|x86.Build.0 = Debug|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Release|Any CPU.Build.0 = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Release|ARM.ActiveCfg = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Release|ARM.Build.0 = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Release|ARM64.ActiveCfg = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Release|ARM64.Build.0 = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Release|x64.ActiveCfg = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Release|x64.Build.0 = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Release|x86.ActiveCfg = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.Release|x86.Build.0 = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU + {90CF2633-58F0-44EE-943B-D70207455F20}.RelWithDebInfo|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -615,6 +699,8 @@ Global {20EBC3C4-917C-402D-B778-9A6E3742BF5A} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7} {1F012743-941B-4915-8C55-02097894CF3F} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7} {41351955-16D5-48D7-AF4C-AF25F5FB2E78} = {B62728C8-1267-4043-B46F-5537BBAEC692} + {ED27F39F-DF5C-4E22-87E0-EC5B5873B503} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7} + {90CF2633-58F0-44EE-943B-D70207455F20} = {19FAB78C-3351-4911-8F0C-8C6056401740} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0} diff --git a/eng/Build-Native.cmd b/eng/Build-Native.cmd index a810a095ba..e847566b01 100644 --- a/eng/Build-Native.cmd +++ b/eng/Build-Native.cmd @@ -331,6 +331,13 @@ if /i "%__DoCrossArchBuild%"=="1" ( endlocal ) +REM Copy the native SOS binaries to where these tools expect for testing + +set "__dotnet_sos=%__RootBinDir%\bin\dotnet-sos\%__BuildType%\netcoreapp2.1\publish\win-%__BuildArch%" +set "__dotnet_dump=%__RootBinDir%\bin\dotnet-dump\%__BuildType%\netcoreapp2.1\publish\win-%__BuildArch%" +xcopy /y /q /i /s %__BinDir% %__dotnet_sos% +xcopy /y /q /i /s %__BinDir% %__dotnet_dump% + REM ========================================================================================= REM === REM === All builds complete! diff --git a/eng/Versions.props b/eng/Versions.props index 588379b525..441e3ea302 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -20,6 +20,7 @@ 1.0.0-dev-63716-01 + 1.0.3 @@ -28,7 +29,8 @@ https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json; https://dotnet.myget.org/F/symstore/api/v3/index.json; https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; - https://dotnet.myget.org/F/dotnet-buildtools/api/v3/index.json + https://dotnet.myget.org/F/dotnet-buildtools/api/v3/index.json; + https://dotnet.myget.org/F/system-commandline/api/v3/index.json diff --git a/eng/build-native.sh b/eng/build-native.sh index deb6c858c6..473915a19a 100755 --- a/eng/build-native.sh +++ b/eng/build-native.sh @@ -469,6 +469,14 @@ if [ $__Build == true ]; then fi build_native "$__BuildArch" "$__IntermediatesDir" "$__ExtraCmakeArgs" + + # Copy the native SOS binaries to where these tools expect for testing + __dotnet_sos=$__RootBinDir/bin/dotnet-sos/$__BuildType/netcoreapp2.1/publish/$__DistroRid + __dotnet_dump=$__RootBinDir/bin/dotnet-dump/$__BuildType/netcoreapp2.1/publish/$__DistroRid + mkdir -p "$__dotnet_sos" + mkdir -p "$__dotnet_dump" + cp "$__BinDir"/* "$__dotnet_sos" + cp "$__BinDir"/* "$__dotnet_dump" fi # Run SOS/lldbplugin tests diff --git a/eng/create-gallery-zip.ps1 b/eng/create-gallery-zip.ps1 index 98d77067df..352dc7f0e0 100644 --- a/eng/create-gallery-zip.ps1 +++ b/eng/create-gallery-zip.ps1 @@ -10,7 +10,7 @@ $BinDir = "$SourceDirectory\artifacts\bin" $SOSGalleryVersion = cat $BinDir\VersionPrefix.txt $SOSGalleryVersion = "$SOSGalleryVersion" + ".0" -$SOSNETCorePath = "$BinDir\SOS.NETCore\Release\netcoreapp2.0\publish" +$SOSNETCorePath = "$BinDir\SOS.NETCore\Release\netstandard2.0\publish" $GalleryDir = "$BinDir\gallery\$SOSGalleryVersion" $ZipFilePath = "$SourceDirectory\artifacts\packages\Release" $ZipFile = Join-Path -Path $ZipFilePath "$SOSGalleryVersion.zip" diff --git a/src/Microsoft.Diagnostic.Repl/Command/Attributes.cs b/src/Microsoft.Diagnostic.Repl/Command/Attributes.cs new file mode 100644 index 0000000000..8083b5e17d --- /dev/null +++ b/src/Microsoft.Diagnostic.Repl/Command/Attributes.cs @@ -0,0 +1,61 @@ +// -------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// -------------------------------------------------------------------- +using System; + +namespace Microsoft.Diagnostic.Repl +{ + /// + /// Base command option attribute. + /// + public class BaseAttribute : Attribute + { + /// + /// Name of the command + /// + public string Name; + + /// + /// Displayed in the help for the command + /// + public string Help; + } + + /// + /// Marks the class as a Command. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class CommandAttribute : BaseAttribute + { + /// + /// Sets the value of the CommandBase.AliasExpansion when the command is executed. + /// + public string AliasExpansion; + } + + /// + /// Marks the property as a Option. + /// + [AttributeUsage(AttributeTargets.Property)] + public class OptionAttribute : BaseAttribute + { + } + + /// + /// Adds an alias to the Option. Help is ignored. + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] + public class OptionAliasAttribute : BaseAttribute + { + } + + /// + /// Marks the property the command Argument. + /// + [AttributeUsage(AttributeTargets.Property)] + public class ArgumentAttribute : BaseAttribute + { + } +} diff --git a/src/Microsoft.Diagnostic.Repl/Command/CommandBase.cs b/src/Microsoft.Diagnostic.Repl/Command/CommandBase.cs new file mode 100644 index 0000000000..7c18f6963d --- /dev/null +++ b/src/Microsoft.Diagnostic.Repl/Command/CommandBase.cs @@ -0,0 +1,59 @@ +// -------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// -------------------------------------------------------------------- +using System; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostic.Repl +{ + /// + /// The common command context + /// + public abstract class CommandBase + { + public const string EntryPointName = nameof(InvokeAsync); + + /// + /// Parser invocation context. Contains the ParseResult, CommandResult, etc. + /// + public InvocationContext InvocationContext { get; set; } + + /// + /// Console instance + /// + public IConsole Console { get { return InvocationContext.Console; } } + + /// + /// The AliasExpansion value from the CommandAttribute or null if none. + /// + public string AliasExpansion { get; set; } + + /// + /// Execute the command + /// + public abstract Task InvokeAsync(); + + /// + /// Display text + /// + /// text message + protected void WriteLine(string message) + { + Console.Out.WriteLine(message); + } + + /// + /// Display formatted text + /// + /// format string + /// arguments + protected void WriteLine(string format, params object[] args) + { + Console.Out.WriteLine(string.Format(format, args)); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Diagnostic.Repl/Command/CommandProcessor.cs b/src/Microsoft.Diagnostic.Repl/Command/CommandProcessor.cs new file mode 100644 index 0000000000..957559932f --- /dev/null +++ b/src/Microsoft.Diagnostic.Repl/Command/CommandProcessor.cs @@ -0,0 +1,233 @@ +// -------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// -------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Builder; +using System.CommandLine.Invocation; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostic.Repl +{ + public class CommandProcessor + { + private readonly Parser _parser; + private readonly Command _rootCommand; + + /// + /// Domain specific context passed to commands + /// + public object CommandContext { get; set; } + + /// + /// Create an instance of the command processor; + /// + /// The list of assemblies to look for commands + public CommandProcessor(IEnumerable assemblies) + { + var rootBuilder = new CommandLineBuilder(); + rootBuilder.UseHelp() + .UseParseDirective() + .UseSuggestDirective() + .UseParseErrorReporting() + .UseExceptionHandler(); + BuildCommands(rootBuilder, assemblies); + _rootCommand = rootBuilder.Command; + _parser = rootBuilder.Build(); + } + + /// + /// Parse the command line. + /// + /// command line txt + /// option console + /// exit code + public Task Parse(string commandLine, IConsole console = null) + { + ParseResult result = _parser.Parse(commandLine); + return _parser.InvokeAsync(result, console); + } + + /// + /// Display all the help or a specific command's help. + /// + /// command name or null + /// command instance or null if not found + public Command GetCommand(string name) + { + if (string.IsNullOrEmpty(name)) { + return _rootCommand; + } + else { + return _rootCommand.Children.OfType().FirstOrDefault((cmd) => name == cmd.Name || cmd.Aliases.Any((alias) => name == alias)); + } + } + + private void BuildCommands(CommandLineBuilder rootBuilder, IEnumerable assemblies) + { + foreach (Type type in assemblies.SelectMany((assembly) => assembly.GetExportedTypes())) + { + Command command = null; + + var commandAttributes = (CommandAttribute[])type.GetCustomAttributes(typeof(CommandAttribute), inherit: false); + foreach (CommandAttribute commandAttribute in commandAttributes) + { + // If there is a previous command and the current command doesn't have help or alias expansion, use "simple" aliasing + if (command != null && commandAttribute.Help == null && commandAttribute.AliasExpansion == null) { + command.AddAlias(commandAttribute.Name); + continue; + } + command = new Command(commandAttribute.Name, commandAttribute.Help); + var builder = new CommandLineBuilder(command); + builder.UseHelp(); + + var properties = new List<(PropertyInfo, Option)>(); + PropertyInfo argument = null; + + foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite)) + { + var argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault(); + if (argumentAttribute != null) + { + if (argument != null) { + throw new ArgumentException($"More than one ArgumentAttribute in command class: {type.Name}"); + } + command.Argument = new Argument { + Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(), + Description = argumentAttribute.Help, + ArgumentType = property.PropertyType, + Arity = new ArgumentArity(0, int.MaxValue) + }; + argument = property; + } + else + { + var optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault(); + if (optionAttribute != null) + { + var option = new Option(optionAttribute.Name ?? BuildAlias(property.Name), optionAttribute.Help, new Argument { ArgumentType = property.PropertyType }); + command.AddOption(option); + properties.Add((property, option)); + + foreach (var optionAliasAttribute in (OptionAliasAttribute[])property.GetCustomAttributes(typeof(OptionAliasAttribute), inherit: false)) + { + option.AddAlias(optionAliasAttribute.Name); + } + } + else + { + // If not an option, add as just a settable properties + properties.Add((property, null)); + } + } + } + + command.Handler = new Handler(this, commandAttribute.AliasExpansion, argument, properties, type); + rootBuilder.AddCommand(command); + } + } + } + + private static string BuildAlias(string parameterName) + { + if (string.IsNullOrWhiteSpace(parameterName)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(parameterName)); + } + return parameterName.Length > 1 ? $"--{parameterName.ToKebabCase()}" : $"-{parameterName.ToLowerInvariant()}"; + } + + class Handler : ICommandHandler + { + private readonly CommandProcessor _commandProcessor; + private readonly string _aliasExpansion; + private readonly PropertyInfo _argument; + private readonly IEnumerable<(PropertyInfo Property, Option Option)> _properties; + + private readonly ConstructorInfo _constructor; + private readonly MethodInfo _methodInfo; + + public Handler(CommandProcessor commandProcessor, string aliasExpansion, PropertyInfo argument, IEnumerable<(PropertyInfo, Option)> properties, Type type) + { + _commandProcessor = commandProcessor; + _aliasExpansion = aliasExpansion; + _argument = argument; + _properties = properties; + + _constructor = type.GetConstructors().SingleOrDefault((info) => info.GetParameters().Length == 0) ?? + throw new ArgumentException($"No eligible constructor found in {type}"); + + _methodInfo = type.GetMethod(CommandBase.EntryPointName, new Type[] { typeof(IHelpBuilder) }) ?? type.GetMethod(CommandBase.EntryPointName) ?? + throw new ArgumentException($"{CommandBase.EntryPointName} method not found in {type}"); + } + + public Task InvokeAsync(InvocationContext context) + { + try + { + // Assumes zero parameter constructor + object instance = _constructor.Invoke(new object[0]); + SetProperties(context, instance); + + var methodBinder = new MethodBinder(_methodInfo, () => instance); + return methodBinder.InvokeAsync(context); + } + catch (TargetInvocationException ex) + { + throw ex.InnerException; + } + } + + private void SetProperties(InvocationContext context, object instance) + { + IEnumerable optionResults = context.ParseResult.CommandResult.Children.OfType(); + + foreach (var property in _properties) + { + object value = property.Property.GetValue(instance); + + if (property.Property.Name == nameof(CommandBase.AliasExpansion)) { + value = _aliasExpansion; + } + else + { + Type propertyType = property.Property.PropertyType; + if (propertyType == typeof(InvocationContext)) { + value = context; + } + else if (propertyType == typeof(IConsole)) { + value = context.Console; + } + else if (propertyType == typeof(CommandProcessor)) { + value = _commandProcessor; + } + else if (propertyType == _commandProcessor.CommandContext?.GetType()) { + value = _commandProcessor.CommandContext; + } + else if (property.Option != null) + { + OptionResult optionResult = optionResults.Where((result) => result.Option == property.Option).SingleOrDefault(); + if (optionResult != null) { + value = optionResult.GetValueOrDefault(); + } + } + } + + property.Property.SetValue(instance, value); + } + + if (_argument != null) + { + object value = context.ParseResult.CommandResult.GetValueOrDefault(); + _argument.SetValue(instance, value); + } + } + } + } +} diff --git a/src/Microsoft.Diagnostic.Repl/Console/CharToLineConverter.cs b/src/Microsoft.Diagnostic.Repl/Console/CharToLineConverter.cs new file mode 100644 index 0000000000..ffb2b3c478 --- /dev/null +++ b/src/Microsoft.Diagnostic.Repl/Console/CharToLineConverter.cs @@ -0,0 +1,59 @@ +// -------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// -------------------------------------------------------------------- + +using System; +using System.Text; + +namespace Microsoft.Diagnostic.Repl +{ + public sealed class CharToLineConverter + { + readonly Action m_callback; + readonly StringBuilder m_text = new StringBuilder(); + + public CharToLineConverter(Action callback) + { + m_callback = callback; + } + + public void Input(byte[] buffer, int offset, int count) + { + for (int i = 0; i < count; i++) { + char c = (char)buffer[offset + i]; + if (c == '\r') { + continue; + } + if (c == '\n') { + Flush(); + } + else if (c == '\t' || (c >= (char)0x20 && c <= (char)127)) { + m_text.Append(c); + } + } + } + + public void Input(string text) + { + foreach (char c in text) { + if (c == '\r') { + continue; + } + if (c == '\n') { + Flush(); + } + else if (c == '\t' || (c >= (char)0x20 && c <= (char)127)) { + m_text.Append(c); + } + } + } + + public void Flush() + { + m_callback(m_text.ToString()); + m_text.Clear(); + } + } +} diff --git a/src/Microsoft.Diagnostic.Repl/Console/ConsoleProvider.cs b/src/Microsoft.Diagnostic.Repl/Console/ConsoleProvider.cs new file mode 100644 index 0000000000..d4f9bbd2b2 --- /dev/null +++ b/src/Microsoft.Diagnostic.Repl/Console/ConsoleProvider.cs @@ -0,0 +1,487 @@ +// -------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// -------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostic.Repl +{ + public sealed class ConsoleProvider : IConsole + { + private readonly List m_history; + + private readonly CharToLineConverter m_consoleConverter; + private readonly CharToLineConverter m_warningConverter; + private readonly CharToLineConverter m_errorConverter; + + private string m_prompt = "> "; + + private bool m_shutdown; + private CancellationTokenSource m_interruptExecutingCommand; + + private string m_clearLine; + private bool m_refreshingLine; + private StringBuilder m_activeLine; + + private int m_selectedHistory; + + private bool m_modified; + private int m_cursorPosition; + private int m_scrollPosition; + private bool m_insertMode; + + private int m_commandExecuting; + private string m_lastCommandLine; + + /// + /// Create an instance of the console provider + /// + /// error color (default red) + /// warning color (default yellow) + public ConsoleProvider(ConsoleColor errorColor = ConsoleColor.Red, ConsoleColor warningColor = ConsoleColor.Yellow) + { + m_history = new List(); + m_activeLine = new StringBuilder(); + + m_consoleConverter = new CharToLineConverter(text => { + NewOutput(text); + }); + + m_warningConverter = new CharToLineConverter(text => { + NewOutput(text, warningColor); + }); + + m_errorConverter = new CharToLineConverter(text => { + NewOutput(text, errorColor); + }); + + Out = new StandardStreamWriter((text) => WriteOutput(OutputType.Normal, text)); + Error = new StandardStreamWriter((text) => WriteOutput(OutputType.Error, text)); + + // Hook ctrl-C and ctrl-break + Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCtrlBreakKeyPress); + } + + /// + /// Start input processing and command dispatching + /// + /// Called to dispatch a command on ENTER + public async Task Start(Func dispatchCommand) + { + m_lastCommandLine = null; + m_shutdown = false; + RefreshLine(); + + // Start keyboard processing + while (!m_shutdown) { + ConsoleKeyInfo keyInfo = Console.ReadKey(true); + await ProcessKeyInfo(keyInfo, dispatchCommand); + } + } + + /// + /// Stop input processing/dispatching + /// + public void Stop() + { + ClearLine(); + m_shutdown = true; + Console.CancelKeyPress -= new ConsoleCancelEventHandler(OnCtrlBreakKeyPress); + } + + /// + /// Change the command prompt + /// + /// new prompt + public void SetPrompt(string prompt) + { + m_prompt = prompt; + RefreshLine(); + } + + /// + /// Writes a message with a new line to console. + /// + public void WriteLine(OutputType type, string format, params object[] parameters) + { + WriteOutput(type, string.Format(format + Environment.NewLine, parameters)); + } + + /// + /// Write text on the console screen + /// + /// output type + /// text + /// ctrl-c interrupted the command + public void WriteOutput(OutputType type, string message) + { + switch (type) + { + case OutputType.Normal: + m_consoleConverter.Input(message); + break; + + case OutputType.Warning: + m_warningConverter.Input(message); + break; + + case OutputType.Error: + m_errorConverter.Input(message); + break; + } + } + + /// + /// Clear the console screen + /// + public void ClearScreen() + { + Console.Clear(); + PrintActiveLine(); + } + + /// + /// Write a line to the console. + /// + /// line of text + /// color of the text + private void NewOutput(string text, ConsoleColor? color = null) + { + ClearLine(); + + ConsoleColor? originalColor = null; + if (color.HasValue) { + originalColor = Console.ForegroundColor; + Console.ForegroundColor = color.Value; + } + Console.WriteLine(text); + if (originalColor.HasValue) { + Console.ForegroundColor = originalColor.Value; + } + + PrintActiveLine(); + } + + /// + /// This is the ctrl-c/ctrl-break handler + /// + private void OnCtrlBreakKeyPress(object sender, ConsoleCancelEventArgs e) + { + if (!m_shutdown) { + if (m_interruptExecutingCommand != null) { + m_interruptExecutingCommand.Cancel(); + } + e.Cancel = true; + } + } + + private void CommandStarting() + { + if (m_commandExecuting == 0) { + ClearLine(); + } + m_commandExecuting++; + } + + private void CommandFinished() + { + if (--m_commandExecuting == 0) { + RefreshLine(); + } + } + + private void ClearLine() + { + if (m_commandExecuting != 0) { + return; + } + + if (m_clearLine == null || m_clearLine.Length != Console.WindowWidth) { + m_clearLine = "\r" + new string(' ', Console.WindowWidth - 1); + } + + Console.Write(m_clearLine); + Console.CursorLeft = 0; + } + + private void PrintActiveLine() + { + if (m_shutdown) { + return; + } + + if (m_commandExecuting != 0) { + return; + } + + string prompt = m_prompt; + + int lineWidth = 80; + if (Console.WindowWidth > prompt.Length) { + lineWidth = Console.WindowWidth - prompt.Length - 1; + } + int scrollIncrement = lineWidth / 3; + + int activeLineLen = m_activeLine.Length; + + m_scrollPosition = Math.Min(Math.Max(m_scrollPosition, 0), activeLineLen); + m_cursorPosition = Math.Min(Math.Max(m_cursorPosition, 0), activeLineLen); + + while (m_cursorPosition < m_scrollPosition) { + m_scrollPosition = Math.Max(m_scrollPosition - scrollIncrement, 0); + } + + while (m_cursorPosition - m_scrollPosition > lineWidth - 5) { + m_scrollPosition += scrollIncrement; + } + + int lineRest = activeLineLen - m_scrollPosition; + int max = Math.Min(lineRest, lineWidth); + string text = m_activeLine.ToString(m_scrollPosition, max); + + Console.Write("{0}{1}", prompt, text); + Console.CursorLeft = prompt.Length + (m_cursorPosition - m_scrollPosition); + } + + private void RefreshLine() + { + // Check for recursions. + if (m_refreshingLine) { + return; + } + m_refreshingLine = true; + ClearLine(); + PrintActiveLine(); + m_refreshingLine = false; + } + + private async Task ProcessKeyInfo(ConsoleKeyInfo keyInfo, Func dispatchCommand) + { + int activeLineLen = m_activeLine.Length; + + switch (keyInfo.Key) { + case ConsoleKey.Backspace: // The BACKSPACE key. + if (m_cursorPosition > 0) { + EnsureNewEntry(); + m_activeLine.Remove(m_cursorPosition - 1, 1); + m_cursorPosition--; + RefreshLine(); + } + break; + + case ConsoleKey.Insert: // The INS (INSERT) key. + m_insertMode = !m_insertMode; + RefreshLine(); + break; + + case ConsoleKey.Delete: // The DEL (DELETE) key. + if (m_cursorPosition < activeLineLen) { + EnsureNewEntry(); + m_activeLine.Remove(m_cursorPosition, 1); + RefreshLine(); + } + break; + + case ConsoleKey.Enter: // The ENTER key. + string newCommand = m_activeLine.ToString(); + + if (m_modified) { + m_history.Add(m_activeLine); + } + m_selectedHistory = m_history.Count; + + await Dispatch(newCommand, dispatchCommand); + + SwitchToHistoryEntry(); + break; + + case ConsoleKey.Escape: // The ESC (ESCAPE) key. + EnsureNewEntry(); + m_activeLine.Clear(); + m_cursorPosition = 0; + RefreshLine(); + break; + + case ConsoleKey.End: // The END key. + m_cursorPosition = activeLineLen; + RefreshLine(); + break; + + case ConsoleKey.Home: // The HOME key. + m_cursorPosition = 0; + RefreshLine(); + break; + + case ConsoleKey.LeftArrow: // The LEFT ARROW key. + if (keyInfo.Modifiers == ConsoleModifiers.Control) { + while (m_cursorPosition > 0 && char.IsWhiteSpace(m_activeLine[m_cursorPosition - 1])) { + m_cursorPosition--; + } + + while (m_cursorPosition > 0 && !char.IsWhiteSpace(m_activeLine[m_cursorPosition - 1])) { + m_cursorPosition--; + } + } + else { + m_cursorPosition--; + } + + RefreshLine(); + break; + + case ConsoleKey.UpArrow: // The UP ARROW key. + if (m_selectedHistory > 0) { + m_selectedHistory--; + } + SwitchToHistoryEntry(); + break; + + case ConsoleKey.RightArrow: // The RIGHT ARROW key. + if (keyInfo.Modifiers == ConsoleModifiers.Control) { + while (m_cursorPosition < activeLineLen && !char.IsWhiteSpace(m_activeLine[m_cursorPosition])) { + m_cursorPosition++; + } + + while (m_cursorPosition < activeLineLen && char.IsWhiteSpace(m_activeLine[m_cursorPosition])) { + m_cursorPosition++; + } + } + else { + m_cursorPosition++; + } + + RefreshLine(); + break; + + case ConsoleKey.DownArrow: // The DOWN ARROW key. + if (m_selectedHistory < m_history.Count) { + m_selectedHistory++; + } + SwitchToHistoryEntry(); + + RefreshLine(); + break; + + default: + if (keyInfo.KeyChar != 0) { + if ((keyInfo.Modifiers & (ConsoleModifiers.Control | ConsoleModifiers.Alt)) == 0) { + AppendNewText(new string(keyInfo.KeyChar, 1)); + } + } + break; + } + } + + private async Task Dispatch(string newCommand, Func dispatchCommand) + { + CommandStarting(); + m_interruptExecutingCommand = new CancellationTokenSource(); + try + { + newCommand = newCommand.Trim(); + if (string.IsNullOrEmpty(newCommand) && m_lastCommandLine != null) { + newCommand = m_lastCommandLine; + } + try + { + WriteLine(OutputType.Normal, "{0}{1}", m_prompt, newCommand); + await dispatchCommand(newCommand, m_interruptExecutingCommand.Token); + m_lastCommandLine = newCommand; + } + catch (OperationCanceledException) + { + // ctrl-c interrupted the command + m_lastCommandLine = null; + } + catch (Exception ex) when (!(ex is NullReferenceException || ex is ArgumentNullException)) + { + WriteLine(OutputType.Error, "ERROR: {0}", ex.Message); + m_lastCommandLine = null; + } + } + finally + { + m_interruptExecutingCommand = null; + CommandFinished(); + } + } + + private void AppendNewText(string text) + { + EnsureNewEntry(); + + foreach (char c in text) { + // Filter unwanted characters. + switch (c) { + case '\t': + case '\r': + case '\n': + continue; + } + + if (m_insertMode && m_cursorPosition < m_activeLine.Length) { + m_activeLine[m_cursorPosition] = c; + } + else { + m_activeLine.Insert(m_cursorPosition, c); + } + m_modified = true; + m_cursorPosition++; + } + + RefreshLine(); + } + + private void SwitchToHistoryEntry() + { + if (m_selectedHistory < m_history.Count) { + m_activeLine = m_history[m_selectedHistory]; + } + else { + m_activeLine = new StringBuilder(); + } + + m_cursorPosition = m_activeLine.Length; + m_modified = false; + + RefreshLine(); + } + + private void EnsureNewEntry() + { + if (!m_modified) { + m_activeLine = new StringBuilder(m_activeLine.ToString()); + m_modified = true; + } + } + + #region IConsole + + public IStandardStreamWriter Out { get; } + + bool IStandardOut.IsOutputRedirected { get { return false; } } + + public IStandardStreamWriter Error { get; } + + bool IStandardError.IsErrorRedirected { get { return false; } } + + bool IStandardIn.IsInputRedirected { get { return false; } } + + class StandardStreamWriter : IStandardStreamWriter + { + readonly Action _write; + + public StandardStreamWriter(Action write) => _write = write; + + void IStandardStreamWriter.Write(string value) => _write(value); + } + + #endregion + } +} diff --git a/src/Microsoft.Diagnostic.Repl/Console/OutputType.cs b/src/Microsoft.Diagnostic.Repl/Console/OutputType.cs new file mode 100644 index 0000000000..f1530aea89 --- /dev/null +++ b/src/Microsoft.Diagnostic.Repl/Console/OutputType.cs @@ -0,0 +1,18 @@ +// -------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// -------------------------------------------------------------------- + +namespace Microsoft.Diagnostic.Repl +{ + /// + /// The type of output. + /// + public enum OutputType + { + Normal = 1, + Error = 2, + Warning = 3, + } +} diff --git a/src/Microsoft.Diagnostic.Repl/Microsoft.Diagnostic.Repl.csproj b/src/Microsoft.Diagnostic.Repl/Microsoft.Diagnostic.Repl.csproj new file mode 100644 index 0000000000..3fcbc83235 --- /dev/null +++ b/src/Microsoft.Diagnostic.Repl/Microsoft.Diagnostic.Repl.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0 + true + ;1591;1701 + Diagnostic utility functions and helpers + $(Description) + embedded + true + + + + + + + diff --git a/src/SOS/SOS.Hosting/Amd64Context.cs b/src/SOS/SOS.Hosting/Amd64Context.cs new file mode 100644 index 0000000000..c7030aa538 --- /dev/null +++ b/src/SOS/SOS.Hosting/Amd64Context.cs @@ -0,0 +1,114 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; + +namespace SOS +{ + [StructLayout(LayoutKind.Explicit)] + internal struct AMD64Context + { + [FieldOffset(0x0)] + public ulong P1Home; + [FieldOffset(0x8)] + public ulong P2Home; + [FieldOffset(0x10)] + public ulong P3Home; + [FieldOffset(0x18)] + public ulong P4Home; + [FieldOffset(0x20)] + public ulong P5Home; + [FieldOffset(0x28)] + public ulong P6Home; + + [FieldOffset(0x30)] + public int ContextFlags; + + [FieldOffset(0x34)] + public int MxCsr; + + [FieldOffset(0x38)] + public short SegCs; + [FieldOffset(0x3a)] + public short SegDs; + [FieldOffset(0x3c)] + public short SegEs; + [FieldOffset(0x3e)] + public short SegFs; + [FieldOffset(0x40)] + public short SegGs; + [FieldOffset(0x42)] + public short SegSs; + [FieldOffset(0x44)] + public int EFlags; + + [FieldOffset(0x48)] + public ulong Dr0; + [FieldOffset(0x50)] + public ulong Dr1; + [FieldOffset(0x58)] + public ulong Dr2; + [FieldOffset(0x60)] + public ulong Dr3; + [FieldOffset(0x68)] + public ulong Dr6; + [FieldOffset(0x70)] + public ulong Dr7; + + [FieldOffset(0x78)] + public ulong Rax; + [FieldOffset(0x80)] + public ulong Rcx; + [FieldOffset(0x88)] + public ulong Rdx; + [FieldOffset(0x90)] + public ulong Rbx; + [FieldOffset(0x98)] + public ulong Rsp; + [FieldOffset(0xa0)] + public ulong Rbp; + [FieldOffset(0xa8)] + public ulong Rsi; + [FieldOffset(0xb0)] + public ulong Rdi; + [FieldOffset(0xb8)] + public ulong R8; + [FieldOffset(0xc0)] + public ulong R9; + [FieldOffset(0xc8)] + public ulong R10; + [FieldOffset(0xd0)] + public ulong R11; + [FieldOffset(0xd8)] + public ulong R12; + [FieldOffset(0xe0)] + public ulong R13; + [FieldOffset(0xe8)] + public ulong R14; + [FieldOffset(0xf0)] + public ulong R15; + + [FieldOffset(0xf8)] + public ulong Rip; + + //[FieldOffset(0x100)] + //public XmmSaveArea FltSave; + + //[FieldOffset(0x300)] + //public VectorRegisterArea VectorRegisters; + + [FieldOffset(0x4a8)] + public ulong DebugControl; + [FieldOffset(0x4b0)] + public ulong LastBranchToRip; + [FieldOffset(0x4b8)] + public ulong LastBranchFromRip; + [FieldOffset(0x4c0)] + public ulong LastExceptionToRip; + [FieldOffset(0x4c8)] + public ulong LastExceptionFromRip; + + public static int Size => Marshal.SizeOf(typeof(AMD64Context)); + } +} \ No newline at end of file diff --git a/src/SOS/SOS.Hosting/AssemblyResolver.cs b/src/SOS/SOS.Hosting/AssemblyResolver.cs new file mode 100644 index 0000000000..a6734c7e23 --- /dev/null +++ b/src/SOS/SOS.Hosting/AssemblyResolver.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; + +namespace SOS +{ + /// + /// Used to enable app-local assembly unification. + /// + internal static class AssemblyResolver + { + static AssemblyResolver() + { + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + } + + /// + /// Call to enable the assembly resolver for the current AppDomain. + /// + public static void Enable() + { + // intentionally empty. This is just meant to ensure the static constructor + // has run. + } + + private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + // apply any existing policy + AssemblyName referenceName = new AssemblyName(AppDomain.CurrentDomain.ApplyPolicy(args.Name)); + + string fileName = referenceName.Name + ".dll"; + string assemblyPath = null; + string probingPath = null; + Assembly assm = null; + + // look next to requesting assembly + assemblyPath = args.RequestingAssembly?.Location; + if (!String.IsNullOrEmpty(assemblyPath)) + { + probingPath = Path.Combine(Path.GetDirectoryName(assemblyPath), fileName); + Debug.WriteLine($"Considering {probingPath} based on RequestingAssembly"); + if (Probe(probingPath, referenceName.Version, out assm)) + { + return assm; + } + } + + // look next to the executing assembly + assemblyPath = Assembly.GetExecutingAssembly().Location; + if (!String.IsNullOrEmpty(assemblyPath)) + { + probingPath = Path.Combine(Path.GetDirectoryName(assemblyPath), fileName); + + Debug.WriteLine($"Considering {probingPath} based on ExecutingAssembly"); + if (Probe(probingPath, referenceName.Version, out assm)) + { + return assm; + } + } + + // look in AppDomain base directory + probingPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + Debug.WriteLine($"Considering {probingPath} based on BaseDirectory"); + if (Probe(probingPath, referenceName.Version, out assm)) + { + return assm; + } + + return null; + } + + /// + /// Considers a path to load for satisfying an assembly ref and loads it + /// if the file exists and version is sufficient. + /// + /// Path to consider for load + /// Minimum version to consider + /// loaded assembly + /// true if assembly was loaded + private static bool Probe(string filePath, Version minimumVersion, out Assembly assembly) + { + if (File.Exists(filePath)) + { + AssemblyName name = AssemblyName.GetAssemblyName(filePath); + + if (name.Version >= minimumVersion) + { + assembly = Assembly.LoadFile(filePath); + return true; + } + } + + assembly = null; + return false; + } + } +} diff --git a/src/SOS/SOS.Hosting/ISOSHostContext.cs b/src/SOS/SOS.Hosting/ISOSHostContext.cs new file mode 100644 index 0000000000..3157a30361 --- /dev/null +++ b/src/SOS/SOS.Hosting/ISOSHostContext.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace SOS +{ + /// + /// Context/services provided to the SOS host. + /// + public interface ISOSHostContext + { + /// + /// Display text on the console + /// + /// message + void Write(string text); + + /// + /// Get/set the current native thread id + /// + int CurrentThreadId { get; set; } + + /// + /// Cancellation token for current operation + /// + CancellationToken CancellationToken { get; } + } +} \ No newline at end of file diff --git a/src/SOS/SOS.Hosting/LLDBServicesWrapper.cs b/src/SOS/SOS.Hosting/LLDBServicesWrapper.cs new file mode 100644 index 0000000000..74670f59cb --- /dev/null +++ b/src/SOS/SOS.Hosting/LLDBServicesWrapper.cs @@ -0,0 +1,1065 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Diagnostics.Runtime; +using Microsoft.Diagnostics.Runtime.Interop; +using Microsoft.Diagnostics.Runtime.Utilities; +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace SOS +{ + internal unsafe class LLDBServicesWrapper : COMCallableIUnknown + { + private static readonly Guid IID_ILLDBServices = new Guid("2E6C569A-9E14-4DA4-9DFC-CDB73A532566"); + private static readonly Guid IID_ILLDBServices2 = new Guid("012F32F0-33BA-4E8E-BC01-037D382D8A5E"); + private static readonly Guid IID_SOSHostServices = new Guid("D13608FB-AD14-4B49-990A-80284F934C41"); + + public IntPtr ILLDBServices { get; } + + #region SOS.NETCore function delegates + + private delegate bool InitializeSymbolStoreDelegate( + bool logging, + bool msdl, + bool symweb, + string symbolServerPath, + string symbolCachePath, + string windowsSymbolPath); + + private delegate void DisplaySymbolStoreDelegate(); + + private delegate void DisableSymbolStoreDelegate(); + + private delegate void LoadNativeSymbolsDelegate( + SymbolReader.SymbolFileCallback callback, + IntPtr parameter, + string tempDirectory, + string moduleFilePath, + ulong address, + int size, + SymbolReader.ReadMemoryDelegate readMemory); + + private delegate IntPtr LoadSymbolsForModuleDelegate( + string assemblyPath, + bool isFileLayout, + ulong loadedPeAddress, + int loadedPeSize, + ulong inMemoryPdbAddress, + int inMemoryPdbSize, + SymbolReader.ReadMemoryDelegate readMemory); + + private delegate void DisposeDelegate(IntPtr symbolReaderHandle); + + private delegate bool ResolveSequencePointDelegate( + IntPtr symbolReaderHandle, + string filePath, + int lineNumber, + out int methodToken, + out int ilOffset); + + private delegate bool GetLineByILOffsetDelegate( + IntPtr symbolReaderHandle, + int methodToken, + long ilOffset, + out int lineNumber, + out IntPtr fileName); + + private delegate bool GetLocalVariableNameDelegate( + IntPtr symbolReaderHandle, + int methodToken, + int localIndex, + out IntPtr localVarName); + + #endregion + + /// + /// Used by ISOSHostServices.GetSOSNETCoreCallbacks. + /// + [StructLayout(LayoutKind.Sequential)] + struct SOSNetCoreCallbacks + { + public InitializeSymbolStoreDelegate InitializeSymbolStoreDelegate; + public DisplaySymbolStoreDelegate DisplaySymbolStoreDelegate; + public DisableSymbolStoreDelegate DisableSymbolStoreDelegate; + public LoadNativeSymbolsDelegate LoadNativeSymbolsDelegate; + public LoadSymbolsForModuleDelegate LoadSymbolsForModuleDelegate; + public DisposeDelegate DisposeDelegate; + public ResolveSequencePointDelegate ResolveSequencePointDelegate; + public GetLineByILOffsetDelegate GetLineByILOffsetDelegate; + public GetLocalVariableNameDelegate GetLocalVariableNameDelegate; + } + + static SOSNetCoreCallbacks s_callbacks = new SOSNetCoreCallbacks { + InitializeSymbolStoreDelegate = SymbolReader.InitializeSymbolStore, + DisplaySymbolStoreDelegate = SymbolReader.DisplaySymbolStore, + DisableSymbolStoreDelegate = SymbolReader.DisableSymbolStore, + LoadNativeSymbolsDelegate = SymbolReader.LoadNativeSymbols, + LoadSymbolsForModuleDelegate = SymbolReader.LoadSymbolsForModule, + DisposeDelegate = SymbolReader.Dispose, + ResolveSequencePointDelegate = SymbolReader.ResolveSequencePoint, + GetLineByILOffsetDelegate = SymbolReader.GetLineByILOffset, + GetLocalVariableNameDelegate = SymbolReader.GetLocalVariableName, + }; + + static readonly string s_coreclrModuleName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "coreclr" : "libcoreclr.so"; + + readonly SOSHost _sosHost; + readonly IDataReader _dataReader; + readonly ISOSHostContext _context; + + /// + /// Create an instance of the service wrapper SOS uses. + /// + /// clrmd data reader instance + /// sos hosting context + public LLDBServicesWrapper(SOSHost host, IDataReader dataReader, ISOSHostContext context) + { + _sosHost = host; + _dataReader = dataReader; + _context = context; + + VTableBuilder builder = AddInterface(IID_ILLDBServices, validate: false); + + builder.AddMethod(new GetCoreClrDirectoryDelegate(GetCoreClrDirectory)); + builder.AddMethod(new GetExpressionDelegate(GetExpression)); + builder.AddMethod(new VirtualUnwindDelegate(VirtualUnwind)); + builder.AddMethod(new SetExceptionCallbackDelegate(SetExceptionCallback)); + builder.AddMethod(new ClearExceptionCallbackDelegate(ClearExceptionCallback)); + + builder.AddMethod(new GetInterruptDelegate(GetInterrupt)); + builder.AddMethod(new OutputVaListDelegate(OutputVaList)); + builder.AddMethod(new GetDebuggerTypeDelegate(GetDebuggerType)); + builder.AddMethod(new GetPageSizeDelegate(GetPageSize)); + builder.AddMethod(new GetExecutingProcessorTypeDelegate(GetExecutingProcessorType)); + builder.AddMethod(new ExecuteDelegate(Execute)); + builder.AddMethod(new GetLastEventInformationDelegate(GetLastEventInformation)); + builder.AddMethod(new DisassembleDelegate(Disassemble)); + + builder.AddMethod(new GetContextStackTraceDelegate(GetContextStackTrace)); + builder.AddMethod(new ReadVirtualDelegate(ReadVirtual)); + builder.AddMethod(new WriteVirtualDelegate(WriteVirtual)); + + builder.AddMethod(new GetSymbolOptionsDelegate(GetSymbolOptions)); + builder.AddMethod(new GetNameByOffsetDelegate(GetNameByOffset)); + builder.AddMethod(new GetNumberModulesDelegate(GetNumberModules)); + builder.AddMethod(new GetModuleByIndexDelegate(GetModuleByIndex)); + builder.AddMethod(new GetModuleByModuleNameDelegate(GetModuleByModuleName)); + builder.AddMethod(new GetModuleByOffsetDelegate(GetModuleByOffset)); + builder.AddMethod(new GetModuleNamesDelegate(GetModuleNames)); + builder.AddMethod(new GetLineByOffsetDelegate(GetLineByOffset)); + builder.AddMethod(new GetSourceFileLineOffsetsDelegate(GetSourceFileLineOffsets)); + builder.AddMethod(new FindSourceFileDelegate(FindSourceFile)); + + builder.AddMethod(new GetCurrentProcessIdDelegate(GetCurrentProcessId)); + builder.AddMethod(new GetCurrentThreadIdDelegate(GetCurrentThreadId)); + builder.AddMethod(new SetCurrentThreadIdDelegate(SetCurrentThreadId)); + builder.AddMethod(new GetCurrentThreadSystemIdDelegate(GetCurrentThreadSystemId)); + builder.AddMethod(new GetThreadIdBySystemIdDelegate(GetThreadIdBySystemId)); + builder.AddMethod(new GetThreadContextByIdDelegate(GetThreadContextById)); + + builder.AddMethod(new GetValueByNameDelegate(GetValueByName)); + builder.AddMethod(new GetInstructionOffsetDelegate(GetInstructionOffset)); + builder.AddMethod(new GetStackOffsetDelegate(GetStackOffset)); + builder.AddMethod(new GetFrameOffsetDelegate(GetFrameOffset)); + + ILLDBServices = builder.Complete(); + + builder = AddInterface(IID_ILLDBServices2, validate: false); + builder.AddMethod(new LoadNativeSymbolsDelegate2(LoadNativeSymbols2)); + builder.AddMethod(new AddModuleSymbolDelegate(AddModuleSymbol)); + builder.Complete(); + + builder = AddInterface(IID_SOSHostServices, validate: false); + builder.AddMethod(new GetSOSNETCoreCallbacksDelegate(GetSOSNETCoreCallbacks)); + builder.Complete(); + + AddRef(); + } + + #region ILLDBServices + + string GetCoreClrDirectory( + IntPtr self) + { + foreach (ModuleInfo module in _dataReader.EnumerateModules()) + { + if (string.Equals(Path.GetFileName(module.FileName), s_coreclrModuleName)) + { + return Path.GetDirectoryName(module.FileName) + Path.DirectorySeparatorChar; + } + } + return null; + } + + ulong GetExpression( + IntPtr self, + string text) + { + if (ulong.TryParse(text.Replace("0x", ""), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong result)) { + return result; + } + return 0; + } + + int VirtualUnwind( + IntPtr self, + uint threadId, + uint contextSize, + byte[] context) + { + return E_NOTIMPL; + } + + int SetExceptionCallback( + IntPtr self, + PFN_EXCEPTION_CALLBACK callback) + { + return S_OK; + } + + int ClearExceptionCallback( + IntPtr self) + { + return S_OK; + } + + int GetInterrupt( + IntPtr self) + { + return _context.CancellationToken.IsCancellationRequested ? S_OK : E_FAIL; + } + + int OutputVaList( + IntPtr self, + DEBUG_OUTPUT mask, + string format, + IntPtr va_list) + { + try + { + _context.Write(format); + } + catch (OperationCanceledException) + { + // ctrl-c interrupted the command + } + return S_OK; + } + + int GetDebuggerType( + IntPtr self, + out DEBUG_CLASS debugClass, + out DEBUG_CLASS_QUALIFIER qualifier) + { + debugClass = DEBUG_CLASS.USER_WINDOWS; + qualifier = DEBUG_CLASS_QUALIFIER.USER_WINDOWS_DUMP; + return S_OK; + } + + int GetPageSize( + IntPtr self, + out uint size) + { + size = 4096; + return S_OK; + } + + int GetExecutingProcessorType( + IntPtr self, + out IMAGE_FILE_MACHINE type) + { + switch (_dataReader.GetArchitecture()) + { + case Microsoft.Diagnostics.Runtime.Architecture.Amd64: + type = IMAGE_FILE_MACHINE.AMD64; + break; + case Microsoft.Diagnostics.Runtime.Architecture.X86: + type = IMAGE_FILE_MACHINE.I386; + break; + case Microsoft.Diagnostics.Runtime.Architecture.Arm: + type = IMAGE_FILE_MACHINE.ARM; + break; + default: + type = IMAGE_FILE_MACHINE.UNKNOWN; + break; + } + return S_OK; + } + + int Execute( + IntPtr self, + DEBUG_OUTCTL outputControl, + string command, + DEBUG_EXECUTE flags) + { + return E_NOTIMPL; + } + + int GetLastEventInformation( + IntPtr self, + out uint type, + out uint processId, + out uint threadId, + IntPtr extraInformation, + uint extraInformationSize, + out uint extraInformationUsed, + string description, + uint descriptionSize, + out uint descriptionUsed) + { + type = 0; + processId = 0; + threadId = 0; + extraInformationSize = 0; + extraInformationUsed = 0; + descriptionUsed = 0; + return E_NOTIMPL; + } + + int Disassemble( + IntPtr self, + ulong offset, + DEBUG_DISASM flags, + StringBuilder buffer, + uint bufferSize, + out uint disassemblySize, + out ulong endOffset) + { + disassemblySize = 0; + endOffset = 0; + return E_NOTIMPL; + } + + int GetContextStackTrace( + IntPtr self, + IntPtr startContext, + uint startContextSize, + DEBUG_STACK_FRAME[] frames, + uint framesSize, + IntPtr frameContexts, + uint frameContextsSize, + uint frameContextsEntrySize, + IntPtr pframesFilled) + { + return E_NOTIMPL; + } + + int ReadVirtual( + IntPtr self, + ulong address, + IntPtr buffer, + int bytesRequested, + IntPtr pbytesRead) + { + if (_dataReader.ReadMemory(address, buffer, bytesRequested, out int bytesRead)) + { + if (pbytesRead != IntPtr.Zero) + { + Marshal.WriteInt32(pbytesRead, bytesRead); + } + return S_OK; + } + return E_FAIL; + } + + int WriteVirtual( + IntPtr self, + ulong address, + IntPtr buffer, + uint bytesRequested, + IntPtr pbytesWritten) + { + // This gets used by MemoryBarrier() calls in the dac, which really shouldn't matter what we do here. + if (pbytesWritten != IntPtr.Zero) { + Marshal.WriteInt32(pbytesWritten, (int)bytesRequested); + } + return S_OK; + } + + int GetSymbolOptions( + IntPtr self, + out SYMOPT options) + { + options = SYMOPT.LOAD_LINES; + return S_OK; + } + + int GetNameByOffset( + IntPtr self, + ulong offset, + StringBuilder nameBuffer, + uint nameBufferSize, + out uint nameSize, + out ulong displacement) + { + nameSize = 0; + displacement = 0; + return E_NOTIMPL; + } + + int GetNumberModules( + IntPtr self, + out uint loaded, + out uint unloaded) + { + loaded = (uint)_dataReader.EnumerateModules().Count(); + unloaded = 0; + return S_OK; + } + + int GetModuleByIndex( + IntPtr self, + uint index, + out ulong baseAddress) + { + baseAddress = 0; + + try + { + ModuleInfo module = _dataReader.EnumerateModules().ElementAt((int)index); + if (module == null) + { + return E_FAIL; + } + baseAddress = module.ImageBase; + } + catch (ArgumentOutOfRangeException) + { + return E_FAIL; + } + + return S_OK; + } + + int GetModuleByModuleName( + IntPtr self, + string name, + uint startIndex, + IntPtr pIndex, + out ulong baseAddress) + { + // The returned "index" is never used by SOS. Always passes startIndex = 0; + Debug.Assert(pIndex == IntPtr.Zero); + Debug.Assert(startIndex == 0); + + baseAddress = 0; + + foreach (ModuleInfo module in _dataReader.EnumerateModules()) + { + if (string.Equals(Path.GetFileName(module.FileName), name)) + { + baseAddress = module.ImageBase; + return S_OK; + } + } + return E_FAIL; + } + + int GetModuleByOffset( + IntPtr self, + ulong offset, + uint startIndex, + out uint index, + out ulong baseAddress) + { + index = 0; + baseAddress = 0; + return E_NOTIMPL; + } + + int GetModuleNames( + IntPtr self, + uint index, + ulong baseAddress, + StringBuilder imageNameBuffer, + uint imageNameBufferSize, + out uint imageNameSize, + StringBuilder moduleNameBuffer, + uint ModuleNameBufferSize, + out uint moduleNameSize, + StringBuilder loadedImageNameBuffer, + uint loadedImageNameBufferSize, + out uint loadedImageNameSize) + { + imageNameSize = 0; + moduleNameSize = 0; + loadedImageNameSize = 0; + return E_NOTIMPL; + } + + int GetLineByOffset( + IntPtr self, + ulong offset, + out uint line, + StringBuilder fileBuffer, + uint fileBufferSize, + out uint fileSize, + out ulong displacement) + { + line = 0; + fileSize = 0; + displacement = 0; + return E_NOTIMPL; + } + + int GetSourceFileLineOffsets( + IntPtr self, + string file, + ulong[] buffer, + uint bufferLines, + out uint fileLines) + { + fileLines = 0; + return E_NOTIMPL; + } + + int FindSourceFile( + IntPtr self, + uint startElement, + string file, + uint flags, + out uint foundElement, + StringBuilder buffer, + uint bufferSize, + out uint foundSize) + { + foundElement = 0; + foundSize = 0; + return E_NOTIMPL; + } + + int GetCurrentProcessId( + IntPtr self, + out uint id) + { + id = 0; + if (_dataReader is IDataReader2 dataReader2) { + id = dataReader2.ProcessId; + } + return S_OK; + } + + int GetCurrentThreadId( + IntPtr self, + out uint id) + { + return GetThreadIdBySystemId(self, (uint)_context.CurrentThreadId, out id); + } + + int SetCurrentThreadId( + IntPtr self, + uint id) + { + try + { + unchecked { + _context.CurrentThreadId = (int)_dataReader.EnumerateAllThreads().ElementAt((int)id); + } + } + catch (ArgumentOutOfRangeException) + { + return E_FAIL; + } + return S_OK; + } + + int GetCurrentThreadSystemId( + IntPtr self, + out uint sysId) + { + sysId = (uint)_context.CurrentThreadId; + return S_OK; + } + + int GetThreadIdBySystemId( + IntPtr self, + uint sysId, + out uint id) + { + id = 0; + if (sysId != 0) + { + foreach (uint s in _dataReader.EnumerateAllThreads()) + { + if (s == sysId) { + return S_OK; + } + id++; + } + } + return E_FAIL; + } + + int GetThreadContextById( + IntPtr self, + uint threadId, + uint contextFlags, + uint contextSize, + IntPtr context) + { + if (_dataReader.GetThreadContext(threadId, contextFlags, contextSize, context)) { + return S_OK; + } + return E_FAIL; + } + + int GetValueByName( + IntPtr self, + string name, + out ulong value) + { + return GetRegister(name, out value); + } + + int GetInstructionOffset( + IntPtr self, + out ulong offset) + { + // TODO: Support other architectures + return GetRegister("rip", out offset); + } + + int GetStackOffset( + IntPtr self, + out ulong offset) + { + // TODO: Support other architectures + return GetRegister("rsp", out offset); + } + + int GetFrameOffset( + IntPtr self, + out ulong offset) + { + // TODO: Support other architectures + return GetRegister("rbp", out offset); + } + + #endregion + + // TODO: Support other architectures + int GetRegister(string register, out ulong value) + { + value = 0; + int hr = GetCurrentThreadSystemId(IntPtr.Zero, out uint threadId); + if (hr != 0) { + return hr; + } + byte[] buffer = new byte[AMD64Context.Size]; + if (!_dataReader.GetThreadContext(threadId, uint.MaxValue, (uint)AMD64Context.Size, buffer)) + { + return E_FAIL; + } + fixed (byte* ptr = buffer) + { + AMD64Context* context = (AMD64Context*)ptr; + switch (register.ToLower()) + { + case "rax": + value = context->Rax; + break; + case "rbx": + value = context->Rbx; + break; + case "rcx": + value = context->Rcx; + break; + case "rdx": + value = context->Rdx; + break; + case "rsi": + value = context->Rsi; + break; + case "rdi": + value = context->Rdi; + break; + case "r8": + value = context->R8; + break; + case "r9": + value = context->R9; + break; + case "r10": + value = context->R10; + break; + case "r11": + value = context->R11; + break; + case "r12": + value = context->R12; + break; + case "r13": + value = context->R13; + break; + case "r14": + value = context->R14; + break; + case "r15": + value = context->R15; + break; + case "rip": + value = context->Rip; + break; + case "rsp": + value = context->Rsp; + break; + case "rbp": + value = context->Rbp; + break; + default: + return E_FAIL; + } + } + return S_OK; + } + + #region ILLDBServices2 + + int LoadNativeSymbols2( + IntPtr self, + bool runtimeOnly, + ModuleLoadCallback callback) + { + foreach (ModuleInfo module in _dataReader.EnumerateModules()) + { + callback(IntPtr.Zero, module.FileName, module.ImageBase, unchecked((int)module.FileSize)); + } + return S_OK; + } + + int AddModuleSymbol( + IntPtr self, + IntPtr parameter, + string symbolFilename) + { + return S_OK; + } + + #endregion + + #region ISOSHostServices + + int GetSOSNETCoreCallbacks( + IntPtr self, + int version, + IntPtr pCallbacks) + { + if (version < 1) + { + return E_FAIL; + } + try + { + Marshal.StructureToPtr(s_callbacks, pCallbacks, false); + } + catch (ArgumentException) + { + return E_FAIL; + } + return S_OK; + } + + #endregion + + #region ILLDBServices delegates + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + [return: MarshalAs(UnmanagedType.LPStr)] + private delegate string GetCoreClrDirectoryDelegate( + IntPtr self); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate ulong GetExpressionDelegate( + IntPtr self, + [In][MarshalAs(UnmanagedType.LPStr)] string text); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int VirtualUnwindDelegate( + IntPtr self, + uint threadId, + uint contextSize, + byte[] context); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int PFN_EXCEPTION_CALLBACK(LLDBServicesWrapper services); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int SetExceptionCallbackDelegate( + IntPtr self, + PFN_EXCEPTION_CALLBACK callback); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int ClearExceptionCallbackDelegate( + IntPtr self); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetInterruptDelegate( + IntPtr self); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int OutputVaListDelegate( + IntPtr self, + DEBUG_OUTPUT mask, + [In, MarshalAs(UnmanagedType.LPStr)] string format, + IntPtr va_list); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetDebuggerTypeDelegate( + IntPtr self, + out DEBUG_CLASS debugClass, + out DEBUG_CLASS_QUALIFIER qualifier); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetPageSizeDelegate( + IntPtr self, + out uint size); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetExecutingProcessorTypeDelegate( + IntPtr self, + out IMAGE_FILE_MACHINE type); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int ExecuteDelegate( + IntPtr self, + DEBUG_OUTCTL outputControl, + [In, MarshalAs(UnmanagedType.LPStr)] string command, + DEBUG_EXECUTE flags); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetLastEventInformationDelegate( + IntPtr self, + out uint type, + out uint processId, + out uint threadId, + IntPtr extraInformation, + uint extraInformationSize, + out uint extraInformationUsed, + [In][MarshalAs(UnmanagedType.LPStr)] string description, + uint descriptionSize, + out uint descriptionUsed); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int DisassembleDelegate( + IntPtr self, + ulong offset, + DEBUG_DISASM flags, + [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder buffer, + uint bufferSize, + out uint disassemblySize, + out ulong endOffset); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetContextStackTraceDelegate( + IntPtr self, + IntPtr startContext, + uint startContextSize, + [Out, MarshalAs(UnmanagedType.LPArray)] DEBUG_STACK_FRAME[] frames, + uint framesSize, + IntPtr frameContexts, + uint frameContextsSize, + uint frameContextsEntrySize, + IntPtr pframesFilled); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int ReadVirtualDelegate( + IntPtr self, + ulong address, + IntPtr buffer, + int bytesRequested, + IntPtr pbytesRead); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int WriteVirtualDelegate( + IntPtr self, + ulong address, + IntPtr buffer, + uint bytesRequested, + IntPtr pbytesWritten); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetSymbolOptionsDelegate( + IntPtr self, + out SYMOPT options); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetNameByOffsetDelegate( + IntPtr self, + ulong offset, + [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder nameBuffer, + uint nameBufferSize, + out uint nameSize, + out ulong displacement); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetNumberModulesDelegate( + IntPtr self, + out uint loaded, + out uint unloaded); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetModuleByIndexDelegate( + IntPtr self, + uint index, + out ulong baseAddress); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetModuleByModuleNameDelegate( + IntPtr self, + [In, MarshalAs(UnmanagedType.LPStr)] string name, + uint startIndex, + IntPtr index, + out ulong baseAddress); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetModuleByOffsetDelegate( + IntPtr self, + ulong offset, + uint startIndex, + out uint index, + out ulong baseAddress); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetModuleNamesDelegate( + IntPtr self, + uint index, + ulong baseAddress, + [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder imageNameBuffer, + uint imageNameBufferSize, + out uint imageNameSize, + [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder moduleNameBuffer, + uint ModuleNameBufferSize, + out uint moduleNameSize, + [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder loadedImageNameBuffer, + uint loadedImageNameBufferSize, + out uint loadedImageNameSize); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetLineByOffsetDelegate( + IntPtr self, + ulong offset, + out uint line, + [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder fileBuffer, + uint fileBufferSize, + out uint fileSize, + out ulong displacement); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetSourceFileLineOffsetsDelegate( + IntPtr self, + [In, MarshalAs(UnmanagedType.LPStr)] string file, + [Out, MarshalAs(UnmanagedType.LPArray)] ulong[] buffer, + uint bufferLines, + out uint fileLines); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int FindSourceFileDelegate( + IntPtr self, + uint startElement, + [In, MarshalAs(UnmanagedType.LPStr)] string file, + uint flags, + out uint foundElement, + [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder buffer, + uint bufferSize, + out uint foundSize); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetCurrentProcessIdDelegate( + IntPtr self, + out uint id); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetCurrentThreadIdDelegate( + IntPtr self, + out uint id); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int SetCurrentThreadIdDelegate( + IntPtr self, + uint id); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetCurrentThreadSystemIdDelegate( + IntPtr self, + out uint sysId); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetThreadIdBySystemIdDelegate( + IntPtr self, + uint sysId, + out uint id); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetThreadContextByIdDelegate( + IntPtr self, + uint threadId, + uint contextFlags, + uint contextSize, + IntPtr context); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetValueByNameDelegate( + IntPtr self, + [In, MarshalAs(UnmanagedType.LPStr)] string name, + out ulong value); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetInstructionOffsetDelegate( + IntPtr self, + out ulong offset); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetStackOffsetDelegate( + IntPtr self, + out ulong offset); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetFrameOffsetDelegate( + IntPtr self, + out ulong offset); + + #endregion + + #region ILLDBServices2 delegates + + /// + /// The LoadNativeSymbolsDelegate2 callback + /// + public delegate void ModuleLoadCallback( + IntPtr parameter, + [MarshalAs(UnmanagedType.LPStr)] string moduleFilePath, + ulong moduleAddress, + int moduleSize); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int LoadNativeSymbolsDelegate2( + IntPtr self, + bool runtimeOnly, + ModuleLoadCallback callback); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int AddModuleSymbolDelegate( + IntPtr self, + IntPtr parameter, + [MarshalAs(UnmanagedType.LPStr)] string symbolFilename); + + #endregion + + #region ISOSHostServices delegates + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int GetSOSNETCoreCallbacksDelegate( + IntPtr self, + int version, + IntPtr pCallbacks); + + #endregion + } +} \ No newline at end of file diff --git a/src/SOS/SOS.Hosting/SOS.Hosting.csproj b/src/SOS/SOS.Hosting/SOS.Hosting.csproj new file mode 100644 index 0000000000..777fb9e6cf --- /dev/null +++ b/src/SOS/SOS.Hosting/SOS.Hosting.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + SOS.Hosting + ;1591;1701 + SOS Hosting support + true + true + + + + + + + + + + + diff --git a/src/SOS/SOS.Hosting/SOSHost.cs b/src/SOS/SOS.Hosting/SOSHost.cs new file mode 100644 index 0000000000..9df525ee90 --- /dev/null +++ b/src/SOS/SOS.Hosting/SOSHost.cs @@ -0,0 +1,99 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Diagnostics.Runtime; +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace SOS +{ + /// + /// Helper code to hosting SOS under ClrMD + /// + public sealed class SOSHost + { + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + delegate int SOSCommandDelegate(IntPtr ILLDBServices, [In, MarshalAs(UnmanagedType.LPStr)] string args); + + readonly LLDBServicesWrapper _wrapper; + IntPtr _sosLibrary = IntPtr.Zero; + + /// + /// Enable the assembly resolver to get the right SOS.NETCore version (the one + /// in the same directory as SOS.Hosting). + /// + static SOSHost() + { + AssemblyResolver.Enable(); + } + + /// + /// The native SOS binaries path. Default is OS/architecture (RID) named directory in the same directory as this assembly. + /// + public string SOSPath { get; set; } + + /// + /// Create an instance of the hosting class + /// + public SOSHost(IDataReader dataReader, ISOSHostContext context) + { + string os = null; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + os = "win"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + os = "linux"; + } + if (os == null) { + throw new PlatformNotSupportedException($"{RuntimeInformation.OSDescription} not supported"); + } + string architecture = RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant(); + string rid = os + "-" + architecture; + SOSPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), rid); + + _wrapper = new LLDBServicesWrapper(this, dataReader, context); + } + + public void ExecuteCommand(string commandLine) + { + string command = "Help"; + string arguments = null; + + if (commandLine != null) + { + int firstSpace = commandLine.IndexOf(' '); + command = firstSpace == -1 ? commandLine : commandLine.Substring(0, firstSpace); + arguments = firstSpace == -1 ? null : commandLine.Substring(firstSpace); + } + ExecuteCommand(command, arguments); + } + + public void ExecuteCommand(string command, string arguments) + { + if (_sosLibrary == IntPtr.Zero) + { + string sosPath = Path.Combine(SOSPath, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "sos.dll" : "libsos.so"); + _sosLibrary = DataTarget.PlatformFunctions.LoadLibrary(sosPath); + if (_sosLibrary == IntPtr.Zero) + { + throw new FileNotFoundException($"SOS module {sosPath} not found"); + } + } + IntPtr commandAddress = DataTarget.PlatformFunctions.GetProcAddress(_sosLibrary, command); + if (commandAddress == IntPtr.Zero) + { + throw new EntryPointNotFoundException($"Can not find SOS command: {command}"); + } + var commandFunc = (SOSCommandDelegate)Marshal.GetDelegateForFunctionPointer(commandAddress, typeof(SOSCommandDelegate)); + + int result = commandFunc(_wrapper.ILLDBServices, arguments ?? ""); + if (result != 0) + { + throw new InvalidOperationException($"SOS command FAILED 0x{result:X8}"); + } + } + } +} diff --git a/src/SOS/SOS.NETCore/CMakeLists.txt b/src/SOS/SOS.NETCore/CMakeLists.txt index 9dcd85b140..4c09fbf616 100644 --- a/src/SOS/SOS.NETCore/CMakeLists.txt +++ b/src/SOS/SOS.NETCore/CMakeLists.txt @@ -1,7 +1,7 @@ project(SOS.NETCore) if(NOT ${CLR_MANAGED_BINARY_DIR} STREQUAL "") - set(MANAGED_BINDIR ${CLR_MANAGED_BINARY_DIR}/SOS.NETCore/${CLR_BUILD_TYPE}/netcoreapp2.0/publish) + set(MANAGED_BINDIR ${CLR_MANAGED_BINARY_DIR}/SOS.NETCore/${CLR_BUILD_TYPE}/netstandard2.0/publish) install(FILES ${MANAGED_BINDIR}/SOS.NETCore.dll DESTINATION . ) install(FILES ${MANAGED_BINDIR}/SOS.NETCore.pdb DESTINATION . ) diff --git a/src/SOS/SOS.NETCore/SOS.NETCore.csproj b/src/SOS/SOS.NETCore/SOS.NETCore.csproj index a0f80a208e..4c57095b1d 100644 --- a/src/SOS/SOS.NETCore/SOS.NETCore.csproj +++ b/src/SOS/SOS.NETCore/SOS.NETCore.csproj @@ -1,7 +1,7 @@  - netcoreapp2.0 + netstandard2.0 SOS.NETCore true ;1591;1701 diff --git a/src/SOS/SOS.NETCore/SymbolReader.cs b/src/SOS/SOS.NETCore/SymbolReader.cs index 4681c43f59..1c128fc7ae 100644 --- a/src/SOS/SOS.NETCore/SymbolReader.cs +++ b/src/SOS/SOS.NETCore/SymbolReader.cs @@ -18,12 +18,11 @@ using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; -using System.Text; using System.Threading; namespace SOS { - internal class SymbolReader + public class SymbolReader { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] internal struct DebugInfo @@ -145,23 +144,20 @@ public override void Write(byte[] buffer, int offset, int count) /// Read memory callback /// /// number of bytes read or 0 for error - internal unsafe delegate int ReadMemoryDelegate(ulong address, byte* buffer, int count); + public unsafe delegate int ReadMemoryDelegate(ulong address, byte* buffer, int count); /// /// Writeline delegate for symbol store logging /// /// - internal delegate void WriteLine([MarshalAs(UnmanagedType.LPStr)] string message); + public delegate void WriteLine([MarshalAs(UnmanagedType.LPStr)] string message); /// /// The LoadNativeSymbols callback /// /// module file name /// symbol file name and path - internal delegate void SymbolFileCallback( - IntPtr parameter, - [MarshalAs(UnmanagedType.LPStr)] string moduleFileName, - [MarshalAs(UnmanagedType.LPStr)] string symbolFileName); + public delegate void SymbolFileCallback(IntPtr parameter, [MarshalAs(UnmanagedType.LPStr)] string moduleFileName, [MarshalAs(UnmanagedType.LPStr)] string symbolFileName); static SymbolStore s_symbolStore = null; static bool s_symbolCacheAdded = false; @@ -178,7 +174,7 @@ internal delegate void SymbolFileCallback( /// symbol cache directory path (optional) /// windows symbol path (optional) /// - internal static bool InitializeSymbolStore(bool logging, bool msdl, bool symweb, string symbolServerPath, string symbolCachePath, string windowsSymbolPath) + public static bool InitializeSymbolStore(bool logging, bool msdl, bool symweb, string symbolServerPath, string symbolCachePath, string windowsSymbolPath) { if (s_tracer == null) { s_tracer = new Tracer(enabled: logging, enabledVerbose: logging, Console.WriteLine); @@ -206,7 +202,7 @@ internal static bool InitializeSymbolStore(bool logging, bool msdl, bool symweb, /// /// Displays the symbol server and cache configuration /// - internal static void DisplaySymbolStore() + public static void DisplaySymbolStore() { if (s_tracer != null) { @@ -230,7 +226,7 @@ internal static void DisplaySymbolStore() /// /// This function disables any symbol downloading support. /// - internal static void DisableSymbolStore() + public static void DisableSymbolStore() { s_tracer = null; s_symbolStore = null; @@ -242,18 +238,15 @@ internal static void DisableSymbolStore() /// /// called back for each symbol file loaded /// callback parameter - /// module path - /// module file name + /// module path /// module base address /// module size /// read memory callback delegate - internal static void LoadNativeSymbols(SymbolFileCallback callback, IntPtr parameter, string tempDirectory, string moduleDirectory, string moduleFileName, - ulong address, int size, ReadMemoryDelegate readMemory) + public static void LoadNativeSymbols(SymbolFileCallback callback, IntPtr parameter, string tempDirectory, string moduleFilePath, ulong address, int size, ReadMemoryDelegate readMemory) { if (s_symbolStore != null) { Debug.Assert(s_tracer != null); - string path = Path.Combine(moduleDirectory, moduleFileName); Stream stream = new TargetStream(address, size, readMemory); KeyTypeFlags flags = KeyTypeFlags.SymbolKey | KeyTypeFlags.ClrKeys; KeyGenerator generator = null; @@ -261,12 +254,12 @@ internal static void LoadNativeSymbols(SymbolFileCallback callback, IntPtr param if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { var elfFile = new ELFFile(new StreamAddressSpace(stream), 0, true); - generator = new ELFFileKeyGenerator(s_tracer, elfFile, path); + generator = new ELFFileKeyGenerator(s_tracer, elfFile, moduleFilePath); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { var machOFile = new MachOFile(new StreamAddressSpace(stream), 0, true); - generator = new MachOFileKeyGenerator(s_tracer, machOFile, path); + generator = new MachOFileKeyGenerator(s_tracer, machOFile, moduleFilePath); } else { @@ -278,11 +271,11 @@ internal static void LoadNativeSymbols(SymbolFileCallback callback, IntPtr param IEnumerable keys = generator.GetKeys(flags); foreach (SymbolStoreKey key in keys) { - string symbolFileName = Path.GetFileName(key.FullPathName); + string moduleFileName = Path.GetFileName(key.FullPathName); s_tracer.Verbose("{0} {1}", key.FullPathName, key.Index); // Don't download the sos binaries that come with the runtime - if (symbolFileName != "SOS.NETCore.dll" && !symbolFileName.StartsWith("libsos.")) + if (moduleFileName != "SOS.NETCore.dll" && !moduleFileName.StartsWith("libsos.")) { using (SymbolStoreFile file = GetSymbolStoreFile(key)) { @@ -290,20 +283,20 @@ internal static void LoadNativeSymbols(SymbolFileCallback callback, IntPtr param { try { - string downloadFileName = file.FileName; + string downloadFilePath = file.FileName; // If the downloaded doesn't already exists on disk in the cache, then write it to a temporary location. - if (!File.Exists(downloadFileName)) + if (!File.Exists(downloadFilePath)) { - downloadFileName = Path.Combine(tempDirectory, symbolFileName); + downloadFilePath = Path.Combine(tempDirectory, moduleFileName); - using (Stream destinationStream = File.OpenWrite(downloadFileName)) { + using (Stream destinationStream = File.OpenWrite(downloadFilePath)) { file.Stream.CopyTo(destinationStream); } s_tracer.WriteLine("Downloaded symbol file {0}", key.FullPathName); } - s_tracer.Information("{0}: {1}", symbolFileName, downloadFileName); - callback(parameter, symbolFileName, downloadFileName); + s_tracer.Information("{0}: {1}", moduleFileName, downloadFilePath); + callback(parameter, moduleFileName, downloadFilePath); } catch (Exception ex) when (ex is UnauthorizedAccessException || ex is DirectoryNotFoundException) { @@ -316,7 +309,7 @@ internal static void LoadNativeSymbols(SymbolFileCallback callback, IntPtr param } catch (Exception ex) when (ex is BadInputFormatException || ex is InvalidVirtualAddressException) { - s_tracer.Error("Exception: {0}/{1}: {2:X16}", moduleDirectory, moduleFileName, address); + s_tracer.Error("Exception: {0}/{1}: {2:X16}", moduleFilePath, address); } } } @@ -337,7 +330,7 @@ internal static void LoadNativeSymbols(SymbolFileCallback callback, IntPtr param /// in memory PDB address or zero /// in memory PDB size /// Symbol reader handle or zero if error - internal static IntPtr LoadSymbolsForModule(string assemblyPath, bool isFileLayout, ulong loadedPeAddress, int loadedPeSize, + public static IntPtr LoadSymbolsForModule(string assemblyPath, bool isFileLayout, ulong loadedPeAddress, int loadedPeSize, ulong inMemoryPdbAddress, int inMemoryPdbSize, ReadMemoryDelegate readMemory) { try @@ -369,7 +362,7 @@ internal static IntPtr LoadSymbolsForModule(string assemblyPath, bool isFileLayo /// Cleanup and dispose of symbol reader handle /// /// symbol reader handle returned by LoadSymbolsForModule - internal static void Dispose(IntPtr symbolReaderHandle) + public static void Dispose(IntPtr symbolReaderHandle) { Debug.Assert(symbolReaderHandle != IntPtr.Zero); try @@ -392,7 +385,7 @@ internal static void Dispose(IntPtr symbolReaderHandle) /// method token return /// IL offset return /// true if information is available - internal static bool ResolveSequencePoint(IntPtr symbolReaderHandle, string filePath, int lineNumber, out int methodToken, out int ilOffset) + public static bool ResolveSequencePoint(IntPtr symbolReaderHandle, string filePath, int lineNumber, out int methodToken, out int ilOffset) { Debug.Assert(symbolReaderHandle != IntPtr.Zero); methodToken = 0; @@ -435,7 +428,7 @@ internal static bool ResolveSequencePoint(IntPtr symbolReaderHandle, string file /// source line number return /// source file name return /// true if information is available - internal static bool GetLineByILOffset(IntPtr symbolReaderHandle, int methodToken, long ilOffset, out int lineNumber, out IntPtr fileName) + public static bool GetLineByILOffset(IntPtr symbolReaderHandle, int methodToken, long ilOffset, out int lineNumber, out IntPtr fileName) { lineNumber = 0; fileName = IntPtr.Zero; @@ -517,7 +510,7 @@ private static bool GetSourceLineByILOffset(IntPtr symbolReaderHandle, int metho /// local variable index /// local variable name return /// true if name has been found - internal static bool GetLocalVariableName(IntPtr symbolReaderHandle, int methodToken, int localIndex, out IntPtr localVarName) + public static bool GetLocalVariableName(IntPtr symbolReaderHandle, int methodToken, int localIndex, out IntPtr localVarName) { localVarName = IntPtr.Zero; @@ -952,12 +945,20 @@ private static OpenedReader TryOpenReaderFromEmbeddedPdb(PEReader peReader, Debu /// stream or null private static SymbolStoreFile GetSymbolStoreFile(SymbolStoreKey key) { - // Add the default symbol cache if it hasn't already been added - if (!s_symbolCacheAdded) { - s_symbolStore = new CacheSymbolStore(s_tracer, s_symbolStore, GetDefaultSymbolCache()); - s_symbolCacheAdded = true; + try + { + // Add the default symbol cache if it hasn't already been added + if (!s_symbolCacheAdded) { + s_symbolStore = new CacheSymbolStore(s_tracer, s_symbolStore, GetDefaultSymbolCache()); + s_symbolCacheAdded = true; + } + return s_symbolStore.GetFile(key, CancellationToken.None).GetAwaiter().GetResult(); + } + catch (Exception ex) when (ex is UnauthorizedAccessException || ex is BadImageFormatException || ex is IOException) + { + s_tracer.Error("Exception: {0}", ex.ToString()); + return null; } - return s_symbolStore.GetFile(key, CancellationToken.None).GetAwaiter().GetResult(); } /// @@ -968,11 +969,11 @@ private static SymbolStoreFile GetSymbolStoreFile(SymbolStoreKey key) /// if false, error parsing symbol path private static bool ParseSymbolPath(ref SymbolStore store, string symbolPath) { - string[] paths = symbolPath.Split(";", StringSplitOptions.RemoveEmptyEntries); + string[] paths = symbolPath.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (string path in paths.Reverse()) { - string[] parts = path.Split("*", StringSplitOptions.RemoveEmptyEntries); + string[] parts = path.Split(new char[] { '*' }, StringSplitOptions.RemoveEmptyEntries); // UNC or directory paths are ignored (paths not prefixed with srv* or cache*). if (parts.Length > 0) diff --git a/src/SOS/SOS.NETCore/Tracer.cs b/src/SOS/SOS.NETCore/Tracer.cs index 894598cc16..ac046d0ede 100644 --- a/src/SOS/SOS.NETCore/Tracer.cs +++ b/src/SOS/SOS.NETCore/Tracer.cs @@ -24,7 +24,9 @@ public Tracer(bool enabled, bool enabledVerbose, SymbolReader.WriteLine output) public void WriteLine(string message) { - m_output?.Invoke(message); + lock (this) { + m_output?.Invoke(message); + } } public void WriteLine(string format, params object[] arguments) diff --git a/src/SOS/Strike/Strike.vcxproj b/src/SOS/Strike/Strike.vcxproj index ca82bf2b25..060b0c4019 100644 --- a/src/SOS/Strike/Strike.vcxproj +++ b/src/SOS/Strike/Strike.vcxproj @@ -418,6 +418,7 @@ + diff --git a/src/SOS/Strike/Strike.vcxproj.filters b/src/SOS/Strike/Strike.vcxproj.filters index cd5bb759b5..1640905752 100644 --- a/src/SOS/Strike/Strike.vcxproj.filters +++ b/src/SOS/Strike/Strike.vcxproj.filters @@ -57,6 +57,7 @@ inc + diff --git a/src/SOS/Strike/hostcoreclr.cpp b/src/SOS/Strike/hostcoreclr.cpp index 4ff608ac24..4621348464 100644 --- a/src/SOS/Strike/hostcoreclr.cpp +++ b/src/SOS/Strike/hostcoreclr.cpp @@ -237,7 +237,6 @@ HRESULT GetCoreClrDirectory(std::string& coreClrDirectory) } if (!GetAbsolutePath(directory, coreClrDirectory)) { - ExtErr("Error: Failed to get coreclr absolute path\n"); return E_FAIL; } #else @@ -410,7 +409,7 @@ static LPCSTR GetTempDirectory() strcat_s(tmpPath, MAX_LONGPATH, DIRECTORY_SEPARATOR_STR_A); } char pidstr[128]; - sprintf_s(pidstr, _countof(pidstr), "%d", GetCurrentProcessId()); + sprintf_s(pidstr, _countof(pidstr), "sos%d", GetCurrentProcessId()); strcat_s(tmpPath, MAX_LONGPATH, pidstr); strcat_s(tmpPath, MAX_LONGPATH, DIRECTORY_SEPARATOR_STR_A); @@ -503,6 +502,12 @@ LPCSTR GetDacFilePath() g_dacFilePath = _strdup(dacModulePath.c_str()); } } + + if (g_dacFilePath == nullptr) + { + // Attempt to only load the DAC/DBI modules + LoadNativeSymbols(true); + } } return g_dacFilePath; } @@ -524,7 +529,15 @@ LPCSTR GetDbiFilePath() // if DBI file exists if (access(dbiModulePath.c_str(), F_OK) == 0) #endif + { g_dbiFilePath = _strdup(dbiModulePath.c_str()); + } + } + + if (g_dbiFilePath == nullptr) + { + // Attempt to only load the DAC/DBI modules + LoadNativeSymbols(true); } } return g_dbiFilePath; @@ -548,6 +561,17 @@ HRESULT InitializeHosting() { return S_OK; } +#ifdef FEATURE_PAL + ToRelease hostServices(NULL); + if (SUCCEEDED(g_ExtServices->QueryInterface(__uuidof(ISOSHostServices), (void**)&hostServices))) + { + if (SUCCEEDED(hostServices->GetSOSNETCoreCallbacks(SOSNetCoreCallbacksVersion, &g_SOSNetCoreCallbacks))) + { + g_hostingInitialized = true; + return S_OK; + } + } +#endif // FEATURE_PAL coreclr_initialize_ptr initializeCoreCLR = nullptr; coreclr_create_delegate_ptr createDelegate = nullptr; std::string hostRuntimeDirectory; @@ -669,15 +693,6 @@ HRESULT InitializeHosting() return Status; } -/**********************************************************************\ - * Public entry point to set the managed callbacks (unused). -\**********************************************************************/ -extern "C" void InitializeSymbolReaderCallbacks(SOSNetCoreCallbacks sosNetCoreCallbacks) -{ - g_SOSNetCoreCallbacks = sosNetCoreCallbacks; - g_hostingInitialized = true; -} - // // Pass to managed helper code to read in-memory PEs/PDBs. // Returns the number of bytes read. @@ -697,20 +712,20 @@ static int ReadMemoryForSymbols(ULONG64 address, uint8_t *buffer, int cb) // // Symbol downloader callback // -static void SymbolFileCallback(void* param, const char* moduleFileName, const char* symbolFileName) +static void SymbolFileCallback(void* param, const char* moduleFileName, const char* symbolFilePath) { if (strcmp(moduleFileName, MAIN_CLR_DLL_NAME_A) == 0) { return; } if (strcmp(moduleFileName, MAKEDLLNAME_A("mscordaccore")) == 0) { if (g_dacFilePath == nullptr) { - g_dacFilePath = _strdup(symbolFileName); + g_dacFilePath = _strdup(symbolFilePath); } return; } if (strcmp(moduleFileName, MAKEDLLNAME_A("mscordbi")) == 0) { if (g_dbiFilePath == nullptr) { - g_dbiFilePath = _strdup(symbolFileName); + g_dbiFilePath = _strdup(symbolFilePath); } return; } @@ -718,18 +733,18 @@ static void SymbolFileCallback(void* param, const char* moduleFileName, const ch HRESULT Status = g_ExtServices->QueryInterface(__uuidof(ILLDBServices2), (void**)&services2); if (SUCCEEDED(Status)) { - services2->AddModuleSymbol(param, symbolFileName); + services2->AddModuleSymbol(param, symbolFilePath); } } // // Enumerate native module callback // -static void LoadNativeSymbolsCallback(void* param, const char* moduleDirectory, const char* moduleFileName, ULONG64 moduleAddress, int moduleSize) +static void LoadNativeSymbolsCallback(void* param, const char* moduleFilePath, ULONG64 moduleAddress, int moduleSize) { _ASSERTE(g_hostingInitialized); _ASSERTE(g_SOSNetCoreCallbacks.LoadNativeSymbolsDelegate != nullptr); - g_SOSNetCoreCallbacks.LoadNativeSymbolsDelegate(SymbolFileCallback, param, GetTempDirectory(), moduleDirectory, moduleFileName, moduleAddress, moduleSize, ReadMemoryForSymbols); + g_SOSNetCoreCallbacks.LoadNativeSymbolsDelegate(SymbolFileCallback, param, GetTempDirectory(), moduleFilePath, moduleAddress, moduleSize, ReadMemoryForSymbols); } #endif @@ -757,7 +772,7 @@ HRESULT InitializeSymbolStore(BOOL logging, BOOL msdl, BOOL symweb, const char* * for them. Depends on the lldb callback to enumerate modules. Not * necessary on dbgeng because it already downloads native symbols. \**********************************************************************/ -HRESULT LoadNativeSymbols() +HRESULT LoadNativeSymbols(bool runtimeOnly) { HRESULT Status = S_OK; #ifdef FEATURE_PAL @@ -767,7 +782,7 @@ HRESULT LoadNativeSymbols() Status = g_ExtServices->QueryInterface(__uuidof(ILLDBServices2), (void**)&services2); if (SUCCEEDED(Status)) { - Status = services2->LoadNativeSymbols(LoadNativeSymbolsCallback); + Status = services2->LoadNativeSymbols(runtimeOnly, LoadNativeSymbolsCallback); } } #endif diff --git a/src/SOS/Strike/hostcoreclr.h b/src/SOS/Strike/hostcoreclr.h index e757830f5d..88ce27abd8 100644 --- a/src/SOS/Strike/hostcoreclr.h +++ b/src/SOS/Strike/hostcoreclr.h @@ -10,36 +10,11 @@ #ifndef __hostcoreclr_h__ #define __hostcoreclr_h__ +#include + static const char *SymbolReaderDllName = "SOS.NETCore"; static const char *SymbolReaderClassName = "SOS.SymbolReader"; -typedef void (*OutputDelegate)(const char*); -typedef int (*ReadMemoryDelegate)(ULONG64, uint8_t*, int); -typedef void (*SymbolFileCallbackDelegate)(void*, const char* moduleFileName, const char* symbolFileName); - -typedef BOOL (*InitializeSymbolStoreDelegate)(BOOL, BOOL, BOOL, const char*, const char*, const char*); -typedef void (*DisplaySymbolStoreDelegate)(); -typedef void (*DisableSymbolStoreDelegate)(); -typedef void (*LoadNativeSymbolsDelegate)(SymbolFileCallbackDelegate, void*, const char*, const char*, const char*, ULONG64, int, ReadMemoryDelegate); -typedef PVOID (*LoadSymbolsForModuleDelegate)(const char*, BOOL, ULONG64, int, ULONG64, int, ReadMemoryDelegate); -typedef void (*DisposeDelegate)(PVOID); -typedef BOOL (*ResolveSequencePointDelegate)(PVOID, const char*, unsigned int, unsigned int*, unsigned int*); -typedef BOOL (*GetLocalVariableNameDelegate)(PVOID, int, int, BSTR*); -typedef BOOL (*GetLineByILOffsetDelegate)(PVOID, mdMethodDef, ULONG64, ULONG *, BSTR*); - -struct SOSNetCoreCallbacks -{ - InitializeSymbolStoreDelegate InitializeSymbolStoreDelegate; - DisplaySymbolStoreDelegate DisplaySymbolStoreDelegate; - DisableSymbolStoreDelegate DisableSymbolStoreDelegate; - LoadNativeSymbolsDelegate LoadNativeSymbolsDelegate; - LoadSymbolsForModuleDelegate LoadSymbolsForModuleDelegate; - DisposeDelegate DisposeDelegate; - ResolveSequencePointDelegate ResolveSequencePointDelegate; - GetLineByILOffsetDelegate GetLineByILOffsetDelegate; - GetLocalVariableNameDelegate GetLocalVariableNameDelegate; -}; - extern HMODULE g_hInstance; extern LPCSTR g_hostRuntimeDirectory; extern SOSNetCoreCallbacks g_SOSNetCoreCallbacks; @@ -49,7 +24,7 @@ extern LPCSTR GetDbiFilePath(); extern BOOL IsHostingInitialized(); extern HRESULT InitializeHosting(); extern HRESULT InitializeSymbolStore(BOOL logging, BOOL msdl, BOOL symweb, const char* symbolServer, const char* cacheDirectory); -extern HRESULT LoadNativeSymbols(); +extern HRESULT LoadNativeSymbols(bool runtimeOnly = false); extern void DisplaySymbolStore(); extern void DisableSymbolStore(); diff --git a/src/SOS/Strike/sos.def b/src/SOS/Strike/sos.def index dd0d3ea1c6..81964382b2 100644 --- a/src/SOS/Strike/sos.def +++ b/src/SOS/Strike/sos.def @@ -232,6 +232,4 @@ EXPORTS getCodeTypeFlags=GetCodeTypeFlags TraceToCode tracetocode=TraceToCode -#endif - - InitializeSymbolReaderCallbacks \ No newline at end of file +#endif \ No newline at end of file diff --git a/src/SOS/Strike/sos_unixexports.src b/src/SOS/Strike/sos_unixexports.src index ddfd32808d..732da868ff 100644 --- a/src/SOS/Strike/sos_unixexports.src +++ b/src/SOS/Strike/sos_unixexports.src @@ -53,5 +53,3 @@ ThreadState Token2EE u VerifyHeap - -InitializeSymbolReaderCallbacks \ No newline at end of file diff --git a/src/SOS/Strike/soshostservices.h b/src/SOS/Strike/soshostservices.h new file mode 100644 index 0000000000..619a2e49a7 --- /dev/null +++ b/src/SOS/Strike/soshostservices.h @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//---------------------------------------------------------------------------- +// +// LLDB debugger services for sos +// +//---------------------------------------------------------------------------- + +#ifndef __SOSHOSTSERVICES_H__ +#define __SOSHOSTSERVICES_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct SymbolModuleInfo; + +typedef void (*OutputDelegate)(const char*); +typedef int (*ReadMemoryDelegate)(ULONG64, uint8_t*, int); +typedef void (*SymbolFileCallbackDelegate)(void*, const char* moduleFileName, const char* symbolFilePath); + +typedef BOOL (*InitializeSymbolStoreDelegate)(BOOL, BOOL, BOOL, const char*, const char*, const char*); +typedef void (*DisplaySymbolStoreDelegate)(); +typedef void (*DisableSymbolStoreDelegate)(); +typedef void (*LoadNativeSymbolsDelegate)(SymbolFileCallbackDelegate, void*, const char*, const char*, ULONG64, int, ReadMemoryDelegate); +typedef PVOID (*LoadSymbolsForModuleDelegate)(const char*, BOOL, ULONG64, int, ULONG64, int, ReadMemoryDelegate); +typedef void (*DisposeDelegate)(PVOID); +typedef BOOL (*ResolveSequencePointDelegate)(PVOID, const char*, unsigned int, unsigned int*, unsigned int*); +typedef BOOL (*GetLocalVariableNameDelegate)(PVOID, int, int, BSTR*); +typedef BOOL (*GetLineByILOffsetDelegate)(PVOID, mdMethodDef, ULONG64, ULONG *, BSTR*); + +#define SOSNetCoreCallbacksVersion 1 + +struct SOSNetCoreCallbacks +{ + InitializeSymbolStoreDelegate InitializeSymbolStoreDelegate; + DisplaySymbolStoreDelegate DisplaySymbolStoreDelegate; + DisableSymbolStoreDelegate DisableSymbolStoreDelegate; + LoadNativeSymbolsDelegate LoadNativeSymbolsDelegate; + LoadSymbolsForModuleDelegate LoadSymbolsForModuleDelegate; + DisposeDelegate DisposeDelegate; + ResolveSequencePointDelegate ResolveSequencePointDelegate; + GetLineByILOffsetDelegate GetLineByILOffsetDelegate; + GetLocalVariableNameDelegate GetLocalVariableNameDelegate; +}; + +MIDL_INTERFACE("D13608FB-AD14-4B49-990A-80284F934C41") +ISOSHostServices : public IUnknown +{ +public: + //---------------------------------------------------------------------------- + // ISOSHostServices + //---------------------------------------------------------------------------- + + virtual HRESULT GetSOSNETCoreCallbacks( + int version, + SOSNetCoreCallbacks* callbacks) = 0; +}; + +#ifdef __cplusplus +}; +#endif + +#endif // #ifndef __SOSHOSTSERVICES_H__ diff --git a/src/SOS/Strike/strike.cpp b/src/SOS/Strike/strike.cpp index e71b5a3719..7f8a56ee06 100644 --- a/src/SOS/Strike/strike.cpp +++ b/src/SOS/Strike/strike.cpp @@ -5972,6 +5972,8 @@ DECLARE_API(DumpModule) DMLOut("Assembly: %s\n", DMLAssembly(module.Assembly)); + ExtOut("PEFile: %p\n", SOS_PTR(module.File)); + ExtOut("ModuleId: %p\n", SOS_PTR(module.dwModuleID)); ExtOut("LoaderHeap: %p\n", SOS_PTR(module.pLookupTableHeap)); ExtOut("TypeDefToMethodTableMap: %p\n", SOS_PTR(module.TypeDefToMethodTableMap)); ExtOut("TypeRefToMethodTableMap: %p\n", SOS_PTR(module.TypeRefToMethodTableMap)); @@ -6261,7 +6263,7 @@ HRESULT PrintThreadsFromThreadStore(BOOL bMiniDump, BOOL bPrintLiveThreadsOnly) table.SetColAlignment(4, AlignRight); table.WriteColumn(8, "Lock"); - table.WriteRow("", "ID", "OSID", "ThreadOBJ", "State", "GC Mode", "GC Alloc Context", "Domain", "Count", "Apt"); + table.WriteRow("DBG", "ID", "OSID", "ThreadOBJ", "State", "GC Mode", "GC Alloc Context", "Domain", "Count", "Apt"); if (hosted) table.WriteColumn("Fiber"); @@ -12694,7 +12696,10 @@ class ClrStackImplWithICorDebug &cbContextActual, (BYTE *)context.GetPtr())) != S_OK) { - ExtOut("GetFrameContext failed: %lx\n",Status); + if (FAILED(Status)) + { + ExtOut("GetFrameContext failed: %lx\n", Status); + } break; } @@ -12946,64 +12951,66 @@ class ClrStackImpl } CLRDATA_ADDRESS ip = 0, sp = 0; hr = GetFrameLocation(pStackWalk, &ip, &sp); - - DacpFrameData FrameData; - HRESULT frameDataResult = FrameData.Request(pStackWalk); - if (SUCCEEDED(frameDataResult) && FrameData.frameAddr) - sp = FrameData.frameAddr; + if (SUCCEEDED(hr)) + { + DacpFrameData FrameData; + HRESULT frameDataResult = FrameData.Request(pStackWalk); + if (SUCCEEDED(frameDataResult) && FrameData.frameAddr) + sp = FrameData.frameAddr; #ifdef DEBUG_STACK_CONTEXT - while ((numNativeFrames > 0) && (currentNativeFrame->StackOffset <= sp)) - { - if (currentNativeFrame->StackOffset != sp) + while ((numNativeFrames > 0) && (currentNativeFrame->StackOffset <= sp)) { - PrintNativeStackFrame(out, currentNativeFrame, bSuppressLines); + if (currentNativeFrame->StackOffset != sp) + { + PrintNativeStackFrame(out, currentNativeFrame, bSuppressLines); + } + currentNativeFrame++; + numNativeFrames--; } - currentNativeFrame++; - numNativeFrames--; - } #endif // DEBUG_STACK_CONTEXT - // Print the stack pointer. - out.WriteColumn(0, sp); + // Print the stack pointer. + out.WriteColumn(0, sp); - // Print the method/Frame info - if (SUCCEEDED(frameDataResult) && FrameData.frameAddr) - { - // Skip the instruction pointer because it doesn't really mean anything for method frames - out.WriteColumn(1, bFull ? String("") : NativePtr(ip)); - - // This is a clr!Frame. - out.WriteColumn(2, GetFrameFromAddress(TO_TADDR(FrameData.frameAddr), pStackWalk, bFull)); - - // Print out gc references for the Frame. - for (unsigned int i = 0; i < refCount; ++i) - if (pRefs[i].Source == sp) - PrintRef(pRefs[i], out); - - // Print out an error message if we got one. - for (unsigned int i = 0; i < errCount; ++i) - if (pErrs[i].Source == sp) - out.WriteColumn(2, "Failed to enumerate GC references."); - } - else - { - out.WriteColumn(1, InstructionPtr(ip)); - out.WriteColumn(2, MethodNameFromIP(ip, bSuppressLines, bFull, bFull)); - - // Print out gc references. refCount will be zero if bGC is false (or if we - // failed to fetch gc reference information). - for (unsigned int i = 0; i < refCount; ++i) - if (pRefs[i].Source == ip && pRefs[i].StackPointer == sp) - PrintRef(pRefs[i], out); + // Print the method/Frame info + if (SUCCEEDED(frameDataResult) && FrameData.frameAddr) + { + // Skip the instruction pointer because it doesn't really mean anything for method frames + out.WriteColumn(1, bFull ? String("") : NativePtr(ip)); + + // This is a clr!Frame. + out.WriteColumn(2, GetFrameFromAddress(TO_TADDR(FrameData.frameAddr), pStackWalk, bFull)); - // Print out an error message if we got one. - for (unsigned int i = 0; i < errCount; ++i) - if (pErrs[i].Source == sp) - out.WriteColumn(2, "Failed to enumerate GC references."); + // Print out gc references for the Frame. + for (unsigned int i = 0; i < refCount; ++i) + if (pRefs[i].Source == sp) + PrintRef(pRefs[i], out); - if (bParams || bLocals) - PrintArgsAndLocals(pStackWalk, bParams, bLocals); + // Print out an error message if we got one. + for (unsigned int i = 0; i < errCount; ++i) + if (pErrs[i].Source == sp) + out.WriteColumn(2, "Failed to enumerate GC references."); + } + else + { + out.WriteColumn(1, InstructionPtr(ip)); + out.WriteColumn(2, MethodNameFromIP(ip, bSuppressLines, bFull, bFull)); + + // Print out gc references. refCount will be zero if bGC is false (or if we + // failed to fetch gc reference information). + for (unsigned int i = 0; i < refCount; ++i) + if (pRefs[i].Source == ip && pRefs[i].StackPointer == sp) + PrintRef(pRefs[i], out); + + // Print out an error message if we got one. + for (unsigned int i = 0; i < errCount; ++i) + if (pErrs[i].Source == sp) + out.WriteColumn(2, "Failed to enumerate GC references."); + + if (bParams || bLocals) + PrintArgsAndLocals(pStackWalk, bParams, bLocals); + } } if (bDisplayRegVals) @@ -13025,13 +13032,16 @@ class ClrStackImpl { CROSS_PLATFORM_CONTEXT context; HRESULT hr = pStackWalk->GetContext(DT_CONTEXT_FULL, g_targetMachine->GetContextSize(), NULL, (BYTE *)&context); - if (FAILED(hr) || hr == S_FALSE) + if (FAILED(hr)) { - // GetFrameContext returns S_FALSE if the frame iterator is invalid. That's basically an error for us. ExtOut("GetFrameContext failed: %lx\n", hr); + return hr; + } + if (hr == S_FALSE) + { + // GetFrameContext returns S_FALSE if the frame iterator is invalid. That's basically an error for us. return E_FAIL; } - #if defined(SOS_TARGET_AMD64) String outputFormat3 = " %3s=%016x %3s=%016x %3s=%016x\n"; String outputFormat2 = " %3s=%016x %3s=%016x\n"; @@ -13082,13 +13092,16 @@ class ClrStackImpl { CROSS_PLATFORM_CONTEXT context; HRESULT hr = pStackWalk->GetContext(DT_CONTEXT_FULL, g_targetMachine->GetContextSize(), NULL, (BYTE *)&context); - if (FAILED(hr) || hr == S_FALSE) + if (FAILED(hr)) { - // GetFrameContext returns S_FALSE if the frame iterator is invalid. That's basically an error for us. ExtOut("GetFrameContext failed: %lx\n", hr); + return hr; + } + if (hr == S_FALSE) + { + // GetFrameContext returns S_FALSE if the frame iterator is invalid. That's basically an error for us. return E_FAIL; } - // First find the info for the Frame object, if the current frame has an associated clr!Frame. *ip = GetIP(context); *sp = GetSP(context); @@ -13179,8 +13192,11 @@ class ClrStackImpl ExtErr("Failed to request thread at %p\n", CurThread); return; } - ExtOut("OS Thread Id: 0x%x\n", Thread.osThreadId); - PrintThread(Thread.osThreadId, bParams, bLocals, bSuppressLines, bGC, bNative, bDisplayRegVals); + if (Thread.osThreadId != 0) + { + ExtOut("OS Thread Id: 0x%x\n", Thread.osThreadId); + PrintThread(Thread.osThreadId, bParams, bLocals, bSuppressLines, bGC, bNative, bDisplayRegVals); + } CurThread = Thread.nextThread; } } diff --git a/src/SOS/Strike/util.cpp b/src/SOS/Strike/util.cpp index 74feb7903d..13aacac11d 100644 --- a/src/SOS/Strike/util.cpp +++ b/src/SOS/Strike/util.cpp @@ -2582,6 +2582,13 @@ HRESULT GetModuleFromAddress(___in CLRDATA_ADDRESS peAddress, ___out IXCLRDataMo if (FAILED(hr)) { break; } + ULONG32 flags; + if ((hr = module->GetFlags(&flags)) != S_OK) { + continue; + } + if (flags != CLRDATA_MODULE_DEFAULT) { + continue; + } DacpGetModuleData moduleData; HRESULT hr = moduleData.Request(module); if (FAILED(hr)) { diff --git a/src/SOS/lldbplugin/inc/lldbservices.h b/src/SOS/lldbplugin/inc/lldbservices.h index 754e6c812a..69c78e62f7 100644 --- a/src/SOS/lldbplugin/inc/lldbservices.h +++ b/src/SOS/lldbplugin/inc/lldbservices.h @@ -542,7 +542,7 @@ ILLDBServices : public IUnknown PULONG64 offset) = 0; }; -typedef void (*PFN_MODULE_LOAD_CALLBACK)(void* param, const char* moduleName, const char* moduleDirectory, ULONG64 moduleAddress, int moduleSize); +typedef void (*PFN_MODULE_LOAD_CALLBACK)(void* param, const char* moduleFilePath, ULONG64 moduleAddress, int moduleSize); MIDL_INTERFACE("012F32F0-33BA-4E8E-BC01-037D382D8A5E") ILLDBServices2: public IUnknown @@ -553,11 +553,12 @@ ILLDBServices2: public IUnknown //---------------------------------------------------------------------------- virtual HRESULT LoadNativeSymbols( + bool runtimeOnly, PFN_MODULE_LOAD_CALLBACK callback) = 0; virtual HRESULT AddModuleSymbol( void* param, - const char* symbolFileName) = 0; + const char* symbolFilePath) = 0; }; #ifdef __cplusplus diff --git a/src/SOS/lldbplugin/services.cpp b/src/SOS/lldbplugin/services.cpp index 3f9081c1d6..11b92b1952 100644 --- a/src/SOS/lldbplugin/services.cpp +++ b/src/SOS/lldbplugin/services.cpp @@ -88,17 +88,22 @@ LLDBServices::GetCoreClrDirectory() { if (g_coreclrDirectory == nullptr) { - const char *coreclrModule = MAKEDLLNAME_A("coreclr"); - const char *directory = GetModuleDirectory(coreclrModule); - if (directory != nullptr) - { - std::string path(directory); - path.append("/"); - g_coreclrDirectory = strdup(path.c_str()); - } - else + lldb::SBTarget target = m_debugger.GetSelectedTarget(); + if (target.IsValid()) { - Output(DEBUG_OUTPUT_WARNING, "The %s module is not loaded yet in the target process\n", coreclrModule); + const char *coreclrModule = MAKEDLLNAME_A("coreclr"); + lldb::SBFileSpec fileSpec; + fileSpec.SetFilename(coreclrModule); + + lldb::SBModule module = target.FindModule(fileSpec); + if (module.IsValid()) + { + const char *directory = module.GetFileSpec().GetDirectory(); + std::string path(directory); + path.append("/"); + + g_coreclrDirectory = strdup(path.c_str()); + } } } return g_coreclrDirectory; @@ -1304,27 +1309,6 @@ LLDBServices::FindSourceFile( } // Internal functions -PCSTR -LLDBServices::GetModuleDirectory( - PCSTR name) -{ - lldb::SBTarget target = m_debugger.GetSelectedTarget(); - if (!target.IsValid()) - { - return NULL; - } - - lldb::SBFileSpec fileSpec; - fileSpec.SetFilename(name); - - lldb::SBModule module = target.FindModule(fileSpec); - if (!module.IsValid()) - { - return NULL; - } - - return module.GetFileSpec().GetDirectory(); -} ULONG64 LLDBServices::GetModuleBase( @@ -1720,49 +1704,79 @@ LLDBServices::GetFrameOffset( // ILLDBServices2 //---------------------------------------------------------------------------- +void +LLDBServices::LoadNativeSymbols( + lldb::SBTarget target, + lldb::SBModule module, + PFN_MODULE_LOAD_CALLBACK callback) +{ + if (module.IsValid()) + { + const char* directory = nullptr; + const char* filename = nullptr; + + lldb::SBFileSpec symbolFileSpec = module.GetSymbolFileSpec(); + if (symbolFileSpec.IsValid()) + { + directory = symbolFileSpec.GetDirectory(); + filename = symbolFileSpec.GetFilename(); + } + else { + lldb::SBFileSpec fileSpec = module.GetFileSpec(); + if (fileSpec.IsValid()) + { + directory = fileSpec.GetDirectory(); + filename = fileSpec.GetFilename(); + } + } + + if (directory != nullptr && filename != nullptr) + { + ULONG64 moduleAddress = GetModuleBase(target, module); + int moduleSize = INT32_MAX; + if (moduleAddress != UINT64_MAX) + { + std::string path(directory); + path.append("/"); + path.append(filename); + + callback(&module, path.c_str(), moduleAddress, moduleSize); + } + } + } +} + HRESULT LLDBServices::LoadNativeSymbols( + bool runtimeOnly, PFN_MODULE_LOAD_CALLBACK callback) { - uint32_t numTargets = m_debugger.GetNumTargets(); - for (int ti = 0; ti < numTargets; ti++) + if (runtimeOnly) { - lldb::SBTarget target = m_debugger.GetTargetAtIndex(ti); + lldb::SBTarget target = m_debugger.GetSelectedTarget(); if (target.IsValid()) { - uint32_t numModules = target.GetNumModules(); - for (int mi = 0; mi < numModules; mi++) + const char *coreclrModule = MAKEDLLNAME_A("coreclr"); + lldb::SBFileSpec fileSpec; + fileSpec.SetFilename(coreclrModule); + + lldb::SBModule module = target.FindModule(fileSpec); + LoadNativeSymbols(target, module, callback); + } + } + else + { + uint32_t numTargets = m_debugger.GetNumTargets(); + for (int ti = 0; ti < numTargets; ti++) + { + lldb::SBTarget target = m_debugger.GetTargetAtIndex(ti); + if (target.IsValid()) { - lldb::SBModule module = target.GetModuleAtIndex(mi); - if (module.IsValid()) + uint32_t numModules = target.GetNumModules(); + for (int mi = 0; mi < numModules; mi++) { - const char* directory = nullptr; - const char* filename = nullptr; - - lldb::SBFileSpec symbolFileSpec = module.GetSymbolFileSpec(); - if (symbolFileSpec.IsValid()) - { - directory = symbolFileSpec.GetDirectory(); - filename = symbolFileSpec.GetFilename(); - } - else { - lldb::SBFileSpec fileSpec = module.GetFileSpec(); - if (fileSpec.IsValid()) - { - directory = fileSpec.GetDirectory(); - filename = fileSpec.GetFilename(); - } - } - - if (directory != nullptr && filename != nullptr) - { - ULONG64 moduleAddress = GetModuleBase(target, module); - int moduleSize = INT32_MAX; - if (moduleAddress != UINT64_MAX) - { - callback(&module, directory, filename, moduleAddress, moduleSize); - } - } + lldb::SBModule module = target.GetModuleAtIndex(mi); + LoadNativeSymbols(target, module, callback); } } } diff --git a/src/SOS/lldbplugin/services.h b/src/SOS/lldbplugin/services.h index e6d60b7215..8eb79f6a14 100644 --- a/src/SOS/lldbplugin/services.h +++ b/src/SOS/lldbplugin/services.h @@ -270,6 +270,7 @@ class LLDBServices : public ILLDBServices, public ILLDBServices2 //---------------------------------------------------------------------------- HRESULT LoadNativeSymbols( + bool runtimeOnly, PFN_MODULE_LOAD_CALLBACK callback); HRESULT AddModuleSymbol( @@ -280,10 +281,10 @@ class LLDBServices : public ILLDBServices, public ILLDBServices2 // LLDBServices (internal) //---------------------------------------------------------------------------- - PCSTR GetModuleDirectory( - PCSTR name); + void LoadNativeSymbols( + lldb::SBTarget target, + lldb::SBModule module, + PFN_MODULE_LOAD_CALLBACK callback); PCSTR GetPluginModuleDirectory(); - - int StartListenerThread(); }; diff --git a/src/Tools/Directory.Build.props b/src/Tools/Directory.Build.props index db241db996..949ac27c92 100644 --- a/src/Tools/Directory.Build.props +++ b/src/Tools/Directory.Build.props @@ -1,17 +1,4 @@ - - - - - 0.1.0 - alpha - - \ No newline at end of file + + diff --git a/src/Tools/dotnet-analyze/dotnet-analyze.csproj b/src/Tools/dotnet-analyze/dotnet-analyze.csproj index ec801f8b37..0b48075d5c 100644 --- a/src/Tools/dotnet-analyze/dotnet-analyze.csproj +++ b/src/Tools/dotnet-analyze/dotnet-analyze.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/Tools/dotnet-dump/AnalyzeContext.cs b/src/Tools/dotnet-dump/AnalyzeContext.cs new file mode 100644 index 0000000000..52315aa84c --- /dev/null +++ b/src/Tools/dotnet-dump/AnalyzeContext.cs @@ -0,0 +1,77 @@ +// -------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// -------------------------------------------------------------------- +using Microsoft.Diagnostics.Runtime; +using SOS; +using System; +using System.CommandLine; +using System.Threading; + +namespace Microsoft.Diagnostic.Tools.Dump +{ + /// + /// The the common context for analyze commands + /// + public class AnalyzeContext: ISOSHostContext + { + readonly IConsole _console; + ClrRuntime _runtime; + + public AnalyzeContext(IConsole console, DataTarget target, Action exit) + { + _console = console; + Target = target; + Exit = exit; + } + + /// + /// ClrMD data target + /// + public DataTarget Target { get; } + + /// + /// ClrMD runtime info + /// + public ClrRuntime Runtime + { + get + { + if (_runtime == null) + { + if (Target.ClrVersions.Count != 1) + { + throw new InvalidOperationException("More or less than 1 CLR version is present"); + } + _runtime = Target.ClrVersions[0].CreateRuntime(); + } + return _runtime; + } + } + + /// + /// Delegate to invoke to exit repl + /// + public Action Exit { get; } + + /// + /// Current OS thread Id + /// + public int CurrentThreadId { get; set; } + + /// + /// Cancellation token for current command + /// + public CancellationToken CancellationToken { get; set; } + + /// + /// Console write function + /// + /// + void ISOSHostContext.Write(string text) + { + _console.Out.Write(text); + } + } +} \ No newline at end of file diff --git a/src/Tools/dotnet-dump/Analyzer.cs b/src/Tools/dotnet-dump/Analyzer.cs new file mode 100644 index 0000000000..480f57bd61 --- /dev/null +++ b/src/Tools/dotnet-dump/Analyzer.cs @@ -0,0 +1,72 @@ +using Microsoft.Diagnostic.Repl; +using Microsoft.Diagnostics.Runtime; +using System.CommandLine; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostic.Tools.Dump +{ + public class Analyzer + { + private readonly ConsoleProvider _consoleProvider; + private readonly CommandProcessor _commandProcessor; + + public Analyzer() + { + _consoleProvider = new ConsoleProvider(); + _commandProcessor = new CommandProcessor(new Assembly[] { typeof(Analyzer).Assembly }); + } + + public async Task Analyze(FileInfo dump_path, string[] command) + { + _consoleProvider.Out.WriteLine($"Loading core dump: {dump_path} ..."); + + DataTarget target = null; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + target = DataTarget.LoadCoreDump(dump_path.FullName); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + target = DataTarget.LoadCrashDump(dump_path.FullName, CrashDumpReader.ClrMD); + } + else { + _consoleProvider.Error.WriteLine($"{RuntimeInformation.OSDescription} not supported"); + return 1; + } + + using (target) + { + // Create common analyze context for commands + var analyzeContext = new AnalyzeContext(_consoleProvider, target, _consoleProvider.Stop) { + CurrentThreadId = unchecked((int)target.DataReader.EnumerateAllThreads().FirstOrDefault()) + }; + _commandProcessor.CommandContext = analyzeContext; + + // Automatically enable symbol server support on Linux and MacOS + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + await _commandProcessor.Parse("setsymbolserver -ms", _consoleProvider); + } + + // Run the commands from the dotnet-dump command line + if (command != null) + { + foreach (string cmd in command) + { + await _commandProcessor.Parse(cmd, _consoleProvider); + } + } + + // Start interactive command line processing + await _consoleProvider.Start(async (string commandLine, CancellationToken cancellation) => { + analyzeContext.CancellationToken = cancellation; + await _commandProcessor.Parse(commandLine, _consoleProvider); + }); + } + + return 0; + } + } +} diff --git a/src/Tools/dotnet-dump/Commands/ExitCommand.cs b/src/Tools/dotnet-dump/Commands/ExitCommand.cs new file mode 100644 index 0000000000..704173de17 --- /dev/null +++ b/src/Tools/dotnet-dump/Commands/ExitCommand.cs @@ -0,0 +1,19 @@ + +using Microsoft.Diagnostic.Repl; +using System.CommandLine; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostic.Tools.Dump +{ + [Command(Name = "exit", Help = "Exit interactive mode.")] + public class ExitCommand : CommandBase + { + public AnalyzeContext AnalyzeContext { get; set; } + + public override Task InvokeAsync() + { + AnalyzeContext.Exit(); + return Task.CompletedTask; + } + } +} diff --git a/src/Tools/dotnet-dump/Commands/HelpCommand.cs b/src/Tools/dotnet-dump/Commands/HelpCommand.cs new file mode 100644 index 0000000000..eef9514011 --- /dev/null +++ b/src/Tools/dotnet-dump/Commands/HelpCommand.cs @@ -0,0 +1,36 @@ +using Microsoft.Diagnostic.Repl; +using System.CommandLine; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostic.Tools.Dump +{ + [Command(Name = "help", Help = "Display help for a command.")] + public class HelpCommand : CommandBase + { + [Argument(Help = "Command to find help.")] + public string Command { get; set; } + + public CommandProcessor CommandProcessor { get; set; } + + public override Task InvokeAsync() + { + return Task.CompletedTask; + } + + /// + /// Get help builder interface + /// + /// help builder + public Task InvokeAsync(IHelpBuilder helpBuilder) + { + Command command = CommandProcessor.GetCommand(Command); + if (command != null) { + helpBuilder.Write(command); + } + else { + Console.Error.WriteLine($"Help for {Command} not found."); + } + return Task.CompletedTask; + } + } +} diff --git a/src/Tools/dotnet-dump/Commands/ModulesCommand.cs b/src/Tools/dotnet-dump/Commands/ModulesCommand.cs new file mode 100644 index 0000000000..e805904598 --- /dev/null +++ b/src/Tools/dotnet-dump/Commands/ModulesCommand.cs @@ -0,0 +1,44 @@ + +using Microsoft.Diagnostic.Repl; +using Microsoft.Diagnostics.Runtime; +using System.CommandLine; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostic.Tools.Dump +{ + [Command(Name = "modules", Help = "Displays the native modules in the process.")] + [Command(Name = "lm")] + public class ModulesCommand : CommandBase + { + [Option(Name = "--verbose", Help = "Displays more details.")] + [OptionAlias(Name = "-v")] + public bool Verbose { get; set; } + + public AnalyzeContext AnalyzeContext { get; set; } + + public override Task InvokeAsync() + { + foreach (ModuleInfo module in AnalyzeContext.Target.DataReader.EnumerateModules()) + { + if (Verbose) + { + WriteLine("{0}", module.FileName); + WriteLine(" Address: {0:X16}", module.ImageBase); + WriteLine(" FileSize: {0:X8}", module.FileSize); + WriteLine(" TimeStamp: {0:X8}", module.TimeStamp); + if (module.BuildId != null) { + WriteLine(" BuildId: {0}", string.Concat(module.BuildId.Select((b) => b.ToString("x2")))); + } + WriteLine(" IsRuntime: {0}", module.IsRuntime); + WriteLine(" IsManaged: {0}", module.IsManaged); + } + else + { + WriteLine("{0:X16} {1:X8} {2}", module.ImageBase, module.FileSize, module.FileName); + } + } + return Task.CompletedTask; + } + } +} diff --git a/src/Tools/dotnet-dump/Commands/SOSCommand.cs b/src/Tools/dotnet-dump/Commands/SOSCommand.cs new file mode 100644 index 0000000000..fb111716a6 --- /dev/null +++ b/src/Tools/dotnet-dump/Commands/SOSCommand.cs @@ -0,0 +1,70 @@ +using Microsoft.Diagnostic.Repl; +using SOS; +using System; +using System.CommandLine; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostic.Tools.Dump +{ + [Command(Name = "clrstack", AliasExpansion = "ClrStack", Help = "Provides a stack trace of managed code only.")] + [Command(Name = "clrthreads", AliasExpansion = "Threads", Help = "List the managed threads running.")] + [Command(Name = "dumpasync", AliasExpansion = "DumpAsync", Help = "Displays info about async state machines on the garbage-collected heap.")] + [Command(Name = "dumpclass", AliasExpansion = "DumpClass", Help = "Displays information about a EE class structure at the specified address.")] + [Command(Name = "dumpdelegate", AliasExpansion = "DumpDelegate", Help = "Displays information about a delegate.")] + [Command(Name = "dumpdomain", AliasExpansion = "DumpDomain", Help = "Displays information all the AppDomains and all assemblies within the domains.")] + [Command(Name = "dumpheap", AliasExpansion = "DumpHeap", Help = "Displays info about the garbage-collected heap and collection statistics about objects.")] + [Command(Name = "dumpil", AliasExpansion = "DumpIL", Help = "Displays the Microsoft intermediate language (MSIL) that is associated with a managed method.")] + [Command(Name = "dumplog", AliasExpansion = "DumpLog", Help = "Writes the contents of an in-memory stress log to the specified file.")] + [Command(Name = "dumpmd", AliasExpansion = "DumpMD", Help = "Displays information about a MethodDesc structure at the specified address.")] + [Command(Name = "dumpmodule", AliasExpansion = "DumpModule", Help = "Displays information about a EE module structure at the specified address.")] + [Command(Name = "dumpmt", AliasExpansion = "DumpMT", Help = "Displays information about a method table at the specified address.")] + [Command(Name = "dumpobj", AliasExpansion = "DumpObj", Help = "Displays info about an object at the specified address.")] + [Command(Name = "dumpstack", AliasExpansion = "DumpStack", Help = "Displays a native and managed stack trace.")] + [Command(Name = "dso", AliasExpansion = "DumpStackObjects", Help = "Displays all managed objects found within the bounds of the current stack.")] + [Command(Name = "eeheap", AliasExpansion = "EEHeap", Help = "Displays info about process memory consumed by internal runtime data structures.")] + [Command(Name = "eestack", AliasExpansion = "EEStack", Help = "Runs dumpstack on all threads in the process.")] + [Command(Name = "finalizequeue", AliasExpansion = "FinalizeQueue", Help = "Displays all objects registered for finalization.")] + [Command(Name = "gcroot", AliasExpansion = "GCRoot", Help = "Displays info about references (or roots) to an object at the specified address.")] + [Command(Name = "ip2md", AliasExpansion = "IP2MD", Help = "Displays the MethodDesc structure at the specified address in code that has been JIT-compiled.")] + [Command(Name = "name2ee", AliasExpansion = "Name2EE", Help = "Displays the MethodTable structure and EEClass structure for the specified type or method in the specified module.")] + [Command(Name = "pe", AliasExpansion = "PrintException", Help = "Displays and formats fields of any object derived from the Exception class at the specified address.")] + [Command(Name = "syncblk", AliasExpansion = "SyncBlk", Help = "Displays the SyncBlock holder info.")] + [Command(Name = "histclear", AliasExpansion = "HistClear", Help = "Releases any resources used by the family of Hist commands.")] + [Command(Name = "histinit", AliasExpansion = "HistInit", Help = "Initializes the SOS structures from the stress log saved in the debuggee.")] + [Command(Name = "histobj", AliasExpansion = "HistObj", Help = "Examines all stress log relocation records and displays the chain of garbage collection relocations that may have led to the address passed in as an argument.")] + [Command(Name = "histobjfind", AliasExpansion = "HistObjFind", Help = "Displays all the log entries that reference an object at the specified address.")] + [Command(Name = "histroot", AliasExpansion = "HistRoot", Help = "Displays information related to both promotions and relocations of the specified root.")] + [Command(Name = "setsymbolserver", AliasExpansion = "SetSymbolServer", Help = "Enables the symbol server support ")] + [Command(Name = "soshelp", AliasExpansion = "Help", Help = "Displays all available commands when no parameter is specified, or displays detailed help information about the specified command. soshelp ")] + public class SOSCommand : CommandBase + { + [Argument(Name = "arguments", Help = "Arguments to SOS command.")] + public string[] Arguments { get; set; } + + public AnalyzeContext AnalyzeContext { get; set; } + + private SOSHost _sosHost; + + public override Task InvokeAsync() + { + try { + if (_sosHost == null) { + _sosHost = new SOSHost(AnalyzeContext.Target.DataReader, AnalyzeContext); + } + string arguments = null; + if (Arguments.Length > 0) { + arguments = string.Concat(Arguments.Select((arg) => arg + " ")); + } + _sosHost.ExecuteCommand(AliasExpansion, arguments); + } + catch (Exception ex) when (ex is FileNotFoundException || ex is EntryPointNotFoundException || ex is InvalidOperationException) { + Console.Error.WriteLine(ex.Message); + } + return Task.CompletedTask; + } + } +} diff --git a/src/Tools/dotnet-dump/Commands/SetThreadCommand.cs b/src/Tools/dotnet-dump/Commands/SetThreadCommand.cs new file mode 100644 index 0000000000..35b10da8d5 --- /dev/null +++ b/src/Tools/dotnet-dump/Commands/SetThreadCommand.cs @@ -0,0 +1,35 @@ + +using Microsoft.Diagnostic.Repl; +using System.CommandLine; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostic.Tools.Dump +{ + [Command(Name = "setthread", Help = "Sets or displays the current thread id for the SOS commands.")] + [Command(Name = "threads")] + public class SetThreadCommand : CommandBase + { + [Argument(Help = "The thread id to set, otherwise displays the current id.")] + public int? ThreadId { get; set; } = null; + + public AnalyzeContext AnalyzeContext { get; set; } + + public override Task InvokeAsync() + { + if (ThreadId.HasValue) + { + AnalyzeContext.CurrentThreadId = ThreadId.Value; + } + else + { + int index = 0; + foreach (uint threadId in AnalyzeContext.Target.DataReader.EnumerateAllThreads()) + { + WriteLine("{0}{1} 0x{2:X4} ({2})", threadId == AnalyzeContext.CurrentThreadId ? "*" : " ", index, threadId); + index++; + } + } + return Task.CompletedTask; + } + } +} diff --git a/src/Tools/dotnet-dump/Dumper.Linux.cs b/src/Tools/dotnet-dump/Dumper.Linux.cs index 58ad219f25..db3f7fd3f9 100644 --- a/src/Tools/dotnet-dump/Dumper.Linux.cs +++ b/src/Tools/dotnet-dump/Dumper.Linux.cs @@ -4,39 +4,39 @@ using System.Threading.Tasks; using System.IO; -namespace Microsoft.Diagnostics.Tools.Dump +namespace Microsoft.Diagnostic.Tools.Dump { - public static partial class Dumper + public partial class Dumper { private static class Linux { internal static async Task CollectDumpAsync(Process process, string fileName) { // We don't work on WSL :( - var ostype = await File.ReadAllTextAsync("/proc/sys/kernel/osrelease"); - if(ostype.Contains("Microsoft")) + string ostype = await File.ReadAllTextAsync("/proc/sys/kernel/osrelease"); + if (ostype.Contains("Microsoft")) { throw new PlatformNotSupportedException("Cannot collect memory dumps from Windows Subsystem for Linux."); } // First step is to find the .NET runtime. To do this we look for coreclr.so - var coreclr = process.Modules.Cast().FirstOrDefault(m => string.Equals(m.ModuleName, "libcoreclr.so")); - if(coreclr == null) + ProcessModule coreclr = process.Modules.Cast().FirstOrDefault(m => string.Equals(m.ModuleName, "libcoreclr.so")); + if (coreclr == null) { throw new NotSupportedException("Unable to locate .NET runtime associated with this process!"); } // Find createdump next to that file - var runtimeDirectory = Path.GetDirectoryName(coreclr.FileName); - var createDumpPath = Path.Combine(runtimeDirectory, "createdump"); - if(!File.Exists(createDumpPath)) + string runtimeDirectory = Path.GetDirectoryName(coreclr.FileName); + string createDumpPath = Path.Combine(runtimeDirectory, "createdump"); + if (!File.Exists(createDumpPath)) { throw new NotSupportedException($"Unable to locate 'createdump' tool in '{runtimeDirectory}'"); } // Create the dump - var exitCode = await CreateDumpAsync(createDumpPath, fileName, process.Id); - if(exitCode != 0) + int exitCode = await CreateDumpAsync(createDumpPath, fileName, process.Id); + if (exitCode != 0) { throw new Exception($"createdump exited with non-zero exit code: {exitCode}"); } @@ -50,10 +50,10 @@ private static Task CreateDumpAsync(string exePath, string fileName, int pr StartInfo = new ProcessStartInfo() { FileName = exePath, - Arguments = $"-f {fileName} {processId}", - RedirectStandardError = true, - RedirectStandardOutput = true, - RedirectStandardInput = true, + Arguments = $"--diag -f {fileName} {processId}", + //RedirectStandardError = true, + //RedirectStandardOutput = true, + //RedirectStandardInput = true, }, EnableRaisingEvents = true, }; diff --git a/src/Tools/dotnet-dump/Dumper.Windows.cs b/src/Tools/dotnet-dump/Dumper.Windows.cs index 89350b7c29..a78626d826 100644 --- a/src/Tools/dotnet-dump/Dumper.Windows.cs +++ b/src/Tools/dotnet-dump/Dumper.Windows.cs @@ -5,9 +5,9 @@ using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; -namespace Microsoft.Diagnostics.Tools.Dump +namespace Microsoft.Diagnostic.Tools.Dump { - public static partial class Dumper + public partial class Dumper { private static class Windows { diff --git a/src/Tools/dotnet-dump/Dumper.cs b/src/Tools/dotnet-dump/Dumper.cs index fc412e4ff6..cede967d13 100644 --- a/src/Tools/dotnet-dump/Dumper.cs +++ b/src/Tools/dotnet-dump/Dumper.cs @@ -1,26 +1,60 @@ using System; +using System.CommandLine; using System.Diagnostics; +using System.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; -namespace Microsoft.Diagnostics.Tools.Dump +namespace Microsoft.Diagnostic.Tools.Dump { - public static partial class Dumper + public partial class Dumper { - public static Task CollectDumpAsync(Process process, string fileName) + public Dumper() { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return Windows.CollectDumpAsync(process, fileName); + } + + public async Task Collect(IConsole console, int processId, string outputDirectory) + { + if (processId == 0) { + console.Error.WriteLine("ProcessId is required."); + return 1; } - else if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + + // System.CommandLine has a bug in the default value handling + if (outputDirectory == null) { + outputDirectory = Directory.GetCurrentDirectory(); + } + + // Get the process + Process process = null; + try { - return Linux.CollectDumpAsync(process, fileName); + process = Process.GetProcessById(processId); } - else + catch (Exception ex) when (ex is ArgumentException || ex is InvalidOperationException) { - throw new PlatformNotSupportedException("Can't collect a memory dump on this platform."); + console.Error.WriteLine($"Invalid process id: {processId}"); + return 1; } + + // Generate the file name + string fileName = Path.Combine(outputDirectory, $"{process.ProcessName}-{process.Id}-{DateTime.Now:yyyyMMdd-HHmmss-fff}.dmp"); + + console.Out.WriteLine($"Collecting memory dump for {process.ProcessName} (ID: {process.Id}) ..."); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + await Windows.CollectDumpAsync(process, fileName); + } + else if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + await Linux.CollectDumpAsync(process, fileName); + } + else { + console.Error.WriteLine($"Unsupported operating system {RuntimeInformation.OSDescription}"); + return 1; + } + + console.Out.WriteLine($"Dump saved to {fileName}"); + return 0; } } } diff --git a/src/Tools/dotnet-dump/Program.cs b/src/Tools/dotnet-dump/Program.cs index cdb386411a..8509b5c9c3 100644 --- a/src/Tools/dotnet-dump/Program.cs +++ b/src/Tools/dotnet-dump/Program.cs @@ -1,61 +1,59 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.Diagnostics; +using System.CommandLine; +using System.CommandLine.Builder; +using System.CommandLine.Invocation; using System.IO; -using System.Runtime.InteropServices; using System.Threading.Tasks; -using McMaster.Extensions.CommandLineUtils; -using Microsoft.Internal.Utilities; -namespace Microsoft.Diagnostics.Tools.Dump +namespace Microsoft.Diagnostic.Tools.Dump { - [Command(Name = "dotnet-dump", Description = "Captures memory dumps of .NET processes")] - internal class Program + class Program { - [Required(ErrorMessage = "You must provide a process ID to be dumped.")] - [Option("-p|--process-id ", Description = "The ID of the process to collect a memory dump for")] - public int ProcessId { get; set; } - - [Option("-o|--output ", Description = "The directory to write the dump to. Defaults to the current working directory.")] - public string OutputDir { get; set; } - - public async Task OnExecute(IConsole console, CommandLineApplication app) + public static Task Main(string[] args) { - if (string.IsNullOrEmpty(OutputDir)) - { - OutputDir = Directory.GetCurrentDirectory(); - } - - // Get the process - var process = Process.GetProcessById(ProcessId); - - // Generate the file name - var fileName = Path.Combine(OutputDir, $"{process.ProcessName}-{process.Id}-{DateTime.Now:yyyyMMdd-HHmmss-fff}.dmp"); + var parser = new CommandLineBuilder() + .AddCommand(CollectCommand()) + .AddCommand(AnalyzeCommand()) + .UseDefaults() + .Build(); - console.WriteLine($"Collecting memory dump for {process.ProcessName} (ID: {process.Id}) ..."); - await Dumper.CollectDumpAsync(process, fileName); - console.WriteLine($"Dump saved to {fileName}"); - - return 0; + return parser.InvokeAsync(args); } - private static int Main(string[] args) - { - DebugUtil.WaitForDebuggerIfRequested(ref args); - - try - { - return CommandLineApplication.Execute(args); - } - catch(PlatformNotSupportedException ex) - { - Console.Error.WriteLine(ex.Message); - return 1; - } - catch (OperationCanceledException) - { - return 0; - } - } + private static Command CollectCommand() => + new Command( + "collect", + "Captures memory dumps of .NET processes.", + new Option[] { ProcessIdOption(), OutputOption() }, + handler: CommandHandler.Create(new Dumper().Collect)); + + private static Option ProcessIdOption() => + new Option( + new[] { "-p", "--process-id" }, + "The ID of the process to collect a memory dump.", + new Argument { Name = "processId" }); + + private static Option OutputOption() => + new Option( + new[] { "-o", "--output" }, + "The directory to write the dump. Defaults to the current working directory.", + new Argument(Directory.GetCurrentDirectory()) { Name = "directory" }); + + private static Command AnalyzeCommand() => + new Command( + "analyze", + "Start interactive dump analyze.", + new Option[] { RunCommand() }, argument: DumpPath(), + handler: CommandHandler.Create(new Analyzer().Analyze)); + + private static Argument DumpPath() => + new Argument { + Name = "dump_path", + Description = "Name of the dump file to analyze." }.ExistingOnly(); + + private static Option RunCommand() => + new Option( + new[] { "-c", "--command" }, + "Run the command on start.", + new Argument() { Name = "command" }); } } diff --git a/src/Tools/dotnet-dump/dotnet-dump.csproj b/src/Tools/dotnet-dump/dotnet-dump.csproj index b8453cade2..408ce5dd93 100644 --- a/src/Tools/dotnet-dump/dotnet-dump.csproj +++ b/src/Tools/dotnet-dump/dotnet-dump.csproj @@ -2,23 +2,83 @@ Exe - netcoreapp2.1 - Microsoft.Diagnostics.Tools.Dump - + 2.1.0 + win-x64;win-x86;osx-x64 + + $(VersionPrefix) + $(VersionPrefix) + dotnet-dump + Microsoft.Diagnostic.Tools.Dump + Diagnostic dump collect and analyze tool + Diagnostic + $(Description) + + $(OutputPath) + $(ArtifactsBinDir)\SOS.NETCore\$(Configuration)\netstandard2.0\publish\*.dll - - + - + - + + + + <_PackageFiles Include="$(SOSNETCoreBinaries)"> + None + tools/netcoreapp2.1/any/win-x64 + + <_PackageFiles Include="$(ArtifactsBinDir)\Windows_NT.x64.$(Configuration)\sos.dll"> + None + tools/netcoreapp2.1/any/win-x64 + + <_PackageFiles Include="$(SOSNETCoreBinaries)"> + None + tools/netcoreapp2.1/any/win-x86 + + <_PackageFiles Include="$(ArtifactsBinDir)\Windows_NT.x86.$(Configuration)\sos.dll"> + None + tools/netcoreapp2.1/any/win-x86 + + <_PackageFiles Include="$(SOSNETCoreBinaries)"> + None + tools/netcoreapp2.1/any/linux-x64 + + <_PackageFiles Include="$(ArtifactsBinDir)\Linux.x64.$(Configuration)\libsosplugin.so"> + None + tools/netcoreapp2.1/any/linux-x64 + + <_PackageFiles Include="$(ArtifactsBinDir)\Linux.x64.$(Configuration)\libsos.so"> + None + tools/netcoreapp2.1/any/linux-x64 + + <_PackageFiles Include="$(ArtifactsBinDir)\Linux.x64.$(Configuration)\sosdocsunix.txt"> + None + tools/netcoreapp2.1/any/linux-x64 + + <_PackageFiles Include="$(SOSNETCoreBinaries)"> + None + tools/netcoreapp2.1/any/osx-x64 + + <_PackageFiles Include="$(ArtifactsBinDir)\OSX.x64.$(Configuration)\libsosplugin.dylib"> + None + tools/netcoreapp2.1/any/osx-x64 + + <_PackageFiles Include="$(ArtifactsBinDir)\OSX.x64.$(Configuration)\libsos.dylib"> + None + tools/netcoreapp2.1/any/osx-x64 + + <_PackageFiles Include="$(ArtifactsBinDir)\OSX.x64.$(Configuration)\sosdocsunix.txt"> + None + tools/netcoreapp2.1/any/osx-x64 + + diff --git a/src/Tools/dotnet-sos/dotnet-sos.csproj b/src/Tools/dotnet-sos/dotnet-sos.csproj index 00f1271ac6..0fe8a0758b 100644 --- a/src/Tools/dotnet-sos/dotnet-sos.csproj +++ b/src/Tools/dotnet-sos/dotnet-sos.csproj @@ -7,8 +7,8 @@ true win-x64;win-x86;osx-x64 - 1.0.0 - 1.0.0 + $(VersionPrefix) + $(VersionPrefix) dotnet-sos Microsoft.Diagnostics.Tools.SOS Diagnostic SOS installer @@ -16,7 +16,7 @@ $(Description) $(OutputPath) - $(ArtifactsBinDir)\SOS.NETCore\$(Configuration)\netcoreapp2.0\publish\*.dll + $(ArtifactsBinDir)\SOS.NETCore\$(Configuration)\netstandard2.0\publish\*.dll diff --git a/src/pal/src/loader/module.cpp b/src/pal/src/loader/module.cpp index 4755fb9fb9..04a9ba1bf5 100644 --- a/src/pal/src/loader/module.cpp +++ b/src/pal/src/loader/module.cpp @@ -366,8 +366,9 @@ GetProcAddress( } else { - TRACE("Symbol %s not found in module %p (named %S)\n", - lpProcName, module, MODNAME(module)); + TRACE("Symbol %s not found in module %p (named %S) %s\n", + lpProcName, module, MODNAME(module), dlerror()); + SetLastError(ERROR_PROC_NOT_FOUND); } done: