Add new dotnet-sos tool
authornoahfalk <noahfalk@microsoft.com>
Tue, 5 Feb 2019 08:43:32 +0000 (00:43 -0800)
committernoahfalk <noahfalk@microsoft.com>
Tue, 5 Feb 2019 08:43:32 +0000 (00:43 -0800)
(work originally by mikem, I just split his mega-commit so history would a little more understandable)

diagnostics.sln
src/SOS/SOS.InstallHelper/InstallHelper.cs [new file with mode: 0644]
src/SOS/SOS.InstallHelper/SOS.InstallHelper.csproj [new file with mode: 0644]
src/SOS/SOS.NETCore/SOS.NETCore.csproj
src/Tools/dotnet-sos/Program.cs [new file with mode: 0644]
src/Tools/dotnet-sos/dotnet-sos.csproj [new file with mode: 0644]

index 324aba897f3014837ca3b5163c357e3b599b76d1..03b8f947348c1ff004962434b8e01c19097ffac4 100644 (file)
@@ -35,6 +35,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "debugshim", "src\SOS\debugs
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gcdump", "src\SOS\gcdump\gcdump.vcxproj", "{20EBC3C4-917C-402D-B778-9A6E3742BF5A}"
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SOS.InstallHelper", "src\SOS\SOS.InstallHelper\SOS.InstallHelper.csproj", "{1F012743-941B-4915-8C55-02097894CF3F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-sos", "src\Tools\dotnet-sos\dotnet-sos.csproj", "{41351955-16D5-48D7-AF4C-AF25F5FB2E78}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Checked|Any CPU = Checked|Any CPU
@@ -509,6 +513,86 @@ Global
                {20EBC3C4-917C-402D-B778-9A6E3742BF5A}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
                {20EBC3C4-917C-402D-B778-9A6E3742BF5A}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
                {20EBC3C4-917C-402D-B778-9A6E3742BF5A}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
+               {1F012743-941B-4915-8C55-02097894CF3F}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Checked|Any CPU.Build.0 = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Checked|ARM.ActiveCfg = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Checked|ARM.Build.0 = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Checked|ARM64.ActiveCfg = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Checked|ARM64.Build.0 = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Checked|x64.ActiveCfg = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Checked|x64.Build.0 = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Checked|x86.ActiveCfg = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Checked|x86.Build.0 = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Debug|ARM.ActiveCfg = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Debug|ARM.Build.0 = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Debug|ARM64.Build.0 = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Debug|x64.ActiveCfg = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Debug|x64.Build.0 = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Debug|x86.Build.0 = Debug|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Release|Any CPU.Build.0 = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Release|ARM.ActiveCfg = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Release|ARM.Build.0 = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Release|ARM64.ActiveCfg = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Release|ARM64.Build.0 = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Release|x64.ActiveCfg = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Release|x64.Build.0 = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Release|x86.ActiveCfg = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.Release|x86.Build.0 = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
+               {1F012743-941B-4915-8C55-02097894CF3F}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Checked|Any CPU.Build.0 = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Checked|ARM.ActiveCfg = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Checked|ARM.Build.0 = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Checked|ARM64.ActiveCfg = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Checked|ARM64.Build.0 = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Checked|x64.ActiveCfg = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Checked|x64.Build.0 = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Checked|x86.ActiveCfg = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Checked|x86.Build.0 = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Debug|ARM.ActiveCfg = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Debug|ARM.Build.0 = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Debug|ARM64.Build.0 = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Debug|x64.ActiveCfg = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Debug|x64.Build.0 = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Debug|x86.Build.0 = Debug|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Release|Any CPU.Build.0 = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Release|ARM.ActiveCfg = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Release|ARM.Build.0 = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Release|ARM64.ActiveCfg = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Release|ARM64.Build.0 = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Release|x64.ActiveCfg = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Release|x64.Build.0 = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Release|x86.ActiveCfg = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.Release|x86.Build.0 = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
+               {41351955-16D5-48D7-AF4C-AF25F5FB2E78}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+               {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
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
@@ -529,6 +613,8 @@ Global
                {A9A7C879-C320-3327-BB84-16E1322E17AE} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
                {6A94C5FE-8706-3505-834E-DA16242F3864} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
                {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}
        EndGlobalSection
        GlobalSection(ExtensibilityGlobals) = postSolution
                SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}
diff --git a/src/SOS/SOS.InstallHelper/InstallHelper.cs b/src/SOS/SOS.InstallHelper/InstallHelper.cs
new file mode 100644 (file)
index 0000000..4253bb1
--- /dev/null
@@ -0,0 +1,307 @@
+// 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.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Security;
+
+namespace SOS
+{
+    /// <summary>
+    /// Functions to install and configure SOS from the package containing this code.
+    /// </summary>
+    public sealed class InstallHelper
+    {
+        /// <summary>
+        /// Well known location to install SOS. Defaults to $HOME/.dotnet/sos on xplat and %USERPROFILE%/.dotnet/sos on Windows.
+        /// </summary>
+        public string InstallLocation { get; set; }
+
+        /// <summary>
+        /// On Linux/MacOS, the location of the lldb ".lldbinit" file. Defaults to $HOME/.lldbinit.
+        /// </summary>
+        public string LLDBInitFile { get; set; }
+
+        /// <summary>
+        /// If true, enable the symbol server support when configuring lldb.
+        /// </summary>
+        public bool EnableSymbolServer { get; set; } = true;
+
+        /// <summary>
+        /// The source path from which SOS is installed. Default is OS/architecture (RID) named directory in the same directory as this assembly.
+        /// </summary>
+        public string SOSSourcePath { get; set; }
+
+        /// <summary>
+        /// Console output delegate
+        /// </summary>
+        private Action<string> m_writeLine;
+
+        /// <summary>
+        /// Create an instance of the installer.
+        /// </summary>
+        /// <exception cref="PlatformNotSupportedException">unknown operating system</exception>
+        /// <exception cref="InvalidOperationException">environment variable not found</exception>
+        public InstallHelper(Action<string> writeLine)
+        {
+            m_writeLine = writeLine;
+            string home = null;
+            string os = null;
+
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 
+            {
+                home = Environment.GetEnvironmentVariable("USERPROFILE");
+                if (string.IsNullOrEmpty(home)) {
+                    throw new InvalidOperationException("USERPROFILE environment variable not found");
+                }
+                os = "win";
+            }
+            else
+            {
+                home = Environment.GetEnvironmentVariable("HOME");
+                if (string.IsNullOrEmpty(home)) {
+                    throw new InvalidOperationException("HOME environment variable not found");
+                }
+                LLDBInitFile = Path.Combine(home, ".lldbinit");
+
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
+                    os = "osx";
+                }
+                else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
+                    os = "linux";
+                }
+            }
+            if (os == null) {
+                throw new PlatformNotSupportedException($"Unsupported operating system {RuntimeInformation.OSDescription}");
+            }
+            InstallLocation = Path.GetFullPath(Path.Combine(home, ".dotnet", "sos"));
+
+            string architecture = RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant();
+            string rid = os + "-" + architecture;
+            SOSSourcePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), rid);
+        }
+
+        /// <summary>
+        /// Install SOS to well known location (InstallLocation).
+        /// </summary>
+        /// <exception cref="PlatformNotSupportedException">SOS not found for OS/architecture</exception>
+        /// <exception cref="ArgumentException">various</exception>
+        public void Install()
+        {
+            WriteLine("Installing SOS to {0} from {1}", InstallLocation, SOSSourcePath);
+
+            if (string.IsNullOrEmpty(SOSSourcePath)) {
+                throw new ArgumentException("SOS source path not valid");
+            }
+            if (!Directory.Exists(SOSSourcePath)) {
+                throw new PlatformNotSupportedException($"Operating system or architecture not supported: installing from {SOSSourcePath}");
+            }
+            if (string.IsNullOrEmpty(InstallLocation)) {
+                throw new ArgumentException($"Installation path {InstallLocation} not valid");
+            }
+
+            // Rename any existing installation
+            string previousInstall = null;
+            if (Directory.Exists(InstallLocation))
+            {
+                WriteLine("Installing over existing installation...");
+                previousInstall = Path.Combine(Path.GetDirectoryName(InstallLocation), Path.GetRandomFileName());
+                RetryOperation($"Installation path '{InstallLocation}' not valid", () => Directory.Move(InstallLocation, previousInstall));
+            }
+
+            bool installSuccess = false;
+            try
+            {
+                // Create the installation directory
+                WriteLine("Creating installation directory...");
+                RetryOperation($"Installation path '{InstallLocation}' not valid", () => Directory.CreateDirectory(InstallLocation));
+
+                // Copy SOS files
+                WriteLine("Copying files...");
+                RetryOperation("Problem installing SOS", () =>
+                {
+                    foreach (string file in Directory.EnumerateFiles(SOSSourcePath))
+                    {
+                        string destinationFile = Path.Combine(InstallLocation, Path.GetFileName(file));
+                        File.Copy(file, destinationFile, overwrite: true);
+                    }
+                });
+
+                // Configure lldb 
+                if (LLDBInitFile != null) {
+                    Configure();
+                }
+
+                // If we get here without an exception, success!
+                installSuccess = true;
+            }
+            finally
+            {
+                if (previousInstall != null)
+                {
+                    WriteLine("Cleaning up...");
+                    if (installSuccess)
+                    {
+                        // Delete the previous installation if the install was successful
+                        RetryOperation(null, () => Directory.Delete(previousInstall, recursive: true));
+                    }
+                    else
+                    {
+                        // Delete partial installation
+                        RetryOperation(null, () => Directory.Delete(InstallLocation, recursive: true));
+
+                        // Restore previous install
+                        WriteLine("Restoring previous installation...");
+                        RetryOperation(null, () => Directory.Move(previousInstall, InstallLocation));
+                    }
+                }
+            }
+
+            Debug.Assert(installSuccess);
+            WriteLine("SOS install succeeded");
+        }
+
+        /// <summary>
+        /// Uninstalls and removes the SOS configuration.
+        /// </summary>
+        /// <exception cref="ArgumentException">various</exception>
+        public void Uninstall()
+        {
+            WriteLine("Uninstalling SOS from {0}", InstallLocation);
+            if (!string.IsNullOrEmpty(LLDBInitFile))
+            {
+                Configure(remove: true);
+            }
+            if (Directory.Exists(InstallLocation))
+            {
+                RetryOperation("Problem uninstalling SOS", () => Directory.Delete(InstallLocation, recursive: true));
+                WriteLine("SOS uninstall succeeded");
+            }
+            else
+            {
+                WriteLine("SOS not installed");
+            }
+        }
+
+        const string InitFileStart = "#START - ADDED BY SOS INSTALLER";
+        const string InitFileEnd = "#END - ADDED BY SOS INSTALLER";
+
+        /// <summary>
+        /// Configure lldb to load SOS.
+        /// </summary>
+        /// <param name="remove">if true, remove the configuration from the init file</param>
+        /// <exception cref="ArgumentException"></exception>
+        public void Configure(bool remove = false)
+        {
+            if (string.IsNullOrEmpty(LLDBInitFile)) {
+                throw new ArgumentException("No lldb configuration file path");
+            }
+            bool changed = false;
+            bool existing = false;
+
+            // Remove the start/end marker from an existing .lldbinit file
+            var lines = new List<string>();
+            if (File.Exists(LLDBInitFile))
+            {
+                existing = true;
+                bool markerFound = false;
+                foreach (string line in File.ReadAllLines(LLDBInitFile))
+                {
+                    if (line.Contains(InitFileEnd)) {
+                        markerFound = false;
+                        changed = true;
+                        continue;
+                    }
+                    if (!markerFound) {
+                        if (line.Contains(InitFileStart)) {
+                            markerFound = true;
+                            changed = true;
+                            continue;
+                        }
+                        lines.Add(line);
+                    }
+                }
+                if (markerFound) {
+                    throw new ArgumentException(".lldbinit file end marker not found");
+                }
+            }
+
+            // If configure (not remove), add the plugin load, etc. configuration between the start/end markers.
+            if (!remove)
+            {
+                lines.Add(InitFileStart);
+                string plugin = Path.Combine(InstallLocation, "libsosplugin");
+                string extension = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? ".dylib" : ".so";
+                lines.Add($"plugin load {plugin}{extension}");
+
+                if (EnableSymbolServer) {
+                    lines.Add(string.Format("setsymbolserver -ms"));
+                }
+                lines.Add(InitFileEnd);
+                changed = true;
+            }
+
+            // If there is anything to write, write the lldb init file
+            if (changed)
+            {
+                if (remove) {
+                    WriteLine("Reverting {0} file - LLDB will no longer load SOS at startup", LLDBInitFile);
+                }
+                else {
+                    WriteLine("{0} {1} file - LLDB will load SOS automatically at startup", existing ? "Updating existing" : "Creating new", LLDBInitFile);
+                }
+                RetryOperation($"Problem writing lldb init file {LLDBInitFile}", () => File.WriteAllLines(LLDBInitFile, lines.ToArray()));
+            }
+        }
+
+        /// <summary>
+        /// Retries any IO operation failures.
+        /// </summary>
+        /// <param name="errorMessage">text message or null (don't throw exception)</param>
+        /// <param name="operation">callback</param>
+        /// <exception cref="ArgumentException">errorMessage</exception>
+        private void RetryOperation(string errorMessage, Action operation)
+        {
+            Exception lastfailure = null;
+
+            for (int retry = 0; retry < 5; retry++)
+            {
+                try
+                {
+                    operation();
+                    return;
+                }
+                catch (Exception ex) when (ex is IOException)
+                {
+                    // Retry file copy possible recoverable exception
+                    lastfailure = ex;
+                }
+                catch (Exception ex) when (ex is ArgumentException || ex is UnauthorizedAccessException || ex is SecurityException)
+                {
+                    if (errorMessage == null) {
+                        return;
+                    }
+                    throw new ArgumentException($"{errorMessage}: {ex.Message}", ex);
+                }
+            }
+
+            if (lastfailure != null)
+            {
+                if (errorMessage == null) {
+                    return;
+                }
+                throw new ArgumentException($"{errorMessage}: {lastfailure.Message}", lastfailure);
+            }
+        }
+
+        private void WriteLine(string format, params object[] args)
+        {
+            m_writeLine?.Invoke(string.Format(format, args));
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/SOS/SOS.InstallHelper/SOS.InstallHelper.csproj b/src/SOS/SOS.InstallHelper/SOS.InstallHelper.csproj
new file mode 100644 (file)
index 0000000..89203e3
--- /dev/null
@@ -0,0 +1,12 @@
+<!-- Copyright (c)  Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information. -->
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <AssemblyName>SOS.InstallHelper</AssemblyName>
+    <NoWarn>;1591;1701</NoWarn>
+    <Description>Diagnostic SOS Install Helper</Description>
+    <PackageReleaseNotes>$(Description)</PackageReleaseNotes>
+    <PackageTags>SOS</PackageTags>
+    <DebugSymbols>true</DebugSymbols>
+  </PropertyGroup>
+</Project>
index a4d723675802c9b6851c1bfb71377414a2e61e54..a0f80a208e3da7f62b5690a1db899977dc121a8b 100644 (file)
@@ -5,72 +5,11 @@
     <AssemblyName>SOS.NETCore</AssemblyName>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <NoWarn>;1591;1701</NoWarn>
-    <IsPackable>true</IsPackable>
-    <IsPublishable>true</IsPublishable>
     <Description>.NET Core SOS</Description>
-    <PackageId>SOS</PackageId>
-    <PackageReleaseNotes>$(Description)</PackageReleaseNotes>
-    <PackageTags>SOS</PackageTags>
-    <IncludeBuildOutput>false</IncludeBuildOutput>
-    <SOSNETCoreBinaries>$(ArtifactsBinDir)\SOS.NETCore\$(Configuration)\netcoreapp2.0\publish\*.dll</SOSNETCoreBinaries>
   </PropertyGroup>
   
   <ItemGroup>
     <PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
     <PackageReference Include="Microsoft.SymbolStore" Version="$(MicrosoftSymbolStoreVersion)" />
   </ItemGroup>
-
-  <ItemGroup>
-    <_PackageFiles Include="$(SOSNETCoreBinaries)">
-      <BuildAction>None</BuildAction>
-      <PackagePath>tools/win-x64</PackagePath>
-    </_PackageFiles>
-    <_PackageFiles Include="$(ArtifactsBinDir)\Windows_NT.x64.$(Configuration)\sos.dll">
-      <BuildAction>None</BuildAction>
-      <PackagePath>tools/win-x64</PackagePath>
-    </_PackageFiles>
-
-    <_PackageFiles Include="$(SOSNETCoreBinaries)">
-      <BuildAction>None</BuildAction>
-      <PackagePath>tools/win-x86</PackagePath>
-    </_PackageFiles>
-    <_PackageFiles Include="$(ArtifactsBinDir)\Windows_NT.x86.$(Configuration)\sos.dll">
-      <BuildAction>None</BuildAction>
-      <PackagePath>tools/win-x86</PackagePath>
-    </_PackageFiles>
-
-    <_PackageFiles Include="$(SOSNETCoreBinaries)">
-      <BuildAction>None</BuildAction>
-      <PackagePath>tools/linux-x64</PackagePath>
-    </_PackageFiles>
-    <_PackageFiles Include="$(ArtifactsBinDir)\Linux.x64.$(Configuration)\libsosplugin.so">
-      <BuildAction>None</BuildAction>
-      <PackagePath>tools/linux-x64</PackagePath>
-    </_PackageFiles>
-    <_PackageFiles Include="$(ArtifactsBinDir)\Linux.x64.$(Configuration)\libsos.so">
-      <BuildAction>None</BuildAction>
-      <PackagePath>tools/linux-x64</PackagePath>
-    </_PackageFiles>
-    <_PackageFiles Include="$(ArtifactsBinDir)\Linux.x64.$(Configuration)\sosdocsunix.txt">
-      <BuildAction>None</BuildAction>
-      <PackagePath>tools/linux-x64</PackagePath>
-    </_PackageFiles>
-
-    <_PackageFiles Include="$(SOSNETCoreBinaries)">
-      <BuildAction>None</BuildAction>
-      <PackagePath>tools/osx-x64</PackagePath>
-    </_PackageFiles>
-    <_PackageFiles Include="$(ArtifactsBinDir)\OSX.x64.$(Configuration)\libsosplugin.dylib">
-      <BuildAction>None</BuildAction>
-      <PackagePath>tools/osx-x64</PackagePath>
-    </_PackageFiles>
-    <_PackageFiles Include="$(ArtifactsBinDir)\OSX.x64.$(Configuration)\libsos.dylib">
-      <BuildAction>None</BuildAction>
-      <PackagePath>tools/osx-x64</PackagePath>
-    </_PackageFiles>
-    <_PackageFiles Include="$(ArtifactsBinDir)\OSX.x64.$(Configuration)\sosdocsunix.txt">
-      <BuildAction>None</BuildAction>
-      <PackagePath>tools/osx-x64</PackagePath>
-    </_PackageFiles>
-  </ItemGroup>
 </Project>
diff --git a/src/Tools/dotnet-sos/Program.cs b/src/Tools/dotnet-sos/Program.cs
new file mode 100644 (file)
index 0000000..7ab778b
--- /dev/null
@@ -0,0 +1,64 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using McMaster.Extensions.CommandLineUtils;
+using SOS;
+using System;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostics.Tools.SOS
+{
+    [Command(Name = "dotnet-sos", Description = "Install and configure SOS")]
+    internal class Program
+    {
+        [Option("--install", Description = "Install and configure SOS.")]
+        public bool InstallSOS { get; set; }
+
+        [Option("--uninstall", Description = "Uninstall SOS.")]
+        public bool UninstallSOS { get; set; }
+
+        [Option("--source", Description = "SOS binaries source path.")]
+        public string SOSSourcePath { get; set; }
+
+        public int OnExecute(IConsole console, CommandLineApplication app)
+        {
+            if (InstallSOS || UninstallSOS)
+            {
+                var sosInstaller = new InstallHelper((message) => console.WriteLine(message));
+                if (SOSSourcePath != null)
+                {
+                    sosInstaller.SOSSourcePath = SOSSourcePath;
+                }
+                try
+                {
+                    if (UninstallSOS)
+                    {
+                        sosInstaller.Uninstall();
+                    }
+                    else 
+                    {
+                        sosInstaller.Install();
+                    }
+                }
+                catch (Exception ex) when (ex is ArgumentException || ex is PlatformNotSupportedException || ex is InvalidOperationException)
+                {
+                    console.Error.WriteLine(ex.Message);
+                    return 1;
+                }
+            }
+            return 0;
+        }
+
+        private static int Main(string[] args)
+        {
+            try
+            {
+                return CommandLineApplication.Execute<Program>(args);
+            }
+            catch (OperationCanceledException)
+            {
+                return 0;
+            }
+        }
+    }
+}
diff --git a/src/Tools/dotnet-sos/dotnet-sos.csproj b/src/Tools/dotnet-sos/dotnet-sos.csproj
new file mode 100644 (file)
index 0000000..00f1271
--- /dev/null
@@ -0,0 +1,80 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+    <RuntimeFrameworkVersion>2.1.0</RuntimeFrameworkVersion>
+    <IsPackable>true</IsPackable>
+    <PackAsTool>true</PackAsTool>
+    <PackAsToolShimRuntimeIdentifiers>win-x64;win-x86;osx-x64</PackAsToolShimRuntimeIdentifiers>
+    <!-- The package version needs to be hard coded as a stable version so "dotnet tool install -g dotnet-sos" works -->
+    <Version>1.0.0</Version>
+    <PackageVersion>1.0.0</PackageVersion>
+    <ToolCommandName>dotnet-sos</ToolCommandName>
+    <RootNamespace>Microsoft.Diagnostics.Tools.SOS</RootNamespace>
+    <Description>Diagnostic SOS installer</Description>
+    <PackageTags>Diagnostic</PackageTags>
+    <PackageReleaseNotes>$(Description)</PackageReleaseNotes>
+    <!-- Need to put the shims here to sign -->
+    <PackagedShimOutputRootDirectory>$(OutputPath)</PackagedShimOutputRootDirectory>
+    <SOSNETCoreBinaries>$(ArtifactsBinDir)\SOS.NETCore\$(Configuration)\netcoreapp2.0\publish\*.dll</SOSNETCoreBinaries>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.2.5" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\SOS\SOS.InstallHelper\SOS.InstallHelper.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <_PackageFiles Include="$(SOSNETCoreBinaries)">
+      <BuildAction>None</BuildAction>
+      <PackagePath>tools/netcoreapp2.1/any/win-x64</PackagePath>
+    </_PackageFiles>
+    <_PackageFiles Include="$(ArtifactsBinDir)\Windows_NT.x64.$(Configuration)\sos.dll">
+      <BuildAction>None</BuildAction>
+      <PackagePath>tools/netcoreapp2.1/any/win-x64</PackagePath>
+    </_PackageFiles>
+    <_PackageFiles Include="$(SOSNETCoreBinaries)">
+      <BuildAction>None</BuildAction>
+      <PackagePath>tools/netcoreapp2.1/any/win-x86</PackagePath>
+    </_PackageFiles>
+    <_PackageFiles Include="$(ArtifactsBinDir)\Windows_NT.x86.$(Configuration)\sos.dll">
+      <BuildAction>None</BuildAction>
+      <PackagePath>tools/netcoreapp2.1/any/win-x86</PackagePath>
+    </_PackageFiles>
+    <_PackageFiles Include="$(SOSNETCoreBinaries)">
+      <BuildAction>None</BuildAction>
+      <PackagePath>tools/netcoreapp2.1/any/linux-x64</PackagePath>
+    </_PackageFiles>
+    <_PackageFiles Include="$(ArtifactsBinDir)\Linux.x64.$(Configuration)\libsosplugin.so">
+      <BuildAction>None</BuildAction>
+      <PackagePath>tools/netcoreapp2.1/any/linux-x64</PackagePath>
+    </_PackageFiles>
+    <_PackageFiles Include="$(ArtifactsBinDir)\Linux.x64.$(Configuration)\libsos.so">
+      <BuildAction>None</BuildAction>
+      <PackagePath>tools/netcoreapp2.1/any/linux-x64</PackagePath>
+    </_PackageFiles>
+    <_PackageFiles Include="$(ArtifactsBinDir)\Linux.x64.$(Configuration)\sosdocsunix.txt">
+      <BuildAction>None</BuildAction>
+      <PackagePath>tools/netcoreapp2.1/any/linux-x64</PackagePath>
+    </_PackageFiles>
+    <_PackageFiles Include="$(SOSNETCoreBinaries)">
+      <BuildAction>None</BuildAction>
+      <PackagePath>tools/netcoreapp2.1/any/osx-x64</PackagePath>
+    </_PackageFiles>
+    <_PackageFiles Include="$(ArtifactsBinDir)\OSX.x64.$(Configuration)\libsosplugin.dylib">
+      <BuildAction>None</BuildAction>
+      <PackagePath>tools/netcoreapp2.1/any/osx-x64</PackagePath>
+    </_PackageFiles>
+    <_PackageFiles Include="$(ArtifactsBinDir)\OSX.x64.$(Configuration)\libsos.dylib">
+      <BuildAction>None</BuildAction>
+      <PackagePath>tools/netcoreapp2.1/any/osx-x64</PackagePath>
+    </_PackageFiles>
+    <_PackageFiles Include="$(ArtifactsBinDir)\OSX.x64.$(Configuration)\sosdocsunix.txt">
+      <BuildAction>None</BuildAction>
+      <PackagePath>tools/netcoreapp2.1/any/osx-x64</PackagePath>
+    </_PackageFiles>
+  </ItemGroup>
+</Project>