From 31b9a5cc225487498c3ccbe877bc8b131b08d39b Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Fri, 31 Mar 2017 11:18:51 -0700 Subject: [PATCH] Copy System.Diagnostics.Debug implementation from corefx Commit migrated from https://github.com/dotnet/coreclr/commit/d37fd15d5c6388c770742610f651fe3ae16b7eb1 --- .../Windows/kernel32/Interop.OutputDebugString.cs | 14 + .../mscorlib/src/System/Diagnostics/Debug.Unix.cs | 120 ++++++++ .../src/System/Diagnostics/Debug.Windows.cs | 79 +++++ .../src/mscorlib/src/System/Diagnostics/Debug.cs | 331 +++++++++++++++++++++ 4 files changed, 544 insertions(+) create mode 100644 src/coreclr/src/mscorlib/src/Interop/Windows/kernel32/Interop.OutputDebugString.cs create mode 100644 src/coreclr/src/mscorlib/src/System/Diagnostics/Debug.Unix.cs create mode 100644 src/coreclr/src/mscorlib/src/System/Diagnostics/Debug.Windows.cs create mode 100644 src/coreclr/src/mscorlib/src/System/Diagnostics/Debug.cs diff --git a/src/coreclr/src/mscorlib/src/Interop/Windows/kernel32/Interop.OutputDebugString.cs b/src/coreclr/src/mscorlib/src/Interop/Windows/kernel32/Interop.OutputDebugString.cs new file mode 100644 index 0000000..8da50ff --- /dev/null +++ b/src/coreclr/src/mscorlib/src/Interop/Windows/kernel32/Interop.OutputDebugString.cs @@ -0,0 +1,14 @@ +// 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; + +internal partial class Interop +{ + internal partial class Kernel32 + { + [DllImport(Interop.Libraries.Kernel32, CharSet = CharSet.Unicode, EntryPoint = "OutputDebugStringW", ExactSpelling = true)] + internal static extern void OutputDebugString(string message); + } +} diff --git a/src/coreclr/src/mscorlib/src/System/Diagnostics/Debug.Unix.cs b/src/coreclr/src/mscorlib/src/System/Diagnostics/Debug.Unix.cs new file mode 100644 index 0000000..6c9ec04 --- /dev/null +++ b/src/coreclr/src/mscorlib/src/System/Diagnostics/Debug.Unix.cs @@ -0,0 +1,120 @@ +// 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.Win32.SafeHandles; + +namespace System.Diagnostics +{ + // Intentionally excluding visibility so it defaults to internal except for + // the one public version in System.Diagnostics.Debug which defines + // another version of this partial class with the public visibility + static partial class Debug + { + private static string NewLine => "\n"; + + // internal and not readonly so that the tests can swap this out. + internal static IDebugLogger s_logger = new UnixDebugLogger(); + + // -------------- + // PAL ENDS HERE + // -------------- + + internal sealed class UnixDebugLogger : IDebugLogger + { + private const string EnvVar_DebugWriteToStdErr = "COMPlus_DebugWriteToStdErr"; + private static readonly bool s_shouldWriteToStdErr = + Internal.Runtime.Augments.EnvironmentAugments.GetEnvironmentVariable(EnvVar_DebugWriteToStdErr) == "1"; + + public void ShowAssertDialog(string stackTrace, string message, string detailMessage) + { + if (Debugger.IsAttached) + { + Debugger.Break(); + } + else + { + // TODO: #3708 Determine if/how to put up a dialog instead. + var exc = new DebugAssertException(message, detailMessage, stackTrace); + if (!s_shouldWriteToStdErr) + { + // We always want to print out Debug.Assert failures to stderr, even if + // !s_shouldWriteToStdErr, so if it wouldn't have been printed in + // WriteCore (only when s_shouldWriteToStdErr), print it here. + WriteToStderr(exc.Message); + } + throw exc; + } + } + + public void WriteCore(string message) + { + WriteToDebugger(message); + + if (s_shouldWriteToStdErr) + { + WriteToStderr(message); + } + } + + private static void WriteToDebugger(string message) + { + if (Debugger.IsLogging()) + { + Debugger.Log(0, null, message); + } + else + { + Interop.Sys.SysLog(Interop.Sys.SysLogPriority.LOG_USER | Interop.Sys.SysLogPriority.LOG_DEBUG, "%s", message); + } + } + + private static void WriteToStderr(string message) + { + // We don't want to write UTF-16 to a file like standard error. Ideally we would transcode this + // to UTF8, but the downside of that is it pulls in a bunch of stuff into what is ideally + // a path with minimal dependencies (as to prevent re-entrency), so we'll take the strategy + // of just throwing away any non ASCII characters from the message and writing the rest + + const int BufferLength = 256; + + unsafe + { + byte* buf = stackalloc byte[BufferLength]; + int bufCount; + int i = 0; + + using (SafeFileHandle fileHandle = SafeFileHandleHelper.Open(() => Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDERR_FILENO))) + { + while (i < message.Length) + { + for (bufCount = 0; bufCount < BufferLength && i < message.Length; i++) + { + if (message[i] <= 0x7F) + { + buf[bufCount] = (byte)message[i]; + bufCount++; + } + } + + int totalBytesWritten = 0; + while (bufCount > 0) + { + int bytesWritten = Interop.Sys.Write(fileHandle, buf + totalBytesWritten, bufCount); + if (bytesWritten < 0) + { + // On error, simply stop writing the debug output. This could commonly happen if stderr + // was piped to a program that ended before this program did, resulting in EPIPE errors. + return; + } + + bufCount -= bytesWritten; + totalBytesWritten += bytesWritten; + } + } + } + } + } + } + } +} diff --git a/src/coreclr/src/mscorlib/src/System/Diagnostics/Debug.Windows.cs b/src/coreclr/src/mscorlib/src/System/Diagnostics/Debug.Windows.cs new file mode 100644 index 0000000..f0f589c --- /dev/null +++ b/src/coreclr/src/mscorlib/src/System/Diagnostics/Debug.Windows.cs @@ -0,0 +1,79 @@ +// 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.Security; + +namespace System.Diagnostics +{ + // Intentionally excluding visibility so it defaults to internal except for + // the one public version in System.Diagnostics.Debug which defines + // another version of this partial class with the public visibility + static partial class Debug + { + private static string NewLine => "\r\n"; + + // internal and not read only so that the tests can swap this out. + internal static IDebugLogger s_logger = new WindowsDebugLogger(); + + // -------------- + // PAL ENDS HERE + // -------------- + + internal sealed class WindowsDebugLogger : IDebugLogger + { + [SecuritySafeCritical] + public void ShowAssertDialog(string stackTrace, string message, string detailMessage) + { + if (Debugger.IsAttached) + { + Debugger.Break(); + } + else + { + // TODO: #3708 Determine if/how to put up a dialog instead. + throw new DebugAssertException(message, detailMessage, stackTrace); + } + } + + public void WriteCore(string message) + { + // really huge messages mess up both VS and dbmon, so we chop it up into + // reasonable chunks if it's too big. This is the number of characters + // that OutputDebugstring chunks at. + const int WriteChunkLength = 4091; + + // We don't want output from multiple threads to be interleaved. + lock (s_ForLock) + { + if (message == null || message.Length <= WriteChunkLength) + { + WriteToDebugger(message); + } + else + { + int offset; + for (offset = 0; offset < message.Length - WriteChunkLength; offset += WriteChunkLength) + { + WriteToDebugger(message.Substring(offset, WriteChunkLength)); + } + WriteToDebugger(message.Substring(offset)); + } + } + } + + [System.Security.SecuritySafeCritical] + private static void WriteToDebugger(string message) + { + if (Debugger.IsLogging()) + { + Debugger.Log(0, null, message); + } + else + { + Interop.Kernel32.OutputDebugString(message ?? string.Empty); + } + } + } + } +} diff --git a/src/coreclr/src/mscorlib/src/System/Diagnostics/Debug.cs b/src/coreclr/src/mscorlib/src/System/Diagnostics/Debug.cs new file mode 100644 index 0000000..5f893f8 --- /dev/null +++ b/src/coreclr/src/mscorlib/src/System/Diagnostics/Debug.cs @@ -0,0 +1,331 @@ +// 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. + +// Do not remove this, it is needed to retain calls to these conditional methods in release builds +#define DEBUG + +namespace System.Diagnostics +{ + /// + /// Provides a set of properties and methods for debugging code. + /// + static partial class Debug + { + private static readonly object s_lock = new object(); + + public static bool AutoFlush { get { return true; } set { } } + + [ThreadStatic] + private static int s_indentLevel; + public static int IndentLevel + { + get + { + return s_indentLevel; + } + set + { + s_indentLevel = value < 0 ? 0 : value; + } + } + + private static int s_indentSize = 4; + public static int IndentSize + { + get + { + return s_indentSize; + } + set + { + s_indentSize = value < 0 ? 0 : value; + } + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Close() { } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Flush() { } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Indent() + { + IndentLevel++; + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Unindent() + { + IndentLevel--; + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Print(string message) + { + Write(message); + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Print(string format, params object[] args) + { + Write(string.Format(null, format, args)); + } + + private static readonly object s_ForLock = new Object(); + + [System.Diagnostics.Conditional("DEBUG")] + public static void Assert(bool condition) + { + Assert(condition, string.Empty, string.Empty); + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Assert(bool condition, string message) + { + Assert(condition, message, string.Empty); + } + + [System.Diagnostics.Conditional("DEBUG")] + [System.Security.SecuritySafeCritical] + public static void Assert(bool condition, string message, string detailMessage) + { + if (!condition) + { + string stackTrace; + + try + { + stackTrace = Internal.Runtime.Augments.EnvironmentAugments.StackTrace; + } + catch + { + stackTrace = ""; + } + + WriteLine(FormatAssert(stackTrace, message, detailMessage)); + s_logger.ShowAssertDialog(stackTrace, message, detailMessage); + } + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Fail(string message) + { + Assert(false, message, string.Empty); + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Fail(string message, string detailMessage) + { + Assert(false, message, detailMessage); + } + + private static string FormatAssert(string stackTrace, string message, string detailMessage) + { + string newLine = GetIndentString() + NewLine; + return SR.DebugAssertBanner + newLine + + SR.DebugAssertShortMessage + newLine + + message + newLine + + SR.DebugAssertLongMessage + newLine + + detailMessage + newLine + + stackTrace; + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Assert(bool condition, string message, string detailMessageFormat, params object[] args) + { + Assert(condition, message, string.Format(detailMessageFormat, args)); + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteLine(string message) + { + Write(message + NewLine); + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Write(string message) + { + lock (s_lock) + { + if (message == null) + { + WriteCore(string.Empty); + return; + } + if (s_needIndent) + { + message = GetIndentString() + message; + s_needIndent = false; + } + WriteCore(message); + if (message.EndsWith(NewLine)) + { + s_needIndent = true; + } + } + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteLine(object value) + { + WriteLine(value?.ToString()); + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteLine(object value, string category) + { + WriteLine(value?.ToString(), category); + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteLine(string format, params object[] args) + { + WriteLine(string.Format(null, format, args)); + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteLine(string message, string category) + { + if (category == null) + { + WriteLine(message); + } + else + { + WriteLine(category + ":" + message); + } + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Write(object value) + { + Write(value?.ToString()); + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Write(string message, string category) + { + if (category == null) + { + Write(message); + } + else + { + Write(category + ":" + message); + } + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void Write(object value, string category) + { + Write(value?.ToString(), category); + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteIf(bool condition, string message) + { + if (condition) + { + Write(message); + } + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteIf(bool condition, object value) + { + if (condition) + { + Write(value); + } + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteIf(bool condition, string message, string category) + { + if (condition) + { + Write(message, category); + } + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteIf(bool condition, object value, string category) + { + if (condition) + { + Write(value, category); + } + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteLineIf(bool condition, object value) + { + if (condition) + { + WriteLine(value); + } + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteLineIf(bool condition, object value, string category) + { + if (condition) + { + WriteLine(value, category); + } + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteLineIf(bool condition, string message) + { + if (condition) + { + WriteLine(message); + } + } + + [System.Diagnostics.Conditional("DEBUG")] + public static void WriteLineIf(bool condition, string message, string category) + { + if (condition) + { + WriteLine(message, category); + } + } + + private static bool s_needIndent; + + private static string s_indentString; + + private static string GetIndentString() + { + int indentCount = IndentSize * IndentLevel; + if (s_indentString?.Length == indentCount) + { + return s_indentString; + } + return s_indentString = new string(' ', indentCount); + } + + private static void WriteCore(string message) + { + s_logger.WriteCore(message); + } + + internal interface IDebugLogger + { + void ShowAssertDialog(string stackTrace, string message, string detailMessage); + void WriteCore(string message); + } + + private sealed class DebugAssertException : Exception + { + internal DebugAssertException(string message, string detailMessage, string stackTrace) : + base(message + NewLine + detailMessage + NewLine + stackTrace) + { + } + } + } +} -- 2.7.4