diff --git a/CMakeLists.txt b/CMakeLists.txt index 488392bd33..c93eba23ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,11 @@ elseif (CLR_CMAKE_HOST_ARCH_MIPS64) add_definitions(-DMIPS64) add_definitions(-D_WIN64) add_definitions(-DBIT64=1) +elseif (CLR_CMAKE_HOST_ARCH_RISCV64) + add_definitions(-D_RISCV64_) + add_definitions(-DRISCV64) + add_definitions(-D_WIN64) + add_definitions(-DBIT64=1) else () clr_unknown_arch() endif () @@ -143,6 +148,14 @@ elseif (CLR_CMAKE_TARGET_ARCH_MIPS64) add_definitions(-DDBG_TARGET_64BIT=1) add_definitions(-DDBG_TARGET_WIN64=1) add_definitions(-DFEATURE_MULTIREG_RETURN) +elseif (CLR_CMAKE_TARGET_ARCH_RISCV64) + add_definitions(-DDBG_TARGET_RISCV64_UNIX) + add_definitions(-D_TARGET_RISCV64_=1) + add_definitions(-D_TARGET_64BIT_=1) + add_definitions(-DDBG_TARGET_RISCV64=1) + add_definitions(-DDBG_TARGET_64BIT=1) + add_definitions(-DDBG_TARGET_WIN64=1) + add_definitions(-DFEATURE_MULTIREG_RETURN) else () clr_unknown_arch() endif (CLR_CMAKE_TARGET_ARCH_AMD64) diff --git a/diagnostics.sln b/diagnostics.sln index c53cec4f3f..23ae2ff91a 100644 --- a/diagnostics.sln +++ b/diagnostics.sln @@ -266,7 +266,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonTestRunner", "src\tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetStack.UnitTests", "src\tests\dotnet-stack\DotnetStack.UnitTests.csproj", "{E8F133F8-4D20-475D-9D16-2BA236DAB65F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Diagnostics.WebSocketServer", "src\Microsoft.Diagnostics.WebSocketServer\Microsoft.Diagnostics.WebSocketServer.csproj", "{1043FA82-37CC-4809-80DC-C1EB06A55133}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.WebSocketServer", "src\Microsoft.Diagnostics.WebSocketServer\Microsoft.Diagnostics.WebSocketServer.csproj", "{1043FA82-37CC-4809-80DC-C1EB06A55133}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestExtension", "src\tests\TestExtension\TestExtension.csproj", "{C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1907,6 +1909,46 @@ Global {1043FA82-37CC-4809-80DC-C1EB06A55133}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU {1043FA82-37CC-4809-80DC-C1EB06A55133}.RelWithDebInfo|x86.ActiveCfg = Debug|Any CPU {1043FA82-37CC-4809-80DC-C1EB06A55133}.RelWithDebInfo|x86.Build.0 = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|Any CPU.Build.0 = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|ARM.ActiveCfg = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|ARM.Build.0 = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|ARM64.ActiveCfg = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|ARM64.Build.0 = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|x64.ActiveCfg = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|x64.Build.0 = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|x86.ActiveCfg = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|x86.Build.0 = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|ARM.ActiveCfg = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|ARM.Build.0 = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|ARM64.Build.0 = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|x64.ActiveCfg = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|x64.Build.0 = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|x86.ActiveCfg = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|x86.Build.0 = Debug|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|Any CPU.Build.0 = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|ARM.ActiveCfg = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|ARM.Build.0 = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|ARM64.ActiveCfg = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|ARM64.Build.0 = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|x64.ActiveCfg = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|x64.Build.0 = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|x86.ActiveCfg = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|x86.Build.0 = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1966,6 +2008,7 @@ Global {DFF48CB6-4504-41C6-A8F1-F4A3D316D49F} = {03479E19-3F18-49A6-910A-F5041E27E7C0} {E8F133F8-4D20-475D-9D16-2BA236DAB65F} = {03479E19-3F18-49A6-910A-F5041E27E7C0} {1043FA82-37CC-4809-80DC-C1EB06A55133} = {19FAB78C-3351-4911-8F0C-8C6056401740} + {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E} = {03479E19-3F18-49A6-910A-F5041E27E7C0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0} diff --git a/documentation/debugging-coredump.md b/documentation/debugging-coredump.md index eb43cee166..4cfb0e33e9 100644 --- a/documentation/debugging-coredump.md +++ b/documentation/debugging-coredump.md @@ -1,3 +1,5 @@ +This documentation is now located at [debug-linux-dumps](https://learn.microsoft.com/dotnet/core/diagnostics/debug-linux-dumps) This doc is no longer being updated. + Debugging Linux or MacOS Core Dump ================================== diff --git a/documentation/design-docs/dotnet-tools.md b/documentation/design-docs/dotnet-tools.md index 0af1b58b44..bd36698d01 100644 --- a/documentation/design-docs/dotnet-tools.md +++ b/documentation/design-docs/dotnet-tools.md @@ -1,4 +1,5 @@ -# Dotnet Diagnostic Tools CLI Design +For the latest public documentation on using dotnet-* diagnostic tools see [dotnet-trace](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-trace), [dotnet-counters](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-counters), [dotnet-dump](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-dump), [dotnet-gcdump](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-gcdump), [dotnet-dsrouter](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-dsrouter), [dotnet-monitor](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-monitor), [dotnet-symbol](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-symbol), [dotnet-sos](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-sos). +# Dotnet Diagnostic Tools CLI Design ## User workflows diff --git a/documentation/design-docs/eventcounters.md b/documentation/design-docs/eventcounters.md index 2815eca2c9..0cbd98fe2e 100644 --- a/documentation/design-docs/eventcounters.md +++ b/documentation/design-docs/eventcounters.md @@ -1,3 +1,5 @@ +The newest documentation is now maintained at [eventcounters](https://learn.microsoft.com/dotnet/core/diagnostics/event-counters). This doc is no longer being updated. + # EventCounters in .NET Core 3.0 ## Introduction @@ -332,4 +334,4 @@ The official dotnet-trace documentation contains a [section](https://github.com/ TraceEvent is a managed library that makes it easy to consume ETW and EventPipe events. For more information, refer to the [TraceEvent Library Programmers Guide](https://github.com/Microsoft/perfview/blob/main/documentation/TraceEvent/TraceEventProgrammersGuide.md). -For some more detailed code samples, you can also try reading [Criteo Labs blog](https://medium.com/criteo-labs/net-core-counters-internals-how-to-integrate-counters-in-your-monitoring-pipeline-5354cd61b42e) on how to do this. \ No newline at end of file +For some more detailed code samples, you can also try reading [Criteo Labs blog](https://medium.com/criteo-labs/net-core-counters-internals-how-to-integrate-counters-in-your-monitoring-pipeline-5354cd61b42e) on how to do this. diff --git a/documentation/dotnet-counters-instructions.md b/documentation/dotnet-counters-instructions.md index 4faf0868b4..86beeba17c 100644 --- a/documentation/dotnet-counters-instructions.md +++ b/documentation/dotnet-counters-instructions.md @@ -1,3 +1,5 @@ +This documentation is now being maintained here: [dotnet-counters](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-counters). This doc is no longer being updated. + # dotnet-counters NOTE: This documentation page may contain information on some features that are still work-in-progress. For most up-to-date documentation on released version of `dotnet-counters`, please refer to [its official documentation](https://docs.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters) page. @@ -209,4 +211,4 @@ $ dotnet-counters monitor --counters System.Runtime[assembly-count] -- my-aspnet provider and counter names, use the list command. -- (for target applications running .NET 5.0 or later only) - After the collection configuration parameters, the user can append `--` followed by a command to start a .NET application with at least a 5.0 runtime. `dotnet-counters` will launch a process with the provided command and collect the requested metrics. \ No newline at end of file + After the collection configuration parameters, the user can append `--` followed by a command to start a .NET application with at least a 5.0 runtime. `dotnet-counters` will launch a process with the provided command and collect the requested metrics. diff --git a/documentation/dotnet-dump-instructions.md b/documentation/dotnet-dump-instructions.md index c75ac8a606..b3ea9ab8fc 100644 --- a/documentation/dotnet-dump-instructions.md +++ b/documentation/dotnet-dump-instructions.md @@ -1,3 +1,5 @@ +This documentation is now being maintained at [dotnet-dump](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-dump). This doc is no longer being updated. + Dump collection and analysis utility (dotnet-dump) ================================================== diff --git a/documentation/dotnet-gcdump-instructions.md b/documentation/dotnet-gcdump-instructions.md index 078785bc83..7ecbc75e58 100644 --- a/documentation/dotnet-gcdump-instructions.md +++ b/documentation/dotnet-gcdump-instructions.md @@ -1,3 +1,5 @@ +This documentation is now being maintained at [dotnet-gcdump](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-gcdump). This doc is no longer being updated. + # Heap Analysis Tool (dotnet-gcdump) NOTE: This documentation page may contain information on some features that are still work-in-progress. For most up-to-date documentation on released version of `dotnet-gcdump`, please refer to [its official documentation](https://docs.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-gcdump) page. diff --git a/documentation/dotnet-trace-instructions.md b/documentation/dotnet-trace-instructions.md index 154996a927..4ad26dfe64 100644 --- a/documentation/dotnet-trace-instructions.md +++ b/documentation/dotnet-trace-instructions.md @@ -1,3 +1,5 @@ +This documentation is now being maintained at [dotnet-trace](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-trace). This doc is no longer being updated. + # Trace for performance analysis utility (dotnet-trace) NOTE: This documentation page may contain information on some features that are still work-in-progress. For most up-to-date documentation on released version of `dotnet-trace`, please refer to [its official documentation](https://docs.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-trace) page. diff --git a/documentation/installing-sos-instructions.md b/documentation/installing-sos-instructions.md index 4db1420e32..620e5c9cc5 100644 --- a/documentation/installing-sos-instructions.md +++ b/documentation/installing-sos-instructions.md @@ -1,3 +1,5 @@ +This documentation is now being maintained at [dotnet-sos](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-sos). This doc is no longer being updated. + Installing SOS on Linux and MacOS ================================= diff --git a/documentation/installing-sos-windows-instructions.md b/documentation/installing-sos-windows-instructions.md index cbde73e79e..923f29004c 100644 --- a/documentation/installing-sos-windows-instructions.md +++ b/documentation/installing-sos-windows-instructions.md @@ -1,3 +1,5 @@ +This documentation is now being maintained at [dotnet-sos](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-sos). This doc is no longer being updated. + Installing SOS on Windows ========================= diff --git a/documentation/sos.md b/documentation/sos.md index e4de62587d..45d83740c6 100644 --- a/documentation/sos.md +++ b/documentation/sos.md @@ -1,3 +1,5 @@ +This documentation is now being maintained at [dotnet-sos](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-sos). This doc is no longer being updated. + SOS === diff --git a/documentation/tutorial/app_is_leaking_memory_eventual_crash.md b/documentation/tutorial/app_is_leaking_memory_eventual_crash.md index dc29ecf6f9..bb97c26f01 100644 --- a/documentation/tutorial/app_is_leaking_memory_eventual_crash.md +++ b/documentation/tutorial/app_is_leaking_memory_eventual_crash.md @@ -1,3 +1,5 @@ +The newest documentation is now being maintained at [debug-memory-leak](https://learn.microsoft.com/dotnet/core/diagnostics/debug-memory-leak). This documentation is no longer being updated. + # App is leaking memory (eventual crash/stops responding) http://localhost:5000/api/diagscenario/memleak/{kb} diff --git a/documentation/tutorial/app_running_slow_highcpu.md b/documentation/tutorial/app_running_slow_highcpu.md index aa56ea3558..2fd8fef245 100644 --- a/documentation/tutorial/app_running_slow_highcpu.md +++ b/documentation/tutorial/app_running_slow_highcpu.md @@ -1,3 +1,5 @@ +The newest documentation is now being maintained at [debug-highcpu](https://learn.microsoft.com/dotnet/core/diagnostics/debug-highcpu). This documentation is no longer being updated. + # App is running slow (due to high CPU) http://localhost:5000/api/diagscenario/highcpu/{milliseconds} diff --git a/documentation/tutorial/hung_app.md b/documentation/tutorial/hung_app.md index a4fa89f390..3e05005629 100644 --- a/documentation/tutorial/hung_app.md +++ b/documentation/tutorial/hung_app.md @@ -1,3 +1,5 @@ +The newest documentation is now being maintained at [debug-deadlock](https://learn.microsoft.com/dotnet/core/diagnostics/debug-deadlock). This documentation is no longer being updated. + # App stops responding **IMPORTANT: This tutorial uses API/methods available in dotnet core preview 5. These API/methods are _subject to change._** diff --git a/documentation/tutorial/installing_the_diagnostics_tools.md b/documentation/tutorial/installing_the_diagnostics_tools.md index e3efd27750..56bf5e78c8 100644 --- a/documentation/tutorial/installing_the_diagnostics_tools.md +++ b/documentation/tutorial/installing_the_diagnostics_tools.md @@ -1,3 +1,5 @@ +The newest documentation is now maintained at [dotnet-trace](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-trace), [dotnet-counters](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-counters), [dotnet-dump](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-dump), [dotnet-gcdump](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-gcdump), [dotnet-dsrouter](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-dsrouter), [dotnet-monitor](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-monitor), [dotnet-symbol](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-symbol), [dotnet-sos](https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-sos). This documentation is no longer being updated. + # Installing the diagnostics tools Depending on the diagnostics scenario you will use one or more of the tools below to get to root cause. By default, these tools are installed to ~/.dotnet/tools. diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 9136113505..355fddd00f 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,55 +1,55 @@ - + https://github.com/dotnet/symstore - 8cc6f03fdbd9c79f0bf9ffbe0a788dca1a81348a + 21508f9ab057e19d4060855647edeb376ac7d59d - + https://github.com/microsoft/clrmd - 6d7c5a7288c0e93e5eb56893a6064575ac6e3ea8 + b64f583396784300c7ded11d22c65053197e958a - + https://github.com/microsoft/clrmd - 6d7c5a7288c0e93e5eb56893a6064575ac6e3ea8 + b64f583396784300c7ded11d22c65053197e958a - + https://github.com/dotnet/arcade - 1d451c32dda2314c721adbf8829e1c0cd4e681ff + 6a5ca678aef84f34a1fccc46f76623f41da56553 - + https://github.com/dotnet/arcade - 1d451c32dda2314c721adbf8829e1c0cd4e681ff + 6a5ca678aef84f34a1fccc46f76623f41da56553 https://github.com/dotnet/arcade ccfe6da198c5f05534863bbb1bff66e830e0c6ab - + https://github.com/dotnet/installer - f8a61a24ac843529a82a8f6ede35fc08a6fb8c35 + 30d7d24a6592aa0c97f81ac36b563fab2b451b14 - + https://github.com/dotnet/aspnetcore - 96da75d67ff058d95e14de12d9fc2ff215074e3c + c9fa5f3a34605c93bffd1459a5e39e6bc63f50cc - + https://github.com/dotnet/aspnetcore - 96da75d67ff058d95e14de12d9fc2ff215074e3c + c9fa5f3a34605c93bffd1459a5e39e6bc63f50cc - + https://github.com/dotnet/runtime - 885100b00bc944cbb698bc4cc2ec3ec18007534f + 11ad607efb2b31c5e1b906303fcd70341e9d5206 - + https://github.com/dotnet/runtime - 885100b00bc944cbb698bc4cc2ec3ec18007534f + 11ad607efb2b31c5e1b906303fcd70341e9d5206 - + https://github.com/dotnet/source-build-reference-packages - 0650b50b2a5263c735d12b5c36c5deb34e7e6b60 + d3fbf3c3d4c4f142ea12efceaa6efece9ad2e6b5 diff --git a/eng/Versions.props b/eng/Versions.props index 3f186e6d6d..6a38791823 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -2,7 +2,7 @@ https://github.com/dotnet/diagnostics - 7.0.0 + 8.0.0 true true - 1.0.446801 + 1.0.451601 - 8.0.0-rtm.23477.9 - 8.0.0-rtm.23477.9 + 8.0.0-rtm.23520.16 + 8.0.0-rtm.23520.16 - 8.0.0-rtm.23477.14 - 8.0.0-rtm.23477.14 + 8.0.0-rtm.23520.10 + 8.0.0-rtm.23520.10 - 8.0.100-rtm.23474.2 + 8.0.100-rtm.23522.1 @@ -35,7 +35,7 @@ $(MicrosoftNETCoreApp60Version) $(MicrosoftNETCoreApp70Version) - 8.0.0-rtm.23472.12 + 8.0.0-rtm.23519.13 @@ -43,9 +43,10 @@ true 5.0.0 + 6.0.0 6.0.0 - 3.0.447501 + 3.1.451701 16.11.27-beta1.23180.1 3.0.7 6.0.0 @@ -58,15 +59,15 @@ 4.5.1 4.5.5 4.3.0 - 4.7.2 - 4.7.1 + 6.0.0 + 6.0.8 2.0.3 - 8.0.0-beta.23463.1 + 9.0.0-beta.23518.2 1.2.0-beta.406 7.0.0-beta.22316.2 10.0.18362 13.0.1 - 9.0.0-alpha.1.23475.1 + 9.0.0-alpha.1.23519.2 3.11.0 diff --git a/eng/common/build.sh b/eng/common/build.sh index 50af40cdd2..2c17ba529b 100755 --- a/eng/common/build.sh +++ b/eng/common/build.sh @@ -105,7 +105,7 @@ while [[ $# > 0 ]]; do -binarylog|-bl) binary_log=true ;; - -excludeCIBinarylog|-nobl) + -excludecibinarylog|-nobl) exclude_ci_binary_log=true ;; -pipelineslog|-pl) diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index 9caf9b021d..4228f202e5 100644 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -487,7 +487,7 @@ if [[ "$__CodeName" == "alpine" ]]; then -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \ -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" \ - search 'llvm*-libs' | sort | tail -1 | sed 's/-[^-]*//2g')" + search 'llvm*-libs' | grep -E '^llvm' | sort | tail -1 | sed 's/-[^-]*//2g')" fi # install all packages in one go diff --git a/eng/common/cross/riscv64/tizen/tizen.patch b/eng/common/cross/riscv64/tizen/tizen.patch new file mode 100644 index 0000000000..eb6d1c0747 --- /dev/null +++ b/eng/common/cross/riscv64/tizen/tizen.patch @@ -0,0 +1,9 @@ +diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so +--- a/usr/lib64/libc.so 2016-12-30 23:00:08.284951863 +0900 ++++ b/usr/lib64/libc.so 2016-12-30 23:00:32.140951815 +0900 +@@ -2,4 +2,4 @@ + Use the shared library, but some functions are only in + the static library, so try that secondarily. */ + OUTPUT_FORMAT(elf64-littleriscv) +-GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a AS_NEEDED ( /lib64/ld-linux-riscv64-lp64d.so.1 ) ) ++GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux-riscv64-lp64d.so.1 ) ) diff --git a/eng/common/cross/tizen-build-rootfs.sh b/eng/common/cross/tizen-build-rootfs.sh index ac84173d44..ba31c93285 100644 --- a/eng/common/cross/tizen-build-rootfs.sh +++ b/eng/common/cross/tizen-build-rootfs.sh @@ -22,6 +22,10 @@ case "$ARCH" in TIZEN_ARCH="x86_64" LINK_ARCH="x86" ;; + riscv64) + TIZEN_ARCH="riscv64" + LINK_ARCH="riscv" + ;; *) echo "Unsupported architecture for tizen: $ARCH" exit 1 @@ -58,4 +62,21 @@ rm -rf $TIZEN_TMP_DIR echo ">>Start configuring Tizen rootfs" ln -sfn asm-${LINK_ARCH} ./usr/include/asm patch -p1 < $__TIZEN_CROSSDIR/tizen.patch +if [[ "$TIZEN_ARCH" == "riscv64" ]]; then + echo "Fixing broken symlinks in $PWD" + rm ./usr/lib64/libresolv.so + ln -s ../../lib64/libresolv.so.2 ./usr/lib64/libresolv.so + rm ./usr/lib64/libpthread.so + ln -s ../../lib64/libpthread.so.0 ./usr/lib64/libpthread.so + rm ./usr/lib64/libdl.so + ln -s ../../lib64/libdl.so.2 ./usr/lib64/libdl.so + rm ./usr/lib64/libutil.so + ln -s ../../lib64/libutil.so.1 ./usr/lib64/libutil.so + rm ./usr/lib64/libm.so + ln -s ../../lib64/libm.so.6 ./usr/lib64/libm.so + rm ./usr/lib64/librt.so + ln -s ../../lib64/librt.so.1 ./usr/lib64/librt.so + rm ./lib/ld-linux-riscv64-lp64d.so.1 + ln -s ../lib64/ld-linux-riscv64-lp64d.so.1 ./lib/ld-linux-riscv64-lp64d.so.1 +fi echo "< public class CommandService : ICommandService { - private Parser _parser; - private readonly CommandLineBuilder _rootBuilder; - private readonly Dictionary _commandHandlers = new(); + private readonly List _commandGroups = new(); + private readonly string _commandPrompt; /// /// Create an instance of the command processor; @@ -32,8 +30,10 @@ public class CommandService : ICommandService /// command prompted used in help message public CommandService(string commandPrompt = null) { - _rootBuilder = new CommandLineBuilder(new Command(commandPrompt ?? ">")); - _rootBuilder.UseHelpBuilder((bindingContext) => new LocalHelpBuilder(this, bindingContext.Console, useHelpBuilder: false)); + _commandPrompt = commandPrompt ?? ">"; + + // Create default command group (should always be last in this list) + _commandGroups.Add(new CommandGroup(_commandPrompt)); } /// @@ -41,125 +41,168 @@ public CommandService(string commandPrompt = null) /// /// command line text /// services for the command - /// true success, false failure + /// true - found command, false - command not found + /// empty command line + /// other errors + /// parsing error public bool Execute(string commandLine, IServiceProvider services) { - // Parse the command line and invoke the command - ParseResult parseResult = Parser.Parse(commandLine); + string[] commandLineArray = CommandLineStringSplitter.Instance.Split(commandLine).ToArray(); + if (commandLineArray.Length <= 0) + { + throw new ArgumentException("Empty command line", nameof(commandLine)); + } + string commandName = commandLineArray[0].Trim(); + return Execute(commandName, commandLineArray, services); + } + + /// + /// Parse and execute the command. + /// + /// command name + /// command arguments/options + /// services for the command + /// true - found command, false - command not found + /// empty command name or arguments + /// other errors + /// parsing error + public bool Execute(string commandName, string commandArguments, IServiceProvider services) + { + commandName = commandName.Trim(); + string[] commandLineArray = CommandLineStringSplitter.Instance.Split(commandName + " " + (commandArguments ?? "")).ToArray(); + if (commandLineArray.Length <= 0) + { + throw new ArgumentException("Empty command name or arguments", nameof(commandArguments)); + } + return Execute(commandName, commandLineArray, services); + } - InvocationContext context = new(parseResult, new LocalConsole(services)); - if (parseResult.Errors.Count > 0) + /// + /// Find, parse and execute the command. + /// + /// command name + /// command line + /// services for the command + /// true - found command, false - command not found + /// empty command name + /// other errors + /// parsing error + private bool Execute(string commandName, string[] commandLineArray, IServiceProvider services) + { + if (string.IsNullOrEmpty(commandName)) { - context.InvocationResult = new ParseErrorResult(); + throw new ArgumentException("Empty command name", nameof(commandName)); } - else + List messages = new(); + foreach (CommandGroup group in _commandGroups) { - if (parseResult.CommandResult.Command is Command command) + if (group.TryGetCommandHandler(commandName, out CommandHandler handler)) { - if (command.Handler is CommandHandler handler) + try { - ITarget target = services.GetService(); - if (!handler.IsValidPlatform(target)) + if (handler.IsCommandSupported(group.Parser, services)) { - if (target != null) - { - context.Console.Error.WriteLine($"Command '{command.Name}' not supported on this target"); - } - else + if (group.Execute(commandLineArray, services)) { - context.Console.Error.WriteLine($"Command '{command.Name}' needs a target"); + return true; } - return false; - } - try - { - handler.Invoke(context, services); } - catch (Exception ex) + if (handler.FilterInvokeMessage != null) { - if (ex is NullReferenceException or - ArgumentException or - ArgumentNullException or - ArgumentOutOfRangeException or - NotImplementedException) - { - context.Console.Error.WriteLine(ex.ToString()); - } - else - { - context.Console.Error.WriteLine(ex.Message); - } - Trace.TraceError(ex.ToString()); - return false; + messages.Add(handler.FilterInvokeMessage); } } + catch (CommandNotFoundException ex) + { + messages.Add(ex.Message); + } } } - - context.InvocationResult?.Apply(context); - return context.ResultCode == 0; + if (messages.Count > 0) + { + throw new CommandNotFoundException(string.Concat(messages.Select(s => s + Environment.NewLine))); + } + return false; } /// /// Displays the help for a command /// - /// name of the command or alias /// service provider - /// true if success, false if command not found - public bool DisplayHelp(string commandName, IServiceProvider services) + /// command invocation and help enumeration + public IEnumerable<(string Invocation, string Help)> GetAllCommandHelp(IServiceProvider services) { - Command command = null; - if (!string.IsNullOrEmpty(commandName)) + List<(string Invocation, string Help)> help = new(); + foreach (CommandGroup group in _commandGroups) { - command = _rootBuilder.Command.Children.OfType().FirstOrDefault((cmd) => commandName == cmd.Name || cmd.Aliases.Any((alias) => commandName == alias)); - if (command == null) - { - return false; - } - if (command.Handler is CommandHandler handler) + foreach (CommandHandler handler in group.CommandHandlers) { - ITarget target = services.GetService(); - if (!handler.IsValidPlatform(target)) + try + { + if (handler.IsCommandSupported(group.Parser, services)) + { + string invocation = handler.HelpInvocation; + help.Add((invocation, handler.Help)); + } + } + catch (CommandNotFoundException) { - return false; } } } - else - { - ITarget target = services.GetService(); + return help; + } - // Create temporary builder adding only the commands that are valid for the target - CommandLineBuilder builder = new(new Command(_rootBuilder.Command.Name)); - foreach (Command cmd in _rootBuilder.Command.Children.OfType()) + /// + /// Displays the detailed help for a command + /// + /// name of the command or alias + /// service provider + /// the width to format the help or int.MaxValue + /// help text or null if not found + public string GetDetailedHelp(string commandName, IServiceProvider services, int consoleWidth) + { + if (string.IsNullOrWhiteSpace(commandName)) + { + throw new ArgumentNullException(nameof(commandName)); + } + List messages = new(); + foreach (CommandGroup group in _commandGroups) + { + if (group.TryGetCommand(commandName, out Command command)) { - if (cmd.Handler is CommandHandler handler) + if (command.Handler is CommandHandler handler) { - if (handler.IsValidPlatform(target)) + try + { + if (handler.IsCommandSupported(group.Parser, services)) + { + return group.GetDetailedHelp(command, services, consoleWidth); + } + if (handler.FilterInvokeMessage != null) + { + messages.Add(handler.FilterInvokeMessage); + } + } + catch (CommandNotFoundException ex) { - builder.AddCommand(cmd); + messages.Add(ex.Message); } } } - command = builder.Command; } - Debug.Assert(command != null); - IHelpBuilder helpBuilder = new LocalHelpBuilder(this, new LocalConsole(services), useHelpBuilder: true); - helpBuilder.Write(command); - return true; + if (messages.Count > 0) + { + return string.Concat(messages.Select(s => s + Environment.NewLine)); + } + return null; } /// - /// Does this command or alias exists? - /// - /// command or alias name - /// true if command exists - public bool IsCommand(string commandName) => _rootBuilder.Command.Children.Contains(commandName); - - /// - /// Enumerates all the command's name and help + /// Enumerates all the command's name, help and aliases /// - public IEnumerable<(string name, string help, IEnumerable aliases)> Commands => _commandHandlers.Select((keypair) => (keypair.Value.Name, keypair.Value.Help, keypair.Value.Aliases)); + public IEnumerable<(string name, string help, IEnumerable aliases)> Commands => + _commandGroups.SelectMany((group) => group.CommandHandlers).Select((handler) => (handler.Name, handler.Help, handler.Aliases)); /// /// Add the commands and aliases attributes found in the type. @@ -185,84 +228,242 @@ public void AddCommands(Type type, Func factory) CommandAttribute[] commandAttributes = (CommandAttribute[])baseType.GetCustomAttributes(typeof(CommandAttribute), inherit: true); foreach (CommandAttribute commandAttribute in commandAttributes) { - if ((commandAttribute.Flags & CommandFlags.Manual) == 0 || factory != null) + factory ??= (services) => Utilities.CreateInstance(type, services); + + bool dup = true; + foreach (CommandGroup group in _commandGroups) { - factory ??= (services) => Utilities.CreateInstance(type, services); - CreateCommand(baseType, commandAttribute, factory); + // If the group doesn't contain a duplicate command name, add it to that group + if (!group.Contains(commandAttribute.Name)) + { + group.CreateCommand(baseType, commandAttribute, factory); + dup = false; + break; + } + } + // If this is a duplicate command, create a new group and add it to the beginning. The default group must be last. + if (dup) + { + CommandGroup group = new(_commandPrompt); + _commandGroups.Insert(0, group); + group.CreateCommand(baseType, commandAttribute, factory); } } } - - // Build or re-build parser instance after all these commands and aliases are added - FlushParser(); } } - private void CreateCommand(Type type, CommandAttribute commandAttribute, Func factory) + /// + /// This groups like commands that may have the same name as another group or the default one. + /// + private sealed class CommandGroup { - Command command = new(commandAttribute.Name, commandAttribute.Help); - List<(PropertyInfo, Option)> properties = new(); - List<(PropertyInfo, Argument)> arguments = new(); + private Parser _parser; + private readonly CommandLineBuilder _rootBuilder; + private readonly Dictionary _commandHandlers = new(); - foreach (string alias in commandAttribute.Aliases) + /// + /// Create an instance of the command processor; + /// + /// command prompted used in help message + public CommandGroup(string commandPrompt = null) { - command.AddAlias(alias); + _rootBuilder = new CommandLineBuilder(new Command(commandPrompt)); } - foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite)) + /// + /// Parse and execute the command line. + /// + /// command line text + /// services for the command + /// true if command was found and executed without error + /// parsing error + internal bool Execute(IReadOnlyList commandLine, IServiceProvider services) { - ArgumentAttribute argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault(); - if (argumentAttribute != null) - { - IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne; + // Parse the command line and invoke the command + ParseResult parseResult = Parser.Parse(commandLine); - Argument argument = new() + if (parseResult.Errors.Count > 0) + { + StringBuilder sb = new(); + foreach (ParseError error in parseResult.Errors) { - Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(), - Description = argumentAttribute.Help, - ArgumentType = property.PropertyType, - Arity = arity - }; - command.AddArgument(argument); - arguments.Add((property, argument)); + sb.AppendLine(error.Message); + } + string helpText = GetDetailedHelp(parseResult.CommandResult.Command, services, int.MaxValue); + throw new CommandParsingException(sb.ToString(), helpText); } else { - OptionAttribute optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault(); - if (optionAttribute != null) + if (parseResult.CommandResult.Command is Command command) { - Option option = new(optionAttribute.Name ?? BuildOptionAlias(property.Name), optionAttribute.Help) + if (command.Handler is CommandHandler handler) { - Argument = new Argument { ArgumentType = property.PropertyType } - }; - command.AddOption(option); - properties.Add((property, option)); + InvocationContext context = new(parseResult, new LocalConsole(services.GetService())); + handler.Invoke(context, services); + return true; + } + } + } + return false; + } + + /// + /// Build/return parser + /// + internal Parser Parser => _parser ??= _rootBuilder.Build(); + + /// + /// Returns all the command handler instances + /// + internal IEnumerable CommandHandlers => _commandHandlers.Values; + + /// + /// Returns true if command or command alias is found + /// + internal bool Contains(string commandName) => _rootBuilder.Command.Children.Contains(commandName); + + /// + /// Returns the command handler for the command or command alias + /// + /// command or alias + /// handler instance + /// true if found + internal bool TryGetCommandHandler(string commandName, out CommandHandler handler) + { + handler = null; + if (TryGetCommand(commandName, out Command command)) + { + handler = command.Handler as CommandHandler; + } + return handler != null; + } + + /// + /// Returns the command instance for the command or command alias + /// + /// command or alias + /// command instance + /// true if found + internal bool TryGetCommand(string commandName, out Command command) + { + command = _rootBuilder.Command.Children.GetByAlias(commandName) as Command; + return command != null; + } + + /// + /// Add the commands and aliases attributes found in the type. + /// + /// Command type to search + /// function to create command instance + internal void AddCommands(Type type, Func factory) + { + for (Type baseType = type; baseType != null; baseType = baseType.BaseType) + { + if (baseType == typeof(CommandBase)) + { + break; + } + CommandAttribute[] commandAttributes = (CommandAttribute[])baseType.GetCustomAttributes(typeof(CommandAttribute), inherit: false); + foreach (CommandAttribute commandAttribute in commandAttributes) + { + factory ??= (services) => Utilities.CreateInstance(type, services); + CreateCommand(baseType, commandAttribute, factory); + } + } + + // Build or re-build parser instance after all these commands and aliases are added + FlushParser(); + } + + internal void CreateCommand(Type type, CommandAttribute commandAttribute, Func factory) + { + Command command = new(commandAttribute.Name, commandAttribute.Help); + List<(PropertyInfo, Argument)> arguments = new(); + List<(PropertyInfo, Option)> options = new(); - foreach (string alias in optionAttribute.Aliases) + foreach (string alias in commandAttribute.Aliases) + { + command.AddAlias(alias); + } + + foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite)) + { + ArgumentAttribute argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault(); + if (argumentAttribute != null) + { + IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne; + + Argument argument = new() + { + Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(), + Description = argumentAttribute.Help, + ArgumentType = property.PropertyType, + Arity = arity + }; + command.AddArgument(argument); + arguments.Add((property, argument)); + } + else + { + OptionAttribute optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault(); + if (optionAttribute != null) { - option.AddAlias(alias); + Option option = new(optionAttribute.Name ?? BuildOptionAlias(property.Name), optionAttribute.Help) + { + Argument = new Argument { ArgumentType = property.PropertyType } + }; + command.AddOption(option); + options.Add((property, option)); + + foreach (string alias in optionAttribute.Aliases) + { + option.AddAlias(alias); + } } } } + + CommandHandler handler = new(commandAttribute, arguments, options, type, factory); + _commandHandlers.Add(command.Name, handler); + command.Handler = handler; + _rootBuilder.AddCommand(command); + + // Build or re-build parser instance after this command is added + FlushParser(); } - CommandHandler handler = new(commandAttribute, arguments, properties, type, factory); - _commandHandlers.Add(command.Name, handler); - command.Handler = handler; - _rootBuilder.AddCommand(command); - } + internal string GetDetailedHelp(ICommand command, IServiceProvider services, int windowWidth) + { + CaptureConsole console = new(); - private Parser Parser => _parser ??= _rootBuilder.Build(); + // Get the command help + HelpBuilder helpBuilder = new(console, maxWidth: windowWidth); + helpBuilder.Write(command); - private void FlushParser() => _parser = null; + // Get the detailed help if any + if (TryGetCommandHandler(command.Name, out CommandHandler handler)) + { + string helpText = handler.GetDetailedHelp(Parser, services); + if (helpText is not null) + { + console.Out.Write(helpText); + } + } - private static string BuildOptionAlias(string parameterName) - { - if (string.IsNullOrWhiteSpace(parameterName)) + return console.ToString(); + } + + private void FlushParser() => _parser = null; + + private static string BuildOptionAlias(string parameterName) { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(parameterName)); + if (string.IsNullOrWhiteSpace(parameterName)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(parameterName)); + } + return parameterName.Length > 1 ? $"--{parameterName.ToKebabCase()}" : $"-{parameterName.ToLowerInvariant()}"; } - return parameterName.Length > 1 ? $"--{parameterName.ToKebabCase()}" : $"-{parameterName.ToLowerInvariant()}"; } /// @@ -272,28 +473,68 @@ private sealed class CommandHandler : ICommandHandler { private readonly CommandAttribute _commandAttribute; private readonly IEnumerable<(PropertyInfo Property, Argument Argument)> _arguments; - private readonly IEnumerable<(PropertyInfo Property, Option Option)> _properties; + private readonly IEnumerable<(PropertyInfo Property, Option Option)> _options; private readonly Func _factory; private readonly MethodInfo _methodInfo; private readonly MethodInfo _methodInfoHelp; + private readonly MethodInfo _methodInfoFilter; + private readonly FilterInvokeAttribute _filterInvokeAttribute; public CommandHandler( CommandAttribute commandAttribute, IEnumerable<(PropertyInfo, Argument)> arguments, - IEnumerable<(PropertyInfo, Option)> properties, + IEnumerable<(PropertyInfo, Option)> options, Type type, Func factory) { _commandAttribute = commandAttribute; _arguments = arguments; - _properties = properties; + _options = options; _factory = factory; - _methodInfo = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute() != null).SingleOrDefault() ?? + // Now search for the command, help and filter attributes in the command type + foreach (MethodInfo methodInfo in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy)) + { + if (methodInfo.GetCustomAttribute() != null) + { + if (_methodInfo != null) + { + throw new ArgumentException($"Multiple CommandInvokeAttribute's found in {type}"); + } + _methodInfo = methodInfo; + } + if (methodInfo.GetCustomAttribute() != null) + { + if (_methodInfoHelp != null) + { + throw new ArgumentException($"Multiple HelpInvokeAttribute's found in {type}"); + } + if (methodInfo.ReturnType != typeof(string)) + { + throw new ArgumentException($"HelpInvokeAttribute doesn't return string in {type}"); + } + _methodInfoHelp = methodInfo; + } + FilterInvokeAttribute filterInvokeAttribute = methodInfo.GetCustomAttribute(); + if (filterInvokeAttribute != null) + { + if (_methodInfoFilter != null) + { + throw new ArgumentException($"Multiple FilterInvokeAttribute's found in {type}"); + } + if (methodInfo.ReturnType != typeof(bool)) + { + throw new ArgumentException($"FilterInvokeAttribute doesn't return bool in {type}"); + } + _filterInvokeAttribute = filterInvokeAttribute; + _methodInfoFilter = methodInfo; + } + } + if (_methodInfo == null) + { throw new ArgumentException($"No command invoke method found in {type}"); - - _methodInfoHelp = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute() != null).SingleOrDefault(); + } } Task ICommandHandler.InvokeAsync(InvocationContext context) @@ -311,37 +552,25 @@ Task ICommandHandler.InvokeAsync(InvocationContext context) /// internal string Help => _commandAttribute.Help; + /// + /// Filter invoke message or null if no attribute or message + /// + internal string FilterInvokeMessage => _filterInvokeAttribute?.Message; + /// /// Returns the list of the command's aliases. /// internal IEnumerable Aliases => _commandAttribute.Aliases; /// - /// Returns true if the command should be added. + /// Returns the list of arguments /// - internal bool IsValidPlatform(ITarget target) - { - if ((_commandAttribute.Flags & CommandFlags.Global) != 0) - { - return true; - } - if (target != null) - { - if (target.OperatingSystem == OSPlatform.Windows) - { - return (_commandAttribute.Flags & CommandFlags.Windows) != 0; - } - if (target.OperatingSystem == OSPlatform.Linux) - { - return (_commandAttribute.Flags & CommandFlags.Linux) != 0; - } - if (target.OperatingSystem == OSPlatform.OSX) - { - return (_commandAttribute.Flags & CommandFlags.OSX) != 0; - } - } - return false; - } + internal IEnumerable Arguments => _arguments.Select((a) => a.Argument); + + /// + /// Returns true is the command is supported by the command filter. Calls the FilterInvokeAttribute marked method. + /// + internal bool IsCommandSupported(Parser parser, IServiceProvider services) => _methodInfoFilter == null || (bool)Invoke(_methodInfoFilter, context: null, parser, services); /// /// Execute the command synchronously. @@ -350,32 +579,56 @@ internal bool IsValidPlatform(ITarget target) /// service provider internal void Invoke(InvocationContext context, IServiceProvider services) => Invoke(_methodInfo, context, context.Parser, services); + /// + /// Return the various ways the command can be invoked. For building the help text. + /// + internal string HelpInvocation + { + get + { + IEnumerable rawAliases = new string[] { Name }.Concat(Aliases); + string invocation = string.Join(", ", rawAliases); + foreach (Argument argument in Arguments) + { + string argumentDescriptor = argument.Name; + if (!string.IsNullOrWhiteSpace(argumentDescriptor)) + { + invocation = $"{invocation} <{argumentDescriptor}>"; + } + } + return invocation; + } + } + /// /// Executes the command's help invoke function if exists /// /// parser instance /// service provider /// true help called, false no help function - internal bool InvokeHelp(Parser parser, IServiceProvider services) + internal string GetDetailedHelp(Parser parser, IServiceProvider services) { if (_methodInfoHelp == null) { - return false; + return null; } // The InvocationContext is null so the options and arguments in the // command instance created are not set. The context for the command // requesting help (either the help command or some other command using // --help) won't work for the command instance that implements it's own // help (SOS command). - Invoke(_methodInfoHelp, context: null, parser, services); - return true; + return (string)Invoke(_methodInfoHelp, context: null, parser, services); } - private void Invoke(MethodInfo methodInfo, InvocationContext context, Parser parser, IServiceProvider services) + private object Invoke(MethodInfo methodInfo, InvocationContext context, Parser parser, IServiceProvider services) { - object instance = _factory(services); - SetProperties(context, parser, instance); - Utilities.Invoke(methodInfo, instance, services); + object instance = null; + if (!methodInfo.IsStatic) + { + instance = _factory(services); + SetProperties(context, parser, instance); + } + return Utilities.Invoke(methodInfo, instance, services); } private void SetProperties(InvocationContext context, Parser parser, object instance) @@ -390,31 +643,28 @@ private void SetProperties(InvocationContext context, Parser parser, object inst } // Now initialize the option and service properties from the default and command line options - foreach ((PropertyInfo Property, Option Option) property in _properties) + foreach ((PropertyInfo Property, Option Option) option in _options) { - object value = property.Property.GetValue(instance); + object value = option.Property.GetValue(instance); - if (property.Option != null) + if (defaultParseResult != null) { - if (defaultParseResult != null) + OptionResult defaultOptionResult = defaultParseResult.FindResultFor(option.Option); + if (defaultOptionResult != null) { - OptionResult defaultOptionResult = defaultParseResult.FindResultFor(property.Option); - if (defaultOptionResult != null) - { - value = defaultOptionResult.GetValueOrDefault(); - } + value = defaultOptionResult.GetValueOrDefault(); } - if (context != null) + } + if (context != null) + { + OptionResult optionResult = context.ParseResult.FindResultFor(option.Option); + if (optionResult != null) { - OptionResult optionResult = context.ParseResult.FindResultFor(property.Option); - if (optionResult != null) - { - value = optionResult.GetValueOrDefault(); - } + value = optionResult.GetValueOrDefault(); } } - property.Property.SetValue(instance, value); + option.Property.SetValue(instance, value); } // Initialize any argument properties from the default and command line arguments @@ -463,66 +713,46 @@ private void SetProperties(InvocationContext context, Parser parser, object inst } /// - /// Local help builder that allows commands to provide more detailed help - /// text via the "InvokeHelp" function. + /// IConsole implementation that captures all the output into a string. /// - private sealed class LocalHelpBuilder : IHelpBuilder + private sealed class CaptureConsole : IConsole { - private readonly CommandService _commandService; - private readonly LocalConsole _console; - private readonly bool _useHelpBuilder; + private readonly StringBuilder _builder = new(); - public LocalHelpBuilder(CommandService commandService, IConsole console, bool useHelpBuilder) + public CaptureConsole() { - _commandService = commandService; - _console = (LocalConsole)console; - _useHelpBuilder = useHelpBuilder; + Out = Error = new StandardStreamWriter((text) => _builder.Append(text)); } - void IHelpBuilder.Write(ICommand command) - { - bool useHelpBuilder = _useHelpBuilder; - if (_commandService._commandHandlers.TryGetValue(command.Name, out CommandHandler handler)) - { - if (handler.InvokeHelp(_commandService.Parser, _console.Services)) - { - return; - } - useHelpBuilder = true; - } - if (useHelpBuilder) - { - HelpBuilder helpBuilder = new(_console, maxWidth: _console.ConsoleService.WindowWidth); - helpBuilder.Write(command); - } - } + public override string ToString() => _builder.ToString(); + + #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; } } + + #endregion } /// - /// This class does two things: wraps the IConsoleService and provides the IConsole interface and - /// pipes through the System.CommandLine parsing allowing per command invocation data (service - /// provider and raw command line) to be passed through. + /// This class wraps the IConsoleService and provides the IConsole interface for System.CommandLine. /// private sealed class LocalConsole : IConsole { - private IConsoleService _console; + private readonly IConsoleService _consoleService; - public LocalConsole(IServiceProvider services) + public LocalConsole(IConsoleService consoleService) { - Services = services; - Out = new StandardStreamWriter(ConsoleService.Write); - Error = new StandardStreamWriter(ConsoleService.WriteError); - } - - internal readonly IServiceProvider Services; - - internal IConsoleService ConsoleService - { - get - { - _console ??= Services.GetService(); - return _console; - } + _consoleService = consoleService; + Out = new StandardStreamWriter(_consoleService.Write); + Error = new StandardStreamWriter(_consoleService.WriteError); } #region IConsole @@ -537,16 +767,16 @@ internal IConsoleService ConsoleService bool IStandardIn.IsInputRedirected { get { return false; } } - private sealed class StandardStreamWriter : IStandardStreamWriter - { - private readonly Action _write; + #endregion + } - public StandardStreamWriter(Action write) => _write = write; + private sealed class StandardStreamWriter : IStandardStreamWriter + { + private readonly Action _write; - void IStandardStreamWriter.Write(string value) => _write(value); - } + public StandardStreamWriter(Action write) => _write = write; - #endregion + void IStandardStreamWriter.Write(string value) => _write(value); } } } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs new file mode 100644 index 0000000000..2f7282e0ae --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs @@ -0,0 +1,199 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Diagnostics.DebugServices.Implementation +{ + public class CrashInfoService : ICrashInfoService + { + /// + /// This is a "transport" exception code required by Watson to trigger the proper analyzer/provider for bucketing + /// + public const uint STATUS_STACK_BUFFER_OVERRUN = 0xC0000409; + + /// + /// This is the Native AOT fail fast subcode used by Watson + /// + public const uint FAST_FAIL_EXCEPTION_DOTNET_AOT = 0x48; + + public sealed class CrashInfoJson + { + [JsonPropertyName("version")] + public string Version { get; set; } + + [JsonPropertyName("reason")] + public int Reason { get; set; } + + [JsonPropertyName("runtime_type")] + public int RuntimeType { get; set; } + + [JsonPropertyName("runtime_base")] + [JsonConverter(typeof(HexUInt64Converter))] + public ulong RuntimeBaseAddress { get; set; } + + [JsonPropertyName("runtime_version")] + public string RuntimeVersion { get; set; } + + [JsonPropertyName("thread")] + [JsonConverter(typeof(HexUInt32Converter))] + public uint Thread { get; set; } + + [JsonPropertyName("message")] + public string Message { get; set; } + + [JsonPropertyName("exception")] + public CrashInfoException Exception { get; set; } + } + + public sealed class CrashInfoException : IManagedException + { + [JsonPropertyName("address")] + [JsonConverter(typeof(HexUInt64Converter))] + public ulong Address { get; set; } + + [JsonPropertyName("hr")] + [JsonConverter(typeof(HexUInt32Converter))] + public uint HResult { get; set; } + + [JsonPropertyName("message")] + public string Message { get; set; } + + [JsonPropertyName("type")] + public string Type { get; set; } + + [JsonPropertyName("stack")] + public CrashInfoStackFrame[] Stack { get; set; } + + IEnumerable IManagedException.Stack => Stack; + + [JsonPropertyName("inner")] + public CrashInfoException[] InnerExceptions { get; set; } + + IEnumerable IManagedException.InnerExceptions => InnerExceptions; + } + + public sealed class CrashInfoStackFrame : IStackFrame + { + [JsonPropertyName("ip")] + [JsonConverter(typeof(HexUInt64Converter))] + public ulong InstructionPointer { get; set; } + + [JsonPropertyName("sp")] + [JsonConverter(typeof(HexUInt64Converter))] + public ulong StackPointer { get; set; } + + [JsonPropertyName("module")] + [JsonConverter(typeof(HexUInt64Converter))] + public ulong ModuleBase { get; set; } + + [JsonPropertyName("offset")] + [JsonConverter(typeof(HexUInt32Converter))] + public uint Offset { get; set; } + + [JsonPropertyName("name")] + public string MethodName { get; set; } + } + + public sealed class HexUInt64Converter : JsonConverter + { + public override ulong Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string valueString = reader.GetString(); + if (valueString == null || + !valueString.StartsWith("0x") || + !ulong.TryParse(valueString.Substring(2), System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out ulong value)) + { + throw new JsonException("Invalid hex value"); + } + return value; + } + + public override void Write(Utf8JsonWriter writer, ulong value, JsonSerializerOptions options) => throw new NotImplementedException(); + } + + public sealed class HexUInt32Converter : JsonConverter + { + public override uint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string valueString = reader.GetString(); + if (valueString == null || + !valueString.StartsWith("0x") || + !uint.TryParse(valueString.Substring(2), System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out uint value)) + { + throw new JsonException("Invalid hex value"); + } + return value; + } + + public override void Write(Utf8JsonWriter writer, uint value, JsonSerializerOptions options) => throw new NotImplementedException(); + } + + public static ICrashInfoService Create(uint hresult, ReadOnlySpan triageBuffer) + { + CrashInfoService crashInfoService = null; + try + { + JsonSerializerOptions options = new() { AllowTrailingCommas = true, NumberHandling = JsonNumberHandling.AllowReadingFromString }; + CrashInfoJson crashInfo = JsonSerializer.Deserialize(triageBuffer, options); + if (crashInfo != null) + { + if (Version.TryParse(crashInfo.Version, out Version protocolVersion) && protocolVersion.Major >= 1) + { + crashInfoService = new(crashInfo.Thread, hresult, crashInfo); + } + else + { + Trace.TraceError($"CrashInfoService: invalid or not supported protocol version {crashInfo.Version}"); + } + } + else + { + Trace.TraceError($"CrashInfoService: JsonSerializer.Deserialize failed"); + } + } + catch (Exception ex) when (ex is JsonException or NotSupportedException or DecoderFallbackException or ArgumentException) + { + Trace.TraceError($"CrashInfoService: {ex}"); + } + return crashInfoService; + } + + private CrashInfoService(uint threadId, uint hresult, CrashInfoJson crashInfo) + { + ThreadId = threadId; + HResult = hresult; + CrashReason = (CrashReason)crashInfo.Reason; + RuntimeBaseAddress = crashInfo.RuntimeBaseAddress; + RuntimeVersion = crashInfo.RuntimeVersion; + RuntimeType = (RuntimeType)crashInfo.RuntimeType; + Message = crashInfo.Message; + Exception = crashInfo.Exception; + } + + #region ICrashInfoService + + public uint ThreadId { get; } + + public uint HResult { get; } + + public CrashReason CrashReason { get; } + + public ulong RuntimeBaseAddress { get; } + + public RuntimeType RuntimeType { get; } + + public string RuntimeVersion { get; } + + public string Message { get; } + + public IManagedException Exception { get; } + + #endregion + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs index ba1ecc6d21..826eaecdf1 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs @@ -13,7 +13,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { /// - /// ClrMD runtime service implementation + /// ClrMD runtime service implementation. This MUST never be disposable. /// [ServiceExport(Type = typeof(IDataReader), Scope = ServiceScope.Target)] public class DataReader : IDataReader @@ -48,7 +48,7 @@ public DataReader(ITarget target) int IDataReader.ProcessId => unchecked((int)_target.ProcessId.GetValueOrDefault()); - IEnumerable IDataReader.EnumerateModules() => _modules ??= ModuleService.EnumerateModules().Select((module) => new DataReaderModule(module)).ToList(); + IEnumerable IDataReader.EnumerateModules() => _modules ??= ModuleService.EnumerateModules().Select((module) => new DataReaderModule(this, module)).ToList(); bool IDataReader.GetThreadContext(uint threadId, uint contextFlags, Span context) { @@ -114,11 +114,14 @@ ulong IMemoryReader.ReadPointer(ulong address) private sealed class DataReaderModule : ModuleInfo { + private readonly IDataReader _reader; private readonly IModule _module; + private IResourceNode _resourceRoot; - public DataReaderModule(IModule module) + public DataReaderModule(IDataReader reader, IModule module) : base(module.ImageBase, module.FileName) { + _reader = reader; _module = module; } @@ -202,7 +205,7 @@ public override ulong GetExportSymbolAddress(string symbol) return 0; } - public override IResourceNode ResourceRoot => base.ResourceRoot; + public override IResourceNode ResourceRoot => _resourceRoot ??= ModuleInfo.TryCreateResourceRoot(_reader, _module.ImageBase, _module.ImageSize, _module.IsFileLayout.GetValueOrDefault(false)); } } } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj b/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj index 06c99c30c2..63facc266b 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj @@ -1,7 +1,8 @@ - + netstandard2.0 + true ;1591;1701 Diagnostics debug services true @@ -20,6 +21,7 @@ + diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs index 11b2308e2f..351bd02df7 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs @@ -252,7 +252,7 @@ private string DownloadFile(DebugLibraryInfo libraryInfo) if (key is not null) { // Now download the DAC module from the symbol server - filePath = _symbolService.DownloadFile(key); + filePath = _symbolService.DownloadFile(key.Index, key.FullPathName); } } else diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs index 090270e288..19904d2f10 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs @@ -363,6 +363,7 @@ private sealed class ExtensionLoadContext : AssemblyLoadContext public ExtensionLoadContext(string extensionPath) { + Trace.TraceInformation($"ExtensionLoadContext: {extensionPath}"); _extensionPath = extensionPath; } @@ -387,12 +388,15 @@ protected override Assembly Load(AssemblyName assemblyName) { throw new InvalidOperationException($"Extension assembly reference version not supported for {assemblyName.Name} {assemblyName.Version}"); } + Trace.TraceInformation($"ExtensionLoadContext: loading SOS assembly {assembly.CodeBase}"); return assembly; } else if (_extensionPaths.TryGetValue(assemblyName.Name, out string path)) { + Trace.TraceInformation($"ExtensionLoadContext: loading from extension path {path}"); return LoadFromAssemblyPath(path); } + Trace.TraceInformation($"ExtensionLoadContext: returning null {assemblyName}"); return null; } } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs new file mode 100644 index 0000000000..32d7989a93 --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ****************************************************************************** +// WARNING!!!: This code is also used by createdump in the runtime repo. +// See: https://github.com/dotnet/runtime/blob/main/src/coreclr/debug/createdump/specialdiaginfo.h +// ****************************************************************************** + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.Diagnostics.DebugServices.Implementation +{ + /// + /// This is a special memory region added to ELF and MachO dumps that contains extra diagnostics + /// information like the exception record for a crash for a NativeAOT app. The exception record + /// contains the pointer to the JSON formatted crash info. + /// + public unsafe class SpecialDiagInfo + { + private static readonly byte[] SPECIAL_DIAGINFO_SIGNATURE = Encoding.ASCII.GetBytes("DIAGINFOHEADER"); + private const int SPECIAL_DIAGINFO_VERSION = 1; + + private const ulong SpecialDiagInfoAddressMacOS64 = 0x7fffffff10000000; + private const ulong SpecialDiagInfoAddress64 = 0x00007ffffff10000; + private const ulong SpecialDiagInfoAddress32 = 0x7fff1000; + + [StructLayout(LayoutKind.Sequential)] + private struct SpecialDiagInfoHeader + { + public const int SignatureSize = 16; + public fixed byte Signature[SignatureSize]; + public int Version; + public ulong ExceptionRecordAddress; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct EXCEPTION_RECORD64 + { + public uint ExceptionCode; + public uint ExceptionFlags; + public ulong ExceptionRecord; + public ulong ExceptionAddress; + public uint NumberParameters; + public uint __unusedAlignment; + public fixed ulong ExceptionInformation[15]; //EXCEPTION_MAXIMUM_PARAMETERS + } + + private readonly ITarget _target; + private readonly IMemoryService _memoryService; + + public SpecialDiagInfo(ITarget target, IMemoryService memoryService) + { + _target = target; + _memoryService = memoryService; + } + + private ulong SpecialDiagInfoAddress + { + get + { + if (_target.OperatingSystem == OSPlatform.OSX) + { + if (_memoryService.PointerSize == 8) + { + return SpecialDiagInfoAddressMacOS64; + } + } + else if (_target.OperatingSystem == OSPlatform.Linux) + { + if (_memoryService.PointerSize == 8) + { + return SpecialDiagInfoAddress64; + } + else + { + return SpecialDiagInfoAddress32; + } + } + return 0; + } + } + + public static ICrashInfoService CreateCrashInfoService(IServiceProvider services) + { + EXCEPTION_RECORD64 exceptionRecord; + + SpecialDiagInfo diagInfo = new(services.GetService(), services.GetService()); + exceptionRecord = diagInfo.GetExceptionRecord(); + + if (exceptionRecord.ExceptionCode == CrashInfoService.STATUS_STACK_BUFFER_OVERRUN && + exceptionRecord.NumberParameters >= 4 && + exceptionRecord.ExceptionInformation[0] == CrashInfoService.FAST_FAIL_EXCEPTION_DOTNET_AOT) + { + uint hresult = (uint)exceptionRecord.ExceptionInformation[1]; + ulong triageBufferAddress = exceptionRecord.ExceptionInformation[2]; + int triageBufferSize = (int)exceptionRecord.ExceptionInformation[3]; + + Span buffer = new byte[triageBufferSize]; + if (services.GetService().ReadMemory(triageBufferAddress, buffer, out int bytesRead) && bytesRead == triageBufferSize) + { + return CrashInfoService.Create(hresult, buffer); + } + else + { + Trace.TraceError($"SpecialDiagInfo: ReadMemory({triageBufferAddress}) failed"); + } + } + return null; + } + + internal EXCEPTION_RECORD64 GetExceptionRecord() + { + Span headerBuffer = stackalloc byte[Unsafe.SizeOf()]; + if (_memoryService.ReadMemory(SpecialDiagInfoAddress, headerBuffer, out int bytesRead) && bytesRead == headerBuffer.Length) + { + SpecialDiagInfoHeader header = Unsafe.As(ref MemoryMarshal.GetReference(headerBuffer)); + ReadOnlySpan signature = new(header.Signature, SPECIAL_DIAGINFO_SIGNATURE.Length); + if (signature.SequenceEqual(SPECIAL_DIAGINFO_SIGNATURE)) + { + if (header.Version >= SPECIAL_DIAGINFO_VERSION && header.ExceptionRecordAddress != 0) + { + Span exceptionRecordBuffer = stackalloc byte[Unsafe.SizeOf()]; + if (_memoryService.ReadMemory(header.ExceptionRecordAddress, exceptionRecordBuffer, out bytesRead) && bytesRead == exceptionRecordBuffer.Length) + { + return Unsafe.As(ref MemoryMarshal.GetReference(exceptionRecordBuffer)); + } + } + } + } + return default; + } + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs index f764a4b336..4b5ad2b9ba 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs @@ -400,44 +400,9 @@ public string DownloadSymbolFile(IModule module) /// /// Download a file from the symbol stores/server. /// - /// index of the file to download - /// path to the downloaded file either in the cache or in the temp directory or null if error - public string DownloadFile(SymbolStoreKey key) - { - string downloadFilePath = null; - - if (IsSymbolStoreEnabled) - { - using SymbolStoreFile file = GetSymbolStoreFile(key); - if (file != null) - { - try - { - downloadFilePath = file.FileName; - - // Make sure the stream is at the beginning of the module - file.Stream.Position = 0; - - // If the downloaded doesn't already exists on disk in the cache, then write it to a temporary location. - if (!File.Exists(downloadFilePath)) - { - downloadFilePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + "-" + Path.GetFileName(key.FullPathName)); - using (Stream destinationStream = File.OpenWrite(downloadFilePath)) - { - file.Stream.CopyTo(destinationStream); - } - Trace.WriteLine($"Downloaded symbol file {key.FullPathName}"); - } - } - catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException) - { - Trace.TraceError("{0}: {1}", file.FileName, ex.Message); - downloadFilePath = null; - } - } - } - return downloadFilePath; - } + /// index to lookup on symbol server + /// the full path name of the file + public string DownloadFile(string index, string file) => DownloadFile(new SymbolStoreKey(index, file)); /// /// Returns the metadata for the assembly @@ -842,6 +807,48 @@ private string DownloadMachO(IModule module, KeyTypeFlags flags) return null; } + /// + /// Download a file from the symbol stores/server. + /// + /// index of the file to download + /// path to the downloaded file either in the cache or in the temp directory or null if error + private string DownloadFile(SymbolStoreKey key) + { + string downloadFilePath = null; + + if (IsSymbolStoreEnabled) + { + using SymbolStoreFile file = GetSymbolStoreFile(key); + if (file != null) + { + try + { + downloadFilePath = file.FileName; + + // Make sure the stream is at the beginning of the module + file.Stream.Position = 0; + + // If the downloaded doesn't already exists on disk in the cache, then write it to a temporary location. + if (!File.Exists(downloadFilePath)) + { + downloadFilePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + "-" + Path.GetFileName(key.FullPathName)); + using (Stream destinationStream = File.OpenWrite(downloadFilePath)) + { + file.Stream.CopyTo(destinationStream); + } + Trace.WriteLine($"Downloaded symbol file {key.FullPathName}"); + } + } + catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException) + { + Trace.TraceError("{0}: {1}", file.FileName, ex.Message); + downloadFilePath = null; + } + } + } + return downloadFilePath; + } + private static void ReadPortableDebugTableEntries(PEReader peReader, out DebugDirectoryEntry codeViewEntry, out DebugDirectoryEntry embeddedPdbEntry) { // See spec: https://github.com/dotnet/runtime/blob/main/docs/design/specs/PE-COFF.md diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs index 9150458413..9addd99b10 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs @@ -43,6 +43,8 @@ protected void Finished() Host.OnTargetCreate.Fire(this); } + protected void FlushService() => _serviceContainer?.RemoveService(typeof(T)); + #region ITarget /// diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs index 604205ae69..5266ea006c 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs @@ -66,6 +66,10 @@ public TargetFromDataReader(IDataReader dataReader, OSPlatform targetOS, IHost h return memoryService; }); + // Add optional crash info service (currently only for Native AOT on Linux/MacOS). + _serviceContainerFactory.AddServiceFactory((services) => SpecialDiagInfo.CreateCrashInfoService(services)); + OnFlushEvent.Register(() => FlushService()); + Finished(); } } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs index cc69c944aa..fd231f2270 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs @@ -9,6 +9,8 @@ using System.Reflection; using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; +using System.Text; +using System.Threading; using Microsoft.FileFormats; using Microsoft.FileFormats.ELF; using Microsoft.FileFormats.MachO; @@ -34,7 +36,7 @@ public static class Utilities /// /// This function is neither commutative nor associative; the hash codes must be combined in /// a deterministic order. Do not use this when hashing collections whose contents are - /// nondeterministically ordered! + /// non-deterministically ordered! /// public static int CombineHashCodes(int hashCode0, int hashCode1) { @@ -412,4 +414,46 @@ private static object[] BuildArguments(MethodBase methodBase, IServiceProvider s return arguments; } } + + public class CaptureConsoleService : IConsoleService + { + private readonly StringBuilder _builder = new(); + + public CaptureConsoleService() + { + } + + public void Clear() => _builder.Clear(); + + public override string ToString() => _builder.ToString(); + + #region IConsoleService + + public void Write(string text) + { + _builder.Append(text); + } + + public void WriteWarning(string text) + { + _builder.Append(text); + } + + public void WriteError(string text) + { + _builder.Append(text); + } + + public bool SupportsDml => false; + + public void WriteDml(string text) => throw new NotSupportedException(); + + public void WriteDmlExec(string text, string _) => throw new NotSupportedException(); + + public CancellationToken CancellationToken { get; set; } = CancellationToken.None; + + int IConsoleService.WindowWidth => int.MaxValue; + + #endregion + } } diff --git a/src/Microsoft.Diagnostics.DebugServices/CommandAttributes.cs b/src/Microsoft.Diagnostics.DebugServices/CommandAttributes.cs index 4b1df4b601..bd988f0c20 100644 --- a/src/Microsoft.Diagnostics.DebugServices/CommandAttributes.cs +++ b/src/Microsoft.Diagnostics.DebugServices/CommandAttributes.cs @@ -6,32 +6,6 @@ namespace Microsoft.Diagnostics.DebugServices { - /// - /// Command flags to filter by OS Platforms, control scope and how the command is registered. - /// - [Flags] - public enum CommandFlags : byte - { - Windows = 0x01, - Linux = 0x02, - OSX = 0x04, - - /// - /// Command is supported when there is no target - /// - Global = 0x08, - - /// - /// Command is not added through reflection, but manually with command service API. - /// - Manual = 0x10, - - /// - /// Default. All operating system, but target is required - /// - Default = Windows | Linux | OSX - } - /// /// Marks the class as a Command. /// @@ -53,11 +27,6 @@ public class CommandAttribute : Attribute /// public string[] Aliases = Array.Empty(); - /// - /// Command flags to filter by OS Platforms, control scope and how the command is registered. - /// - public CommandFlags Flags = CommandFlags.Default; - /// /// A string of options that are parsed before the command line options /// @@ -121,10 +90,24 @@ public class CommandInvokeAttribute : Attribute } /// - /// Marks the function to invoke to display alternate help for command. + /// Marks the function to invoke to return the alternate help for command. The function returns + /// a string. The Argument and Option properties of the command are not set. /// [AttributeUsage(AttributeTargets.Method)] public class HelpInvokeAttribute : Attribute { } + + /// + /// Marks the function to invoke to filter a command. The function returns a bool; true if + /// the command is supported. The Argument and Option properties of the command are not set. + /// + [AttributeUsage(AttributeTargets.Method)] + public class FilterInvokeAttribute : Attribute + { + /// + /// Message to display if the filter fails + /// + public string Message; + } } diff --git a/src/Microsoft.Diagnostics.DebugServices/DiagnosticsException.cs b/src/Microsoft.Diagnostics.DebugServices/DiagnosticsException.cs index c6ea751959..fa5d9c85cd 100644 --- a/src/Microsoft.Diagnostics.DebugServices/DiagnosticsException.cs +++ b/src/Microsoft.Diagnostics.DebugServices/DiagnosticsException.cs @@ -27,23 +27,43 @@ public DiagnosticsException(string message, Exception innerException) } /// - /// Thrown if a command is not supported on the configuration, platform or runtime + /// Thrown if a command is not found. /// - public class CommandNotSupportedException : DiagnosticsException + public class CommandNotFoundException : DiagnosticsException { - public CommandNotSupportedException() - : base() + public const string NotFoundMessage = $"Unrecognized SOS command"; + + public CommandNotFoundException(string message) + : base(message) + { + } + + public CommandNotFoundException(string message, Exception innerException) + : base(message, innerException) { } + } + + /// + /// Thrown if a command is not found. + /// + public class CommandParsingException : DiagnosticsException + { + /// + /// The detailed help of the command + /// + public string DetailedHelp { get; } - public CommandNotSupportedException(string message) + public CommandParsingException(string message, string detailedHelp) : base(message) { + DetailedHelp = detailedHelp; } - public CommandNotSupportedException(string message, Exception innerException) + public CommandParsingException(string message, string detailedHelp, Exception innerException) : base(message, innerException) { + DetailedHelp = detailedHelp; } } } diff --git a/src/Microsoft.Diagnostics.DebugServices/ICommandService.cs b/src/Microsoft.Diagnostics.DebugServices/ICommandService.cs index 2a4dbe8109..6455767449 100644 --- a/src/Microsoft.Diagnostics.DebugServices/ICommandService.cs +++ b/src/Microsoft.Diagnostics.DebugServices/ICommandService.cs @@ -12,7 +12,7 @@ namespace Microsoft.Diagnostics.DebugServices public interface ICommandService { /// - /// Enumerates all the command's name and help + /// Enumerates all the command's name, help and aliases /// IEnumerable<(string name, string help, IEnumerable aliases)> Commands { get; } @@ -23,11 +23,19 @@ public interface ICommandService void AddCommands(Type type); /// - /// Displays the help for a command + /// Gets help for all of the commands + /// + /// service provider + /// command invocation and help enumeration + public IEnumerable<(string Invocation, string Help)> GetAllCommandHelp(IServiceProvider services); + + /// + /// Displays the detailed help for a command /// /// name of the command or alias /// service provider - /// true if success, false if command not found - bool DisplayHelp(string commandName, IServiceProvider services); + /// the width to format the help or int.MaxValue + /// help text or null if not found + string GetDetailedHelp(string commandName, IServiceProvider services, int consoleWidth); } } diff --git a/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs new file mode 100644 index 0000000000..1d04cab520 --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DebugServices +{ + /// + /// The kind or reason of crash for the triage JSON + /// + public enum CrashReason + { + Unknown = 0, + UnhandledException = 1, + EnvironmentFailFast = 2, + InternalFailFast = 3, + } + + /// + /// Crash information service. Details about the unhandled exception or crash. + /// + public interface ICrashInfoService + { + /// + /// The kind or reason for the crash + /// + CrashReason CrashReason { get; } + + /// + /// Crashing OS thread id + /// + uint ThreadId { get; } + + /// + /// The HRESULT passed to Watson + /// + uint HResult { get; } + + /// + /// Runtime type or flavor + /// + RuntimeType RuntimeType { get; } + + /// + /// The module base address that contains the runtime + /// + ulong RuntimeBaseAddress { get; } + + /// + /// Runtime version and possible commit id + /// + string RuntimeVersion { get; } + + /// + /// Crash or FailFast message + /// + string Message { get; } + + /// + /// The exception that caused the crash or null + /// + IManagedException Exception { get; } + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices/IManagedException.cs b/src/Microsoft.Diagnostics.DebugServices/IManagedException.cs new file mode 100644 index 0000000000..f73b172e5c --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices/IManagedException.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DebugServices +{ + /// + /// Describes a managed exception + /// + public interface IManagedException + { + /// + /// Exception object address + /// + ulong Address { get; } + + /// + /// The exception type name + /// + string Type { get; } + + /// + /// The exception message + /// + string Message { get; } + + /// + /// Exception.HResult + /// + uint HResult { get; } + + /// + /// Stack trace of exception + /// + IEnumerable Stack { get; } + + /// + /// The inner exception or exceptions in the AggregateException case + /// + IEnumerable InnerExceptions { get; } + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs b/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs index e369639384..5dd9e4ce93 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs @@ -14,7 +14,8 @@ public enum RuntimeType Desktop = 1, NetCore = 2, SingleFile = 3, - Other = 4 + NativeAOT = 4, + Other = 5 } /// diff --git a/src/Microsoft.Diagnostics.DebugServices/IStackFrame.cs b/src/Microsoft.Diagnostics.DebugServices/IStackFrame.cs new file mode 100644 index 0000000000..42f33eb7f6 --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices/IStackFrame.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DebugServices +{ + /// + /// Describes a stack frame + /// + public interface IStackFrame + { + /// + /// The instruction pointer for this frame + /// + ulong InstructionPointer { get; } + + /// + /// The stack pointer of this frame or 0 + /// + ulong StackPointer { get; } + + /// + /// The module base of the IP + /// + public ulong ModuleBase { get; } + + /// + /// Offset from beginning of method + /// + uint Offset { get; } + + /// + /// The exception type name + /// + string MethodName { get; } + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs b/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs index af5dc291c9..13e66f4a63 100644 --- a/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs +++ b/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs @@ -3,7 +3,6 @@ using System.Collections.Immutable; using System.IO; -using Microsoft.SymbolStore; namespace Microsoft.Diagnostics.DebugServices { @@ -96,9 +95,9 @@ public interface ISymbolService /// /// Download a file from the symbol stores/server. /// - /// index of the file to download - /// path to the downloaded file either in the cache or in the temp directory or null if error - string DownloadFile(SymbolStoreKey key); + /// index to lookup on symbol server + /// the full path name of the file + string DownloadFile(string index, string file); /// /// Returns the metadata for the assembly diff --git a/src/Microsoft.Diagnostics.DebugServices/IType.cs b/src/Microsoft.Diagnostics.DebugServices/IType.cs index 28a1355709..6682f059f7 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IType.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IType.cs @@ -20,11 +20,6 @@ public interface IType /// IModule Module { get; } - /// - /// A list of all the fields in the type - /// - List Fields { get; } - /// /// Get a field by name /// diff --git a/src/Microsoft.Diagnostics.DebugServices/Microsoft.Diagnostics.DebugServices.csproj b/src/Microsoft.Diagnostics.DebugServices/Microsoft.Diagnostics.DebugServices.csproj index 33f0fe3e90..e550154f29 100644 --- a/src/Microsoft.Diagnostics.DebugServices/Microsoft.Diagnostics.DebugServices.csproj +++ b/src/Microsoft.Diagnostics.DebugServices/Microsoft.Diagnostics.DebugServices.csproj @@ -13,8 +13,8 @@ true false - + - + diff --git a/src/Microsoft.Diagnostics.DebugServices/ProviderExportAttribute.cs b/src/Microsoft.Diagnostics.DebugServices/ProviderExportAttribute.cs index 8a70aff359..be60004fa1 100644 --- a/src/Microsoft.Diagnostics.DebugServices/ProviderExportAttribute.cs +++ b/src/Microsoft.Diagnostics.DebugServices/ProviderExportAttribute.cs @@ -13,7 +13,7 @@ public class ProviderExportAttribute : Attribute { /// /// The interface or type to register the provider. If null, the provider type registered will be - /// he class itself or the return type of the method. + /// the class itself or the return type of the method. /// public Type Type { get; set; } diff --git a/src/Microsoft.Diagnostics.DebugServices/ServiceContainer.cs b/src/Microsoft.Diagnostics.DebugServices/ServiceContainer.cs index 9b497c53fe..df9aa18d95 100644 --- a/src/Microsoft.Diagnostics.DebugServices/ServiceContainer.cs +++ b/src/Microsoft.Diagnostics.DebugServices/ServiceContainer.cs @@ -29,9 +29,8 @@ public class ServiceContainer : IServiceProvider /// /// search this provider if service isn't found in this instance or null /// service factories to initialize provider or null - public ServiceContainer(IServiceProvider parent, Dictionary factories) + public ServiceContainer(IServiceProvider parent, Dictionary factories = null) { - Debug.Assert(factories != null); _parent = parent; _factories = factories; _instances = new Dictionary(); @@ -88,7 +87,7 @@ public object GetService(Type type) { return service; } - if (_factories.TryGetValue(type, out ServiceFactory factory)) + if (_factories != null && _factories.TryGetValue(type, out ServiceFactory factory)) { service = factory(this); _instances.Add(type, service); diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/AnalyzeOOMCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/AnalyzeOOMCommand.cs index 35e38607a0..ad970e05bb 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/AnalyzeOOMCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/AnalyzeOOMCommand.cs @@ -7,12 +7,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "analyzeoom", Help = "Displays the info of the last OOM that occurred on an allocation request to the GC heap.")] - public class AnalyzeOOMCommand : CommandBase + [Command(Name = "analyzeoom", Aliases = new[] { "AnalyzeOOM" }, Help = "Displays the info of the last OOM that occurred on an allocation request to the GC heap.")] + public class AnalyzeOOMCommand : ClrRuntimeCommandBase { - [ServiceImport] - public ClrRuntime Runtime { get; set; } - public override void Invoke() { bool foundOne = false; diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ClrModulesCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/AssembliesCommand.cs similarity index 79% rename from src/Microsoft.Diagnostics.ExtensionCommands/ClrModulesCommand.cs rename to src/Microsoft.Diagnostics.ExtensionCommands/AssembliesCommand.cs index 6e19a346e4..ebeda4ed1d 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ClrModulesCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/AssembliesCommand.cs @@ -9,28 +9,21 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "clrmodules", Help = "Lists the managed modules in the process.")] - public class ClrModulesCommand : CommandBase + [Command(Name = "assemblies", Aliases = new[] { "clrmodules" }, Help = "Lists the managed assemblies in the process.")] + public class AssembliesCommand : ClrRuntimeCommandBase { - [ServiceImport(Optional = true)] - public ClrRuntime Runtime { get; set; } - [ServiceImport] public IModuleService ModuleService { get; set; } - [Option(Name = "--name", Aliases = new string[] { "-n" }, Help = "RegEx filter on module name (path not included).")] - public string ModuleName { get; set; } + [Option(Name = "--name", Aliases = new string[] { "-n" }, Help = "RegEx filter on assembly name (path not included).")] + public string AssemblyName { get; set; } - [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays detailed information about the modules.")] + [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays detailed information about the assemblies.")] public bool Verbose { get; set; } public override void Invoke() { - if (Runtime == null) - { - throw new DiagnosticsException("No CLR runtime set"); - } - Regex regex = ModuleName is not null ? new Regex(ModuleName, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) : null; + Regex regex = AssemblyName is not null ? new Regex(AssemblyName, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) : null; foreach (ClrModule module in Runtime.EnumerateModules()) { if (regex is null || !string.IsNullOrEmpty(module.Name) && regex.IsMatch(Path.GetFileName(module.Name))) diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs index 76cda04435..1d87e10de3 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs @@ -17,7 +17,7 @@ public class ClrMDHelper private readonly ClrHeap _heap; [ServiceExport(Scope = ServiceScope.Runtime)] - public static ClrMDHelper Create([ServiceImport(Optional = true)] ClrRuntime clrRuntime) + public static ClrMDHelper TryCreate([ServiceImport(Optional = true)] ClrRuntime clrRuntime) { return clrRuntime != null ? new ClrMDHelper(clrRuntime) : null; } @@ -995,7 +995,7 @@ private IEnumerable EnumerateConcurrentQueueCore(ulong address) ClrType slotType = _heap.GetObjectType(slotEntry.ToUInt64()); if (slotType.IsString) { - yield return $"\"{new ClrObject(slotEntry.ToUInt64(), slotType).AsString()}\""; + yield return $"\"{_heap.GetObject(slotEntry.ToUInt64(), slotType).AsString()}\""; } else { @@ -1106,7 +1106,7 @@ private static bool IsSimpleType(string typeName) } } - private static string DumpPropertyValue(ClrObject obj, string propertyName) + private string DumpPropertyValue(ClrObject obj, string propertyName) { const string defaultContent = "?"; @@ -1115,7 +1115,7 @@ private static string DumpPropertyValue(ClrObject obj, string propertyName) { if (fieldType.IsString) { - return $"\"{new ClrObject(field.Address, fieldType).AsString()}\""; + return $"\"{_heap.GetObject(field.Address, fieldType).AsString()}\""; } else if (fieldType.IsArray) { diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelperCommandBase.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelperCommandBase.cs new file mode 100644 index 0000000000..b91d528e68 --- /dev/null +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelperCommandBase.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DebugServices; + +namespace Microsoft.Diagnostics.ExtensionCommands +{ + public abstract class ClrMDHelperCommandBase : CommandBase + { + /// + /// Helper bound to the current ClrRuntime that provides high level services on top of ClrMD. + /// + [ServiceImport(Optional = true)] + public ClrMDHelper Helper { get; set; } + + [FilterInvoke(Message = ClrRuntimeCommandBase.RuntimeNotFoundMessage)] + public static bool FilterInvoke([ServiceImport(Optional = true)] ClrMDHelper helper) => helper != null; + } +} diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ClrRuntimeCommandBase.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ClrRuntimeCommandBase.cs new file mode 100644 index 0000000000..2f139129d6 --- /dev/null +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ClrRuntimeCommandBase.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DebugServices; +using Microsoft.Diagnostics.Runtime; + +namespace Microsoft.Diagnostics.ExtensionCommands +{ + public abstract class ClrRuntimeCommandBase : CommandBase + { + public const string RuntimeNotFoundMessage = "No CLR runtime found. This means that a .NET runtime module or the DAC for the runtime can not be found or downloaded."; + + [ServiceImport(Optional = true)] + public ClrRuntime Runtime { get; set; } + + [FilterInvoke(Message = RuntimeNotFoundMessage)] + public static bool FilterInvoke([ServiceImport(Optional = true)] ClrRuntime runtime) => runtime != null; + } +} diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs new file mode 100644 index 0000000000..67c69b3aed --- /dev/null +++ b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DebugServices; + +namespace Microsoft.Diagnostics.ExtensionCommands +{ + [Command(Name = "crashinfo", Help = "Displays the crash details that created the dump.")] + public class CrashInfoCommand : CommandBase + { + [ServiceImport(Optional = true)] + public ICrashInfoService CrashInfo { get; set; } + + [ServiceImport] + public IModuleService ModuleService { get; set; } + + public override void Invoke() + { + if (CrashInfo == null) + { + throw new DiagnosticsException("No crash info to display"); + } + WriteLine(); + + WriteLine($"CrashReason: {CrashInfo.CrashReason}"); + WriteLine($"ThreadId: {CrashInfo.ThreadId:X4}"); + WriteLine($"HResult: {CrashInfo.HResult:X4}"); + WriteLine($"RuntimeType: {CrashInfo.RuntimeType}"); + WriteLine($"RuntimeBaseAddress: {CrashInfo.RuntimeBaseAddress:X16}"); + WriteLine($"RuntimeVersion: {CrashInfo.RuntimeVersion}"); + WriteLine($"Message: {CrashInfo.Message}"); + + if (CrashInfo.Exception != null) + { + WriteLine("-----------------------------------------------"); + PrintException(CrashInfo.Exception, string.Empty); + } + } + + private void PrintException(IManagedException exception, string indent) + { + WriteLine($"{indent}Exception object: {exception.Address:X16}"); + WriteLine($"{indent}Exception type: {exception.Type}"); + WriteLine($"{indent}HResult: {exception.HResult:X8}"); + WriteLine($"{indent}Message: {exception.Message}"); + + if (exception.Stack != null && exception.Stack.Any()) + { + WriteLine($"{indent}StackTrace:"); + WriteLine($"{indent} IP Function"); + foreach (IStackFrame frame in exception.Stack) + { + string moduleName = ""; + if (frame.ModuleBase != 0) + { + IModule module = ModuleService.GetModuleFromBaseAddress(frame.ModuleBase); + if (module != null) + { + moduleName = Path.GetFileName(module.FileName); + } + } + string methodName = frame.MethodName ?? ""; + WriteLine($"{indent} {frame.InstructionPointer:X16} {moduleName}!{methodName} + 0x{frame.Offset:X}"); + } + } + + if (exception.InnerExceptions != null) + { + WriteLine("InnerExceptions:"); + foreach (IManagedException inner in exception.InnerExceptions) + { + WriteLine("-----------------------------------------------"); + PrintException(inner, " "); + } + } + } + } +} diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs index 0acbca362e..954fd6fa61 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs @@ -14,45 +14,17 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [Command(Name = CommandName, Aliases = new string[] { "DumpAsync" }, Help = "Displays information about async \"stacks\" on the garbage-collected heap.")] - public sealed class DumpAsyncCommand : ExtensionCommandBase + public sealed class DumpAsyncCommand : ClrRuntimeCommandBase { /// The name of the command. private const string CommandName = "dumpasync"; /// Indent width. private const int TabWidth = 2; + /// The command invocation syntax when used in Debugger Markup Language (DML) commands. private const string DmlCommandInvoke = $"!{CommandName}"; - /// The help text to render when asked for help. - private static readonly string s_detailedHelpText = - $"Usage: {CommandName} [--stats] [--coalesce] [--address ] [--methodtable ] [--type ] [--tasks] [--completed] [--fields]" + Environment.NewLine + - Environment.NewLine + - "Displays information about async \"stacks\" on the garbage-collected heap. Stacks" + Environment.NewLine + - "are synthesized by finding all task objects (including async state machine box" + Environment.NewLine + - "objects) on the GC heap and chaining them together based on continuations." + Environment.NewLine + - Environment.NewLine + - "Options:" + Environment.NewLine + - " --stats Summarize all async frames found rather than showing detailed stacks." + Environment.NewLine + - " --coalesce Coalesce stacks and portions of stacks that are the same." + Environment.NewLine + - " --address Only show stacks that include the object with the specified address." + Environment.NewLine + - " --methodtable Only show stacks that include objects with the specified method table." + Environment.NewLine + - " --type Only show stacks that include objects whose type includes the specified name in its name." + Environment.NewLine + - " --tasks Include stacks that contain only non-state machine task objects." + Environment.NewLine + - " --completed Include completed tasks in stacks." + Environment.NewLine + - " --fields Show fields for each async stack frame." + Environment.NewLine + - Environment.NewLine + - "Examples:" + Environment.NewLine + - $"Summarize all async frames associated with a specific method table address: !{CommandName} --stats --methodtable 0x00007ffbcfbe0970" + Environment.NewLine + - $"Show all stacks coalesced by common frames: !{CommandName} --coalesce" + Environment.NewLine + - $"Show each stack that includes \"ReadAsync\": !{CommandName} --type ReadAsync" + Environment.NewLine + - $"Show each stack that includes an object at a specific address, and include fields: !{CommandName} --address 0x000001264adce778 --fields"; - - /// Gets the runtime for the process. Set by the command framework. - [ServiceImport(Optional = true)] - public ClrRuntime? Runtime { get; set; } - - /// Gets whether to only show stacks that include the object with the specified address. [Option(Name = "--address", Aliases = new string[] { "-addr" }, Help = "Only show stacks that include the object with the specified address.")] public string? ObjectAddress @@ -96,27 +68,19 @@ public string? MethodTableAddress public bool CoalesceStacks { get; set; } /// Invokes the command. - public override void ExtensionInvoke() + public override void Invoke() { - ClrRuntime? runtime = Runtime; - if (runtime is null) - { - WriteLineError("Unable to access runtime."); - return; - } - + ClrRuntime runtime = Runtime; ClrHeap heap = runtime.Heap; if (!heap.CanWalkHeap) { - WriteLineError("Unable to examine the heap."); - return; + throw new DiagnosticsException("Unable to examine the heap."); } ClrType? taskType = runtime.BaseClassLibrary.GetTypeByName("System.Threading.Tasks.Task"); if (taskType is null) { - WriteLineError("Unable to find required type."); - return; + throw new DiagnosticsException("Unable to find required type."); } ClrStaticField? taskCompletionSentinelType = taskType.GetStaticFieldByName("s_taskCompletionSentinel"); @@ -1191,7 +1155,18 @@ void Append(string s) } /// Gets detailed help for the command. - protected override string GetDetailedHelp() => s_detailedHelpText; + [HelpInvoke] + public static string GetDetailedHelp() => +@"Displays information about async ""stacks"" on the garbage-collected heap. Stacks +are synthesized by finding all task objects (including async state machine box +objects) on the GC heap and chaining them together based on continuations. + +Examples: + Summarize all async frames associated with a specific method table address: dumpasync --stats --methodtable 0x00007ffbcfbe0970 + Show all stacks coalesced by common frames: dumpasync --coalesce + Show each stack that includes ""ReadAsync"": dumpasync --type ReadAsync + Show each stack that includes an object at a specific address, and include fields: dumpasync --address 0x000001264adce778 --fields +"; /// Represents an async object to be used as a frame in an async "stack". private sealed class AsyncObject diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentDictionaryCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentDictionaryCommand.cs index b723a5d012..06bee8bc6e 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentDictionaryCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentDictionaryCommand.cs @@ -9,40 +9,36 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [Command(Name = "dumpconcurrentdictionary", Aliases = new string[] { "dcd" }, Help = "Displays concurrent dictionary content.")] - public class DumpConcurrentDictionaryCommand : ExtensionCommandBase + public class DumpConcurrentDictionaryCommand : ClrMDHelperCommandBase { [Argument(Help = "The address of a ConcurrentDictionary object.")] public string Address { get; set; } - [ServiceImport] + [ServiceImport(Optional = true)] public ClrRuntime Runtime { get; set; } - public override void ExtensionInvoke() + public override void Invoke() { if (string.IsNullOrEmpty(Address)) { - WriteLine("Missing ConcurrentDictionary address..."); - return; + throw new DiagnosticsException("Missing ConcurrentDictionary address..."); } if (!TryParseAddress(Address, out ulong address)) { - WriteLine("Hexadecimal address expected..."); - return; + throw new DiagnosticsException("Hexadecimal address expected..."); } ClrHeap heap = Runtime.Heap; ClrType type = heap.GetObjectType(address); if (type?.Name is null) { - WriteLine($"{Address:x16} is not referencing an object..."); - return; + throw new DiagnosticsException($"{Address:x16} is not referencing an object..."); } if (!type.Name.StartsWith("System.Collections.Concurrent.ConcurrentDictionary<")) { - WriteLine($"{Address:x16} is not a ConcurrentDictionary but an instance of {type.Name}..."); - return; + throw new DiagnosticsException($"{Address:x16} is not a ConcurrentDictionary but an instance of {type.Name}..."); } WriteLine($"{type.Name}"); @@ -67,9 +63,8 @@ public override void ExtensionInvoke() WriteLine(string.Empty); } - protected override string GetDetailedHelp() - { - return + [HelpInvoke] + public static string GetDetailedHelp() => @"------------------------------------------------------------------------------- DumpConcurrentDictionary Lists all items (key/value pairs) in the given concurrent dictionary. @@ -89,7 +84,6 @@ 2 items - In case of reference types, the command to dump each object is shown (e.g. dumpobj <[item] address>). - For value types, the command to dump each value type is shown (e.g. dumpvc <[item] address>). "; - } private static string Truncate(string str, int nbMaxChars) { diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentQueueCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentQueueCommand.cs index 62c31f97f6..9252a6379e 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentQueueCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentQueueCommand.cs @@ -8,41 +8,36 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [Command(Name = "dumpconcurrentqueue", Aliases = new string[] { "dcq" }, Help = "Displays concurrent queue content.")] - public class DumpConcurrentQueueCommand : ExtensionCommandBase + public class DumpConcurrentQueueCommand : ClrMDHelperCommandBase { [Argument(Help = "The address of a ConcurrentQueue object.")] public string Address { get; set; } - [ServiceImport] + [ServiceImport(Optional = true)] public ClrRuntime Runtime { get; set; } - public override void ExtensionInvoke() + public override void Invoke() { if (string.IsNullOrEmpty(Address)) { - WriteLine("Missing ConcurrentQueue address..."); - return; + throw new DiagnosticsException("Missing ConcurrentQueue address..."); } if (!TryParseAddress(Address, out ulong address)) { - WriteLine("Hexadecimal address expected..."); - return; + throw new DiagnosticsException("Hexadecimal address expected..."); } ClrHeap heap = Runtime.Heap; ClrType type = heap.GetObjectType(address); if (type == null) { - WriteLine($"{Address:x16} is not referencing an object..."); - return; + throw new DiagnosticsException($"{Address:x16} is not referencing an object..."); } - if (!type.Name.StartsWith("System.Collections.Concurrent.ConcurrentQueue<")) { - WriteLine($"{Address:x16} is not a ConcurrentQueue but an instance of {type.Name}..."); - return; + throw new DiagnosticsException($"{Address:x16} is not a ConcurrentQueue but an instance of {type.Name}..."); } WriteLine($"{type.Name}"); @@ -64,39 +59,33 @@ public override void ExtensionInvoke() WriteLine(""); } - protected override string GetDetailedHelp() - { - return DetailedHelpText; - } + [HelpInvoke] + public static string GetDetailedHelp() => +@"------------------------------------------------------------------------------- +DumpConcurrentQueue + +Lists all items in the given concurrent queue. + +For simple types such as numbers, boolean and string, values are shown. +> dcq 00000202a79320e8 +System.Collections.Concurrent.ConcurrentQueue + 1 - 0 + 2 - 1 + 3 - 2 + +In case of reference types, the command to dump each object is shown. +> dcq 00000202a79337f8 +System.Collections.Concurrent.ConcurrentQueue + 1 - dumpobj 0x202a7934e38 + 2 - dumpobj 0x202a7934fd0 + 3 - dumpobj 0x202a7935078 - private readonly string DetailedHelpText = - "-------------------------------------------------------------------------------" + Environment.NewLine + - "DumpConcurrentQueue" + Environment.NewLine + - Environment.NewLine + - "Lists all items in the given concurrent queue." + Environment.NewLine + - Environment.NewLine + - "For simple types such as numbers, boolean and string, values are shown." + Environment.NewLine + - "> dcq 00000202a79320e8" + Environment.NewLine + - "System.Collections.Concurrent.ConcurrentQueue" + Environment.NewLine + - " 1 - 0" + Environment.NewLine + - " 2 - 1" + Environment.NewLine + - " 3 - 2" + Environment.NewLine + - Environment.NewLine + - "In case of reference types, the command to dump each object is shown." + Environment.NewLine + - "> dcq 00000202a79337f8" + Environment.NewLine + - "System.Collections.Concurrent.ConcurrentQueue" + Environment.NewLine + - " 1 - dumpobj 0x202a7934e38" + Environment.NewLine + - " 2 - dumpobj 0x202a7934fd0" + Environment.NewLine + - " 3 - dumpobj 0x202a7935078" + Environment.NewLine + - Environment.NewLine + - "For value types, the command to dump each array segment is shown." + Environment.NewLine + - "The next step is to manually dump each element with dumpvc <[item] address>." + Environment.NewLine + - "> dcq 00000202a7933370" + Environment.NewLine + - "System.Collections.Concurrent.ConcurrentQueue" + Environment.NewLine + - " 1 - dumparray 202a79334e0" + Environment.NewLine + - " 2 - dumparray 202a7938a88" + Environment.NewLine + - Environment.NewLine + - "" - ; +For value types, the command to dump each array segment is shown. +The next step is to manually dump each element with dumpvc <[item] address>. +> dcq 00000202a7933370 +System.Collections.Concurrent.ConcurrentQueue + 1 - dumparray 202a79334e0 + 2 - dumparray 202a7938a88 +"; } } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpExceptionsCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpExceptionsCommand.cs index 4f57cf3897..b2384cfada 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpExceptionsCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpExceptionsCommand.cs @@ -14,11 +14,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [Command(Name = "dumpexceptions", Help = "Displays a list of all managed exceptions.")] - public class DumpExceptionsCommand : CommandBase + public class DumpExceptionsCommand : ClrRuntimeCommandBase { - [ServiceImport] - public ClrRuntime Runtime { get; set; } = null!; - [ServiceImport] public LiveObjectService LiveObjects { get; set; } = null!; diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpGenCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpGenCommand.cs index db020e7926..a3ec51e17b 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpGenCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpGenCommand.cs @@ -8,7 +8,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [Command(Name = "dumpgen", Aliases = new string[] { "dg" }, Help = "Displays heap content for the specified generation.")] - public class DumpGenCommand : ExtensionCommandBase + public class DumpGenCommand : ClrMDHelperCommandBase { private const string statsHeader32bits = " MT Count TotalSize Class Name"; private const string statsHeader64bits = " MT Count TotalSize Class Name"; @@ -24,7 +24,7 @@ public class DumpGenCommand : ExtensionCommandBase [Option(Name = "-mt", Help = "The address pointing on a Method table.")] public string MethodTableAddress { get; set; } - public override void ExtensionInvoke() + public override void Invoke() { GCGeneration generation = ParseGenerationArgument(Generation); if (generation != GCGeneration.NotSet) @@ -43,7 +43,7 @@ public override void ExtensionInvoke() } else { - WriteLine("Hexadecimal address expected for -mt option"); + throw new DiagnosticsException("Hexadecimal address expected for -mt option"); } } WriteLine(string.Empty); @@ -88,12 +88,11 @@ private void WriteStatistics(IEnumerable dumpGenResult) WriteLine($"Total {objectsCount} objects"); } - private GCGeneration ParseGenerationArgument(string generation) + private static GCGeneration ParseGenerationArgument(string generation) { if (string.IsNullOrEmpty(generation)) { - WriteLine("Generation argument is missing"); - return GCGeneration.NotSet; + throw new DiagnosticsException("Generation argument is missing"); } string lowerString = generation.ToLowerInvariant(); GCGeneration result = lowerString switch @@ -106,17 +105,16 @@ private GCGeneration ParseGenerationArgument(string generation) "foh" => GCGeneration.FrozenObjectHeap, _ => GCGeneration.NotSet, }; + if (result == GCGeneration.NotSet) { - WriteLine($"{generation} is not a supported generation (gen0, gen1, gen2, loh, poh, foh)"); + throw new DiagnosticsException($"{generation} is not a supported generation (gen0, gen1, gen2, loh, poh, foh)"); } return result; } - - protected override string GetDetailedHelp() - { - return + [HelpInvoke] + public static string GetDetailedHelp() => @"------------------------------------------------------------------------------- DumpGen This command can be used for 2 use cases: @@ -160,6 +158,5 @@ 00000184aa23e8f0 00007ff9ea6e75b8 40 00000184aa23e918 00007ff9ea6e75b8 40 Total 3 objects "; - } } } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapCommand.cs index 5f2faf6549..38aa12c773 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapCommand.cs @@ -9,15 +9,12 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "dumpheap", Help = "Displays a list of all managed objects.")] - public class DumpHeapCommand : CommandBase + [Command(Name = "dumpheap", Aliases = new[] { "DumpHeap" }, Help = "Displays a list of all managed objects.")] + public class DumpHeapCommand : ClrRuntimeCommandBase { [ServiceImport] public IMemoryService MemoryService { get; set; } - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [ServiceImport] public LiveObjectService LiveObjects { get; set; } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpObjGCRefsHelper.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpObjGCRefsCommand.cs similarity index 96% rename from src/Microsoft.Diagnostics.ExtensionCommands/DumpObjGCRefsHelper.cs rename to src/Microsoft.Diagnostics.ExtensionCommands/DumpObjGCRefsCommand.cs index adae249ab2..cdd53f260d 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpObjGCRefsHelper.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpObjGCRefsCommand.cs @@ -11,13 +11,10 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [Command(Name = "dumpobjgcrefs", Help = "A helper command to implement !dumpobj -refs")] - public sealed class DumpObjGCRefsHelper : CommandBase + public sealed class DumpObjGCRefsCommand : ClrRuntimeCommandBase { private readonly StringBuilderPool _stringBuilderPool = new(260); - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [Argument(Name = "object")] public string ObjectAddress { get; set; } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpRuntimeTypeCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpRuntimeTypeCommand.cs index cfc8579e6f..8c2de445c6 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpRuntimeTypeCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpRuntimeTypeCommand.cs @@ -8,12 +8,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "dumpruntimetypes", Help = "Finds all System.RuntimeType objects in the GC heap and prints the type name and MethodTable they refer too.")] - public sealed class DumpRuntimeTypeCommand : CommandBase + [Command(Name = "dumpruntimetypes", Aliases = new[] { "DumpRuntimeTypes" }, Help = "Finds all System.RuntimeType objects in the GC heap and prints the type name and MethodTable they refer too.")] + public sealed class DumpRuntimeTypeCommand : ClrRuntimeCommandBase { - [ServiceImport] - public ClrRuntime Runtime { get; set; } - public override void Invoke() { Table output = null; diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpStackObjectsCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpStackObjectsCommand.cs index 131450fe8b..2a211c1eb5 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpStackObjectsCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpStackObjectsCommand.cs @@ -15,8 +15,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "dumpstackobjects", Aliases = new string[] { "dso" }, Help = "Displays all managed objects found within the bounds of the current stack.")] - public class DumpStackObjectsCommand : CommandBase + [Command(Name = "dumpstackobjects", Aliases = new string[] { "dso", "DumpStackObjects" }, Help = "Displays all managed objects found within the bounds of the current stack.")] + public class DumpStackObjectsCommand : ClrRuntimeCommandBase { [ServiceImport] public IMemoryService MemoryService { get; set; } @@ -27,13 +27,10 @@ public class DumpStackObjectsCommand : CommandBase [ServiceImport] public IThreadService ThreadService { get; set; } - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [Option(Name = "-verify", Help = "Verify each object and only print ones that are valid objects.")] public bool Verify { get; set; } - [Argument(Name = "StackBounds", Help = "The top and bottom of the stack (in hex).")] + [Argument(Name = "stackbounds", Help = "The top and bottom of the stack (in hex).")] public string[] Bounds { get; set; } public override void Invoke() diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/EEHeapCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/EEHeapCommand.cs index c565148650..59fb9fc3ce 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/EEHeapCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/EEHeapCommand.cs @@ -13,8 +13,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = CommandName, Help = "Displays information about native memory that CLR has allocated.")] - public class EEHeapCommand : CommandBase + [Command(Name = CommandName, Aliases = new[] { "EEHeap" }, Help = "Displays information about native memory that CLR has allocated.")] + public class EEHeapCommand : ClrRuntimeCommandBase { private const string CommandName = "eeheap"; @@ -23,9 +23,6 @@ public class EEHeapCommand : CommandBase // Don't use the word "Total" if we have filtered out entries private string TotalString => HeapWithFilters.HasFilters ? "Partial" : "Total"; - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [ServiceImport] public IMemoryService MemoryService { get; set; } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionCommandBase.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionCommandBase.cs deleted file mode 100644 index 319b45db8d..0000000000 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionCommandBase.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Diagnostics.DebugServices; - -namespace Microsoft.Diagnostics.ExtensionCommands -{ - public abstract class ExtensionCommandBase : CommandBase - { - /// - /// Helper bound to the current ClrRuntime that provides high level services on top of ClrMD. - /// - [ServiceImport(Optional = true)] - public ClrMDHelper Helper { get; set; } - - public override void Invoke() - { - if (Helper == null) - { - throw new DiagnosticsException("No CLR runtime set"); - } - ExtensionInvoke(); - } - - public abstract void ExtensionInvoke(); - - [HelpInvoke] - public void InvokeHelp() - { - WriteLine(GetDetailedHelp()); - } - - protected abstract string GetDetailedHelp(); - } -} diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionMethodHelpers.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionMethodHelpers.cs index 348a23eb84..778aab9693 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionMethodHelpers.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionMethodHelpers.cs @@ -11,6 +11,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands internal static class ExtensionMethodHelpers { public static string ConvertToHumanReadable(this ulong totalBytes) => ConvertToHumanReadable((double)totalBytes); + public static string ConvertToHumanReadable(this long totalBytes) => ConvertToHumanReadable((double)totalBytes); public static string ConvertToHumanReadable(this double totalBytes) diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/FinalizeQueueCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/FinalizeQueueCommand.cs index 9d4efb2ede..f74aee474a 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/FinalizeQueueCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/FinalizeQueueCommand.cs @@ -11,8 +11,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "finalizequeue", Help = "Displays all objects registered for finalization.")] - public class FinalizeQueueCommand : CommandBase + [Command(Name = "finalizequeue", Aliases = new[] { "fq", "FinalizeQueue" }, Help = "Displays all objects registered for finalization.")] + public class FinalizeQueueCommand : ClrRuntimeCommandBase { [Option(Name = "-detail", Help = "Will display extra information on any SyncBlocks that need to be cleaned up, and on any RuntimeCallableWrappers (RCWs) that await cleanup. Both of these data structures are cached and cleaned up by the finalizer thread when it gets a chance to run.")] public bool Detail { get; set; } @@ -38,9 +38,6 @@ public class FinalizeQueueCommand : CommandBase [ServiceImport] public DumpHeapService DumpHeap { get; set; } - [ServiceImport] - public ClrRuntime Runtime { get; set; } - public override void Invoke() { ulong mt = 0; @@ -87,6 +84,7 @@ public override void Invoke() DumpHeap.PrintHeap(objects, displayKind, Stat, printFragmentation: false); } + private IEnumerable EnumerateFinalizableObjects(bool allReady, ulong mt) { IEnumerable result = EnumerateValidFinalizableObjectsWithTypeFilter(mt); diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/FindEphemeralReferencesToLOHCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/FindEphemeralReferencesToLOHCommand.cs index e1715396da..221c06f502 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/FindEphemeralReferencesToLOHCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/FindEphemeralReferencesToLOHCommand.cs @@ -12,14 +12,11 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [Command(Name = "ephtoloh", Help = "Finds ephemeral objects which reference the large object heap.")] - public class FindEphemeralReferencesToLOHCommand : CommandBase + public class FindEphemeralReferencesToLOHCommand : ClrRuntimeCommandBase { // IComparer for binary search private readonly IComparer<(ClrObject, ClrObject)> _firstObjectComparer = Comparer<(ClrObject, ClrObject)>.Create((x, y) => x.Item1.Address.CompareTo(y.Item1.Address)); - [ServiceImport] - public ClrRuntime Runtime { get; set; } - public override void Invoke() { int segments = Runtime.Heap.Segments.Count(seg => seg.Kind is not GCSegmentKind.Frozen or GCSegmentKind.Pinned); diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/FindPointersInCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/FindPointersInCommand.cs index 752bb338c3..755328cf40 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/FindPointersInCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/FindPointersInCommand.cs @@ -62,6 +62,9 @@ public override void Invoke() PrintPointers(!ShowAllObjects, Regions); } + [FilterInvoke(Message = "The memory region service does not exists. This command is only supported under windbg/cdb debuggers.")] + public static bool FilterInvoke([ServiceImport(Optional = true)] ClrRuntime runtime, [ServiceImport(Optional = true)] NativeAddressHelper helper) => runtime != null && helper != null; + private void PrintPointers(bool pinnedOnly, params string[] memTypes) { DescribedRegion[] allRegions = AddressHelper.EnumerateAddressSpace(tagClrMemoryRanges: true, includeReserveMemory: false, tagReserveMemoryHeuristically: false, includeHandleTableIfSlow: false).ToArray(); @@ -496,9 +499,7 @@ public bool IsPinnedObject(ulong address, out ClrObject found) } [HelpInvoke] - public void HelpInvoke() - { - WriteLine( + public static string GetDetailedHelp() => @"------------------------------------------------------------------------------- The findpointersin command will search the regions of memory given by MADDRESS_TYPE_LIST to find all pointers to other memory regions and display them. By default, pointers @@ -508,15 +509,15 @@ random pointer to the GC heap to a non-pinned object is either an old/leftover then this command print out ALL objects that are pointed to instead of collapsing them into one entry. -usage: !findpointersin [--all] MADDRESS_TYPE_LIST +usage: findpointersin [--all] MADDRESS_TYPE_LIST -Note: The MADDRESS_TYPE_LIST must be a memory type as printed by !maddress. +Note: The MADDRESS_TYPE_LIST must be a memory type as printed by maddress. -Example: ""!findpointersin PAGE_READWRITE"" will only search for regions of memory that +Example: ""findpointersin PAGE_READWRITE"" will only search for regions of memory that !maddress marks as ""PAGE_READWRITE"" and not every page of memory that's marked with PAGE_READWRITE protection. -Example: Running the command ""!findpointersin Stack PAGE_READWRITE"" will find all pointers +Example: Running the command ""findpointersin Stack PAGE_READWRITE"" will find all pointers on any ""Stack"" and ""PAGE_READWRITE"" memory segments and summarize those contents into three tables: One table for pointers to the GC heap, one table for pointers where symbols could be resolved, and one table of pointers where we couldn't resolve symbols. @@ -549,7 +550,6 @@ Microsoft.Caching.ClrMD.RawResult[] 2 14 7f063822ae58 ... --------------------------------------------------------- [ TOTALS ] ---------33,360---------72,029--------------- -"); - } +"; } } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/FindReferencesToEphemeralCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/FindReferencesToEphemeralCommand.cs index 6614438d78..571a4b97d2 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/FindReferencesToEphemeralCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/FindReferencesToEphemeralCommand.cs @@ -11,11 +11,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [Command(Name = "ephrefs", Help = "Finds older generation objects which reference objects in the ephemeral segment.")] - public class FindReferencesToEphemeralCommand : CommandBase + public class FindReferencesToEphemeralCommand : ClrRuntimeCommandBase { - [ServiceImport] - public ClrRuntime Runtime { get; set; } - private readonly HashSet _referenced = new(); private ulong _referencedSize; @@ -71,7 +68,6 @@ group item by (item.ObjectGeneration, item.ReferenceGeneration) into g Console.WriteLine($"{objCount:n0} older generation objects referenced {_referenced.Count:n0} younger objects ({_referencedSize:n0} bytes)"); } - private IEnumerable FindObjectsWithEphemeralReferences() { foreach (ClrSegment seg in Runtime.Heap.Segments) diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/GCHeapStatCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/GCHeapStatCommand.cs index 7a3da01995..3552daa85b 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/GCHeapStatCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/GCHeapStatCommand.cs @@ -11,12 +11,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "gcheapstat", DefaultOptions = "GCHeapStat", Help = "Displays various GC heap stats.")] - public class GCHeapStatCommand : CommandBase + [Command(Name = "gcheapstat", Aliases = new[] { "GCHeapStat" }, Help = "Displays various GC heap stats.")] + public class GCHeapStatCommand : ClrRuntimeCommandBase { - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [ServiceImport] public LiveObjectService LiveObjects { get; set; } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/GCRootCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/GCRootCommand.cs index 7260a22eef..1cf459013a 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/GCRootCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/GCRootCommand.cs @@ -11,8 +11,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "gcroot", Help = "Displays info about references (or roots) to an object at the specified address.")] - public class GCRootCommand : CommandBase + [Command(Name = "gcroot", Aliases = new[] { "GCRoot" }, Help = "Displays info about references (or roots) to an object at the specified address.")] + public class GCRootCommand : ClrRuntimeCommandBase { private StringBuilder _lineBuilder = new(64); private ClrRoot _lastRoot; @@ -20,9 +20,6 @@ public class GCRootCommand : CommandBase [ServiceImport] public IMemoryService Memory { get; set; } - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [ServiceImport] public RootCacheService RootCache { get; set; } @@ -68,8 +65,7 @@ public override void Invoke() ClrSegment seg = Runtime.Heap.GetSegmentByAddress(address); if (seg is null) { - Console.WriteLineError($"Address {address:x} is not in the managed heap."); - return; + throw new DiagnosticsException($"Address {address:x} is not in the managed heap."); } Generation objectGen = seg.GetGeneration(address); diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/GCToNativeCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/GCToNativeCommand.cs index e5157d4d31..e3e46d6d9f 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/GCToNativeCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/GCToNativeCommand.cs @@ -24,10 +24,10 @@ public sealed class GCToNativeCommand : CommandBase public bool ShowAll { get; set; } [ServiceImport] - public ClrRuntime Runtime { get; set; } + public NativeAddressHelper AddressHelper { get; set; } [ServiceImport] - public NativeAddressHelper AddressHelper { get; set; } + public ClrRuntime Runtime { get; set; } private int Width { @@ -58,6 +58,9 @@ public override void Invoke() PrintGCPointersToMemory(ShowAll, MemoryTypes); } + [FilterInvoke(Message = "The memory region service does not exists. This command is only supported under windbg/cdb debuggers.")] + public static bool FilterInvoke([ServiceImport(Optional = true)] ClrRuntime runtime, [ServiceImport(Optional = true)] NativeAddressHelper helper) => runtime != null && helper != null; + public void PrintGCPointersToMemory(bool showAll, params string[] memoryTypes) { // Strategy: @@ -556,24 +559,22 @@ private readonly struct MemoryBlockImpl } [HelpInvoke] - public void HelpInvoke() - { - WriteLine( + public static string GetDetailedHelp() => @"------------------------------------------------------------------------------- -!gctonative searches the GC heap for pointers to native memory. This is used +gctonative searches the GC heap for pointers to native memory. This is used to help locate regions of native memory that are referenced (or possibly held alive) by objects on the GC heap. -usage: !gctonative [--all] MADDRESS_TYPE_LIST +usage: gctonative [--all] MADDRESS_TYPE_LIST -Note: The MADDRESS_TYPE_LIST must be a memory type as printed by !maddress. +Note: The MADDRESS_TYPE_LIST must be a memory type as printed by maddress. If --all is set, a full list of every pointer from the GC heap to the specified memory will be displayed instead of just a summary table. Sample Output: - 0:000> !gctonative PAGE_READWRITE + 0:000> gctonative PAGE_READWRITE Walking GC heap to find pointers... Resolving object names... ================================================ PAGE_READWRITE Regions ================================================ @@ -618,7 +619,6 @@ Resolving object names... System.Net.Sockets.SocketAsyncEngine | 1 | 7f059800edd0 Microsoft.Extensions.Caching.Memory.CacheEntry | 1 | 7f05241e0000 System.Runtime.CompilerServices.AsyncTaskMethodBuilder<...>+AsyncStateMachine... | 1 | 7f0500000004 -"); - } +"; } } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/GCWhereCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/GCWhereCommand.cs index 96a61f3783..2ae3193846 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/GCWhereCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/GCWhereCommand.cs @@ -11,12 +11,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "gcwhere", Help = "Displays the location in the GC heap of the specified address.")] - public class GCWhereCommand : CommandBase + [Command(Name = "gcwhere", Aliases = new[] { "GCWhere" }, Help = "Displays the location in the GC heap of the specified address.")] + public class GCWhereCommand : ClrRuntimeCommandBase { - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [ServiceImport] public IMemoryService MemoryService { get; set; } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/ConsoleLoggingCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/ConsoleLoggingCommand.cs index f274ba4839..38f972e83b 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/ConsoleLoggingCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/ConsoleLoggingCommand.cs @@ -5,8 +5,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "logopen", Help = "Enables console file logging.", Flags = CommandFlags.Global)] - [Command(Name = "logclose", DefaultOptions = "--disable", Help = "Disables console file logging.", Flags = CommandFlags.Global)] + [Command(Name = "logopen", Help = "Enables console file logging.")] + [Command(Name = "logclose", DefaultOptions = "--disable", Help = "Disables console file logging.")] public class ConsoleLoggingCommand : CommandBase { [ServiceImport(Optional = true)] diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/HelpCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/HelpCommand.cs index f7c6db56ad..2770c53247 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/HelpCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/HelpCommand.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using System.Linq; using Microsoft.Diagnostics.DebugServices; namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "help", Help = "Displays help for a command.", Flags = CommandFlags.Global)] + [Command(Name = "help", Aliases = new string[] { "soshelp" }, Help = "Displays help for a command.")] public class HelpCommand : CommandBase { [Argument(Help = "Command to find help.")] @@ -20,9 +22,21 @@ public class HelpCommand : CommandBase public override void Invoke() { - if (!CommandService.DisplayHelp(Command, Services)) + if (string.IsNullOrWhiteSpace(Command)) { - throw new NotSupportedException($"Help for {Command} not found"); + IEnumerable<(string Invocation, string Help)> commands = CommandService.GetAllCommandHelp(Services); + int invocationWidth = commands.Max((item) => item.Invocation.Length) + 4; + + Write(string.Concat(commands. + OrderBy(item => item.Invocation, StringComparer.OrdinalIgnoreCase). + Select((item) => $"{FormatInvocation(item.Invocation)}{item.Help}{Environment.NewLine}"))); + + string FormatInvocation(string invocation) => invocation + new string(' ', invocationWidth - invocation.Length); + } + else + { + string helpText = CommandService.GetDetailedHelp(Command, Services, Console.WindowWidth) ?? throw new DiagnosticsException($"Help for {Command} not found"); + Write(helpText); } } } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/LoggingCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/LoggingCommand.cs index 18183f6d7e..de53f34e49 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/LoggingCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/LoggingCommand.cs @@ -5,7 +5,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "logging", Help = "Enables/disables internal diagnostic logging.", Flags = CommandFlags.Global)] + [Command(Name = "logging", Help = "Enables/disables internal diagnostic logging.")] public class LoggingCommand : CommandBase { [ServiceImport(Optional = true)] diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/RegistersCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/RegistersCommand.cs index 3ce4d83fbe..78d2c07b07 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/RegistersCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/RegistersCommand.cs @@ -12,7 +12,7 @@ public class RegistersCommand : CommandBase [ServiceImport] public IThreadService ThreadService { get; set; } - [ServiceImport] + [ServiceImport(Optional = true)] public IThread CurrentThread { get; set; } [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays more details.")] diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs index d94f035d91..fe9ebc59e7 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs @@ -5,16 +5,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command( - Name = "setsymbolserver", - Aliases = new string[] { "SetSymbolServer" }, - Help = "Enables and sets symbol server support for symbols and module download.", - Flags = CommandFlags.Global)] - [Command( - Name = "loadsymbols", - DefaultOptions = "--loadsymbols", - Help = "Loads symbols for all modules.", - Flags = CommandFlags.Global)] + [Command(Name = "setsymbolserver", Aliases = new string[] { "SetSymbolServer" }, Help = "Enables and sets symbol server support for symbols and module download.")] + [Command(Name = "loadsymbols", DefaultOptions = "--loadsymbols", Help = "Loads symbols for all modules.")] public class SetSymbolServerCommand : CommandBase { [ServiceImport] @@ -107,5 +99,92 @@ public override void Invoke() Write(SymbolService.ToString()); } } + + [HelpInvoke] + public static string GetDetailedHelp(IHost host) + { + switch (host.HostType) + { + case HostType.DbgEng: + return s_detailedHelpTextDbgEng; + case HostType.Lldb: + return s_detailedHelpTextLLDB; + case HostType.DotnetDump: + return s_detailedHelpTextDotNetDump; + } + return null; + } + + private const string s_detailedHelpTextDbgEng = + @" +This commands enables symbol server support for portable PDBs for managed assemblies and +.NET Core native modules files (like the DAC) in SOS. If the .sympath is set, the symbol +server supported is automatically set and this command isn't necessary. +"; + + private const string s_detailedHelpTextLLDB = + @" +This commands enables symbol server support in SOS. The portable PDBs for managed assemblies +and .NET Core native symbol and module (like the DAC) files are downloaded. + +To enable downloading symbols from the Microsoft symbol server: + + (lldb) setsymbolserver -ms + +This command may take some time without any output while it attempts to download the symbol files. + +To disable downloading or clear the current SOS symbol settings allowing new symbol paths to be set: + + (lldb) setsymbolserver -disable + +To add a directory to search for symbols: + + (lldb) setsymbolserver -directory /home/mikem/symbols + +This command can be used so the module/symbol file structure does not have to match the machine +file structure that the core dump was generated. + +To clear the default cache run ""rm -r $HOME/.dotnet/symbolcache"" in a command shell. + +If you receive an error like the one below on a core dump, you need to set the .NET Core +runtime with the ""sethostruntime"" command. Type ""soshelp sethostruntime"" for more details. + + (lldb) setsymbolserver -ms + Error: Fail to initialize CoreCLR 80004005 + SetSymbolServer -ms failed + +The ""-loadsymbols"" option and the ""loadsymbol"" command alias attempts to download the native .NET +Core symbol files. It is only useful for live sessions and not core dumps. This command needs to +be run before the lldb ""bt"" (stack trace) or the ""clrstack -f"" (dumps both managed and native +stack frames). + + (lldb) loadsymbols + (lldb) bt +"; + + private const string s_detailedHelpTextDotNetDump = + @" +This commands enables symbol server support in SOS. The portable PDBs for managed assemblies +and .NET Core native module (like the DAC) files are downloaded. + +To enable downloading symbols from the Microsoft symbol server: + + > setsymbolserver -ms + +This command may take some time without any output while it attempts to download the symbol files. + +To disable downloading or clear the current SOS symbol settings allowing new symbol paths to be set: + + > setsymbolserver -disable + +To add a directory to search for symbols: + + > setsymbolserver -directory /home/mikem/symbols + +This command can be used so the module/symbol file structure does not have to match the machine +file structure that the core dump was generated. + +To clear the default cache run ""rm -r $HOME/.dotnet/symbolcache"" in a command shell. +"; } } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ListNearObjCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ListNearObjCommand.cs index 1b4411925f..afeffcbfed 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ListNearObjCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ListNearObjCommand.cs @@ -11,12 +11,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "listnearobj", Help = "Displays the object preceding and succeeding the specified address.")] - public class ListNearObjCommand : CommandBase + [Command(Name = "listnearobj", Aliases = new[] { "lno", "ListNearObj" }, Help = "Displays the object preceding and succeeding the specified address.")] + public class ListNearObjCommand : ClrRuntimeCommandBase { - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [ServiceImport] public IMemoryService MemoryService { get; set; } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/MAddressCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/MAddressCommand.cs index fa221c96ef..9a1774fdc6 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/MAddressCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/MAddressCommand.cs @@ -196,15 +196,16 @@ orderby Size descending } } + [FilterInvoke(Message = "The memory region service does not exists. This command is only supported under windbg/cdb debuggers.")] + public static bool FilterInvoke([ServiceImport(Optional = true)] NativeAddressHelper helper) => helper != null; + [HelpInvoke] - public void HelpInvoke() - { - WriteLine( + public static string GetDetailedHelp() => $@"------------------------------------------------------------------------------- -maddress is a managed version of !address, which attempts to annotate all memory +!maddress is a managed version of !address, which attempts to annotate all memory with information about CLR's heaps. -usage: !sos maddress [{SummaryFlag}] [{ImagesFlag}] [{ForceHandleTableFlag}] [{ReserveFlag} [{ReserveHeuristicFlag}]] +usage: !maddress [{SummaryFlag}] [{ImagesFlag}] [{ForceHandleTableFlag}] [{ReserveFlag} [{ReserveHeuristicFlag}]] Flags: {SummaryFlag} @@ -238,7 +239,6 @@ A separated list of memory region types (as maddress defines them) to print the {BySizeFlag} Order the list of memory blocks by size (descending) when printing the list of all memory blocks instead of by address. -"); - } +"; } } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Microsoft.Diagnostics.ExtensionCommands.csproj b/src/Microsoft.Diagnostics.ExtensionCommands/Microsoft.Diagnostics.ExtensionCommands.csproj index a18bae9228..a7c8e842cc 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Microsoft.Diagnostics.ExtensionCommands.csproj +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Microsoft.Diagnostics.ExtensionCommands.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -16,6 +16,7 @@ + diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/NativeAddressHelper.cs b/src/Microsoft.Diagnostics.ExtensionCommands/NativeAddressHelper.cs index a8e46ed7c3..0ae5cde836 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/NativeAddressHelper.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/NativeAddressHelper.cs @@ -12,22 +12,29 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [ServiceExport(Scope = ServiceScope.Target)] public sealed class NativeAddressHelper : IDisposable { private readonly IDisposable _onFlushEvent; private ((bool, bool, bool, bool) Key, DescribedRegion[] Result) _previous; - public NativeAddressHelper(ITarget target) + [ServiceExport(Scope = ServiceScope.Target)] + public static NativeAddressHelper TryCreate(ITarget target, [ServiceImport(Optional = true)] IMemoryRegionService memoryRegionService) + { + return memoryRegionService != null ? new NativeAddressHelper(target, memoryRegionService) : null; + } + + private NativeAddressHelper(ITarget target, IMemoryRegionService memoryRegionService) { Target = target; + MemoryRegionService = memoryRegionService; _onFlushEvent = target.OnFlushEvent.Register(() => _previous = default); } public void Dispose() => _onFlushEvent.Dispose(); - [ServiceImport] - public ITarget Target { get; set; } + public ITarget Target { get; } + + public IMemoryRegionService MemoryRegionService { get; } [ServiceImport] public IMemoryService MemoryService { get; set; } @@ -41,9 +48,6 @@ public NativeAddressHelper(ITarget target) [ServiceImport] public IModuleService ModuleService { get; set; } - [ServiceImport] - public IMemoryRegionService MemoryRegionService { get; set; } - [ServiceImport] public IConsoleService Console { get; set; } @@ -107,9 +111,9 @@ private DescribedRegion[] EnumerateAddressSpaceWorker(bool tagClrMemoryRanges, b foreach (IRuntime runtime in RuntimeService.EnumerateRuntimes()) { ClrRuntime clrRuntime = runtime.Services.GetService(); - RootCacheService rootCache = runtime.Services.GetService(); if (clrRuntime is not null) { + RootCacheService rootCache = runtime.Services.GetService() ?? throw new DiagnosticsException("NativeAddressHelper: RootCacheService not found"); foreach ((ulong Address, ulong Size, ClrMemoryKind Kind) mem in EnumerateClrMemoryAddresses(clrRuntime, rootCache, includeHandleTableIfSlow)) { // The GCBookkeeping range is a large region of memory that the GC reserved. We'll simply mark every @@ -192,7 +196,7 @@ private DescribedRegion[] EnumerateAddressSpaceWorker(bool tagClrMemoryRanges, b // by the other region kind. if (region.ClrMemoryKind == ClrMemoryKind.None) { - region.ClrMemoryKind = mem.Kind; + AssignKindIfAppropriate(mem, region); } DescribedRegion middleRegion = new(region) @@ -259,8 +263,10 @@ private DescribedRegion[] EnumerateAddressSpaceWorker(bool tagClrMemoryRanges, b region.Start = newRange.End; if (region.ClrMemoryKind == ClrMemoryKind.None) // see note above { - region.ClrMemoryKind = mem.Kind; + AssignKindIfAppropriate(mem, region); } + + rangeList.Add(newRange); } else { @@ -274,7 +280,6 @@ private DescribedRegion[] EnumerateAddressSpaceWorker(bool tagClrMemoryRanges, b } DescribedRegion[] ranges = rangeList.OrderBy(r => r.Start).ToArray(); - if (tagReserveMemoryHeuristically) { foreach (DescribedRegion mem in ranges) @@ -296,6 +301,29 @@ private DescribedRegion[] EnumerateAddressSpaceWorker(bool tagClrMemoryRanges, b return ranges; } + private void AssignKindIfAppropriate((ulong Address, ulong Size, ClrMemoryKind Kind) mem, DescribedRegion region) + { + // On platforms other than Windows, we may not have accurate region begin/end + // locations. For example, with Windows dumps, we will get two distinct regions + // when one call to virtual alloc maps 0x10000-0x20000 and a different call to + // virtual alloc maps 0x20000-0x30000. On Linux, we seem to only get one region + // defined (0x10000-0x30000) even if that came from two different calls to + // the Linux equivalent of VirtualAlloc. Therefore, we only use this heuristic + // to tag memory on Windows to avoid accidently over-attributing memory to CLR. + // + // Finally, we actually get very accurate data about GC structures, so never + // use this heuristic to tag memory as belonging to the GC because we know it + // doesn't. + if (Target.OperatingSystem == OSPlatform.Windows + && mem.Kind != ClrMemoryKind.GCHeap + && mem.Kind != ClrMemoryKind.GCHeapReserve + && mem.Kind != ClrMemoryKind.GCBookkeeping + && mem.Kind != ClrMemoryKind.GCHeapToBeFreed) + { + region.ClrMemoryKind = mem.Kind; + } + } + /// /// Enumerates pointers to various CLR heaps in memory. /// diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/NotReachableInRangeCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/NotReachableInRangeCommand.cs index 59e399e303..27ec0b6fe4 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/NotReachableInRangeCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/NotReachableInRangeCommand.cs @@ -14,7 +14,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands /// Prints objects and statistics for a range of object pointers. /// [Command(Name = "notreachableinrange", Help = "A helper command for !finalizerqueue")] - public class NotReachableInRangeCommand : CommandBase + public class NotReachableInRangeCommand : ClrRuntimeCommandBase { private HashSet _nonFQLiveObjects; @@ -30,9 +30,6 @@ public class NotReachableInRangeCommand : CommandBase [ServiceImport] public IMemoryService Memory { get; set; } - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [Option(Name = "-short")] public bool Short { get; set; } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ObjSizeCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ObjSizeCommand.cs index 0beb0e98f0..c65f381fa4 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ObjSizeCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ObjSizeCommand.cs @@ -8,12 +8,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "objsize", Help = "Lists the sizes of the all the objects found on managed threads.")] - public class ObjSizeCommand : CommandBase + [Command(Name = "objsize", Aliases = new[] { "ObjSize" }, Help = "Lists the sizes of the all the objects found on managed threads.")] + public class ObjSizeCommand : ClrRuntimeCommandBase { - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [ServiceImport] public DumpHeapService DumpHeap { get; set; } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacksCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacksCommand.cs index f61442a902..3320f11d34 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacksCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacksCommand.cs @@ -9,17 +9,17 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [Command(Name = "parallelstacks", Aliases = new string[] { "pstacks" }, Help = "Displays the merged threads stack similarly to the Visual Studio 'Parallel Stacks' panel.")] - public class ParallelStacksCommand : ExtensionCommandBase + public class ParallelStacksCommand : ClrMDHelperCommandBase { - [ServiceImport] + [ServiceImport(Optional = true)] public ClrRuntime Runtime { get; set; } [Option(Name = "--allthreads", Aliases = new string[] { "-a" }, Help = "Displays all threads per group instead of at most 4 by default.")] public bool AllThreads { get; set; } - public override void ExtensionInvoke() + public override void Invoke() { - ParallelStack ps = ParallelStacks.Runtime.ParallelStack.Build(Runtime); + ParallelStack ps = ParallelStack.Build(Runtime); if (ps == null) { return; @@ -41,47 +41,41 @@ public override void ExtensionInvoke() WriteLine($"==> {ps.ThreadIds.Count} threads with {ps.Stacks.Count} roots{Environment.NewLine}"); } - protected override string GetDetailedHelp() - { - return DetailedHelpText; - } + [HelpInvoke] + public static string GetDetailedHelp() => +@"------------------------------------------------------------------------------- +ParallelStacks + +pstacks groups the callstack of all running threads and shows a merged display a la Visual Studio 'Parallel Stacks' panel +By default, only 4 threads ID per frame group are listed. Use --allThreads/-a to list all threads ID. + +> pstacks +________________________________________________ +~~~~ 8f8c + 1 (dynamicClass).IL_STUB_PInvoke(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr) + ... + 1 System.Console.ReadLine() + 1 NetCoreConsoleApp.Program.Main(String[]) + +________________________________________________ + ~~~~ 7034 + 1 System.Threading.Monitor.Wait(Object, Int32, Boolean) + ... + 1 System.Threading.Tasks.Task.Wait() + 1 NetCoreConsoleApp.Program+c.b__1_4(Object) + ~~~~ 9c6c,4020 + 2 System.Threading.Monitor.Wait(Object, Int32, Boolean) + ... + 2 NetCoreConsoleApp.Program+c__DisplayClass1_0.b__7() + 3 System.Threading.Tasks.Task.InnerInvoke() + 4 System.Threading.Tasks.Task+c.cctor>b__278_1(Object) + ... + 4 System.Threading.Tasks.Task.ExecuteEntryUnsafe() + 4 System.Threading.Tasks.Task.ExecuteWorkItem() + 7 System.Threading.ThreadPoolWorkQueue.Dispatch() + 7 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() - private readonly string DetailedHelpText = - "-------------------------------------------------------------------------------" + Environment.NewLine + - "ParallelStacks" + Environment.NewLine + - Environment.NewLine + - "pstacks groups the callstack of all running threads and shows a merged display a la Visual Studio 'Parallel Stacks' panel" + Environment.NewLine + - "By default, only 4 threads ID per frame group are listed. Use --allThreads/-a to list all threads ID." + Environment.NewLine + - Environment.NewLine + - "> pstacks" + Environment.NewLine + - "________________________________________________" + Environment.NewLine + - "~~~~ 8f8c" + Environment.NewLine + - " 1 (dynamicClass).IL_STUB_PInvoke(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)" + Environment.NewLine + - " ..." + Environment.NewLine + - " 1 System.Console.ReadLine()" + Environment.NewLine + - " 1 NetCoreConsoleApp.Program.Main(String[])" + Environment.NewLine + - Environment.NewLine + - "________________________________________________" + Environment.NewLine + - " ~~~~ 7034" + Environment.NewLine + - " 1 System.Threading.Monitor.Wait(Object, Int32, Boolean)" + Environment.NewLine + - " ..." + Environment.NewLine + - " 1 System.Threading.Tasks.Task.Wait()" + Environment.NewLine + - " 1 NetCoreConsoleApp.Program+c.b__1_4(Object)" + Environment.NewLine + - " ~~~~ 9c6c,4020" + Environment.NewLine + - " 2 System.Threading.Monitor.Wait(Object, Int32, Boolean)" + Environment.NewLine + - " ..." + Environment.NewLine + - " 2 NetCoreConsoleApp.Program+c__DisplayClass1_0.b__7()" + Environment.NewLine + - " 3 System.Threading.Tasks.Task.InnerInvoke()" + Environment.NewLine + - " 4 System.Threading.Tasks.Task+c.cctor>b__278_1(Object)" + Environment.NewLine + - " ..." + Environment.NewLine + - " 4 System.Threading.Tasks.Task.ExecuteEntryUnsafe()" + Environment.NewLine + - " 4 System.Threading.Tasks.Task.ExecuteWorkItem()" + Environment.NewLine + - " 7 System.Threading.ThreadPoolWorkQueue.Dispatch()" + Environment.NewLine + - " 7 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()" + Environment.NewLine + - Environment.NewLine + - "==> 8 threads with 2 roots" + Environment.NewLine + - Environment.NewLine + - "" - ; +==> 8 threads with 2 roots +"; } } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/PathToCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/PathToCommand.cs index e63940d3be..ac0975b859 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/PathToCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/PathToCommand.cs @@ -7,12 +7,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name ="pathto", Help = "Displays the GC path from to .")] - public class PathToCommand : CommandBase + [Command(Name ="pathto", Aliases = new[] { "PathTo" }, Help = "Displays the GC path from to .")] + public class PathToCommand : ClrRuntimeCommandBase { - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [ServiceImport] public RootCacheService RootCache { get; set; } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/SimulateGCHeapCorruption.cs b/src/Microsoft.Diagnostics.ExtensionCommands/SimulateGCHeapCorruption.cs index 2105875b9d..e018cf218b 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/SimulateGCHeapCorruption.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/SimulateGCHeapCorruption.cs @@ -13,16 +13,13 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [DebugCommand(Name=nameof(SimulateGCHeapCorruption), Help = "Writes values to the GC heap in strategic places to simulate heap corruption.")] - public class SimulateGCHeapCorruption : CommandBase + public class SimulateGCHeapCorruption : ClrRuntimeCommandBase { private static readonly List _changes = new(); [ServiceImport] public IMemoryService MemoryService { get; set; } - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [Argument] public string Command { get; set; } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/SizeStatsCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/SizeStatsCommand.cs index b8f67a7a5f..a1b59b07a0 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/SizeStatsCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/SizeStatsCommand.cs @@ -10,11 +10,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [Command(Name = "sizestats", Help = "Size statistics for the GC heap.")] - public sealed class SizeStatsCommand : CommandBase + public sealed class SizeStatsCommand : ClrRuntimeCommandBase { - [ServiceImport] - public ClrRuntime Runtime { get; set; } - public override void Invoke() { SizeStats(Generation.Generation0, isFree: false); diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/TaskStateCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/TaskStateCommand.cs index 63a7e4536c..716fec0903 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/TaskStateCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/TaskStateCommand.cs @@ -7,7 +7,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [Command(Name = "taskstate", Aliases = new string[] { "tks" }, Help = "Displays a Task state in a human readable format.")] - public class TaskStateCommand : ExtensionCommandBase + public class TaskStateCommand : ClrMDHelperCommandBase { [Argument(Help = "The Task instance address.")] public string Address { get; set; } @@ -15,7 +15,7 @@ public class TaskStateCommand : ExtensionCommandBase [Option(Name = "--value", Aliases = new string[] { "-v" }, Help = " is the value of a Task m_stateFlags field.")] public ulong? Value { get; set; } - public override void ExtensionInvoke() + public override void Invoke() { if (string.IsNullOrEmpty(Address) && !Value.HasValue) { @@ -57,23 +57,19 @@ public override void ExtensionInvoke() } - protected override string GetDetailedHelp() - { - return DetailedHelpText; - } + [HelpInvoke] + public static string GetDetailedHelp() => +@"------------------------------------------------------------------------------- +TaskState [hexa address] [-v ] + +TaskState translates a Task m_stateFlags field value into human readable format. +It supports hexadecimal address corresponding to a task instance or -v . + +> tks 000001db16cf98f0 +Running - private readonly string DetailedHelpText = - "-------------------------------------------------------------------------------" + Environment.NewLine + - "TaskState [hexa address] [-v ]" + Environment.NewLine + - Environment.NewLine + - "TaskState translates a Task m_stateFlags field value into human readable format." + Environment.NewLine + - "It supports hexadecimal address corresponding to a task instance or -v ." + Environment.NewLine + - Environment.NewLine + - "> tks 000001db16cf98f0" + Environment.NewLine + - "Running" + Environment.NewLine + - Environment.NewLine + - "> tks -v 73728" + Environment.NewLine + - "WaitingToRun" - ; +> tks -v 73728 +WaitingToRun +"; } } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolCommand.cs index 85f7c4d973..f78549ffca 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolCommand.cs @@ -11,12 +11,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "threadpool", Help = "Displays info about the runtime thread pool.")] - public sealed class ThreadPoolCommand : CommandBase + [Command(Name = "threadpool", Aliases = new[] { "ThreadPool" }, Help = "Displays info about the runtime thread pool.")] + public sealed class ThreadPoolCommand : ClrRuntimeCommandBase { - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [Option(Name = "-ti", Help = "Print the hill climbing log.", Aliases = new string[] { "-hc" })] public bool PrintHillClimbingLog { get; set; } @@ -33,15 +30,25 @@ public override void Invoke() } else { - Table output = new(Console, Text.WithWidth(17), Text); - output.WriteRow("CPU utilization:", $"{threadPool.CpuUtilization}%"); - output.WriteRow("Workers Total:", threadPool.ActiveWorkerThreads + threadPool.IdleWorkerThreads + threadPool.RetiredWorkerThreads); - output.WriteRow("Workers Running:", threadPool.ActiveWorkerThreads); - output.WriteRow("Workers Idle:", threadPool.IdleWorkerThreads); - output.WriteRow("Worker Min Limit:", threadPool.MinThreads); - output.WriteRow("Worker Max Limit:", threadPool.MaxThreads); + string threadpoolType = threadPool.UsingWindowsThreadPool ? "Windows" : "Portable"; + Console.WriteLine($"Using the {threadpoolType} thread pool."); Console.WriteLine(); + Table output = new(Console, Text.WithWidth(17), Text); + if (threadPool.UsingWindowsThreadPool) + { + output.WriteRow("Thread count:", threadPool.WindowsThreadPoolThreadCount); + } + else + { + output.WriteRow("CPU utilization:", $"{threadPool.CpuUtilization}%"); + output.WriteRow("Workers Total:", threadPool.ActiveWorkerThreads + threadPool.IdleWorkerThreads + threadPool.RetiredWorkerThreads); + output.WriteRow("Workers Running:", threadPool.ActiveWorkerThreads); + output.WriteRow("Workers Idle:", threadPool.IdleWorkerThreads); + output.WriteRow("Worker Min Limit:", threadPool.MinThreads); + output.WriteRow("Worker Max Limit:", threadPool.MaxThreads); + } + Console.WriteLine(); ClrType threadPoolType = Runtime.BaseClassLibrary.GetTypeByName("System.Threading.ThreadPool"); ClrStaticField usePortableIOField = threadPoolType?.GetStaticFieldByName("UsePortableThreadPoolForIO"); @@ -68,10 +75,14 @@ public override void Invoke() } } - // We will assume that if UsePortableThreadPoolForIO field is deleted from ThreadPool then we are always - // using C# version. - bool usingPortableCompletionPorts = threadPool.Portable && (usePortableIOField is null || usePortableIOField.Read(usePortableIOField.Type.Module.AppDomain)); - if (!usingPortableCompletionPorts) + /* + The IO completion thread pool exists in .NET 7 and earlier + It is the only option in .NET 6 and below. The UsePortableThreadPoolForIO field doesn't exist. + In .NET 7, the UsePortableThreadPoolForIO field exists and is true by default, in which case the IO completion thread pool is not used, but that can be changed through config + In .NET 8, the UsePortableThreadPoolForIO field doesn't exist and the IO completion thread pool doesn't exist. However, in .NET 8, GetThreadpoolData returns E_NOTIMPL. + */ + bool usingIOCompletionThreadPool = threadPool.HasLegacyData && (usePortableIOField is null || !usePortableIOField.Read(usePortableIOField.Type.Module.AppDomain)); + if (usingIOCompletionThreadPool) { output.Columns[0] = output.Columns[0].WithWidth(19); output.WriteRow("Completion Total:", threadPool.TotalCompletionPorts); @@ -87,28 +98,36 @@ public override void Invoke() if (PrintHillClimbingLog) { - HillClimbingLogEntry[] hcl = threadPool.EnumerateHillClimbingLog().ToArray(); - if (hcl.Length > 0) + if (threadPool.UsingWindowsThreadPool) { - output = new(Console, Text.WithWidth(10).WithAlignment(Align.Right), Column.ForEnum(), Integer, Integer, Text.WithAlignment(Align.Right)); + Console.WriteLine("Hill Climbing Log is not supported by the Windows thread pool."); + Console.WriteLine(); + } + else + { + HillClimbingLogEntry[] hcl = threadPool.EnumerateHillClimbingLog().ToArray(); + if (hcl.Length > 0) + { + output = new(Console, Text.WithWidth(10).WithAlignment(Align.Right), Column.ForEnum(), Integer, Integer, Text.WithAlignment(Align.Right)); - Console.WriteLine("Hill Climbing Log:"); - output.WriteHeader("Time", "Transition", "#New Threads", "#Samples", "Throughput"); + Console.WriteLine("Hill Climbing Log:"); + output.WriteHeader("Time", "Transition", "#New Threads", "#Samples", "Throughput"); - int end = hcl.Last().TickCount; - foreach (HillClimbingLogEntry entry in hcl) - { - Console.CancellationToken.ThrowIfCancellationRequested(); - output.WriteRow($"{(entry.TickCount - end)/1000.0:0.00}", entry.StateOrTransition, entry.NewThreadCount, entry.SampleCount, $"{entry.Throughput:0.00}"); - } + int end = hcl.Last().TickCount; + foreach (HillClimbingLogEntry entry in hcl) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + output.WriteRow($"{(entry.TickCount - end) / 1000.0:0.00}", entry.StateOrTransition, entry.NewThreadCount, entry.SampleCount, $"{entry.Throughput:0.00}"); + } - Console.WriteLine(); + Console.WriteLine(); + } } } } // We can print managed work items even if we failed to request the ThreadPool. - if (PrintWorkItems && (threadPool is null || threadPool.Portable)) + if (PrintWorkItems && (threadPool is null || threadPool.UsingPortableThreadPool || threadPool.UsingWindowsThreadPool)) { DumpWorkItems(); } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolQueueCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolQueueCommand.cs index bee1b08f44..85b5d9b4ed 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolQueueCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolQueueCommand.cs @@ -9,9 +9,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [Command(Name = "threadpoolqueue", Aliases = new string[] { "tpq" }, Help = "Displays queued ThreadPool work items.")] - public class ThreadPoolQueueCommand : ExtensionCommandBase + public class ThreadPoolQueueCommand : ClrMDHelperCommandBase { - public override void ExtensionInvoke() + public override void Invoke() { Dictionary workItems = new(); int workItemCount = 0; @@ -102,42 +102,36 @@ private static void UpdateStats(Dictionary stats, string statN wi.Count++; } - protected override string GetDetailedHelp() - { - return DetailedHelpText; - } + [HelpInvoke] + public static string GetDetailedHelp() => +@"------------------------------------------------------------------------------- +ThreadPoolQueue + +ThreadPoolQueue lists the enqueued work items in the Clr Thread Pool followed by a summary of the different tasks/work items. +The global queue is first iterated before local per-thread queues. +The name of the method to be called (on which instance if any) is also provided when available. + +> tpq + +global work item queue________________________________ +0x000002AC3C1DDBB0 Work | (ASP.global_asax)System.Web.HttpApplication.ResumeStepsWaitCallback + ... +0x000002AABEC19148 Task | System.Threading.Tasks.Dataflow.Internal.TargetCore.b__3 + +local per thread work items_____________________________________ +0x000002AE79D80A00 System.Threading.Tasks.ContinuationTaskFromTask + ... +0x000002AB7CBB84A0 Task | System.Net.Http.HttpClientHandler.StartRequest + + 7 Task System.Threading.Tasks.Dataflow.Internal.TargetCore.b__3 + ... + 84 Task System.Net.Http.HttpClientHandler.StartRequest +---- +6039 - private readonly string DetailedHelpText = - "-------------------------------------------------------------------------------" + Environment.NewLine + - "ThreadPoolQueue" + Environment.NewLine + - Environment.NewLine + - "ThreadPoolQueue lists the enqueued work items in the Clr Thread Pool followed by a summary of the different tasks/work items." + Environment.NewLine + - "The global queue is first iterated before local per-thread queues." + Environment.NewLine + - "The name of the method to be called (on which instance if any) is also provided when available." + Environment.NewLine + - Environment.NewLine + - "> tpq" + Environment.NewLine + - Environment.NewLine + - "global work item queue________________________________" + Environment.NewLine + - "0x000002AC3C1DDBB0 Work | (ASP.global_asax)System.Web.HttpApplication.ResumeStepsWaitCallback" + Environment.NewLine + - " ..." + Environment.NewLine + - "0x000002AABEC19148 Task | System.Threading.Tasks.Dataflow.Internal.TargetCore.b__3" + Environment.NewLine + - "" + Environment.NewLine + - "local per thread work items_____________________________________" + Environment.NewLine + - "0x000002AE79D80A00 System.Threading.Tasks.ContinuationTaskFromTask" + Environment.NewLine + - " ..." + Environment.NewLine + - "0x000002AB7CBB84A0 Task | System.Net.Http.HttpClientHandler.StartRequest" + Environment.NewLine + - "" + Environment.NewLine + - " 7 Task System.Threading.Tasks.Dataflow.Internal.TargetCore.b__3" + Environment.NewLine + - " ..." + Environment.NewLine + - " 84 Task System.Net.Http.HttpClientHandler.StartRequest" + Environment.NewLine + - "----" + Environment.NewLine + - "6039" + Environment.NewLine + - "" + Environment.NewLine + - "1810 Work (ASP.global_asax) System.Web.HttpApplication.ResumeStepsWaitCallback" + Environment.NewLine + - "----" + Environment.NewLine + - "1810" + Environment.NewLine + - "" - ; +1810 Work (ASP.global_asax) System.Web.HttpApplication.ResumeStepsWaitCallback +---- +1810"; private sealed class WorkInfo { diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/TimersCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/TimersCommand.cs index 6a040242a6..2c576c7193 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/TimersCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/TimersCommand.cs @@ -9,9 +9,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands { [Command(Name = "timerinfo", Aliases = new string[] { "ti" }, Help = "Displays information about running timers.")] - public class TimersCommand : ExtensionCommandBase + public class TimersCommand : ClrMDHelperCommandBase { - public override void ExtensionInvoke() + public override void Invoke() { try { @@ -104,33 +104,28 @@ private static string GetTimerString(TimerInfo timer) } - protected override string GetDetailedHelp() - { - return DetailedHelpText; - } + [HelpInvoke] + public static string GetDetailedHelp() => +@"------------------------------------------------------------------------------- +TimerInfo - private readonly string DetailedHelpText = - "-------------------------------------------------------------------------------" + Environment.NewLine + - "TimerInfo" + Environment.NewLine + - Environment.NewLine + - "TimerInfo lists all the running timers followed by a summary of the different items." + Environment.NewLine + - "The name of the method to be called (on which instance if any) is also provided when available." + Environment.NewLine + - Environment.NewLine + - "> ti" + Environment.NewLine + - "0x000001E29BD45848 @ 964 ms every 1000 ms | 0x000001E29BD0C828 (Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.Heartbeat) ->" + Environment.NewLine + - "0x000001E19BD0F868 @ 1 ms every ------ ms | 0x000001E19BD0F800 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1" + Environment.NewLine + - "0x000001E09BD09B40 @ 1 ms every ------ ms | 0x000001E09BD09AD8 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1" + Environment.NewLine + - "0x000001E29BD58C68 @ 1 ms every ------ ms | 0x000001E29BD58C00 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1" + Environment.NewLine + - "0x000001E29BCB1398 @ 5000 ms every ------ ms | 0x0000000000000000 () -> System.Diagnostics.Tracing.EventPipeController.PollForTracingCommand" + Environment.NewLine + - Environment.NewLine + - " 5 timers" + Environment.NewLine + - "-----------------------------------------------" + Environment.NewLine + - " 1 | @ 964 ms every 1000 ms | (Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.Heartbeat) ->" + Environment.NewLine + - " 1 | @ 5000 ms every ------ ms | () -> System.Diagnostics.Tracing.EventPipeController.PollForTracingCommand" + Environment.NewLine + - " 3 | @ 1 ms every ------ ms | (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1" - ; - } +TimerInfo lists all the running timers followed by a summary of the different items. +The name of the method to be called (on which instance if any) is also provided when available. +> ti +0x000001E29BD45848 @ 964 ms every 1000 ms | 0x000001E29BD0C828 (Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.Heartbeat) -> +0x000001E19BD0F868 @ 1 ms every ------ ms | 0x000001E19BD0F800 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1 +0x000001E09BD09B40 @ 1 ms every ------ ms | 0x000001E09BD09AD8 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1 +0x000001E29BD58C68 @ 1 ms every ------ ms | 0x000001E29BD58C00 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1 +0x000001E29BCB1398 @ 5000 ms every ------ ms | 0x0000000000000000 () -> System.Diagnostics.Tracing.EventPipeController.PollForTracingCommand + + 5 timers +----------------------------------------------- + 1 | @ 964 ms every 1000 ms | (Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.Heartbeat) -> + 1 | @ 5000 ms every ------ ms | () -> System.Diagnostics.Tracing.EventPipeController.PollForTracingCommand + 3 | @ 1 ms every ------ ms | (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.b__260_1 +"; + } internal sealed class TimerStat { diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/TraverseHeapCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/TraverseHeapCommand.cs index 40d2820502..57315180bb 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/TraverseHeapCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/TraverseHeapCommand.cs @@ -12,12 +12,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "traverseheap", Help = "Writes out heap information to a file in a format understood by the CLR Profiler.")] - public class TraverseHeapCommand : CommandBase + [Command(Name = "traverseheap", Aliases = new[] { "TraverseHeap" }, Help = "Writes out heap information to a file in a format understood by the CLR Profiler.")] + public class TraverseHeapCommand : ClrRuntimeCommandBase { - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [ServiceImport] public RootCacheService RootCache { get; set; } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs index e5229cbf32..c7b7b19578 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs @@ -9,16 +9,13 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = CommandName, Help = "Searches the managed heap for memory corruption..")] - public class VerifyHeapCommand : CommandBase + [Command(Name = CommandName, Aliases = new[] { "VerifyHeap" }, Help = "Searches the managed heap for memory corruption..")] + public class VerifyHeapCommand : ClrRuntimeCommandBase { private const string CommandName = "verifyheap"; private int _totalObjects; - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [ServiceImport] public IMemoryService MemoryService { get; set; } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/VerifyObjectCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyObjectCommand.cs index f952aca361..a9779d337b 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/VerifyObjectCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyObjectCommand.cs @@ -11,16 +11,13 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "verifyobj", Help = "Checks the given object for signs of corruption.")] - public sealed class VerifyObjectCommand : CommandBase + [Command(Name = "verifyobj", Aliases = new[] { "VerifyObj" }, Help = "Checks the given object for signs of corruption.")] + public sealed class VerifyObjectCommand : ClrRuntimeCommandBase { - [ServiceImport] - public ClrRuntime Runtime { get; set; } - [ServiceImport] public IMemoryService Memory { get; set; } - [Argument(Name = "ObjectAddress", Help = "The object to verify.")] + [Argument(Name = "objectaddress", Help = "The object to verify.")] public string ObjectAddress { get; set; } public override void Invoke() diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/LoggingSourceConfiguration.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/LoggingSourceConfiguration.cs index 82dae4f329..d65acdee05 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/LoggingSourceConfiguration.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/LoggingSourceConfiguration.cs @@ -6,6 +6,7 @@ using System.Diagnostics.Tracing; using System.Text; using Microsoft.Diagnostics.NETCore.Client; +using Microsoft.Diagnostics.Tracing.Parsers; using Microsoft.Extensions.Logging; namespace Microsoft.Diagnostics.Monitoring.EventPipe @@ -15,21 +16,24 @@ public class LoggingSourceConfiguration : MonitoringSourceConfiguration private readonly string _filterSpecs; private readonly long _keywords; private readonly EventLevel _level; + private readonly bool _collectScopes; /// /// Creates a new logging source configuration. /// - public LoggingSourceConfiguration(LogLevel level, LogMessageType messageType, IDictionary filterSpecs, bool useAppFilters) + public LoggingSourceConfiguration(LogLevel level, LogMessageType messageType, IDictionary filterSpecs, bool useAppFilters, + bool collectScopes) { RequestRundown = false; _filterSpecs = ToFilterSpecsString(filterSpecs, useAppFilters); _keywords = (long)ToKeywords(messageType); _level = ToEventLevel(level); + _collectScopes = collectScopes; } public override IList GetProviders() { - return new List() + List providers = new() { new EventPipeProvider( MicrosoftExtensionsLoggingProviderName, @@ -41,6 +45,17 @@ public override IList GetProviders() } ) }; + + if (_collectScopes) + { + // Activity correlation + providers.Add(new EventPipeProvider( + TplEventSource, + EventLevel.Informational, + (long)TplEtwProviderTraceEventParser.Keywords.TasksFlowActivityIds)); + } + + return providers; } private static string ToFilterSpecsString(IDictionary filterSpecs, bool useAppFilters) diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs index 99e3030081..850949b087 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs @@ -7,12 +7,9 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe { - /// - /// TODO This is currently a duplication of the src\Tools\dotnet-counters\CounterPayload.cs stack. The two will be unified in a separate change. - /// - internal class CounterPayload : ICounterPayload + internal abstract class CounterPayload : ICounterPayload { - public CounterPayload(DateTime timestamp, + protected CounterPayload(DateTime timestamp, string provider, string name, string displayName, @@ -20,7 +17,9 @@ public CounterPayload(DateTime timestamp, double value, CounterType counterType, float interval, - string metadata) + int series, + string metadata, + EventType eventType) { Timestamp = timestamp; Name = name; @@ -30,30 +29,11 @@ public CounterPayload(DateTime timestamp, CounterType = counterType; Provider = provider; Interval = interval; + Series = series; Metadata = metadata; - EventType = EventType.Gauge; - } - - // Copied from dotnet-counters - public CounterPayload(string providerName, - string name, - string metadata, - double value, - DateTime timestamp, - string type, - EventType eventType) - { - Provider = providerName; - Name = name; - Metadata = metadata; - Value = value; - Timestamp = timestamp; - CounterType = (CounterType)Enum.Parse(typeof(CounterType), type); EventType = eventType; } - public string Namespace { get; } - public string Name { get; } public string DisplayName { get; protected set; } @@ -73,12 +53,50 @@ public CounterPayload(string providerName, public string Metadata { get; } public EventType EventType { get; set; } + + public virtual bool IsMeter => false; + + public int Series { get; } + } + + internal sealed class EventCounterPayload : CounterPayload + { + public EventCounterPayload(DateTime timestamp, + string provider, + string name, + string displayName, + string unit, + double value, + CounterType counterType, + float interval, + int series, + string metadata) : base(timestamp, provider, name, displayName, unit, value, counterType, interval, series, metadata, EventType.Gauge) + { + } + } + + internal abstract class MeterPayload : CounterPayload + { + protected MeterPayload(DateTime timestamp, + string provider, + string name, + string displayName, + string unit, + double value, + CounterType counterType, + string metadata, + EventType eventType) + : base(timestamp, provider, name, displayName, unit, value, counterType, 0.0f, 0, metadata, eventType) + { + } + + public override bool IsMeter => true; } - internal class GaugePayload : CounterPayload + internal sealed class GaugePayload : MeterPayload { public GaugePayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, DateTime timestamp) : - base(providerName, name, metadata, value, timestamp, "Metric", EventType.Gauge) + base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Metric, metadata, EventType.Gauge) { // In case these properties are not provided, set them to appropriate values. string counterName = string.IsNullOrEmpty(displayName) ? name : displayName; @@ -86,10 +104,10 @@ public GaugePayload(string providerName, string name, string displayName, string } } - internal class UpDownCounterPayload : CounterPayload + internal class UpDownCounterPayload : MeterPayload { public UpDownCounterPayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, DateTime timestamp) : - base(providerName, name, metadata, value, timestamp, "Metric", EventType.UpDownCounter) + base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Metric, metadata, EventType.UpDownCounter) { // In case these properties are not provided, set them to appropriate values. string counterName = string.IsNullOrEmpty(displayName) ? name : displayName; @@ -97,19 +115,26 @@ public UpDownCounterPayload(string providerName, string name, string displayName } } - internal class CounterEndedPayload : CounterPayload + internal sealed class BeginInstrumentReportingPayload : MeterPayload { - public CounterEndedPayload(string providerName, string name, DateTime timestamp) - : base(providerName, name, null, 0.0, timestamp, "Metric", EventType.CounterEnded) + public BeginInstrumentReportingPayload(string providerName, string name, DateTime timestamp) + : base(timestamp, providerName, name, string.Empty, string.Empty, 0.0, CounterType.Metric, null, EventType.BeginInstrumentReporting) { + } + } + internal sealed class CounterEndedPayload : MeterPayload + { + public CounterEndedPayload(string providerName, string name, DateTime timestamp) + : base(timestamp, providerName, name, string.Empty, string.Empty, 0.0, CounterType.Metric, null, EventType.CounterEnded) + { } } - internal class RatePayload : CounterPayload + internal sealed class RatePayload : MeterPayload { public RatePayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, double intervalSecs, DateTime timestamp) : - base(providerName, name, metadata, value, timestamp, "Rate", EventType.Rate) + base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Rate, metadata, EventType.Rate) { // In case these properties are not provided, set them to appropriate values. string counterName = string.IsNullOrEmpty(displayName) ? name : displayName; @@ -119,35 +144,46 @@ public RatePayload(string providerName, string name, string displayName, string } } - internal class PercentilePayload : CounterPayload + internal record struct Quantile(double Percentage, double Value); + + internal sealed class PercentilePayload : MeterPayload { - public PercentilePayload(string providerName, string name, string displayName, string displayUnits, string metadata, IEnumerable quantiles, DateTime timestamp) : - base(providerName, name, metadata, 0.0, timestamp, "Metric", EventType.Histogram) + public PercentilePayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, DateTime timestamp) : + base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Metric, metadata, EventType.Histogram) { // In case these properties are not provided, set them to appropriate values. string counterName = string.IsNullOrEmpty(displayName) ? name : displayName; DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName; - Quantiles = quantiles.ToArray(); } - - public Quantile[] Quantiles { get; } } - internal record struct Quantile(double Percentage, double Value); - - internal class ErrorPayload : CounterPayload + // Dotnet-monitor and dotnet-counters previously had incompatible PercentilePayload implementations before being unified - + // Dotnet-monitor created a single payload that contained all of the quantiles to keep them together, whereas + // dotnet-counters created a separate payload for each quantile (multiple payloads per TraceEvent). + // AggregatePercentilePayload allows dotnet-monitor to construct a PercentilePayload for individual quantiles + // like dotnet-counters, while still keeping the quantiles together as a unit. + internal sealed class AggregatePercentilePayload : MeterPayload { - public ErrorPayload(string errorMessage) : this(errorMessage, DateTime.UtcNow) + public AggregatePercentilePayload(string providerName, string name, string displayName, string displayUnits, string metadata, IEnumerable quantiles, DateTime timestamp) : + base(timestamp, providerName, name, displayName, displayUnits, 0.0, CounterType.Metric, metadata, EventType.Histogram) { + //string counterName = string.IsNullOrEmpty(displayName) ? name : displayName; + //DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName; + Quantiles = quantiles.ToArray(); } - public ErrorPayload(string errorMessage, DateTime timestamp) : - base(string.Empty, string.Empty, null, 0.0, timestamp, "Metric", EventType.Error) + public Quantile[] Quantiles { get; } + } + + internal sealed class ErrorPayload : MeterPayload + { + public ErrorPayload(string errorMessage, DateTime timestamp, EventType eventType) + : base(timestamp, string.Empty, string.Empty, string.Empty, string.Empty, 0.0, CounterType.Metric, null, eventType) { ErrorMessage = errorMessage; } - public string ErrorMessage { get; private set; } + public string ErrorMessage { get; } } internal enum EventType : int @@ -156,7 +192,52 @@ internal enum EventType : int Gauge, Histogram, UpDownCounter, - Error, - CounterEnded + BeginInstrumentReporting, + CounterEnded, + HistogramLimitError, + TimeSeriesLimitError, + ErrorTargetProcess, + MultipleSessionsNotSupportedError, + MultipleSessionsConfiguredIncorrectlyError, + ObservableInstrumentCallbackError + } + + internal static class EventTypeExtensions + { + public static bool IsValuePublishedEvent(this EventType eventType) + { + return eventType is EventType.Gauge + || eventType is EventType.Rate + || eventType is EventType.Histogram + || eventType is EventType.UpDownCounter; + } + + public static bool IsError(this EventType eventType) + { + return eventType is EventType.HistogramLimitError + || eventType is EventType.TimeSeriesLimitError + || eventType is EventType.ErrorTargetProcess + || eventType is EventType.MultipleSessionsNotSupportedError + || eventType is EventType.MultipleSessionsConfiguredIncorrectlyError + || eventType is EventType.ObservableInstrumentCallbackError; + } + + public static bool IsNonFatalError(this EventType eventType) + { + return IsError(eventType) + && !IsTracingError(eventType) + && !IsSessionStartupError(eventType); + } + + public static bool IsTracingError(this EventType eventType) + { + return eventType is EventType.ErrorTargetProcess; + } + + public static bool IsSessionStartupError(this EventType eventType) + { + return eventType is EventType.MultipleSessionsNotSupportedError + || eventType is EventType.MultipleSessionsConfiguredIncorrectlyError; + } } } diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayloadExtensions.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayloadExtensions.cs deleted file mode 100644 index 12d4b862e4..0000000000 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayloadExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.Diagnostics.Monitoring.EventPipe -{ - internal static class CounterPayloadExtensions - { - public static string GetDisplay(this ICounterPayload counterPayload) - { - if (counterPayload.CounterType == CounterType.Rate) - { - return $"{counterPayload.DisplayName} ({counterPayload.Unit} / {counterPayload.Interval} sec)"; - } - if (!string.IsNullOrEmpty(counterPayload.Unit)) - { - return $"{counterPayload.DisplayName} ({counterPayload.Unit})"; - } - return $"{counterPayload.DisplayName}"; - } - } -} diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICounterPayload.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICounterPayload.cs index 4e3ce06f74..4cc5fdb043 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICounterPayload.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICounterPayload.cs @@ -41,6 +41,10 @@ internal interface ICounterPayload /// string Metadata { get; } - EventType EventType { get; set; } + EventType EventType { get; } + + bool IsMeter { get; } + + int Series { get; } } } diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/MetricsPipeline.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/MetricsPipeline.cs index 1c3a29ab8a..9f8f34c150 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/MetricsPipeline.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/MetricsPipeline.cs @@ -17,6 +17,7 @@ internal class MetricsPipeline : EventSourcePipeline private readonly CounterFilter _filter; private string _clientId; private string _sessionId; + private CounterConfiguration _counterConfiguration; public MetricsPipeline(DiagnosticsClient client, MetricsPipelineSettings settings, @@ -51,6 +52,14 @@ protected override MonitoringSourceConfiguration CreateConfiguration() _clientId = config.ClientId; _sessionId = config.SessionId; + _counterConfiguration = new CounterConfiguration(_filter) + { + SessionId = _sessionId, + ClientId = _clientId, + MaxHistograms = Settings.MaxHistograms, + MaxTimeseries = Settings.MaxTimeSeries + }; + return config; } @@ -61,7 +70,7 @@ protected override async Task OnEventSourceAvailable(EventPipeEventSource eventS eventSource.Dynamic.All += traceEvent => { try { - if (traceEvent.TryGetCounterPayload(_filter, _sessionId, _clientId, out ICounterPayload counterPayload)) + if (traceEvent.TryGetCounterPayload(_counterConfiguration, out ICounterPayload counterPayload)) { ExecuteCounterLoggerAction((metricLogger) => metricLogger.Log(counterPayload)); } diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs index 942a7ebddf..06472cf3f3 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs @@ -9,11 +9,29 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe { + internal class CounterConfiguration + { + public CounterConfiguration(CounterFilter filter) + { + CounterFilter = filter ?? throw new ArgumentNullException(nameof(filter)); + } + + public CounterFilter CounterFilter { get; } + + public string SessionId { get; set; } + + public string ClientId { get; set; } + + public int MaxHistograms { get; set; } + + public int MaxTimeseries { get; set; } + } + internal static class TraceEventExtensions { private static HashSet inactiveSharedSessions = new(StringComparer.OrdinalIgnoreCase); - public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilter filter, string sessionId, string clientId, out ICounterPayload payload) + public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterConfiguration counterConfiguration, out ICounterPayload payload) { payload = null; @@ -27,12 +45,12 @@ public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilte string counterName = payloadFields["Name"].ToString(); string metadata = payloadFields["Metadata"].ToString(); - + int seriesValue = GetInterval(series); //CONSIDER //Concurrent counter sessions do not each get a separate interval. Instead the payload //for _all_ the counters changes the Series to be the lowest specified interval, on a per provider basis. //Currently the CounterFilter will remove any data whose Series doesn't match the requested interval. - if (!filter.IsIncluded(traceEvent.ProviderName, counterName, GetInterval(series))) + if (!counterConfiguration.CounterFilter.IsIncluded(traceEvent.ProviderName, counterName, seriesValue)) { return false; } @@ -61,7 +79,7 @@ public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilte // Note that dimensional data such as pod and namespace are automatically added in prometheus and azure monitor scenarios. // We no longer added it here. - payload = new CounterPayload( + payload = new EventCounterPayload( traceEvent.TimeStamp, traceEvent.ProviderName, counterName, displayName, @@ -69,57 +87,57 @@ public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilte value, counterType, intervalSec, + seriesValue / 1000, metadata); return true; } - if (clientId != null && !inactiveSharedSessions.Contains(clientId) && MonitoringSourceConfiguration.SystemDiagnosticsMetricsProviderName.Equals(traceEvent.ProviderName)) + if (counterConfiguration.ClientId != null && !inactiveSharedSessions.Contains(counterConfiguration.ClientId) && MonitoringSourceConfiguration.SystemDiagnosticsMetricsProviderName.Equals(traceEvent.ProviderName)) { if (traceEvent.EventName == "BeginInstrumentReporting") { - // Do we want to log something for this? - //HandleBeginInstrumentReporting(traceEvent); + HandleBeginInstrumentReporting(traceEvent, counterConfiguration, out payload); } if (traceEvent.EventName == "HistogramValuePublished") { - HandleHistogram(traceEvent, filter, sessionId, out payload); + HandleHistogram(traceEvent, counterConfiguration, out payload); } else if (traceEvent.EventName == "GaugeValuePublished") { - HandleGauge(traceEvent, filter, sessionId, out payload); + HandleGauge(traceEvent, counterConfiguration, out payload); } else if (traceEvent.EventName == "CounterRateValuePublished") { - HandleCounterRate(traceEvent, filter, sessionId, out payload); + HandleCounterRate(traceEvent, counterConfiguration, out payload); } else if (traceEvent.EventName == "UpDownCounterRateValuePublished") { - HandleUpDownCounterValue(traceEvent, filter, sessionId, out payload); + HandleUpDownCounterValue(traceEvent, counterConfiguration, out payload); } else if (traceEvent.EventName == "TimeSeriesLimitReached") { - HandleTimeSeriesLimitReached(traceEvent, sessionId, out payload); + HandleTimeSeriesLimitReached(traceEvent, counterConfiguration, out payload); } else if (traceEvent.EventName == "HistogramLimitReached") { - HandleHistogramLimitReached(traceEvent, sessionId, out payload); + HandleHistogramLimitReached(traceEvent, counterConfiguration, out payload); } else if (traceEvent.EventName == "Error") { - HandleError(traceEvent, sessionId, out payload); + HandleError(traceEvent, counterConfiguration, out payload); } else if (traceEvent.EventName == "ObservableInstrumentCallbackError") { - HandleObservableInstrumentCallbackError(traceEvent, sessionId, out payload); + HandleObservableInstrumentCallbackError(traceEvent, counterConfiguration, out payload); } else if (traceEvent.EventName == "MultipleSessionsNotSupportedError") { - HandleMultipleSessionsNotSupportedError(traceEvent, sessionId, out payload); + HandleMultipleSessionsNotSupportedError(traceEvent, counterConfiguration, out payload); } else if (traceEvent.EventName == "MultipleSessionsConfiguredIncorrectlyError") { - HandleMultipleSessionsConfiguredIncorrectlyError(traceEvent, clientId, out payload); + HandleMultipleSessionsConfiguredIncorrectlyError(traceEvent, counterConfiguration.ClientId, out payload); } return payload != null; @@ -128,13 +146,13 @@ public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilte return false; } - private static void HandleGauge(TraceEvent obj, CounterFilter filter, string sessionId, out ICounterPayload payload) + private static void HandleGauge(TraceEvent obj, CounterConfiguration counterConfiguration, out ICounterPayload payload) { payload = null; string payloadSessionId = (string)obj.PayloadValue(0); - if (payloadSessionId != sessionId) + if (payloadSessionId != counterConfiguration.SessionId) { return; } @@ -146,7 +164,7 @@ private static void HandleGauge(TraceEvent obj, CounterFilter filter, string ses string tags = (string)obj.PayloadValue(5); string lastValueText = (string)obj.PayloadValue(6); - if (!filter.IsIncluded(meterName, instrumentName)) + if (!counterConfiguration.CounterFilter.IsIncluded(meterName, instrumentName)) { return; } @@ -164,13 +182,36 @@ private static void HandleGauge(TraceEvent obj, CounterFilter filter, string ses } } - private static void HandleCounterRate(TraceEvent traceEvent, CounterFilter filter, string sessionId, out ICounterPayload payload) + private static void HandleBeginInstrumentReporting(TraceEvent traceEvent, CounterConfiguration counterConfiguration, out ICounterPayload payload) + { + payload = null; + + string payloadSessionId = (string)traceEvent.PayloadValue(0); + if (payloadSessionId != counterConfiguration.SessionId) + { + return; + } + + string meterName = (string)traceEvent.PayloadValue(1); + //string meterVersion = (string)obj.PayloadValue(2); + string instrumentName = (string)traceEvent.PayloadValue(3); + + if (!counterConfiguration.CounterFilter.IsIncluded(meterName, instrumentName)) + { + return; + } + + + payload = new BeginInstrumentReportingPayload(meterName, instrumentName, traceEvent.TimeStamp); + } + + private static void HandleCounterRate(TraceEvent traceEvent, CounterConfiguration counterConfiguration, out ICounterPayload payload) { payload = null; string payloadSessionId = (string)traceEvent.PayloadValue(0); - if (payloadSessionId != sessionId) + if (payloadSessionId != counterConfiguration.SessionId) { return; } @@ -182,14 +223,14 @@ private static void HandleCounterRate(TraceEvent traceEvent, CounterFilter filte string tags = (string)traceEvent.PayloadValue(5); string rateText = (string)traceEvent.PayloadValue(6); - if (!filter.IsIncluded(meterName, instrumentName)) + if (!counterConfiguration.CounterFilter.IsIncluded(meterName, instrumentName)) { return; } - if (double.TryParse(rateText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double rate)) + if (double.TryParse(rateText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) { - payload = new RatePayload(meterName, instrumentName, null, unit, tags, rate, filter.DefaultIntervalSeconds, traceEvent.TimeStamp); + payload = new RatePayload(meterName, instrumentName, null, unit, tags, value, counterConfiguration.CounterFilter.DefaultIntervalSeconds, traceEvent.TimeStamp); } else { @@ -200,13 +241,13 @@ private static void HandleCounterRate(TraceEvent traceEvent, CounterFilter filte } } - private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterFilter filter, string sessionId, out ICounterPayload payload) + private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterConfiguration configuration, out ICounterPayload payload) { payload = null; string payloadSessionId = (string)traceEvent.PayloadValue(0); - if (payloadSessionId != sessionId || traceEvent.Version < 1) // Version 1 added the value field. + if (payloadSessionId != configuration.SessionId || traceEvent.Version < 1) // Version 1 added the value field. { return; } @@ -219,7 +260,7 @@ private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterFilte //string rateText = (string)traceEvent.PayloadValue(6); // Not currently using rate for UpDownCounters. string valueText = (string)traceEvent.PayloadValue(7); - if (!filter.IsIncluded(meterName, instrumentName)) + if (!configuration.CounterFilter.IsIncluded(meterName, instrumentName)) { return; } @@ -239,12 +280,13 @@ private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterFilte } } - private static void HandleHistogram(TraceEvent obj, CounterFilter filter, string sessionId, out ICounterPayload payload) + private static void HandleHistogram(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload) { payload = null; string payloadSessionId = (string)obj.PayloadValue(0); - if (payloadSessionId != sessionId) + + if (payloadSessionId != configuration.SessionId) { return; } @@ -256,72 +298,71 @@ private static void HandleHistogram(TraceEvent obj, CounterFilter filter, string string tags = (string)obj.PayloadValue(5); string quantilesText = (string)obj.PayloadValue(6); - if (!filter.IsIncluded(meterName, instrumentName)) + if (!configuration.CounterFilter.IsIncluded(meterName, instrumentName)) { return; } //Note quantiles can be empty. IList quantiles = ParseQuantiles(quantilesText); - payload = new PercentilePayload(meterName, instrumentName, null, unit, tags, quantiles, obj.TimeStamp); - } - + payload = new AggregatePercentilePayload(meterName, instrumentName, null, unit, tags, quantiles, obj.TimeStamp); + } - private static void HandleHistogramLimitReached(TraceEvent obj, string sessionId, out ICounterPayload payload) + private static void HandleHistogramLimitReached(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload) { payload = null; string payloadSessionId = (string)obj.PayloadValue(0); - if (payloadSessionId != sessionId) + if (payloadSessionId != configuration.SessionId) { return; } - string errorMessage = $"Warning: Histogram tracking limit reached. Not all data is being shown. The limit can be changed with maxHistograms but will use more memory in the target process."; + string errorMessage = $"Warning: Histogram tracking limit ({configuration.MaxHistograms}) reached. Not all data is being shown. The limit can be changed but will use more memory in the target process."; - payload = new ErrorPayload(errorMessage); + payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.HistogramLimitError); } - private static void HandleTimeSeriesLimitReached(TraceEvent obj, string sessionId, out ICounterPayload payload) + private static void HandleTimeSeriesLimitReached(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload) { payload = null; string payloadSessionId = (string)obj.PayloadValue(0); - if (payloadSessionId != sessionId) + if (payloadSessionId != configuration.SessionId) { return; } - string errorMessage = "Warning: Time series tracking limit reached. Not all data is being shown. The limit can be changed with maxTimeSeries but will use more memory in the target process."; + string errorMessage = $"Warning: Time series tracking limit ({configuration.MaxTimeseries}) reached. Not all data is being shown. The limit can be changed but will use more memory in the target process."; - payload = new ErrorPayload(errorMessage, obj.TimeStamp); + payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.TimeSeriesLimitError); } - private static void HandleError(TraceEvent obj, string sessionId, out ICounterPayload payload) + private static void HandleError(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload) { payload = null; string payloadSessionId = (string)obj.PayloadValue(0); string error = (string)obj.PayloadValue(1); - if (payloadSessionId != sessionId) + if (configuration.SessionId != payloadSessionId) { return; } string errorMessage = "Error reported from target process:" + Environment.NewLine + error; - payload = new ErrorPayload(errorMessage, obj.TimeStamp); + payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.ErrorTargetProcess); } - private static void HandleMultipleSessionsNotSupportedError(TraceEvent obj, string sessionId, out ICounterPayload payload) + private static void HandleMultipleSessionsNotSupportedError(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload) { payload = null; string payloadSessionId = (string)obj.PayloadValue(0); - if (payloadSessionId == sessionId) + if (payloadSessionId == configuration.SessionId) { // If our session is the one that is running then the error is not for us, // it is for some other session that came later @@ -332,7 +373,7 @@ private static void HandleMultipleSessionsNotSupportedError(TraceEvent obj, stri string errorMessage = "Error: Another metrics collection session is already in progress for the target process." + Environment.NewLine + "Concurrent sessions are not supported."; - payload = new ErrorPayload(errorMessage, obj.TimeStamp); + payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.MultipleSessionsNotSupportedError); } } @@ -383,20 +424,20 @@ private static void HandleMultipleSessionsConfiguredIncorrectlyError(TraceEvent if (TryCreateSharedSessionConfiguredIncorrectlyMessage(obj, clientId, out string message)) { - payload = new ErrorPayload(message.ToString(), obj.TimeStamp); + payload = new ErrorPayload(message.ToString(), obj.TimeStamp, EventType.MultipleSessionsConfiguredIncorrectlyError); inactiveSharedSessions.Add(clientId); } } - private static void HandleObservableInstrumentCallbackError(TraceEvent obj, string sessionId, out ICounterPayload payload) + private static void HandleObservableInstrumentCallbackError(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload) { payload = null; string payloadSessionId = (string)obj.PayloadValue(0); string error = (string)obj.PayloadValue(1); - if (payloadSessionId != sessionId) + if (payloadSessionId != configuration.SessionId) { return; } @@ -404,10 +445,10 @@ private static void HandleObservableInstrumentCallbackError(TraceEvent obj, stri string errorMessage = "Exception thrown from an observable instrument callback in the target process:" + Environment.NewLine + error; - payload = new ErrorPayload(errorMessage, obj.TimeStamp); + payload = new ErrorPayload(errorMessage, obj.TimeStamp, EventType.ObservableInstrumentCallbackError); } - private static IList ParseQuantiles(string quantileList) + private static List ParseQuantiles(string quantileList) { string[] quantileParts = quantileList.Split(';', StringSplitOptions.RemoveEmptyEntries); List quantiles = new(); @@ -418,11 +459,11 @@ private static IList ParseQuantiles(string quantileList) { continue; } - if (!double.TryParse(keyValParts[0], out double key)) + if (!double.TryParse(keyValParts[0], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double key)) { continue; } - if (!double.TryParse(keyValParts[1], out double val)) + if (!double.TryParse(keyValParts[1], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double val)) { continue; } diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/DiagnosticsEventPipeProcessor.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/DiagnosticsEventPipeProcessor.cs index 7ebc6409b3..cff749c126 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/DiagnosticsEventPipeProcessor.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/DiagnosticsEventPipeProcessor.cs @@ -39,7 +39,7 @@ Func, CancellationToken, Task> onEventSourceAva _sessionStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } - public async Task Process(DiagnosticsClient client, TimeSpan duration, CancellationToken token) + public async Task Process(DiagnosticsClient client, TimeSpan duration, bool resumeRuntime, CancellationToken token) { //No need to guard against reentrancy here, since the calling pipeline does this already. IDisposable registration = token.Register(() => TryCancelCompletionSources(token)); @@ -53,7 +53,7 @@ public async Task Process(DiagnosticsClient client, TimeSpan duration, Cancellat // Allows the event handling routines to stop processing before the duration expires. Func stopFunc = () => Task.Run(() => { streamProvider.StopProcessing(); }); - Stream sessionStream = await streamProvider.ProcessEvents(client, duration, token).ConfigureAwait(false); + Stream sessionStream = await streamProvider.ProcessEvents(client, duration, resumeRuntime, token).ConfigureAwait(false); if (!_sessionStarted.TrySetResult(true)) { diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventPipeStreamProvider.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventPipeStreamProvider.cs index 45cf97be43..23bb51b28f 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventPipeStreamProvider.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventPipeStreamProvider.cs @@ -21,7 +21,7 @@ public EventPipeStreamProvider(MonitoringSourceConfiguration sourceConfig) _stopProcessingSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } - public async Task ProcessEvents(DiagnosticsClient client, TimeSpan duration, CancellationToken cancellationToken) + public async Task ProcessEvents(DiagnosticsClient client, TimeSpan duration, bool resumeRuntime, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -29,6 +29,17 @@ public async Task ProcessEvents(DiagnosticsClient client, TimeSpan durat try { session = await client.StartEventPipeSessionAsync(_sourceConfig.GetProviders(), _sourceConfig.RequestRundown, _sourceConfig.BufferSizeInMB, cancellationToken).ConfigureAwait(false); + if (resumeRuntime) + { + try + { + await client.ResumeRuntimeAsync(cancellationToken).ConfigureAwait(false); + } + catch (UnsupportedCommandException) + { + // Noop if the command is unknown since the target process is most likely a 3.1 app. + } + } } catch (EndOfStreamException e) { diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventSourcePipeline.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventSourcePipeline.cs index 7da5c54ea6..1b36f04e52 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventSourcePipeline.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventSourcePipeline.cs @@ -35,7 +35,7 @@ protected override Task OnRun(CancellationToken token) { try { - return _processor.Value.Process(Client, Settings.Duration, token); + return _processor.Value.Process(Client, Settings.Duration, Settings.ResumeRuntime, token); } catch (InvalidOperationException e) { diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventSourcePipelineSettings.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventSourcePipelineSettings.cs index ece20a2368..1c35878eb3 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventSourcePipelineSettings.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/EventSourcePipelineSettings.cs @@ -8,5 +8,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe internal class EventSourcePipelineSettings { public TimeSpan Duration { get; set; } + + public bool ResumeRuntime { get; set; } } } diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Logs/EventLogsPipeline.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Logs/EventLogsPipeline.cs index eec6facb48..0b6f0824f6 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Logs/EventLogsPipeline.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Logs/EventLogsPipeline.cs @@ -30,7 +30,8 @@ protected override MonitoringSourceConfiguration CreateConfiguration() Settings.LogLevel, LogMessageType.FormattedMessage | LogMessageType.JsonMessage, Settings.FilterSpecs, - Settings.UseAppFilters); + Settings.UseAppFilters, + Settings.CollectScopes); } catch (NotSupportedException ex) { @@ -42,45 +43,55 @@ protected override Task OnEventSourceAvailable(EventPipeEventSource eventSource, { string lastFormattedMessage = string.Empty; - Dictionary logActivities = new(); - Stack stack = new(); + // + // We enable TplEventSource's TasksFlowActivityIds as part of our configuration to enable activity correlation. + // This means that each time an event start occurs the current ActivityId will branch creating a new one with a RelatedActivityId equal to where it branched from. + // Combining this with the fact that scopes are handled as ActivityJson/{Start,Stop} means the ActivityId will branch each time a scope starts. + // When a log message occurs, it'll have an ActivityId equal to the latest applicable scope. + // + // By maintaining a tree with the branching data, we can construct the full scope for a log message: + // - Each time the ActivityId branches, create a node in the tree with it's parent being the node corresponding to the RelatedActivityId. + // - Each node has corresponding scope data. + // - When a log message occurs, grab the node with the corresponding ActivityId and backtrack to the root of the tree. Each node visited is included as part of the log's scope. + // + // NOTE: There are edge cases with concurrent traces, this is described in greater detail above our backtracking code. + // + Dictionary activityIdToScope = new(); + + if (Settings.CollectScopes) + { + eventSource.Dynamic.AddCallbackForProviderEvent(LoggingSourceConfiguration.MicrosoftExtensionsLoggingProviderName, "ActivityJson/Start", (traceEvent) => { + if (traceEvent.ActivityID == Guid.Empty) + { + // Unexpected + return; + } - eventSource.Dynamic.AddCallbackForProviderEvent(LoggingSourceConfiguration.MicrosoftExtensionsLoggingProviderName, "ActivityJson/Start", (traceEvent) => { - int factoryId = (int)traceEvent.PayloadByName("FactoryID"); - string categoryName = (string)traceEvent.PayloadByName("LoggerName"); - string argsJson = (string)traceEvent.PayloadByName("ArgumentsJson"); + string argsJson = (string)traceEvent.PayloadByName("ArgumentsJson"); - // TODO: Store this information by logger factory id - LogActivityItem item = new() - { - ActivityID = traceEvent.ActivityID, - ScopedObject = new LogObject(JsonDocument.Parse(argsJson).RootElement), - }; + // TODO: Store this information by logger factory id + LogScopeItem item = new() + { + ActivityID = traceEvent.ActivityID, + ScopedObject = new LogObject(JsonDocument.Parse(argsJson).RootElement), + }; - if (stack.Count > 0) - { - Guid parentId = stack.Peek(); - if (logActivities.TryGetValue(parentId, out LogActivityItem parentItem)) + if (activityIdToScope.TryGetValue(traceEvent.RelatedActivityID, out LogScopeItem parentItem)) { item.Parent = parentItem; } - } - stack.Push(traceEvent.ActivityID); - logActivities[traceEvent.ActivityID] = item; - }); - - eventSource.Dynamic.AddCallbackForProviderEvent(LoggingSourceConfiguration.MicrosoftExtensionsLoggingProviderName, "ActivityJson/Stop", (traceEvent) => { - int factoryId = (int)traceEvent.PayloadByName("FactoryID"); - string categoryName = (string)traceEvent.PayloadByName("LoggerName"); + if (activityIdToScope.Count < Settings.ScopeLimit || activityIdToScope.ContainsKey(traceEvent.ActivityID)) + { + activityIdToScope[traceEvent.ActivityID] = item; + } + }); - //If we begin collection in the middle of a request, we can receive a stop without having a start. - if (stack.Count > 0) - { - stack.Pop(); - logActivities.Remove(traceEvent.ActivityID); - } - }); + eventSource.Dynamic.AddCallbackForProviderEvent(LoggingSourceConfiguration.MicrosoftExtensionsLoggingProviderName, "ActivityJson/Stop", (traceEvent) => { + // Not all stopped event ActivityIds will exist in our tree since there may be scopes already active when we start the trace session. + _ = activityIdToScope.Remove(traceEvent.ActivityID); + }); + } eventSource.Dynamic.AddCallbackForProviderEvent(LoggingSourceConfiguration.MicrosoftExtensionsLoggingProviderName, "MessageJson", (traceEvent) => { // Level, FactoryID, LoggerName, EventID, EventName, ExceptionJson, ArgumentsJson @@ -110,14 +121,29 @@ protected override Task OnEventSourceAvailable(EventPipeEventSource eventSource, ILogger logger = _factory.CreateLogger(categoryName); List scopes = new(); - if (logActivities.TryGetValue(traceEvent.ActivityID, out LogActivityItem logActivityItem)) + // + // The MessageJson event will occur with an ActivityId equal to the most relevant activity branch and we can backtrack to the root of the tree + // to grab all applicable scopes (where each node we visit is an applicable scope). + // + // Ideally the ActivityId will always exist in our tree, however if another trace is ongoing that is interested in an event start + // within the same async context as our log message then there will be nodes+edges that our tree is unaware of. + // This is because TplEventSource's TasksFlowActivityIds is a singleton implementation that is shared for all traces, + // regardless of if the other traces have TasksFlowActivityIds enabled. + // + // In this scenario there's still a chance that only a single branch has occurred and we're the first event logged with the newly branched ActivityId. + // In which case we can use the RelatedActivityId to find our way back onto the tree. + // + // If not then we will be operating on a subtree without a way of getting back to the root node and will only have a subset (if any) of the + // applicable scopes. + // + if (activityIdToScope.TryGetValue(traceEvent.ActivityID, out LogScopeItem scopeItem) || + activityIdToScope.TryGetValue(traceEvent.RelatedActivityID, out scopeItem)) { - // REVIEW: Does order matter here? We're combining everything anyways. - while (logActivityItem != null) + while (scopeItem != null) { - scopes.Add(logger.BeginScope(logActivityItem.ScopedObject)); + scopes.Add(logger.BeginScope(scopeItem.ScopedObject)); - logActivityItem = logActivityItem.Parent; + scopeItem = scopeItem.Parent; } } @@ -137,7 +163,10 @@ protected override Task OnEventSourceAvailable(EventPipeEventSource eventSource, object[] args = new object[formatter.ValueNames.Count]; for (int i = 0; i < args.Length; i++) { - args[i] = message.GetProperty(formatter.ValueNames[i]).GetString(); + if (message.TryGetProperty(formatter.ValueNames[i], out JsonElement value)) + { + args[i] = value.GetString(); + } } //We want to propagate the timestamp to the underlying logger, but that's not part of the ILogger interface. @@ -187,13 +216,13 @@ private static string MessageFormatter(object state, Exception error) return state.ToString(); } - private class LogActivityItem + private class LogScopeItem { public Guid ActivityID { get; set; } public LogObject ScopedObject { get; set; } - public LogActivityItem Parent { get; set; } + public LogScopeItem Parent { get; set; } } } } diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Logs/EventLogsPipelineSettings.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Logs/EventLogsPipelineSettings.cs index c505b81954..f8133f53a1 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Logs/EventLogsPipelineSettings.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Logs/EventLogsPipelineSettings.cs @@ -16,5 +16,9 @@ internal class EventLogsPipelineSettings : EventSourcePipelineSettings // This setting will collect logs for the application-defined categories and levels. public bool UseAppFilters { get; set; } = true; + + public bool CollectScopes { get; set; } = true; + + public int ScopeLimit { get; set; } = 10_000; } } diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Microsoft.Diagnostics.Monitoring.EventPipe.csproj b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Microsoft.Diagnostics.Monitoring.EventPipe.csproj index 0682848f12..c104003875 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Microsoft.Diagnostics.Monitoring.EventPipe.csproj +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Microsoft.Diagnostics.Monitoring.EventPipe.csproj @@ -40,6 +40,7 @@ + diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Trace/EventTracePipeline.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Trace/EventTracePipeline.cs index 68f1760364..bb4d1fc3af 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Trace/EventTracePipeline.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Trace/EventTracePipeline.cs @@ -31,7 +31,7 @@ protected override async Task OnRun(CancellationToken token) { //It is important that the underlying stream be completely read, or disposed. //If rundown is enabled, the underlying stream must be drained or disposed, or the app hangs. - using Stream eventStream = await _provider.Value.ProcessEvents(Client, Settings.Duration, token).ConfigureAwait(false); + using Stream eventStream = await _provider.Value.ProcessEvents(Client, Settings.Duration, Settings.ResumeRuntime, token).ConfigureAwait(false); await _onStreamAvailable(eventStream, token).ConfigureAwait(false); } diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/EventCounter/EventCounterTrigger.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/EventCounter/EventCounterTrigger.cs index 6350d43038..a1b96bf004 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/EventCounter/EventCounterTrigger.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/EventCounter/EventCounterTrigger.cs @@ -32,6 +32,7 @@ internal sealed class EventCounterTrigger : private readonly CounterFilter _filter; private readonly EventCounterTriggerImpl _impl; private readonly string _providerName; + private CounterConfiguration _counterConfiguration; public EventCounterTrigger(EventCounterTriggerSettings settings) { @@ -45,6 +46,8 @@ public EventCounterTrigger(EventCounterTriggerSettings settings) _filter = new CounterFilter(settings.CounterIntervalSeconds); _filter.AddFilter(settings.ProviderName, new string[] { settings.CounterName }); + _counterConfiguration = new CounterConfiguration(_filter); + _impl = new EventCounterTriggerImpl(settings); _providerName = settings.ProviderName; @@ -58,7 +61,7 @@ public IReadOnlyDictionary> GetProviderEvent public bool HasSatisfiedCondition(TraceEvent traceEvent) { // Filter to the counter of interest before forwarding to the implementation - if (traceEvent.TryGetCounterPayload(_filter, null, null, out ICounterPayload payload)) + if (traceEvent.TryGetCounterPayload(_counterConfiguration, out ICounterPayload payload)) { return _impl.HasSatisfiedCondition(payload); } diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/SystemDiagnosticsMetricsTrigger/SystemDiagnosticsMetricsTrigger.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/SystemDiagnosticsMetricsTrigger/SystemDiagnosticsMetricsTrigger.cs index 378e389065..d9e9a1267a 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/SystemDiagnosticsMetricsTrigger/SystemDiagnosticsMetricsTrigger.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/SystemDiagnosticsMetricsTrigger/SystemDiagnosticsMetricsTrigger.cs @@ -29,6 +29,7 @@ internal sealed class SystemDiagnosticsMetricsTrigger : private readonly string _meterName; private readonly string _clientId; private readonly string _sessionId; + private CounterConfiguration _counterConfiguration; public SystemDiagnosticsMetricsTrigger(SystemDiagnosticsMetricsTriggerSettings settings) { @@ -49,6 +50,10 @@ public SystemDiagnosticsMetricsTrigger(SystemDiagnosticsMetricsTriggerSettings s _clientId = settings.ClientId; _sessionId = settings.SessionId; + + _clientId = settings.ClientId; + + _counterConfiguration = new CounterConfiguration(_filter) { SessionId = _sessionId, ClientId = _clientId }; } public IReadOnlyDictionary> GetProviderEventMap() @@ -59,7 +64,7 @@ public IReadOnlyDictionary> GetProviderEvent public bool HasSatisfiedCondition(TraceEvent traceEvent) { // Filter to the counter of interest before forwarding to the implementation - if (traceEvent.TryGetCounterPayload(_filter, _sessionId, _clientId, out ICounterPayload payload)) + if (traceEvent.TryGetCounterPayload(_counterConfiguration, out ICounterPayload payload)) { return _impl.HasSatisfiedCondition(payload); } diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/SystemDiagnosticsMetricsTrigger/SystemDiagnosticsMetricsTriggerImpl.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/SystemDiagnosticsMetricsTrigger/SystemDiagnosticsMetricsTriggerImpl.cs index cb60f10320..c10d87f1a8 100644 --- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/SystemDiagnosticsMetricsTrigger/SystemDiagnosticsMetricsTriggerImpl.cs +++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/SystemDiagnosticsMetricsTrigger/SystemDiagnosticsMetricsTriggerImpl.cs @@ -55,7 +55,7 @@ public bool HasSatisfiedCondition(ICounterPayload payload) { EventType eventType = payload.EventType; - if (eventType == EventType.Error || eventType == EventType.CounterEnded) + if (!eventType.IsValuePublishedEvent()) { // not currently logging the error messages @@ -63,17 +63,17 @@ public bool HasSatisfiedCondition(ICounterPayload payload) } else { - bool passesValueFilter = (payload is PercentilePayload percentilePayload) ? - _valueFilterHistogram(CreatePayloadDictionary(percentilePayload)) : + bool passesValueFilter = (payload is AggregatePercentilePayload aggregatePercentilePayload) ? + _valueFilterHistogram(CreatePayloadDictionary(aggregatePercentilePayload)) : _valueFilterDefault(payload.Value); return SharedTriggerImplHelper.HasSatisfiedCondition(ref _latestTicks, ref _targetTicks, _windowTicks, _intervalTicks, payload, passesValueFilter); } } - private static Dictionary CreatePayloadDictionary(PercentilePayload percentilePayload) + private static Dictionary CreatePayloadDictionary(AggregatePercentilePayload aggregatePercentilePayload) { - return percentilePayload.Quantiles.ToDictionary(keySelector: p => CounterUtilities.CreatePercentile(p.Percentage), elementSelector: p => p.Value); + return aggregatePercentilePayload.Quantiles.ToDictionary(keySelector: q => CounterUtilities.CreatePercentile(q.Percentage), elementSelector: q => q.Value); } } } diff --git a/src/Microsoft.Diagnostics.Monitoring/Microsoft.Diagnostics.Monitoring.csproj b/src/Microsoft.Diagnostics.Monitoring/Microsoft.Diagnostics.Monitoring.csproj index 975476fa62..a4fff0796f 100644 --- a/src/Microsoft.Diagnostics.Monitoring/Microsoft.Diagnostics.Monitoring.csproj +++ b/src/Microsoft.Diagnostics.Monitoring/Microsoft.Diagnostics.Monitoring.csproj @@ -26,6 +26,8 @@ + + diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSession.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSession.cs index 041502f6f7..d61aace345 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSession.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSession.cs @@ -13,13 +13,13 @@ namespace Microsoft.Diagnostics.NETCore.Client { public class EventPipeSession : IDisposable { - private long _sessionId; + private ulong _sessionId; private IpcEndpoint _endpoint; private bool _disposedValue; // To detect redundant calls private bool _stopped; // To detect redundant calls private readonly IpcResponse _response; - private EventPipeSession(IpcEndpoint endpoint, IpcResponse response, long sessionId) + private EventPipeSession(IpcEndpoint endpoint, IpcResponse response, ulong sessionId) { _endpoint = endpoint; _response = response; @@ -93,7 +93,7 @@ private static EventPipeSession CreateSessionFromResponse(IpcEndpoint endpoint, { DiagnosticsClient.ValidateResponseMessage(response.Value.Message, operationName); - long sessionId = BinaryPrimitives.ReadInt64LittleEndian(new ReadOnlySpan(response.Value.Message.Payload, 0, 8)); + ulong sessionId = BinaryPrimitives.ReadUInt64LittleEndian(new ReadOnlySpan(response.Value.Message.Payload, 0, 8)); EventPipeSession session = new(endpoint, response.Value, sessionId); response = null; diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSessionConfiguration.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSessionConfiguration.cs index 25f813d8ff..0e837d908c 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSessionConfiguration.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeSessionConfiguration.cs @@ -59,7 +59,7 @@ public byte[] SerializeV2() writer.Write(Providers.Count); foreach (EventPipeProvider provider in Providers) { - writer.Write(provider.Keywords); + writer.Write(unchecked((ulong)provider.Keywords)); writer.Write((uint)provider.EventLevel); writer.WriteString(provider.Name); diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsServerRouter/DiagnosticsServerRouterFactory.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsServerRouter/DiagnosticsServerRouterFactory.cs index a8f09868ed..124d7bb333 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsServerRouter/DiagnosticsServerRouterFactory.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsServerRouter/DiagnosticsServerRouterFactory.cs @@ -406,6 +406,8 @@ internal class TcpClientRouterFactory protected int TcpClientRetryTimeoutMs { get; set; } = 500; + protected ILogger Logger => _logger; + public delegate TcpClientRouterFactory CreateInstanceDelegate(string tcpClient, int runtimeTimeoutMs, ILogger logger); public static TcpClientRouterFactory CreateDefaultInstance(string tcpClient, int runtimeTimeoutMs, ILogger logger) diff --git a/src/Microsoft.Diagnostics.Repl/ConsoleService.cs b/src/Microsoft.Diagnostics.Repl/ConsoleService.cs index 06aaf7b478..4147eabb3d 100644 --- a/src/Microsoft.Diagnostics.Repl/ConsoleService.cs +++ b/src/Microsoft.Diagnostics.Repl/ConsoleService.cs @@ -508,8 +508,14 @@ private bool Dispatch(string newCommand, Action - diff --git a/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDump.cs b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDump.cs index 815ba0cfb3..0f24d19275 100644 --- a/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDump.cs +++ b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDump.cs @@ -45,6 +45,17 @@ public TestDump(TestConfiguration config) _symbolService.AddCachePath(_symbolService.DefaultSymbolCache); } + public override void Dispose() + { + base.Dispose(); + _dataTarget?.Dispose(); + _dataTarget = null; + } + + public ServiceManager ServiceManager => _serviceManager; + + public ServiceContainer ServiceContainer => _serviceContainer; + protected override ITarget GetTarget() { _dataTarget = DataTarget.LoadDump(DumpFile); diff --git a/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestHost.cs b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestHost.cs index c86b121a63..93eea0eec2 100644 --- a/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestHost.cs +++ b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestHost.cs @@ -1,11 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using Microsoft.Diagnostics.DebugServices; namespace Microsoft.Diagnostics.TestHelpers { - public abstract class TestHost + public abstract class TestHost : IDisposable { private TestDataReader _testData; private ITarget _target; @@ -17,6 +18,12 @@ public TestHost(TestConfiguration config) Config = config; } + public virtual void Dispose() + { + _target?.Destroy(); + _target = null; + } + public TestDataReader TestData { get diff --git a/src/SOS/SOS.Extensions/DebuggerServices.cs b/src/SOS/SOS.Extensions/DebuggerServices.cs index 0df0a5fd4b..b5702a7a82 100644 --- a/src/SOS/SOS.Extensions/DebuggerServices.cs +++ b/src/SOS/SOS.Extensions/DebuggerServices.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -10,11 +11,12 @@ using Microsoft.Diagnostics.DebugServices; using Microsoft.Diagnostics.Runtime; using Microsoft.Diagnostics.Runtime.Utilities; +using SOS.Hosting; using SOS.Hosting.DbgEng.Interop; -namespace SOS +namespace SOS.Extensions { - internal sealed unsafe class DebuggerServices : CallableCOMWrapper + internal sealed unsafe class DebuggerServices : CallableCOMWrapper, SOSHost.INativeClient { internal enum OperatingSystem { @@ -39,6 +41,7 @@ internal DebuggerServices(IntPtr punk, HostType hostType) : base(new RefCountedFreeLibrary(IntPtr.Zero), IID_IDebuggerServices, punk) { _hostType = hostType; + Client = punk; // This uses COM marshalling code, so we also check that the OSPlatform is Windows. if (hostType == HostType.DbgEng && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -51,6 +54,12 @@ internal DebuggerServices(IntPtr punk, HostType hostType) } } + #region INativeClient + + public IntPtr Client { get; } + + #endregion + public HResult GetOperatingSystem(out OperatingSystem operatingSystem) { return VTable.GetOperatingSystem(Self, out operatingSystem); @@ -424,6 +433,41 @@ public HResult AddModuleSymbol(string symbolFileName) } } + public HResult GetLastException(out uint processId, out int threadId, out EXCEPTION_RECORD64 exceptionRecord) + { + exceptionRecord = default; + + uint type; + HResult hr = VTable.GetLastEventInformation(Self, out type, out processId, out threadId, null, 0, null, null, 0, null); + if (hr.IsOK) + { + if (type != (uint)DEBUG_EVENT.EXCEPTION) + { + return HResult.E_FAIL; + } + } + + DEBUG_LAST_EVENT_INFO_EXCEPTION exceptionInfo; + hr = VTable.GetLastEventInformation( + Self, + out _, + out processId, + out threadId, + &exceptionInfo, + Unsafe.SizeOf(), + null, + null, + 0, + null); + + if (hr.IsOK) + { + exceptionRecord = exceptionInfo.ExceptionRecord; + } + Debug.Assert(hr != HResult.S_FALSE); + return hr; + } + [StructLayout(LayoutKind.Sequential)] private readonly unsafe struct IDebuggerServicesVTable { @@ -455,6 +499,7 @@ private readonly unsafe struct IDebuggerServicesVTable public readonly delegate* unmanaged[Stdcall] SupportsDml; public readonly delegate* unmanaged[Stdcall] OutputDmlString; public readonly delegate* unmanaged[Stdcall] AddModuleSymbol; + public readonly delegate* unmanaged[Stdcall] GetLastEventInformation; } } } diff --git a/src/SOS/SOS.Extensions/HostServices.cs b/src/SOS/SOS.Extensions/HostServices.cs index 900f6f43ca..9ee85d1942 100644 --- a/src/SOS/SOS.Extensions/HostServices.cs +++ b/src/SOS/SOS.Extensions/HostServices.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Reflection; using System.Runtime.InteropServices; -using System.Text; using Microsoft.Diagnostics.DebugServices; using Microsoft.Diagnostics.DebugServices.Implementation; using Microsoft.Diagnostics.ExtensionCommands; @@ -20,7 +20,7 @@ namespace SOS.Extensions /// /// The extension services Wrapper the native hosts are given /// - public sealed unsafe class HostServices : COMCallableIUnknown, IHost + public sealed unsafe class HostServices : COMCallableIUnknown, IHost, SOSLibrary.ISOSModule { private static readonly Guid IID_IHostServices = new("27B2CB8D-BDEE-4CBD-B6EF-75880D76D46F"); @@ -42,6 +42,7 @@ private delegate int InitializeCallbackDelegate( private readonly SymbolService _symbolService; private readonly HostWrapper _hostWrapper; private ServiceContainer _serviceContainer; + private ServiceContainer _servicesWithManagedOnlyFilter; private ContextServiceFromDebuggerServices _contextService; private int _targetIdFactory; private ITarget _target; @@ -101,14 +102,17 @@ public static int Initialize( return HResult.E_FAIL; } Debug.Assert(Instance == null); - Instance = new HostServices(); + Instance = new HostServices(extensionPath, extensionLibrary); return initialializeCallback(Instance.IHostServices); } - private HostServices() + private HostServices(string extensionPath, IntPtr extensionsLibrary) { + SOSPath = Path.GetDirectoryName(extensionPath); + SOSHandle = extensionsLibrary; + _serviceManager = new ServiceManager(); - _commandService = new CommandService(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ">!ext" : null); + _commandService = new CommandService(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ">!sos" : null); _serviceManager.NotifyExtensionLoad.Register(_commandService.AddCommands); _symbolService = new SymbolService(this) @@ -128,7 +132,6 @@ private HostServices() builder.AddMethod(new FlushTargetDelegate(FlushTarget)); builder.AddMethod(new DestroyTargetDelegate(DestroyTarget)); builder.AddMethod(new DispatchCommandDelegate(DispatchCommand)); - builder.AddMethod(new DisplayHelpDelegate(DisplayHelp)); builder.AddMethod(new UninitializeDelegate(Uninitialize)); IHostServices = builder.Complete(); @@ -193,16 +196,15 @@ private int RegisterDebuggerServices( FileLoggingConsoleService fileLoggingConsoleService = new(consoleService); DiagnosticLoggingService.Instance.SetConsole(consoleService, fileLoggingConsoleService); - // Don't register everything in the SOSHost assembly; just the wrappers - _serviceManager.RegisterExportedServices(typeof(TargetWrapper)); - _serviceManager.RegisterExportedServices(typeof(RuntimeWrapper)); - // Register all the services and commands in the Microsoft.Diagnostics.DebugServices.Implementation assembly _serviceManager.RegisterAssembly(typeof(Target).Assembly); // Register all the services and commands in the SOS.Extensions (this) assembly _serviceManager.RegisterAssembly(typeof(HostServices).Assembly); + // Register all the services and commands in the SOS.Hosting assembly + _serviceManager.RegisterAssembly(typeof(SOSHost).Assembly); + // Register all the services and commands in the Microsoft.Diagnostics.ExtensionCommands assembly _serviceManager.RegisterAssembly(typeof(ClrMDHelper).Assembly); @@ -220,6 +222,8 @@ private int RegisterDebuggerServices( _serviceContainer = _serviceManager.CreateServiceContainer(ServiceScope.Global, parent: null); _serviceContainer.AddService(_serviceManager); _serviceContainer.AddService(this); + _serviceContainer.AddService(this); + _serviceContainer.AddService(DebuggerServices); _serviceContainer.AddService(_commandService); _serviceContainer.AddService(_symbolService); _serviceContainer.AddService(fileLoggingConsoleService); @@ -232,6 +236,10 @@ private int RegisterDebuggerServices( ThreadUnwindServiceFromDebuggerServices threadUnwindService = new(DebuggerServices); _serviceContainer.AddService(threadUnwindService); + // Used to invoke only managed commands + _servicesWithManagedOnlyFilter = new(_contextService.Services); + _servicesWithManagedOnlyFilter.AddService(new SOSCommandBase.ManagedOnlyCommandFilter()); + // Add each extension command to the native debugger foreach ((string name, string help, IEnumerable aliases) in _commandService.Commands) { @@ -241,12 +249,6 @@ private int RegisterDebuggerServices( Trace.TraceWarning($"Cannot add extension command {hr:X8} {name} - {help}"); } } - - if (DebuggerServices.DebugClient is IDebugControl5 control) - { - MemoryRegionServiceFromDebuggerServices memRegions = new(DebuggerServices.DebugClient, control); - _serviceContainer.AddService(memRegions); - } } catch (Exception ex) { @@ -349,56 +351,27 @@ private int DispatchCommand( { return HResult.E_INVALIDARG; } - if (!_commandService.IsCommand(commandName)) - { - return HResult.E_NOTIMPL; - } try { - StringBuilder sb = new(); - sb.Append(commandName); - if (!string.IsNullOrWhiteSpace(commandArguments)) - { - sb.Append(' '); - sb.Append(commandArguments); - } - if (_commandService.Execute(sb.ToString(), _contextService.Services)) + if (_commandService.Execute(commandName, commandArguments, commandName == "help" ? _contextService.Services : _servicesWithManagedOnlyFilter)) { return HResult.S_OK; } - } - catch (CommandNotSupportedException) - { - return HResult.E_NOTIMPL; - } - catch (Exception ex) - { - Trace.TraceError(ex.ToString()); - } - return HResult.E_FAIL; - } - - private int DisplayHelp( - IntPtr self, - string commandName) - { - try - { - if (!_commandService.DisplayHelp(commandName, _contextService.Services)) + else { - return HResult.E_INVALIDARG; + // The command was not found or supported + return HResult.E_NOTIMPL; } } - catch (CommandNotSupportedException) - { - return HResult.E_NOTIMPL; - } catch (Exception ex) { Trace.TraceError(ex.ToString()); - return HResult.E_FAIL; + IConsoleService consoleService = Services.GetService(); + // TODO: when we can figure out how to deal with error messages in the scripts that are displayed on STDERROR under lldb + //consoleService.WriteLineError(ex.Message); + consoleService.WriteLine(ex.Message); } - return HResult.S_OK; + return HResult.E_FAIL; } private void Uninitialize( @@ -436,6 +409,14 @@ private void Uninitialize( #endregion + #region SOSLibrary.ISOSModule + + public string SOSPath { get; } + + public IntPtr SOSHandle { get; } + + #endregion + #region IHostServices delegates [UnmanagedFunctionPointer(CallingConvention.Winapi)] @@ -471,11 +452,6 @@ private delegate int DispatchCommandDelegate( [In, MarshalAs(UnmanagedType.LPStr)] string commandName, [In, MarshalAs(UnmanagedType.LPStr)] string commandArguments); - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - private delegate int DisplayHelpDelegate( - [In] IntPtr self, - [In, MarshalAs(UnmanagedType.LPStr)] string commandName); - [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate void UninitializeDelegate( [In] IntPtr self); diff --git a/src/SOS/SOS.Extensions/MemoryRegionServiceFromDebuggerServices.cs b/src/SOS/SOS.Extensions/MemoryRegionServiceFromDebuggerServices.cs index 15c729c1df..403bcbc879 100644 --- a/src/SOS/SOS.Extensions/MemoryRegionServiceFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/MemoryRegionServiceFromDebuggerServices.cs @@ -15,10 +15,10 @@ internal sealed class MemoryRegionServiceFromDebuggerServices : IMemoryRegionSer private readonly IDebugClient5 _client; private readonly IDebugControl5 _control; - public MemoryRegionServiceFromDebuggerServices(IDebugClient5 client, IDebugControl5 control) + public MemoryRegionServiceFromDebuggerServices(IDebugClient5 client) { _client = client; - _control = control; + _control = (IDebugControl5)client; } public IEnumerable EnumerateRegions() diff --git a/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs b/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs index ed88c37705..4137ee1dfd 100644 --- a/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs @@ -49,8 +49,6 @@ public TypeFromDebuggerServices(ModuleServiceFromDebuggerServices moduleService, public string Name { get; } - public List Fields => throw new NotImplementedException(); - public bool TryGetField(string fieldName, out IField field) { HResult hr = _moduleService._debuggerServices.GetFieldOffset(Module.ModuleIndex, _typeId, Name, fieldName, out uint offset); diff --git a/src/SOS/SOS.Extensions/RemoteMemoryService.cs b/src/SOS/SOS.Extensions/RemoteMemoryService.cs index a6d24bcfa2..d844b6d44c 100644 --- a/src/SOS/SOS.Extensions/RemoteMemoryService.cs +++ b/src/SOS/SOS.Extensions/RemoteMemoryService.cs @@ -8,7 +8,7 @@ using Microsoft.Diagnostics.Runtime; using Microsoft.Diagnostics.Runtime.Utilities; -namespace SOS +namespace SOS.Extensions { internal sealed unsafe class RemoteMemoryService : CallableCOMWrapper, IRemoteMemoryService { diff --git a/src/SOS/SOS.Extensions/SOS.Extensions.csproj b/src/SOS/SOS.Extensions/SOS.Extensions.csproj index 5deb184576..444196d7b4 100644 --- a/src/SOS/SOS.Extensions/SOS.Extensions.csproj +++ b/src/SOS/SOS.Extensions/SOS.Extensions.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 SOS.Extensions diff --git a/src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs b/src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs index 71e4e60020..e0c2bee85b 100644 --- a/src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs @@ -7,6 +7,7 @@ using Microsoft.Diagnostics.DebugServices; using Microsoft.Diagnostics.DebugServices.Implementation; using Microsoft.Diagnostics.Runtime.Utilities; +using SOS.Hosting; using SOS.Hosting.DbgEng.Interop; using Architecture = System.Runtime.InteropServices.Architecture; @@ -94,7 +95,48 @@ internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost hos return memoryService; }); + // Add optional crash info service (currently only for Native AOT). + _serviceContainerFactory.AddServiceFactory((services) => CreateCrashInfoService(services, debuggerServices)); + OnFlushEvent.Register(() => FlushService()); + + if (debuggerServices.DebugClient is not null) + { + _serviceContainerFactory.AddServiceFactory((services) => new MemoryRegionServiceFromDebuggerServices(debuggerServices.DebugClient)); + } + Finished(); } + + private unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider services, DebuggerServices debuggerServices) + { + // For Linux/OSX dumps loaded under dbgeng the GetLastException API doesn't return the necessary information + if (Host.HostType == HostType.DbgEng && (OperatingSystem == OSPlatform.Linux || OperatingSystem == OSPlatform.OSX)) + { + return SpecialDiagInfo.CreateCrashInfoService(services); + } + HResult hr = debuggerServices.GetLastException(out uint processId, out int threadIndex, out EXCEPTION_RECORD64 exceptionRecord); + if (hr.IsOK) + { + if (exceptionRecord.ExceptionCode == CrashInfoService.STATUS_STACK_BUFFER_OVERRUN && + exceptionRecord.NumberParameters >= 4 && + exceptionRecord.ExceptionInformation[0] == CrashInfoService.FAST_FAIL_EXCEPTION_DOTNET_AOT) + { + uint hresult = (uint)exceptionRecord.ExceptionInformation[1]; + ulong triageBufferAddress = exceptionRecord.ExceptionInformation[2]; + int triageBufferSize = (int)exceptionRecord.ExceptionInformation[3]; + + Span buffer = new byte[triageBufferSize]; + if (services.GetService().ReadMemory(triageBufferAddress, buffer, out int bytesRead) && bytesRead == triageBufferSize) + { + return CrashInfoService.Create(hresult, buffer); + } + else + { + Trace.TraceError($"CrashInfoService: ReadMemory({triageBufferAddress}) failed"); + } + } + } + return null; + } } } diff --git a/src/SOS/SOS.Hosting/Commands/SOSCommand.cs b/src/SOS/SOS.Hosting/Commands/SOSCommand.cs index 1190fd29ab..dfa4a32532 100644 --- a/src/SOS/SOS.Hosting/Commands/SOSCommand.cs +++ b/src/SOS/SOS.Hosting/Commands/SOSCommand.cs @@ -1,10 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Diagnostics; -using System.IO; using System.Linq; +using System.Runtime.InteropServices; using Microsoft.Diagnostics.DebugServices; namespace SOS.Hosting @@ -43,17 +42,45 @@ namespace SOS.Hosting [Command(Name = "ip2md", DefaultOptions = "IP2MD", Help = "Displays the MethodDesc structure at the specified address in code that has been JIT-compiled.")] [Command(Name = "name2ee", DefaultOptions = "Name2EE", Help = "Displays the MethodTable structure and EEClass structure for the specified type or method in the specified module.")] [Command(Name = "printexception", DefaultOptions = "PrintException", Aliases = new string[] { "pe" }, Help = "Displays and formats fields of any object derived from the Exception class at the specified address.")] - [Command(Name = "soshelp", DefaultOptions = "Help", Help = "Displays help for a specific SOS command.")] [Command(Name = "syncblk", DefaultOptions = "SyncBlk", Help = "Displays the SyncBlock holder info.")] [Command(Name = "threadstate", DefaultOptions = "ThreadState", Help = "Pretty prints the meaning of a threads state.")] - [Command(Name = "comstate", DefaultOptions = "COMState", Flags = CommandFlags.Windows, Help = "Lists the COM apartment model for each thread.")] - [Command(Name = "dumprcw", DefaultOptions = "DumpRCW", Flags = CommandFlags.Windows, Help = "Displays information about a Runtime Callable Wrapper.")] - [Command(Name = "dumpccw", DefaultOptions = "DumpCCW", Flags = CommandFlags.Windows, Help = "Displays information about a COM Callable Wrapper.")] - [Command(Name = "dumppermissionset", DefaultOptions = "DumpPermissionSet", Flags = CommandFlags.Windows, Help = "Displays a PermissionSet object (debug build only).")] - [Command(Name = "gchandleleaks", DefaultOptions = "GCHandleLeaks", Flags = CommandFlags.Windows, Help = "Helps in tracking down GCHandle leaks")] - [Command(Name = "watsonbuckets", DefaultOptions = "WatsonBuckets", Flags = CommandFlags.Windows, Help = "Displays the Watson buckets.")] - public class SOSCommand : CommandBase + public class SOSCommand : SOSCommandBase { + [FilterInvoke] + public static bool FilterInvoke( + [ServiceImport(Optional = true)] ManagedOnlyCommandFilter managedOnly, + [ServiceImport(Optional = true)] IRuntime runtime) => + SOSCommandBase.Filter(managedOnly, runtime); + } + + [Command(Name = "comstate", DefaultOptions = "COMState", Help = "Lists the COM apartment model for each thread.")] + [Command(Name = "dumprcw", DefaultOptions = "DumpRCW", Help = "Displays information about a Runtime Callable Wrapper.")] + [Command(Name = "dumpccw", DefaultOptions = "DumpCCW", Help = "Displays information about a COM Callable Wrapper.")] + [Command(Name = "dumppermissionset", DefaultOptions = "DumpPermissionSet", Help = "Displays a PermissionSet object (debug build only).")] + [Command(Name = "gchandleleaks", DefaultOptions = "GCHandleLeaks", Help = "Helps in tracking down GCHandle leaks.")] + [Command(Name = "watsonbuckets", DefaultOptions = "WatsonBuckets", Help = "Displays the Watson buckets.")] + public class WindowsSOSCommand : SOSCommandBase + { + /// + /// These commands are Windows only. + /// + [FilterInvoke] + public static bool FilterInvoke( + [ServiceImport(Optional = true)] ITarget target, + [ServiceImport(Optional = true)] ManagedOnlyCommandFilter managedOnly, + [ServiceImport(Optional = true)] IRuntime runtime) => + target != null && target.OperatingSystem == OSPlatform.Windows && SOSCommandBase.Filter(managedOnly, runtime); + } + + public class SOSCommandBase : CommandBase + { + /// + /// Empty service used to prevent native commands from being run + /// + public class ManagedOnlyCommandFilter + { + } + [Argument(Name = "arguments", Help = "Arguments to SOS command.")] public string[] Arguments { get; set; } @@ -62,22 +89,30 @@ public class SOSCommand : CommandBase public override void Invoke() { - try - { - Debug.Assert(Arguments != null && Arguments.Length > 0); - string arguments = string.Concat(Arguments.Skip(1).Select((arg) => arg + " ")).Trim(); - SOSHost.ExecuteCommand(Arguments[0], arguments); - } - catch (Exception ex) when (ex is FileNotFoundException or EntryPointNotFoundException or InvalidOperationException) - { - WriteLineError(ex.Message); - } + Debug.Assert(Arguments != null && Arguments.Length > 0); + string arguments = string.Concat(Arguments.Skip(1).Select((arg) => arg + " ")).Trim(); + SOSHost.ExecuteCommand(Arguments[0], arguments); } [HelpInvoke] - public void InvokeHelp() + public string GetDetailedHelp() { - SOSHost.ExecuteCommand("Help", Arguments[0]); + return SOSHost.GetHelpText(Arguments[0]); } + + /// + /// Common native SOS command filter function. + /// + /// not null means to filter out the native C++ SOS commands + /// runtime instance or null + /// + public static bool Filter(ManagedOnlyCommandFilter managedOnly, IRuntime runtime) => + // This filters out these native C++ commands if requested by host (in this case SOS.Extensions) to prevent recursion. + managedOnly == null && + // This commands require a .NET Core, Desktop Framework or .NET Core single file runtime (not a Native AOT runtime) + runtime != null && + (runtime.RuntimeType == RuntimeType.NetCore || + runtime.RuntimeType == RuntimeType.Desktop || + runtime.RuntimeType == RuntimeType.SingleFile); } } diff --git a/src/SOS/SOS.Hosting/RuntimeWrapper.cs b/src/SOS/SOS.Hosting/RuntimeWrapper.cs index 09c93312ed..c537b270e0 100644 --- a/src/SOS/SOS.Hosting/RuntimeWrapper.cs +++ b/src/SOS/SOS.Hosting/RuntimeWrapper.cs @@ -83,7 +83,6 @@ private delegate IntPtr LoadLibraryWDelegate( private readonly IServiceProvider _services; private readonly IRuntime _runtime; - private readonly IDisposable _onFlushEvent; private IntPtr _clrDataProcess = IntPtr.Zero; private IntPtr _corDebugProcess = IntPtr.Zero; private IntPtr _dacHandle = IntPtr.Zero; @@ -97,7 +96,6 @@ public RuntimeWrapper(IServiceProvider services, IRuntime runtime) Debug.Assert(runtime != null); _services = services; _runtime = runtime; - _onFlushEvent = runtime.Target.OnFlushEvent.Register(Flush); VTableBuilder builder = AddInterface(IID_IRuntime, validate: false); @@ -124,34 +122,26 @@ void IDisposable.Dispose() protected override void Destroy() { Trace.TraceInformation("RuntimeWrapper.Destroy"); - _onFlushEvent.Dispose(); - Flush(); - if (_dacHandle != IntPtr.Zero) - { - DataTarget.PlatformFunctions.FreeLibrary(_dacHandle); - _dacHandle = IntPtr.Zero; - } - if (_dbiHandle != IntPtr.Zero) - { - DataTarget.PlatformFunctions.FreeLibrary(_dbiHandle); - _dbiHandle = IntPtr.Zero; - } - } - - private void Flush() - { - // TODO: there is a better way to flush _corDebugProcess with ICorDebugProcess4::ProcessStateChanged(FLUSH_ALL) if (_corDebugProcess != IntPtr.Zero) { ComWrapper.ReleaseWithCheck(_corDebugProcess); _corDebugProcess = IntPtr.Zero; } - // TODO: there is a better way to flush _clrDataProcess with ICLRDataProcess::Flush() if (_clrDataProcess != IntPtr.Zero) { ComWrapper.ReleaseWithCheck(_clrDataProcess); _clrDataProcess = IntPtr.Zero; } + if (_dacHandle != IntPtr.Zero) + { + DataTarget.PlatformFunctions.FreeLibrary(_dacHandle); + _dacHandle = IntPtr.Zero; + } + if (_dbiHandle != IntPtr.Zero) + { + DataTarget.PlatformFunctions.FreeLibrary(_dbiHandle); + _dbiHandle = IntPtr.Zero; + } } #region IRuntime (native) diff --git a/src/SOS/SOS.Hosting/SOS.Hosting.csproj b/src/SOS/SOS.Hosting/SOS.Hosting.csproj index 7fee627a72..df9961fb69 100644 --- a/src/SOS/SOS.Hosting/SOS.Hosting.csproj +++ b/src/SOS/SOS.Hosting/SOS.Hosting.csproj @@ -11,6 +11,7 @@ + diff --git a/src/SOS/SOS.Hosting/SOSHost.cs b/src/SOS/SOS.Hosting/SOSHost.cs index 03d011d0da..0fe569a0fa 100644 --- a/src/SOS/SOS.Hosting/SOSHost.cs +++ b/src/SOS/SOS.Hosting/SOSHost.cs @@ -21,6 +21,17 @@ namespace SOS.Hosting [ServiceExport(Scope = ServiceScope.Target)] public sealed class SOSHost : IDisposable { + /// + /// Provides the native debugger's debug client instance + /// + public interface INativeClient + { + /// + /// Native debugger client interface + /// + IntPtr Client { get; } + } + // This is what dbgeng/IDebuggerServices returns for non-PE modules that don't have a timestamp internal const uint InvalidTimeStamp = 0xFFFFFFFE; internal const uint InvalidChecksum = 0xFFFFFFFF; @@ -45,72 +56,62 @@ public sealed class SOSHost : IDisposable private readonly SOSLibrary _sosLibrary; #pragma warning restore - private readonly IntPtr _interface; + private readonly IntPtr _client; private readonly ulong _ignoreAddressBitsMask; - private bool _disposed; + private readonly bool _releaseClient; /// /// Create an instance of the hosting class. Has the lifetime of the target. /// - public SOSHost(ITarget target, IMemoryService memoryService) + public SOSHost(ITarget target, IMemoryService memoryService, [ServiceImport(Optional = true)] INativeClient client) { - Target = target ?? throw new DiagnosticsException("No target"); + Target = target; MemoryService = memoryService; _ignoreAddressBitsMask = memoryService.SignExtensionMask(); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + // If running under a native debugger, use the client instance supplied by the debugger for commands + if (client != null) { - DebugClient debugClient = new(this); - _interface = debugClient.IDebugClient; + _client = client.Client; } else { - LLDBServices lldbServices = new(this); - _interface = lldbServices.ILLDBServices; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + DebugClient debugClient = new(this); + _client = debugClient.IDebugClient; + } + else + { + LLDBServices lldbServices = new(this); + _client = lldbServices.ILLDBServices; + } + _releaseClient = true; } } void IDisposable.Dispose() { - Trace.TraceInformation($"SOSHost.Dispose {_disposed}"); - if (!_disposed) + Trace.TraceInformation($"SOSHost.Dispose"); + if (_releaseClient) { - _disposed = true; - ComWrapper.ReleaseWithCheck(_interface); + ComWrapper.ReleaseWithCheck(_client); } } /// /// Execute a SOS command. /// - /// command name and arguments - 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); - } + /// just the command name + /// the command arguments and options + public void ExecuteCommand(string command, string arguments) => _sosLibrary.ExecuteCommand(_client, command, arguments); /// - /// Execute a SOS command. + /// Get the detailed help text for a native SOS command. /// - /// just the command name - /// the command arguments and options - public void ExecuteCommand(string command, string arguments) - { - if (_disposed) - { - throw new ObjectDisposedException("SOSHost instance disposed"); - } - _sosLibrary.ExecuteCommand(_interface, command, arguments); - } + /// command name + /// help text or null if not found or error + public string GetHelpText(string command) => _sosLibrary.GetHelpText(command); #region Reverse PInvoke Implementations diff --git a/src/SOS/SOS.Hosting/SOSLibrary.cs b/src/SOS/SOS.Hosting/SOSLibrary.cs index d9e4f03da8..42e1f3e425 100644 --- a/src/SOS/SOS.Hosting/SOSLibrary.cs +++ b/src/SOS/SOS.Hosting/SOSLibrary.cs @@ -16,6 +16,22 @@ namespace SOS.Hosting /// public sealed class SOSLibrary : IDisposable { + /// + /// Provides the SOS module handle + /// + public interface ISOSModule + { + /// + /// The SOS module path + /// + string SOSPath { get; } + + /// + /// The SOS module handle + /// + IntPtr SOSHandle { get; } + } + [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate int SOSCommandDelegate( IntPtr ILLDBServices, @@ -29,10 +45,20 @@ private delegate int SOSInitializeDelegate( [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate void SOSUninitializeDelegate(); + [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true, EntryPoint = "FindResourceA")] + public static extern IntPtr FindResource(IntPtr hModule, string name, string type); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResource); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr LockResource(IntPtr hResource); + private const string SOSInitialize = "SOSInitializeByHost"; private const string SOSUninitialize = "SOSUninitializeByHost"; private readonly HostWrapper _hostWrapper; + private readonly bool _uninitializeLibrary; private IntPtr _sosLibrary = IntPtr.Zero; /// @@ -41,12 +67,12 @@ private delegate int SOSInitializeDelegate( public string SOSPath { get; set; } [ServiceExport(Scope = ServiceScope.Global)] - public static SOSLibrary Create(IHost host) + public static SOSLibrary TryCreate(IHost host, [ServiceImport(Optional = true)] ISOSModule sosModule) { SOSLibrary sosLibrary = null; try { - sosLibrary = new SOSLibrary(host); + sosLibrary = new SOSLibrary(host, sosModule); sosLibrary.Initialize(); } catch @@ -61,10 +87,20 @@ public static SOSLibrary Create(IHost host) /// Create an instance of the hosting class /// /// target instance - private SOSLibrary(IHost host) + /// sos library info or null + private SOSLibrary(IHost host, ISOSModule sosModule) { - string rid = InstallHelper.GetRid(); - SOSPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), rid); + if (sosModule is not null) + { + SOSPath = sosModule.SOSPath; + _sosLibrary = sosModule.SOSHandle; + } + else + { + string rid = InstallHelper.GetRid(); + SOSPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), rid); + _uninitializeLibrary = true; + } _hostWrapper = new HostWrapper(host); } @@ -131,15 +167,15 @@ private void Initialize() /// private void Uninitialize() { - Trace.TraceInformation("SOSHost: Uninitialize"); - if (_sosLibrary != IntPtr.Zero) + Trace.TraceInformation("SOSLibrary: Uninitialize"); + if (_uninitializeLibrary && _sosLibrary != IntPtr.Zero) { SOSUninitializeDelegate uninitializeFunc = SOSHost.GetDelegateFunction(_sosLibrary, SOSUninitialize); uninitializeFunc?.Invoke(); Microsoft.Diagnostics.Runtime.DataTarget.PlatformFunctions.FreeLibrary(_sosLibrary); - _sosLibrary = IntPtr.Zero; } + _sosLibrary = IntPtr.Zero; _hostWrapper.ReleaseWithCheck(); } @@ -156,17 +192,85 @@ public void ExecuteCommand(IntPtr client, string command, string arguments) SOSCommandDelegate commandFunc = SOSHost.GetDelegateFunction(_sosLibrary, command); if (commandFunc == null) { - throw new DiagnosticsException($"SOS command not found: {command}"); + throw new CommandNotFoundException($"{CommandNotFoundException.NotFoundMessage} '{command}'"); } int result = commandFunc(client, arguments ?? ""); if (result == HResult.E_NOTIMPL) { - throw new CommandNotSupportedException($"SOS command not found: {command}"); + throw new CommandNotFoundException($"{CommandNotFoundException.NotFoundMessage} '{command}'"); } if (result != HResult.S_OK) { Trace.TraceError($"SOS command FAILED 0x{result:X8}"); + throw new DiagnosticsException(string.Empty); + } + } + + public string GetHelpText(string command) + { + string helpText; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + IntPtr hResInfo = FindResource(_sosLibrary, "DOCUMENTATION", "TEXT"); + if (hResInfo == IntPtr.Zero) + { + throw new DiagnosticsException("Can not SOS help text"); + } + IntPtr hResource = LoadResource(_sosLibrary, hResInfo); + if (hResource == IntPtr.Zero) + { + throw new DiagnosticsException("Can not SOS help text"); + } + IntPtr helpTextPtr = LockResource(hResource); + if (helpTextPtr == IntPtr.Zero) + { + throw new DiagnosticsException("Can not SOS help text"); + } + helpText = Marshal.PtrToStringAnsi(helpTextPtr); + } + else + { + string helpTextFile = Path.Combine(SOSPath, "sosdocsunix.txt"); + helpText = File.ReadAllText(helpTextFile); + } + command = command.ToLowerInvariant(); + string searchString = $"COMMAND: {command}."; + + // Search for command in help text file + int start = helpText.IndexOf(searchString); + if (start == -1) + { + throw new DiagnosticsException($"Documentation for {command} not found"); + } + + // Go to end of line + start = helpText.IndexOf('\n', start); + if (start == -1) + { + throw new DiagnosticsException($"No newline in documentation resource or file"); + } + + // Find the first occurrence of \\ followed by an \r or an \n on a line by itself. + int end = start++; + while (true) + { + end = helpText.IndexOf("\\\\", end + 1); + if (end == -1) + { + break; + } + char c = helpText[end - 1]; + if (c is '\r' or '\n') + { + break; + } + c = helpText[end + 3]; + if (c is '\r' or '\n') + { + break; + } } + return end == -1 ? helpText.Substring(start) : helpText.Substring(start, end - start); } } } diff --git a/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs b/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs index aebf7bc0c2..6fe61d348c 100644 --- a/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs +++ b/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs @@ -120,7 +120,7 @@ public static int GetICorDebugMetadataLocator( if (symbolService.IsSymbolStoreEnabled) { SymbolStoreKey key = PEFileKeyGenerator.GetKey(imagePath, imageTimestamp, imageSize); - string localFilePath = symbolService.DownloadFile(key); + string localFilePath = symbolService.DownloadFile(key.Index, key.FullPathName); if (!string.IsNullOrWhiteSpace(localFilePath)) { localFilePath += "\0"; // null terminate the string diff --git a/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt b/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt index 5b869886b2..7a435c20b2 100644 --- a/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt +++ b/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt @@ -91,6 +91,16 @@ net6.0 $(RuntimeVersion60) + + diff --git a/src/SOS/SOS.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt b/src/SOS/SOS.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt index 3618f365e9..14898afadc 100644 --- a/src/SOS/SOS.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt +++ b/src/SOS/SOS.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt @@ -107,6 +107,23 @@ net6.0 $(RuntimeVersion60) + + diff --git a/src/SOS/SOS.UnitTests/Debuggees/DesktopClrHost/CMakeLists.txt b/src/SOS/SOS.UnitTests/Debuggees/DesktopClrHost/CMakeLists.txt index 3d06e95d2c..d27bd7a780 100644 --- a/src/SOS/SOS.UnitTests/Debuggees/DesktopClrHost/CMakeLists.txt +++ b/src/SOS/SOS.UnitTests/Debuggees/DesktopClrHost/CMakeLists.txt @@ -6,7 +6,6 @@ include_directories(inc) include_directories("$ENV{VSInstallDir}/DIA SDK/include") add_definitions(-DUSE_STL) -add_definitions(-MT) set(DESKTOPCLRHOST_SOURCES DesktopClrHost.cpp diff --git a/src/SOS/SOS.UnitTests/SOS.UnitTests.csproj b/src/SOS/SOS.UnitTests/SOS.UnitTests.csproj index 3ce4a1bdb5..db08980770 100644 --- a/src/SOS/SOS.UnitTests/SOS.UnitTests.csproj +++ b/src/SOS/SOS.UnitTests/SOS.UnitTests.csproj @@ -31,6 +31,7 @@ + diff --git a/src/SOS/SOS.UnitTests/SOS.cs b/src/SOS/SOS.UnitTests/SOS.cs index 8849bd4a22..d7e8f6eb54 100644 --- a/src/SOS/SOS.UnitTests/SOS.cs +++ b/src/SOS/SOS.UnitTests/SOS.cs @@ -225,7 +225,6 @@ public async Task GCPOHTests(TestConfiguration config) { throw new SkipTestException("This test validates POH behavior, which was introduced in .net 5"); } - await SOSTestHelpers.RunTest( config, debuggeeName: "GCPOH", @@ -311,6 +310,17 @@ await SOSTestHelpers.RunTest( testName: "SOS.StackTests"); } + [SkippableTheory, MemberData(nameof(SOSTestHelpers.GetConfigurations), "TestName", "SOS.TestExtensions", MemberType = typeof(SOSTestHelpers))] + public async Task TestExtensions(TestConfiguration config) + { + await SOSTestHelpers.RunTest( + config, + debuggeeName: "LineNums", + scriptName: "TestExtensions.script", + Output, + testName: "SOS.TestExtensions"); + } + [SkippableTheory, MemberData(nameof(Configurations))] public async Task OtherCommands(TestConfiguration config) { diff --git a/src/SOS/SOS.UnitTests/SOSRunner.cs b/src/SOS/SOS.UnitTests/SOSRunner.cs index 3d345997a0..bee617ec9c 100644 --- a/src/SOS/SOS.UnitTests/SOSRunner.cs +++ b/src/SOS/SOS.UnitTests/SOSRunner.cs @@ -7,6 +7,7 @@ using System.IO; using System.IO.Pipes; using System.Linq; +using System.Reflection.Metadata.Ecma335; using System.Text; using System.Text.RegularExpressions; using System.Threading; @@ -696,6 +697,13 @@ public static async Task StartDebugger(TestInformation information, D // Issue: https://github.com/dotnet/diagnostics/issues/3126 processRunner.WithRuntimeConfiguration("EnableWriteXorExecute", "0"); + // Setup the extension environment variable + string extensions = config.DotNetDiagnosticExtensions(); + if (!string.IsNullOrEmpty(extensions)) + { + processRunner.WithEnvironmentVariable("DOTNET_DIAGNOSTIC_EXTENSIONS", extensions); + } + DumpType? dumpType = null; if (action is DebuggerAction.LoadDump or DebuggerAction.LoadDumpWithDotNetDump) { @@ -793,6 +801,7 @@ public async Task RunScript(string scriptRelativePath) { await ContinueExecution(); } + // Adds the "!" prefix under dbgeng, nothing under lldb. Meant for SOS (native) commands. else if (line.StartsWith("SOSCOMMAND:")) { string input = line.Substring("SOSCOMMAND:".Length).TrimStart(); @@ -801,6 +810,19 @@ public async Task RunScript(string scriptRelativePath) throw new Exception($"SOS command FAILED: {input}"); } } + else if (line.StartsWith("SOSCOMMAND_FAIL:")) + { + string input = line.Substring("SOSCOMMAND_FAIL:".Length).TrimStart(); + if (await RunSosCommand(input)) + { + // The cdb runcommand extension doesn't get the execute command failures (limitation in dbgeng). + if (Debugger != NativeDebugger.Cdb) + { + throw new Exception($"SOS command did not fail: {input}"); + } + } + } + // Adds the "!sos" prefix under dbgeng, "sos " under lldb. Meant for extensions (managed) commands else if (line.StartsWith("EXTCOMMAND:")) { string input = line.Substring("EXTCOMMAND:".Length).TrimStart(); @@ -809,6 +831,19 @@ public async Task RunScript(string scriptRelativePath) throw new Exception($"Extension command FAILED: {input}"); } } + else if (line.StartsWith("EXTCOMMAND_FAIL:")) + { + string input = line.Substring("EXTCOMMAND_FAIL:".Length).TrimStart(); + if (await RunSosCommand(input, extensionCommand: true)) + { + // The cdb runcommand extension doesn't get the execute command failures (limitation in dbgeng). + if (Debugger != NativeDebugger.Cdb) + { + throw new Exception($"Extension command did not fail: {input}"); + } + } + } + // Never adds any prefix. Meant for native debugger commands. else if (line.StartsWith("COMMAND:")) { string input = line.Substring("COMMAND:".Length).TrimStart(); @@ -817,6 +852,18 @@ public async Task RunScript(string scriptRelativePath) throw new Exception($"Debugger command FAILED: {input}"); } } + else if (line.StartsWith("COMMAND_FAIL:")) + { + string input = line.Substring("COMMAND_FAIL:".Length).TrimStart(); + if (await RunCommand(input)) + { + // The cdb runcommand extension doesn't get the execute command failures (limitation in dbgeng). + if (Debugger != NativeDebugger.Cdb) + { + throw new Exception($"Debugger command did not fail: {input}"); + } + } + } else if (line.StartsWith("VERIFY:")) { string verifyLine = line.Substring("VERIFY:".Length); @@ -1060,8 +1107,6 @@ public async Task RunSosCommand(string command, bool extensionCommand = fa } break; case NativeDebugger.Lldb: - command = "sos " + command; - break; case NativeDebugger.DotNetDump: if (extensionCommand) { @@ -1681,6 +1726,11 @@ public static string DotNetDumpPath(this TestConfiguration config) return TestConfiguration.MakeCanonicalPath(dotnetDumpPath); } + public static string DotNetDiagnosticExtensions(this TestConfiguration config) + { + return TestConfiguration.MakeCanonicalPath(config.GetValue("DotNetDiagnosticExtensions")); + } + public static string SOSPath(this TestConfiguration config) { return TestConfiguration.MakeCanonicalPath(config.GetValue("SOSPath")); diff --git a/src/SOS/SOS.UnitTests/Scripts/ConcurrentDictionaries.script b/src/SOS/SOS.UnitTests/Scripts/ConcurrentDictionaries.script index 9d402ecbf3..3a156cb1e8 100644 --- a/src/SOS/SOS.UnitTests/Scripts/ConcurrentDictionaries.script +++ b/src/SOS/SOS.UnitTests/Scripts/ConcurrentDictionaries.script @@ -9,13 +9,13 @@ IFDEF:NETCORE_OR_DOTNETDUMP # Load SOS even though it doesn't actually load the sos module on dotnet-dump but it runs some initial settings/commands. LOADSOS -EXTCOMMAND: dcd +EXTCOMMAND_FAIL: dcd VERIFY: Missing ConcurrentDictionary address -EXTCOMMAND: dcd abcdefgh +EXTCOMMAND_FAIL: dcd abcdefgh VERIFY: Hexadecimal address expected -EXTCOMMAND: dcd 0000000000000001 +EXTCOMMAND_FAIL: dcd 0000000000000001 VERIFY: is not referencing an object # Checks on ConcurrentDictionary diff --git a/src/SOS/SOS.UnitTests/Scripts/DivZero.script b/src/SOS/SOS.UnitTests/Scripts/DivZero.script index 7d2095a5e1..456b10790d 100644 --- a/src/SOS/SOS.UnitTests/Scripts/DivZero.script +++ b/src/SOS/SOS.UnitTests/Scripts/DivZero.script @@ -40,12 +40,7 @@ VERIFY:\s+\s+\s+[Dd]iv[Zz]ero.*!C\.Main(\(.*\))?\+0x\s+ VERIFY:\[.*[\\|/]Debuggees[\\|/].*DivZero[\\|/]DivZero\.cs @ (57|56)\s*\]\s* # Verify that Threads (clrthreads) works -IFDEF:DOTNETDUMP SOSCOMMAND:clrthreads -ENDIF:DOTNETDUMP -!IFDEF:DOTNETDUMP -SOSCOMMAND:Threads -ENDIF:DOTNETDUMP VERIFY:\s*ThreadCount:\s+\s+ VERIFY:\s+UnstartedThread:\s+\s+ VERIFY:\s+BackgroundThread:\s+\s+ diff --git a/src/SOS/SOS.UnitTests/Scripts/DumpGen.script b/src/SOS/SOS.UnitTests/Scripts/DumpGen.script index 55bac91815..525bfb897d 100644 --- a/src/SOS/SOS.UnitTests/Scripts/DumpGen.script +++ b/src/SOS/SOS.UnitTests/Scripts/DumpGen.script @@ -9,17 +9,17 @@ IFDEF:NETCORE_OR_DOTNETDUMP # Load SOS even though it doesn't actually load the sos module on dotnet-dump but it runs some initial settings/commands. LOADSOS -EXTCOMMAND: dumpgen +EXTCOMMAND_FAIL: dumpgen VERIFY: Generation argument is missing -EXTCOMMAND: dumpgen invalid +EXTCOMMAND_FAIL: dumpgen invalid VERIFY: invalid is not a supported generation !IFDEF:LLDB -EXTCOMMAND: dumpgen gen0 -mt +EXTCOMMAND_FAIL: dumpgen gen0 -mt VERIFY: Required argument missing for option: -mt -EXTCOMMAND: dumpgen gen1 -mt zzzzz +EXTCOMMAND_FAIL: dumpgen gen1 -mt zzzzz VERIFY: Hexadecimal address expected for -mt option ENDIF:LLDB diff --git a/src/SOS/SOS.UnitTests/Scripts/GCTests.script b/src/SOS/SOS.UnitTests/Scripts/GCTests.script index 671e4e2e15..a1b172dffb 100644 --- a/src/SOS/SOS.UnitTests/Scripts/GCTests.script +++ b/src/SOS/SOS.UnitTests/Scripts/GCTests.script @@ -89,7 +89,7 @@ VERIFY:\s+\s+\s+\s+GCWhere\s+ IFDEF:WINDOWS SOSCOMMAND:DumpHeap -stat -gen xxx -VERIFY:\s*System\.ArgumentException: Unknown generation: xxx\. Only gen0, gen1, gen2, loh \(large\), poh \(pinned\) and foh \(frozen\) are supported\s+ +VERIFY:\s*Unknown generation: xxx\. Only gen0, gen1, gen2, loh \(large\), poh \(pinned\) and foh \(frozen\) are supported\s+ ENDIF:WINDOWS IFDEF:WINDOWS diff --git a/src/SOS/SOS.UnitTests/Scripts/LineNums.script b/src/SOS/SOS.UnitTests/Scripts/LineNums.script index 6f6c11a30e..39ec83258b 100644 --- a/src/SOS/SOS.UnitTests/Scripts/LineNums.script +++ b/src/SOS/SOS.UnitTests/Scripts/LineNums.script @@ -31,12 +31,7 @@ VERIFY:\s+\s+\s+LineNums.*!LineNums\.Program\.Main.*\+0x VERIFY:\[.*[\\|/]Debuggees[\\|/].*LineNums[\\|/]Program\.cs @ 13\s*\]\s* # Verify that Threads (clrthreads) works -IFDEF:DOTNETDUMP SOSCOMMAND:clrthreads -ENDIF:DOTNETDUMP -!IFDEF:DOTNETDUMP -SOSCOMMAND:Threads -ENDIF:DOTNETDUMP VERIFY:\s*ThreadCount:\s+\s+ VERIFY:\s+UnstartedThread:\s+\s+ VERIFY:\s+BackgroundThread:\s+\s+ diff --git a/src/SOS/SOS.UnitTests/Scripts/NestedExceptionTest.script b/src/SOS/SOS.UnitTests/Scripts/NestedExceptionTest.script index 8c67f036b5..d44f0d7ca9 100644 --- a/src/SOS/SOS.UnitTests/Scripts/NestedExceptionTest.script +++ b/src/SOS/SOS.UnitTests/Scripts/NestedExceptionTest.script @@ -99,12 +99,7 @@ VERIFY:HResult:\s+80131509\s+ VERIFY:There are nested exceptions on this thread. Run with -nested for details # Verify that Threads (clrthreads) works -IFDEF:DOTNETDUMP SOSCOMMAND:clrthreads -ENDIF:DOTNETDUMP -!IFDEF:DOTNETDUMP -SOSCOMMAND:Threads -ENDIF:DOTNETDUMP VERIFY:\s*ThreadCount:\s+\s+ VERIFY:\s+UnstartedThread:\s+\s+ VERIFY:\s+BackgroundThread:\s+\s+ diff --git a/src/SOS/SOS.UnitTests/Scripts/OtherCommands.script b/src/SOS/SOS.UnitTests/Scripts/OtherCommands.script index 8274219dcd..b3473d5b36 100644 --- a/src/SOS/SOS.UnitTests/Scripts/OtherCommands.script +++ b/src/SOS/SOS.UnitTests/Scripts/OtherCommands.script @@ -109,18 +109,9 @@ VERIFY:\s*Class Name:\s+SymbolTestApp.Program\s+ VERIFY:\s*File:\s+.*SymbolTestApp\.(dll|exe)\s+ # Verify DumpMT -!IFDEF:MAJOR_RUNTIME_VERSION_GE_7 -# https://github.com/dotnet/diagnostics/issues/3516 SOSCOMMAND:DumpMT \s*Method Table:\s+()\s+ VERIFY:\s*Name:\s+SymbolTestApp.Program\s+ VERIFY:\s*File:\s+.*SymbolTestApp\.(dll|exe)\s+ -ENDIF:MAJOR_RUNTIME_VERSION_GE_7 - -IFDEF:MAJOR_RUNTIME_VERSION_GE_8 -SOSCOMMAND:DumpMT \s*Method Table:\s+()\s+ -VERIFY:\s*Name:\s+SymbolTestApp.Program\s+ -VERIFY:\s*File:\s+.*SymbolTestApp\.(dll|exe)\s+ -ENDIF:MAJOR_RUNTIME_VERSION_GE_8 SOSCOMMAND:FinalizeQueue VERIFY:\s*SyncBlocks to be cleaned up:\s+\s+ diff --git a/src/SOS/SOS.UnitTests/Scripts/Overflow.script b/src/SOS/SOS.UnitTests/Scripts/Overflow.script index 3deaf16e46..ccc8c8d232 100644 --- a/src/SOS/SOS.UnitTests/Scripts/Overflow.script +++ b/src/SOS/SOS.UnitTests/Scripts/Overflow.script @@ -9,12 +9,7 @@ CONTINUE LOADSOS -IFDEF:DOTNETDUMP SOSCOMMAND:clrthreads -managedexception -ENDIF:DOTNETDUMP -!IFDEF:DOTNETDUMP -SOSCOMMAND:Threads -managedexception -ENDIF:DOTNETDUMP # 5) Verifying that PrintException gives us the right exception in the format above. SOSCOMMAND:PrintException diff --git a/src/SOS/SOS.UnitTests/Scripts/Reflection.script b/src/SOS/SOS.UnitTests/Scripts/Reflection.script index fe8db12b95..b3f34187c0 100644 --- a/src/SOS/SOS.UnitTests/Scripts/Reflection.script +++ b/src/SOS/SOS.UnitTests/Scripts/Reflection.script @@ -43,12 +43,7 @@ VERIFY:(StackTraceString: \s+)? VERIFY:HResult:\s+80131604 # Verify that Threads (clrthreads) works -IFDEF:DOTNETDUMP SOSCOMMAND:clrthreads -ENDIF:DOTNETDUMP -!IFDEF:DOTNETDUMP -SOSCOMMAND:Threads -ENDIF:DOTNETDUMP VERIFY:\s*ThreadCount:\s+\s+ VERIFY:\s+UnstartedThread:\s+\s+ VERIFY:\s+BackgroundThread:\s+\s+ diff --git a/src/SOS/SOS.UnitTests/Scripts/SimpleThrow.script b/src/SOS/SOS.UnitTests/Scripts/SimpleThrow.script index 7740c58804..8ae602b04b 100644 --- a/src/SOS/SOS.UnitTests/Scripts/SimpleThrow.script +++ b/src/SOS/SOS.UnitTests/Scripts/SimpleThrow.script @@ -46,12 +46,7 @@ VERIFY:\s+\s+\s+[Ss]imple[Tt]hrow.*!(\$0_)?Simple\.Main.*\+0x\s+ VERIFY:\s+UnstartedThread:\s+\s+ VERIFY:\s+BackgroundThread:\s+\s+ diff --git a/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script b/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script index 5c31e0ef1d..ec9fed65b8 100644 --- a/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script +++ b/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script @@ -250,20 +250,20 @@ ENDIF:DESKTOP # Verify that "u" works (depends on the IP2MD here) SOSCOMMAND:ClrStack SOSCOMMAND:IP2MD .*\s+()\s+SymbolTestApp\.Program\.Foo4.*\s+ -SOSCOMMAND:u \s*MethodDesc:\s+()\s* +SOSCOMMAND:clru \s*MethodDesc:\s+()\s* VERIFY:\s*Normal JIT generated code\s+ VERIFY:\s+SymbolTestApp\.Program\.Foo4\(System\.String\)\s+ VERIFY:\s+Begin\s+,\s+size\s+\s+ VERIFY:\s+(?i:.*[\\|/]SymbolTestApp\.cs) @ (53|57):\s+ # Verify that "u" with no line info works -SOSCOMMAND:u -n +SOSCOMMAND:clru -n VERIFY:\s*Normal JIT generated code\s+ VERIFY:\s+SymbolTestApp\.Program\.Foo4\(System\.String\)\s+ VERIFY:\s+Begin\s+,\s+size\s+\s+ # Verify that "u" with offsets info works -SOSCOMMAND:u -o +SOSCOMMAND:clru -o VERIFY:\s*Normal JIT generated code\s+ VERIFY:\s+SymbolTestApp\.Program\.Foo4\(System\.String\)\s+ VERIFY:\s+Begin\s+,\s+size\s+\s+ @@ -279,12 +279,7 @@ VERIFY:\s+Name:\s+SymbolTestApp\.Program\.Foo4\(System\.String\)\s+ VERIFY:\s+JITTED Code Address:\s+\s+ # Verify that Threads (clrthreads) works -IFDEF:DOTNETDUMP SOSCOMMAND:clrthreads -ENDIF:DOTNETDUMP -!IFDEF:DOTNETDUMP -SOSCOMMAND:Threads -ENDIF:DOTNETDUMP VERIFY:\s*ThreadCount:\s+\s+ VERIFY:\s+UnstartedThread:\s+\s+ VERIFY:\s+BackgroundThread:\s+\s+ diff --git a/src/SOS/SOS.UnitTests/Scripts/TestExtensions.script b/src/SOS/SOS.UnitTests/Scripts/TestExtensions.script new file mode 100644 index 0000000000..d18a63acdd --- /dev/null +++ b/src/SOS/SOS.UnitTests/Scripts/TestExtensions.script @@ -0,0 +1,15 @@ +CONTINUE + +LOADSOS + +SOSCOMMAND:clrstack +VERIFY:Test command #1 invoked\s+ + +SOSCOMMAND:dumpheap +VERIFY:Test command #2 invoked\s+ + +SOSCOMMAND:assemblies +VERIFY:Test command #4 invoked\s+ + +SOSCOMMAND_FAIL:ip2md 0 +!VERIFY:Test command #5 invoked\s+ diff --git a/src/SOS/SOS.UnitTests/Scripts/WebApp.script b/src/SOS/SOS.UnitTests/Scripts/WebApp.script index b9a0509e0f..a304a2f001 100644 --- a/src/SOS/SOS.UnitTests/Scripts/WebApp.script +++ b/src/SOS/SOS.UnitTests/Scripts/WebApp.script @@ -101,12 +101,7 @@ VERIFY:.*\s+Child\s+SP\s+IP\s+Call Site\s+ VERIFY:.*\s+Stack walk complete.\s+ # Verify that Threads (clrthreads) works -IFDEF:DOTNETDUMP SOSCOMMAND:clrthreads -ENDIF:DOTNETDUMP -!IFDEF:DOTNETDUMP -SOSCOMMAND:Threads -ENDIF:DOTNETDUMP VERIFY:\s*ThreadCount:\s+\s+ VERIFY:\s+UnstartedThread:\s+\s+ VERIFY:\s+BackgroundThread:\s+\s+ @@ -138,7 +133,7 @@ VERIFY:\s*STACK \s* VERIFY?:\s*<< Awaiting: \s+\s+.* >>\s+ VERIFY:\s*\s+\s+\(()?\)\s+.* -SOSCOMMAND:DumpMT --stats +SOSCOMMAND:DumpMT !VERIFY:\s* is not a MethodTable SOSCOMMAND:DumpAsync --coalesce diff --git a/src/SOS/Strike/CMakeLists.txt b/src/SOS/Strike/CMakeLists.txt index 6299c27391..52456d7387 100644 --- a/src/SOS/Strike/CMakeLists.txt +++ b/src/SOS/Strike/CMakeLists.txt @@ -32,9 +32,9 @@ if(CLR_CMAKE_HOST_ARCH_AMD64) add_definitions(-D_TARGET_WIN64_=1) add_definitions(-DDBG_TARGET_64BIT) add_definitions(-DDBG_TARGET_WIN64=1) - if(WIN32) + if (CLR_CMAKE_HOST_WIN32) add_definitions(-DSOS_TARGET_ARM64=1) - endif(WIN32) + endif(CLR_CMAKE_HOST_WIN32) remove_definitions(-D_TARGET_ARM64_=1) add_definitions(-D_TARGET_AMD64_) add_definitions(-DDBG_TARGET_AMD64) @@ -44,9 +44,9 @@ elseif(CLR_CMAKE_HOST_ARCH_I386) add_definitions(-D_TARGET_X86_=1) add_definitions(-DTARGET_X86) add_definitions(-DDBG_TARGET_32BIT) - if(WIN32) + if (CLR_CMAKE_HOST_WIN32) add_definitions(-DSOS_TARGET_ARM=1) - endif(WIN32) + endif(CLR_CMAKE_HOST_WIN32) elseif(CLR_CMAKE_HOST_ARCH_ARM) message(STATUS "CLR_CMAKE_HOST_ARCH_ARM") add_definitions(-DSOS_TARGET_ARM=1) @@ -64,6 +64,11 @@ elseif(CLR_CMAKE_HOST_ARCH_MIPS64) add_definitions(-D_TARGET_WIN64_=1) add_definitions(-DDBG_TARGET_64BIT) add_definitions(-DDBG_TARGET_WIN64=1) +elseif(CLR_CMAKE_HOST_ARCH_RISCV64) + add_definitions(-DSOS_TARGET_RISCV64=1) + add_definitions(-D_TARGET_WIN64_=1) + add_definitions(-DDBG_TARGET_64BIT) + add_definitions(-DDBG_TARGET_WIN64=1) endif() add_definitions(-DSTRIKE) @@ -73,12 +78,9 @@ include_directories(${ROOT_DIR}/src/SOS/extensions) include_directories(${CLR_SHARED_DIR}/gcdump) include_directories(platform) -if(WIN32) +if (CLR_CMAKE_HOST_WIN32) add_definitions(-DUSE_STL) - #use static crt - add_definitions(-MT) - set(SOS_SOURCES disasm.cpp dllsext.cpp @@ -89,6 +91,7 @@ if(WIN32) gchist.cpp gcroot.cpp symbols.cpp + managedcommands.cpp metadata.cpp sigparser.cpp sildasm.cpp @@ -137,7 +140,7 @@ if(WIN32) mscoree.lib) endif(NOT CLR_CMAKE_HOST_ARCH_ARM64 AND NOT CLR_CMAKE_HOST_ARCH_ARM) -else(WIN32) +else(CLR_CMAKE_HOST_WIN32) add_definitions(-DFEATURE_ENABLE_HARDWARE_EXCEPTIONS) add_definitions(-DPAL_STDCPP_COMPAT=1) add_compile_options(-Wno-null-arithmetic) @@ -186,28 +189,28 @@ else(WIN32) coreclrpal ) -endif(WIN32) +endif(CLR_CMAKE_HOST_WIN32) if(CLR_CMAKE_HOST_ARCH_AMD64) set(SOS_SOURCES_ARCH disasmX86.cpp ) - if(WIN32) + if (CLR_CMAKE_HOST_WIN32) list(APPEND SOS_SOURCES_ARCH disasmARM64.cpp ) - endif(WIN32) + endif(CLR_CMAKE_HOST_WIN32) elseif(CLR_CMAKE_HOST_ARCH_I386) set(SOS_SOURCES_ARCH disasmX86.cpp ) - if(WIN32) + if (CLR_CMAKE_HOST_WIN32) list(APPEND SOS_SOURCES_ARCH disasmARM.cpp ) - endif(WIN32) + endif(CLR_CMAKE_HOST_WIN32) elseif(CLR_CMAKE_HOST_ARCH_ARM) set(SOS_SOURCES_ARCH disasmARM.cpp @@ -245,6 +248,6 @@ target_link_libraries(sos ${SOS_LIBRARY}) # add the install targets install_clr(TARGETS sos DESTINATIONS .) -if(NOT WIN32) +if(NOT CLR_CMAKE_HOST_WIN32) install(FILES sosdocsunix.txt DESTINATION .) -endif(NOT WIN32) +endif(NOT CLR_CMAKE_HOST_WIN32) diff --git a/src/SOS/Strike/Strike.vcxproj b/src/SOS/Strike/Strike.vcxproj index be63febcfb..29dde66bfa 100644 --- a/src/SOS/Strike/Strike.vcxproj +++ b/src/SOS/Strike/Strike.vcxproj @@ -393,6 +393,7 @@ + diff --git a/src/SOS/Strike/Strike.vcxproj.filters b/src/SOS/Strike/Strike.vcxproj.filters index ec1f21b1e9..e7b14e9eaf 100644 --- a/src/SOS/Strike/Strike.vcxproj.filters +++ b/src/SOS/Strike/Strike.vcxproj.filters @@ -32,6 +32,7 @@ platform + @@ -93,4 +94,4 @@ - + \ No newline at end of file diff --git a/src/SOS/Strike/dbgengservices.cpp b/src/SOS/Strike/dbgengservices.cpp index d538079f33..58187515b9 100644 --- a/src/SOS/Strike/dbgengservices.cpp +++ b/src/SOS/Strike/dbgengservices.cpp @@ -512,6 +512,30 @@ DbgEngServices::AddModuleSymbol( return S_OK; } +HRESULT +DbgEngServices::GetLastEventInformation( + PULONG type, + PULONG processId, + PULONG threadId, + PVOID extraInformation, + ULONG extraInformationSize, + PULONG extraInformationUsed, + PSTR description, + ULONG descriptionSize, + PULONG descriptionUsed) +{ + return m_control->GetLastEventInformation( + type, + processId, + threadId, + extraInformation, + extraInformationSize, + extraInformationUsed, + description, + descriptionSize, + descriptionUsed); +} + //---------------------------------------------------------------------------- // IRemoteMemoryService //---------------------------------------------------------------------------- diff --git a/src/SOS/Strike/dbgengservices.h b/src/SOS/Strike/dbgengservices.h index d8530646ca..f7ba96cac2 100644 --- a/src/SOS/Strike/dbgengservices.h +++ b/src/SOS/Strike/dbgengservices.h @@ -207,6 +207,17 @@ class DbgEngServices : public IDebuggerServices, public IRemoteMemoryService, pu void* param, const char* symbolFileName); + HRESULT STDMETHODCALLTYPE GetLastEventInformation( + PULONG type, + PULONG processId, + PULONG threadId, + PVOID extraInformation, + ULONG extraInformationSize, + PULONG extraInformationUsed, + PSTR description, + ULONG descriptionSize, + PULONG descriptionUsed); + //---------------------------------------------------------------------------- // IRemoteMemoryService //---------------------------------------------------------------------------- @@ -296,4 +307,4 @@ class DbgEngServices : public IDebuggerServices, public IRemoteMemoryService, pu #ifdef __cplusplus }; -#endif \ No newline at end of file +#endif diff --git a/src/SOS/Strike/exts.cpp b/src/SOS/Strike/exts.cpp index 3ebe83fde0..112e42bd98 100644 --- a/src/SOS/Strike/exts.cpp +++ b/src/SOS/Strike/exts.cpp @@ -65,14 +65,9 @@ ExtQuery(PDEBUG_CLIENT client) HRESULT ExtQuery(ILLDBServices* services) { - // Initialize the PAL and extension suppport in one place and only once. - if (!g_palInitialized) + if (!InitializePAL()) { - if (PAL_InitializeDLL() != 0) - { - return E_FAIL; - } - g_palInitialized = true; + return E_FAIL; } g_ExtServices = services; @@ -196,25 +191,79 @@ ExtRelease(void) ReleaseTarget(); } -#ifndef FEATURE_PAL +// Executes managed extension commands. Returns E_NOTIMPL if the command doesn't exists. +HRESULT +ExecuteCommand(PCSTR commandName, PCSTR args) +{ + if (commandName != nullptr && strlen(commandName) > 0) + { + IHostServices* hostServices = GetHostServices(); + if (hostServices != nullptr) + { + return hostServices->DispatchCommand(commandName, args); + } + } + return E_NOTIMPL; +} -BOOL IsMiniDumpFileNODAC(); -extern HMODULE g_hInstance; +void +EENotLoadedMessage(HRESULT Status) +{ +#ifdef FEATURE_PAL + ExtOut("Failed to find runtime module (%s), 0x%08x\n", GetRuntimeDllName(IRuntime::Core), Status); +#else + ExtOut("Failed to find runtime module (%s or %s or %s), 0x%08x\n", GetRuntimeDllName(IRuntime::Core), GetRuntimeDllName(IRuntime::WindowsDesktop), GetRuntimeDllName(IRuntime::UnixCore), Status); +#endif + ExtOut("Extension commands need it in order to have something to do.\n"); + ExtOut("For more information see https://go.microsoft.com/fwlink/?linkid=2135652\n"); +} -// This function throws an exception that can be caught by the debugger, -// instead of allowing the default CRT behavior of invoking Watson to failfast. -void __cdecl _SOS_invalid_parameter( - const WCHAR * expression, - const WCHAR * function, - const WCHAR * file, - unsigned int line, - uintptr_t pReserved -) +void +DACMessage(HRESULT Status) { - ExtErr("\nSOS failure!\n"); - throw "SOS failure"; + ExtOut("Failed to load data access module, 0x%08x\n", Status); + if (GetHost()->GetHostType() == IHost::HostType::DbgEng) + { + ExtOut("Verify that 1) you have a recent build of the debugger (10.0.18317.1001 or newer)\n"); + ExtOut(" 2) the file %s that matches your version of %s is\n", GetDacDllName(), GetRuntimeDllName()); + ExtOut(" in the version directory or on the symbol path\n"); + ExtOut(" 3) or, if you are debugging a dump file, verify that the file\n"); + ExtOut(" %s___.dll is on your symbol path.\n", GetDacModuleName()); + ExtOut(" 4) you are debugging on a platform and architecture that supports this\n"); + ExtOut(" the dump file. For example, an ARM dump file must be debugged\n"); + ExtOut(" on an X86 or an ARM machine; an AMD64 dump file must be\n"); + ExtOut(" debugged on an AMD64 machine.\n"); + ExtOut("\n"); + ExtOut("You can run the command '!setclrpath ' to control the load path of %s.\n", GetDacDllName()); + ExtOut("\n"); + ExtOut("Or you can also run the debugger command .cordll to control the debugger's\n"); + ExtOut("load of %s. .cordll -ve -u -l will do a verbose reload.\n", GetDacDllName()); + ExtOut("If that succeeds, the SOS command should work on retry.\n"); + ExtOut("\n"); + ExtOut("If you are debugging a minidump, you need to make sure that your executable\n"); + ExtOut("path is pointing to %s as well.\n", GetRuntimeDllName()); + } + else + { + if (Status == CORDBG_E_MISSING_DEBUGGER_EXPORTS) + { + ExtOut("You can run the debugger command 'setclrpath ' to control the load of %s.\n", GetDacDllName()); + ExtOut("If that succeeds, the SOS command should work on retry.\n"); + } + else + { + ExtOut("Can not load or initialize %s. The target runtime may not be initialized.\n", GetDacDllName()); + } + } + ExtOut("\n"); + ExtOut("For more information see https://go.microsoft.com/fwlink/?linkid=2135652\n"); } +#ifndef FEATURE_PAL + +BOOL IsMiniDumpFileNODAC(); +extern HMODULE g_hInstance; + bool g_Initialized = false; const char* g_sosPrefix = ""; @@ -282,12 +331,6 @@ DebugExtensionInitialize(PULONG Version, PULONG Flags) } ExtRelease(); -#ifndef _ARM_ - // Make sure we do not tear down the debugger when a security function fails - // Since we link statically against CRT this will only affect the SOS module. - _set_invalid_parameter_handler(_SOS_invalid_parameter); -#endif - return S_OK; } @@ -321,6 +364,21 @@ DllMain(HANDLE hInstance, DWORD dwReason, LPVOID lpReserved) #else // FEATURE_PAL +BOOL +InitializePAL() +{ + // Initialize the PAL only once + if (!g_palInitialized) + { + if (PAL_InitializeDLL() != 0) + { + return false; + } + g_palInitialized = true; + } + return true; +} + HRESULT DebugClient::QueryInterface( REFIID InterfaceId, diff --git a/src/SOS/Strike/exts.h b/src/SOS/Strike/exts.h index cf29ce595b..96876879a7 100644 --- a/src/SOS/Strike/exts.h +++ b/src/SOS/Strike/exts.h @@ -223,6 +223,7 @@ IsInitializedByDbgEng(); extern ILLDBServices* g_ExtServices; extern ILLDBServices2* g_ExtServices2; +extern BOOL InitializePAL(); #define IsInitializedByDbgEng() false @@ -237,6 +238,15 @@ ArchQuery(void); void ExtRelease(void); +HRESULT +ExecuteCommand(PCSTR commandName, PCSTR args); + +void +EENotLoadedMessage(HRESULT Status); + +void +DACMessage(HRESULT Status); + extern BOOL ControlC; inline BOOL IsInterrupt() @@ -264,57 +274,6 @@ class __ExtensionCleanUp ~__ExtensionCleanUp(){ExtRelease();} }; -inline void EENotLoadedMessage(HRESULT Status) -{ -#ifdef FEATURE_PAL - ExtOut("Failed to find runtime module (%s), 0x%08x\n", GetRuntimeDllName(IRuntime::Core), Status); -#else - ExtOut("Failed to find runtime module (%s or %s or %s), 0x%08x\n", GetRuntimeDllName(IRuntime::Core), GetRuntimeDllName(IRuntime::WindowsDesktop), GetRuntimeDllName(IRuntime::UnixCore), Status); -#endif - ExtOut("Extension commands need it in order to have something to do.\n"); - ExtOut("For more information see https://go.microsoft.com/fwlink/?linkid=2135652\n"); -} - -inline void DACMessage(HRESULT Status) -{ - ExtOut("Failed to load data access module, 0x%08x\n", Status); - if (GetHost()->GetHostType() == IHost::HostType::DbgEng) - { - ExtOut("Verify that 1) you have a recent build of the debugger (10.0.18317.1001 or newer)\n"); - ExtOut(" 2) the file %s that matches your version of %s is\n", GetDacDllName(), GetRuntimeDllName()); - ExtOut(" in the version directory or on the symbol path\n"); - ExtOut(" 3) or, if you are debugging a dump file, verify that the file\n"); - ExtOut(" %s___.dll is on your symbol path.\n", GetDacModuleName()); - ExtOut(" 4) you are debugging on a platform and architecture that supports this\n"); - ExtOut(" the dump file. For example, an ARM dump file must be debugged\n"); - ExtOut(" on an X86 or an ARM machine; an AMD64 dump file must be\n"); - ExtOut(" debugged on an AMD64 machine.\n"); - ExtOut("\n"); - ExtOut("You can run the command '!setclrpath ' to control the load path of %s.\n", GetDacDllName()); - ExtOut("\n"); - ExtOut("Or you can also run the debugger command .cordll to control the debugger's\n"); - ExtOut("load of %s. .cordll -ve -u -l will do a verbose reload.\n", GetDacDllName()); - ExtOut("If that succeeds, the SOS command should work on retry.\n"); - ExtOut("\n"); - ExtOut("If you are debugging a minidump, you need to make sure that your executable\n"); - ExtOut("path is pointing to %s as well.\n", GetRuntimeDllName()); - } - else - { - if (Status == CORDBG_E_MISSING_DEBUGGER_EXPORTS) - { - ExtOut("You can run the debugger command 'setclrpath ' to control the load of %s.\n", GetDacDllName()); - ExtOut("If that succeeds, the SOS command should work on retry.\n"); - } - else - { - ExtOut("Can not load or initialize %s. The target runtime may not be initialized.\n", GetDacDllName()); - } - } - ExtOut("\n"); - ExtOut("For more information see https://go.microsoft.com/fwlink/?linkid=2135652\n"); -} - // The minimum initialization for a command #define INIT_API_EXT() \ HRESULT Status; \ @@ -331,6 +290,11 @@ inline void DACMessage(HRESULT Status) INIT_API_EXT() \ if ((Status = ArchQuery()) != S_OK) return Status; +#define INIT_API_NOEE_PROBE_MANAGED(name) \ + INIT_API_EXT() \ + if ((Status = ExecuteCommand(name, args)) != E_NOTIMPL) return Status; \ + if ((Status = ArchQuery()) != S_OK) return Status; + #define INIT_API_EE() \ if ((Status = GetRuntime(&g_pRuntime)) != S_OK) \ { \ @@ -342,6 +306,10 @@ inline void DACMessage(HRESULT Status) INIT_API_NOEE() \ INIT_API_EE() +#define INIT_API_NODAC_PROBE_MANAGED(name) \ + INIT_API_NOEE_PROBE_MANAGED(name) \ + INIT_API_EE() + #define INIT_API_DAC() \ if ((Status = LoadClrDebugDll()) != S_OK) \ { \ @@ -355,19 +323,27 @@ inline void DACMessage(HRESULT Status) ToRelease spISD(g_sos); \ ResetGlobals(); +#define INIT_API_PROBE_MANAGED(name) \ + INIT_API_NODAC_PROBE_MANAGED(name) \ + INIT_API_DAC() + #define INIT_API() \ INIT_API_NODAC() \ INIT_API_DAC() +#define INIT_API_EFN() \ + INIT_API_NODAC() \ + INIT_API_DAC() + // Attempt to initialize DAC and SOS globals, but do not "return" on failure. // Instead, mark the failure to initialize the DAC by setting g_bDacBroken to TRUE. // This should be used from extension commands that should work OK even when no // runtime is loaded in the debuggee, e.g. DumpLog, DumpStack. These extensions // and functions they call should test g_bDacBroken before calling any DAC enabled // feature. -#define INIT_API_NO_RET_ON_FAILURE() \ - INIT_API_NODAC() \ - if ((Status = LoadClrDebugDll()) != S_OK) \ +#define INIT_API_NO_RET_ON_FAILURE(name) \ + INIT_API_NODAC_PROBE_MANAGED(name) \ + if ((Status = LoadClrDebugDll()) != S_OK) \ { \ ExtOut("Failed to load data access module (%s), 0x%08x\n", GetDacDllName(), Status); \ ExtOut("Some functionality may be impaired\n"); \ @@ -382,6 +358,30 @@ inline void DACMessage(HRESULT Status) ToRelease spISD(g_sos); \ ToRelease spIDP(g_clrData); +#ifdef FEATURE_PAL + +#define MINIDUMP_NOT_SUPPORTED() +#define ONLY_SUPPORTED_ON_WINDOWS_TARGET() + +#else // !FEATURE_PAL + +#define MINIDUMP_NOT_SUPPORTED() \ + if (IsMiniDumpFile()) \ + { \ + ExtOut("This command is not supported in a minidump without full memory\n"); \ + ExtOut("To try the command anyway, run !MinidumpMode 0\n"); \ + return Status; \ + } + +#define ONLY_SUPPORTED_ON_WINDOWS_TARGET() \ + if (!IsWindowsTarget()) \ + { \ + ExtOut("This command is only supported for Windows targets\n"); \ + return Status; \ + } + +#endif // FEATURE_PAL + extern BOOL g_bDacBroken; //----------------------------------------------------------------------------------------- diff --git a/src/SOS/Strike/gchist.cpp b/src/SOS/Strike/gchist.cpp index f656cb1183..82a43a9531 100644 --- a/src/SOS/Strike/gchist.cpp +++ b/src/SOS/Strike/gchist.cpp @@ -31,8 +31,8 @@ #include #include #include - #include "strike.h" + // We need to define the target address type. This will be used in the // functions that read directly from the debuggee address space, vs. using // the DAC tgo read the DAC-ized data structures. @@ -258,7 +258,7 @@ void GcHistAddLog(LPCSTR msg, StressMsg* stressMsg) DECLARE_API(HistStats) { INIT_API(); - + ExtOut ("%8s %8s %8s\n", "GCCount", "Promotes", "Relocs"); ExtOut ("-----------------------------------\n"); @@ -348,12 +348,13 @@ DECLARE_API(HistRoot) }; if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg)) - return Status; - + { + return E_INVALIDARG; + } if (nArg != 1) { ExtOut ("!Root \n"); - return Status; + return E_INVALIDARG; } size_t Root = (size_t) GetExpression(rootstr.data); @@ -463,12 +464,13 @@ DECLARE_API(HistObjFind) }; if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg)) - return Status; - + { + return E_INVALIDARG; + } if (nArg != 1) { ExtOut ("!ObjSearch \n"); - return Status; + return E_INVALIDARG; } size_t object = (size_t) GetExpression(objstr.data); @@ -542,12 +544,13 @@ DECLARE_API(HistObj) }; if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg)) - return Status; - + { + return E_INVALIDARG; + } if (nArg != 1) { ExtOut ("!object \n"); - return Status; + return E_INVALIDARG; } size_t object = (size_t) GetExpression(objstr.data); diff --git a/src/SOS/Strike/managedcommands.cpp b/src/SOS/Strike/managedcommands.cpp new file mode 100644 index 0000000000..2a7b33615b --- /dev/null +++ b/src/SOS/Strike/managedcommands.cpp @@ -0,0 +1,222 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "exts.h" + +// Windows host only managed command stubs + +HRESULT ExecuteManagedOnlyCommand(PCSTR commandName, PCSTR args) +{ + HRESULT hr = ExecuteCommand(commandName, args); + if (hr == E_NOTIMPL) + { + ExtErr("Unrecognized command '%s'\n", commandName); + } + return hr; +} + +DECLARE_API(DumpStackObjects) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("dumpstackobjects", args); +} + +DECLARE_API(EEHeap) +{ + INIT_API_EXT(); + return ExecuteManagedOnlyCommand("eeheap", args); +} + +DECLARE_API(TraverseHeap) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("traverseheap", args); +} + +DECLARE_API(DumpRuntimeTypes) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("dumpruntimetypes", args); +} + +DECLARE_API(DumpHeap) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("dumpheap", args); +} + +DECLARE_API(VerifyHeap) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("verifyheap", args); +} + +DECLARE_API(AnalyzeOOM) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("analyzeoom", args); +} + +DECLARE_API(VerifyObj) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("verifyobj", args); +} + +DECLARE_API(ListNearObj) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("listnearobj", args); +} + +DECLARE_API(GCHeapStat) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("gcheapstat", args); +} + +DECLARE_API(FinalizeQueue) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("finalizequeue", args); +} + +DECLARE_API(ThreadPool) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("threadpool", args); +} + +DECLARE_API(PathTo) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("pathto", args); +} + +DECLARE_API(GCRoot) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("gcroot", args); +} + +DECLARE_API(GCWhere) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("gcwhere", args); +} + +DECLARE_API(ObjSize) +{ + INIT_API_EXT(); + MINIDUMP_NOT_SUPPORTED(); + return ExecuteManagedOnlyCommand("objsize", args); +} + +DECLARE_API(SetSymbolServer) +{ + INIT_API_EXT(); + return ExecuteManagedOnlyCommand("setsymbolserver", args); +} + +DECLARE_API(assemblies) +{ + INIT_API_EXT(); + return ExecuteManagedOnlyCommand("assemblies", args); +} + +DECLARE_API(crashinfo) +{ + INIT_API_EXT(); + return ExecuteManagedOnlyCommand("crashinfo", args); +} + +DECLARE_API(DumpAsync) +{ + INIT_API_EXT(); + return ExecuteManagedOnlyCommand("dumpasync", args); +} + +DECLARE_API(logging) +{ + INIT_API_EXT(); + return ExecuteManagedOnlyCommand("logging", args); +} + +DECLARE_API(maddress) +{ + INIT_API_EXT(); + return ExecuteManagedOnlyCommand("maddress", args); +} + +DECLARE_API(dumpexceptions) +{ + INIT_API_EXT(); + return ExecuteManagedOnlyCommand("dumpexceptions", args); +} + +DECLARE_API(dumpgen) +{ + INIT_API_EXT(); + return ExecuteManagedOnlyCommand("dumpgen", args); +} + +DECLARE_API(sizestats) +{ + INIT_API_EXT(); + return ExecuteManagedOnlyCommand("sizestats", args); +} + +typedef HRESULT (*PFN_COMMAND)(PDEBUG_CLIENT client, PCSTR args); + +// +// Executes managed extension commands (i.e. !sos) +// +DECLARE_API(ext) +{ + INIT_API_EXT(); + + if (args == nullptr || strlen(args) <= 0) + { + args = "Help"; + } + std::string arguments(args); + size_t pos = arguments.find(' '); + std::string commandName = arguments.substr(0, pos); + if (pos != std::string::npos) + { + arguments = arguments.substr(pos + 1); + } + else + { + arguments.clear(); + } + Status = ExecuteCommand(commandName.c_str(), arguments.c_str()); + if (Status == E_NOTIMPL) + { + PFN_COMMAND commandFunc = (PFN_COMMAND)GetProcAddress(g_hInstance, commandName.c_str()); + if (commandFunc != nullptr) + { + Status = (*commandFunc)(client, arguments.c_str()); + } + else + { + ExtErr("Unrecognized command '%s'\n", commandName.c_str()); + } + } + return Status; +} + diff --git a/src/SOS/Strike/platform/datatarget.cpp b/src/SOS/Strike/platform/datatarget.cpp index e575121ad3..eb22b9397b 100644 --- a/src/SOS/Strike/platform/datatarget.cpp +++ b/src/SOS/Strike/platform/datatarget.cpp @@ -99,7 +99,7 @@ HRESULT STDMETHODCALLTYPE DataTarget::GetPointerSize( /* [out] */ ULONG32 *size) { -#if defined(SOS_TARGET_AMD64) || defined(SOS_TARGET_ARM64) || defined(SOS_TARGET_MIPS64) +#if defined(SOS_TARGET_AMD64) || defined(SOS_TARGET_ARM64) || defined(SOS_TARGET_MIPS64) || defined(SOS_TARGET_RISCV64) *size = 8; #elif defined(SOS_TARGET_ARM) || defined(SOS_TARGET_X86) *size = 4; diff --git a/src/SOS/Strike/sos.def b/src/SOS/Strike/sos.def index 0e9876503f..f298a3f9fd 100644 --- a/src/SOS/Strike/sos.def +++ b/src/SOS/Strike/sos.def @@ -7,10 +7,12 @@ EXPORTS AnalyzeOOM analyzeoom=AnalyzeOOM ao=AnalyzeOOM - clrmodules + assemblies + clrmodules=assemblies ClrStack clrstack=ClrStack CLRStack=ClrStack + crashinfo DumpALC dumpalc=DumpALC DumpArray @@ -26,6 +28,7 @@ EXPORTS dumpdelegate=DumpDelegate DumpDomain dumpdomain=DumpDomain + dumpexceptions #ifdef TRACE_GC DumpGCLog dumpgclog=DumpGCLog @@ -37,6 +40,8 @@ EXPORTS DumpGCConfigLog dumpgcconfiglog=DumpGCConfigLog dclog=DumpGCConfigLog + dumpgen + dg=dumpgen DumpHeap dumpheap=DumpHeap DumpIL @@ -120,6 +125,7 @@ EXPORTS ListNearObj listnearobj=ListNearObj lno=ListNearObj + maddress Name2EE name2ee=Name2EE ObjSize @@ -136,6 +142,7 @@ EXPORTS setsymbolserver=SetSymbolServer SetClrPath setclrpath=SetClrPath + sizestats SOSFlush sosflush=SOSFlush StopOnException @@ -160,6 +167,7 @@ EXPORTS Traverseheap=TraverseHeap u U=u + clru=u VerifyHeap verifyheap=VerifyHeap Verifyheap=VerifyHeap diff --git a/src/SOS/Strike/sos_unixexports.src b/src/SOS/Strike/sos_unixexports.src index aa8eaf23d3..cf08981f5d 100644 --- a/src/SOS/Strike/sos_unixexports.src +++ b/src/SOS/Strike/sos_unixexports.src @@ -2,7 +2,6 @@ ; The .NET Foundation licenses this file to you under the MIT license. ; See the LICENSE file in the project root for more information. -AnalyzeOOM bpmd ClrStack dbgout @@ -13,32 +12,24 @@ DumpClass DumpDelegate DumpDomain DumpGCData -DumpHeap DumpIL DumpLog DumpMD DumpModule DumpMT DumpObj -DumpRuntimeTypes DumpSig DumpSigElem DumpStack -DumpStackObjects DumpVC -EEHeap EEVersion EEStack EHInfo enummem -FinalizeQueue FindAppDomain FindRoots GCHandles -GCHeapStat GCInfo -GCRoot -GCWhere Help HistClear HistInit @@ -47,11 +38,8 @@ HistObjFind HistRoot HistStats IP2MD -ListNearObj Name2EE -ObjSize PrintException -PathTo runtimes StopOnCatch SetClrPath @@ -61,13 +49,9 @@ runtimes SuppressJitOptimization SyncBlk Threads -ThreadPool ThreadState Token2EE -TraverseHeap u -VerifyHeap -VerifyObj SOSInitializeByHost SOSUninitializeByHost diff --git a/src/SOS/Strike/sosdocs.txt b/src/SOS/Strike/sosdocs.txt index fa470d2eab..0d424296be 100644 --- a/src/SOS/Strike/sosdocs.txt +++ b/src/SOS/Strike/sosdocs.txt @@ -19,7 +19,7 @@ COMMAND: contents. SOS is a debugger extension DLL designed to aid in the debugging of managed programs. Functions are listed by category, then roughly in order of importance. Shortcut names for popular functions are listed in parenthesis. -Type "!help " for detailed info on that function. +Type "!soshelp " for detailed info on that function. Object Inspection Examining code and stacks ----------------------------- ----------------------------- @@ -682,10 +682,16 @@ The arguments in detail: -allReady Specifying this argument will allow for the display of all objects that are ready for finalization, whether they are already marked by - the GC as such, or whether the next GC will. The objects that are - not in the "Ready for finalization" list are finalizable objects that - are no longer rooted. This option can be very expensive, as it - verifies whether all the objects in the finalizable queues are still + the GC as such or not. The former means GC already put them in the + "Ready for finalization" list and their finalizers are ready to run + but haven't run yet. The latter means there is nothing holding onto + these objects but GC hasn't noticed it yet because a GC that collects + the generation this object lives in has not happened yet. When that + GC happens, this object will be moved to the "Ready for finalization" + list. For example, if a finalizable object lives in gen2 and a gen2 GC + has not happened, even if it's displayed by -allReady it's not actually + ready for finalization. This option can be very expensive, as it + verifies whether all the objects in the finalizable queues are still rooted or not. -short Limits the output to just the address of each object. If used in conjunction with -allReady it enumerates all objects that have a @@ -2638,26 +2644,6 @@ You can use the "dotnet --info" in a command shell to find the path of an instal .NET Core runtime. \\ -COMMAND: setsymbolserver. -!SetSymbolServer [-ms] [-mi] [-disable] [-log] [-cache ] [-directory ] [-timeout ] [-pat ] [] - --ms - Use the public Microsoft symbol server. --mi - Use the internal symweb symbol server. --disable - Disable symbol download support. --directory - Directory to search for symbols. Can be more than one. --timeout - Specify the symbol server timeout in minutes --pat - Access token to the authenticated server. --cache - Specific a symbol cache directory. The default is %%TEMP%%\SymbolCache if not specified. - - Symbol server URL. - -This commands enables symbol server support for portable PDBs in SOS. If the .sympath is set, this -symbol server support is automatically enabled. - -To disable downloading or clear the current SOS symbol settings allowing new symbol paths to be set: - - 0:000> !setsymbolserver -disable -\\ - COMMAND: sosstatus. !SOSStatus [-reset] @@ -2699,8 +2685,3 @@ runtimes [-netfx] [-netcore] List and select the .NET runtimes in the target process. \\ -COMMAND: logging. -logging [enable] [disable] - -Enables or disables the internal trace logging. -\\ diff --git a/src/SOS/Strike/sosdocsunix.txt b/src/SOS/Strike/sosdocsunix.txt index 3310726ca9..d8cb6c2116 100644 --- a/src/SOS/Strike/sosdocsunix.txt +++ b/src/SOS/Strike/sosdocsunix.txt @@ -547,10 +547,16 @@ The arguments in detail: -allReady Specifying this argument will allow for the display of all objects that are ready for finalization, whether they are already marked by - the GC as such, or whether the next GC will. The objects that are - not in the "Ready for finalization" list are finalizable objects that - are no longer rooted. This option can be very expensive, as it - verifies whether all the objects in the finalizable queues are still + the GC as such or not. The former means GC already put them in the + "Ready for finalization" list and their finalizers are ready to run + but haven't run yet. The latter means there is nothing holding onto + these objects but GC hasn't noticed it yet because a GC that collects + the generation this object lives in has not happened yet. When that + GC happens, this object will be moved to the "Ready for finalization" + list. For example, if a finalizable object lives in gen2 and a gen2 GC + has not happened, even if it's displayed by -allReady it's not actually + ready for finalization. This option can be very expensive, as it + verifies whether all the objects in the finalizable queues are still rooted or not. -short Limits the output to just the address of each object. If used in conjunction with -allReady it enumerates all objects that have a @@ -2268,57 +2274,6 @@ You can use the "dotnet --info" in a command shell to find the path of an instal .NET Core runtime. \\ -COMMAND: setsymbolserver. -COMMAND: loadsymbols. -SetSymbolServer [-ms] [-disable] [-log] [-loadsymbols] [-cache ] [-directory ] [-timeout ] [-pat ] [] - --ms - Use the public Microsoft symbol server. --disable - Disable symbol download support. --directory - Directory to search for symbols. Can be more than one. --timeout - Specify the symbol server timeout in minutes --pat - Access token to the authenticated server. --cache - Specific a symbol cache directory. The default is $HOME/.dotnet/symbolcache if not specified. --loadsymbols - Attempts to download the native .NET Core symbols for the runtime - - Symbol server URL. - -This commands enables symbol server support in SOS. The portable PDBs for managed assemblies -and .NET Core native symbol files are downloaded. - -To enable downloading symbols from the Microsoft symbol server: - - (lldb) setsymbolserver -ms - -This command may take some time without any output while it attempts to download the symbol files. - -To disable downloading or clear the current SOS symbol settings allowing new symbol paths to be set: - - (lldb) setsymbolserver -disable - -To add a directory to search for symbols: - - (lldb) setsymbolserver -directory /home/mikem/symbols - -This command can be used so the module/symbol file structure does not have to match the machine -file structure that the core dump was generated. - -To clear the default cache run "rm -r $HOME/.dotnet/symbolcache" in a command shell. - -If you receive an error like the one below on a core dump, you need to set the .NET Core -runtime with the "sethostruntime" command. Type "soshelp sethostruntime" for more details. - - (lldb) setsymbolserver -ms - Error: Fail to initialize CoreCLR 80004005 - SetSymbolServer -ms failed - -The "-loadsymbols" option and the "loadsymbol" command alias attempts to download the native .NET -Core symbol files. It is only useful for live sessions and not core dumps. This command needs to -be run before the lldb "bt" (stack trace) or the "clrstack -f" (dumps both managed and native -stack frames). - - (lldb) loadsymbols - (lldb) bt -\\ - COMMAND: sosstatus. SOSStatus [-reset] @@ -2348,8 +2303,3 @@ runtimes List the .NET runtimes in the target process. \\ -COMMAND: logging. -logging [enable] [disable] - -Enables or disables the internal trace logging. -\\ diff --git a/src/SOS/Strike/strike.cpp b/src/SOS/Strike/strike.cpp index 20feea9e79..0d7aad0d49 100644 --- a/src/SOS/Strike/strike.cpp +++ b/src/SOS/Strike/strike.cpp @@ -70,7 +70,6 @@ #include #endif // !FEATURE_PAL #include - #include "platformspecific.h" #define NOEXTAPI @@ -80,7 +79,6 @@ #undef StackTrace #include - #include #include #include @@ -222,7 +220,7 @@ extern const char* g_sosPrefix; DECLARE_API (MinidumpMode) { - INIT_API (); + INIT_API(); ONLY_SUPPORTED_ON_WINDOWS_TARGET(); DWORD_PTR Value=0; @@ -234,7 +232,7 @@ DECLARE_API (MinidumpMode) size_t nArg; if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } if (nArg == 0) { @@ -269,7 +267,7 @@ DECLARE_API (MinidumpMode) \**********************************************************************/ DECLARE_API(IP2MD) { - INIT_API(); + INIT_API_PROBE_MANAGED("ip2md"); MINIDUMP_NOT_SUPPORTED(); BOOL dml = FALSE; @@ -286,14 +284,14 @@ DECLARE_API(IP2MD) if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); if (IP == 0) { ExtOut("%s is not IP\n", args); - return Status; + return E_INVALIDARG; } CLRDATA_ADDRESS cdaStart = TO_CDADDR(IP); @@ -376,25 +374,6 @@ GetContextStackTrace(ULONG osThreadId, PULONG pnumFrames) return hr; } - -// -// Executes managed extension commands -// -HRESULT ExecuteCommand(PCSTR commandName, PCSTR args) -{ - IHostServices* hostServices = GetHostServices(); - if (hostServices != nullptr) - { - if (commandName != nullptr && strlen(commandName) > 0) - { - return hostServices->DispatchCommand(commandName, args); - } - } - ExtErr("Unrecognized command %s\n", commandName); - return E_NOTIMPL; -} - - /**********************************************************************\ * Routine Description: * * * @@ -457,7 +436,7 @@ void DumpStackInternal(DumpStackFlag *pDSFlag) DECLARE_API(DumpStack) { - INIT_API_NO_RET_ON_FAILURE(); + INIT_API_NO_RET_ON_FAILURE("dumpstack"); MINIDUMP_NOT_SUPPORTED(); @@ -483,7 +462,9 @@ DECLARE_API(DumpStack) }; size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) - return Status; + { + return E_INVALIDARG; + } // symlines will be non-zero only if SYMOPT_LOAD_LINES was set in the symbol options ULONG symlines = 0; @@ -537,7 +518,7 @@ DECLARE_API (EEStack) if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder enableDML(dml); @@ -617,21 +598,6 @@ DECLARE_API (EEStack) return Status; } -/**********************************************************************\ -* Routine Description: * -* * -* This function is called to dump the address and name of all * -* Managed Objects on the stack. * -* * -\**********************************************************************/ -DECLARE_API(DumpStackObjects) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - - return ExecuteCommand("dumpstackobjects", args); -} - /**********************************************************************\ * Routine Description: * * * @@ -641,7 +607,7 @@ DECLARE_API(DumpStackObjects) \**********************************************************************/ DECLARE_API(DumpMD) { - INIT_API(); + INIT_API_PROBE_MANAGED("dumpmd"); MINIDUMP_NOT_SUPPORTED(); DWORD_PTR dwStartAddr = NULL; @@ -659,7 +625,7 @@ DECLARE_API(DumpMD) if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -757,7 +723,7 @@ GetILAddressResult GetILAddress(const DacpMethodDescData& MethodDescData); \**********************************************************************/ DECLARE_API(DumpIL) { - INIT_API(); + INIT_API_PROBE_MANAGED("dumpil"); MINIDUMP_NOT_SUPPORTED(); DWORD_PTR dwStartAddr = NULL; DWORD_PTR dwDynamicMethodObj = NULL; @@ -778,7 +744,7 @@ DECLARE_API(DumpIL) if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -973,12 +939,12 @@ DECLARE_API(DumpSig) size_t nArg; if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } if (nArg != 2) { ExtOut("%sdumpsig \n", SOSPrefix); - return Status; + return E_INVALIDARG; } DWORD_PTR dwSigAddr = GetExpression(sigExpr.data); @@ -1020,13 +986,13 @@ DECLARE_API(DumpSigElem) size_t nArg; if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } if (nArg != 2) { ExtOut("%sdumpsigelem \n", SOSPrefix); - return Status; + return E_INVALIDARG; } DWORD_PTR dwSigAddr = GetExpression(sigExpr.data); @@ -1035,7 +1001,7 @@ DECLARE_API(DumpSigElem) if (dwSigAddr == 0 || dwModuleAddr == 0) { ExtOut("Invalid parameters %s %s\n", sigExpr.data, moduleExpr.data); - return Status; + return E_INVALIDARG; } DumpSigWorker(dwSigAddr, dwModuleAddr, FALSE); @@ -1051,7 +1017,7 @@ DECLARE_API(DumpSigElem) \**********************************************************************/ DECLARE_API(DumpClass) { - INIT_API(); + INIT_API_PROBE_MANAGED("dumpclass"); MINIDUMP_NOT_SUPPORTED(); DWORD_PTR dwStartAddr = 0; @@ -1069,13 +1035,13 @@ DECLARE_API(DumpClass) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } if (nArg == 0) { ExtOut("Missing EEClass address\n"); - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -1170,7 +1136,7 @@ DECLARE_API(DumpMT) DWORD_PTR dwStartAddr=0; DWORD_PTR dwOriginalAddr; - INIT_API(); + INIT_API_PROBE_MANAGED("dumpmt"); MINIDUMP_NOT_SUPPORTED(); @@ -1189,7 +1155,7 @@ DECLARE_API(DumpMT) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -1198,7 +1164,7 @@ DECLARE_API(DumpMT) if (nArg == 0) { Print("Missing MethodTable address\n"); - return Status; + return E_INVALIDARG; } dwOriginalAddr = dwStartAddr; @@ -1207,7 +1173,7 @@ DECLARE_API(DumpMT) if (!IsMethodTable(dwStartAddr)) { Print(dwOriginalAddr, " is not a MethodTable\n"); - return Status; + return E_INVALIDARG; } DacpMethodTableData vMethTable; @@ -1216,7 +1182,7 @@ DECLARE_API(DumpMT) if (vMethTable.bIsFree) { Print("Free MethodTable\n"); - return Status; + return E_INVALIDARG; } DacpMethodTableCollectibleData vMethTableCollectible; @@ -1834,7 +1800,7 @@ HRESULT PrintPermissionSet (TADDR p_PermSet) \**********************************************************************/ DECLARE_API(DumpArray) { - INIT_API(); + INIT_API_PROBE_MANAGED("dumparray"); DumpArrayFlags flags; @@ -1857,7 +1823,7 @@ DECLARE_API(DumpArray) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -1865,7 +1831,7 @@ DECLARE_API(DumpArray) if (p_Object == 0) { ExtOut("Invalid parameter %s\n", flags.strObject); - return Status; + return E_INVALIDARG; } if (!sos::IsObject(p_Object, true)) @@ -2052,7 +2018,7 @@ HRESULT PrintArray(DacpObjectData& objData, DumpArrayFlags& flags, BOOL isPermSe \**********************************************************************/ DECLARE_API(DumpObj) { - INIT_API(); + INIT_API_PROBE_MANAGED("dumpobj"); MINIDUMP_NOT_SUPPORTED(); @@ -2073,7 +2039,7 @@ DECLARE_API(DumpObj) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } DWORD_PTR p_Object = GetExpression(str_Object.data); @@ -2081,7 +2047,7 @@ DECLARE_API(DumpObj) if (p_Object == 0) { ExtOut("Invalid parameter %s\n", args); - return Status; + return E_INVALIDARG; } try { @@ -2129,7 +2095,7 @@ DECLARE_API(DumpALC) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } DWORD_PTR p_Object = GetExpression(str_Object.data); @@ -2137,7 +2103,7 @@ DECLARE_API(DumpALC) if (p_Object == 0) { ExtOut("Invalid parameter %s\n", args); - return Status; + return E_INVALIDARG; } try @@ -2163,7 +2129,7 @@ DECLARE_API(DumpALC) DECLARE_API(DumpDelegate) { - INIT_API(); + INIT_API_PROBE_MANAGED("dumpdelegate"); MINIDUMP_NOT_SUPPORTED(); try @@ -2182,12 +2148,12 @@ DECLARE_API(DumpDelegate) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } if (nArg != 1) { ExtOut("Usage: %sdumpdelegate \n", SOSPrefix); - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -2879,7 +2845,7 @@ HRESULT FormatException(CLRDATA_ADDRESS taObj, BOOL bLineNumbers = FALSE) DECLARE_API(PrintException) { - INIT_API(); + INIT_API_PROBE_MANAGED("printexception"); BOOL dml = FALSE; BOOL bShowNested = FALSE; @@ -2901,7 +2867,7 @@ DECLARE_API(PrintException) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } CheckBreakingRuntimeChange(); @@ -2969,7 +2935,7 @@ DECLARE_API(PrintException) { ExtOut("Invalid exception object %s\n", args); } - return Status; + return E_INVALIDARG; } if (bCCW) @@ -2996,7 +2962,7 @@ DECLARE_API(PrintException) if ((threadAddr == NULL) || (Thread.Request(g_sos, threadAddr) != S_OK)) { ExtOut("The current thread is unmanaged\n"); - return Status; + return E_INVALIDARG; } if (Thread.firstNestedException) @@ -3048,7 +3014,7 @@ DECLARE_API(PrintException) \**********************************************************************/ DECLARE_API(DumpVC) { - INIT_API(); + INIT_API_PROBE_MANAGED("dumpvc"); MINIDUMP_NOT_SUPPORTED(); DWORD_PTR p_MT = NULL; @@ -3067,7 +3033,7 @@ DECLARE_API(DumpVC) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -3109,7 +3075,7 @@ DECLARE_API(DumpRCW) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -3237,7 +3203,7 @@ DECLARE_API(DumpCCW) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -3493,7 +3459,7 @@ DECLARE_API(DumpPermissionSet) size_t nArg; if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } if (nArg!=1) { @@ -3508,18 +3474,6 @@ DECLARE_API(DumpPermissionSet) #endif // _DEBUG #endif // FEATURE_PAL -/**********************************************************************\ -* Routine Description: * -* * -* This function dumps GC heap size. * -* * -\**********************************************************************/ -DECLARE_API(EEHeap) -{ - INIT_API_EXT(); - return ExecuteCommand("eeheap", args); -} - void PrintGCStat(HeapStat *inStat, const char* label=NULL) { if (inStat) @@ -3540,20 +3494,6 @@ void PrintGCStat(HeapStat *inStat, const char* label=NULL) } } -DECLARE_API(TraverseHeap) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - return ExecuteCommand("traverseheap", args); -} - -DECLARE_API(DumpRuntimeTypes) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - return ExecuteCommand("dumpruntimetypes", args); -} - namespace sos { class FragmentationBlock @@ -3591,52 +3531,6 @@ namespace sos }; } -DECLARE_API(DumpHeap) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - return ExecuteCommand("dumpheap", args); -} - -DECLARE_API(VerifyHeap) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - return ExecuteCommand("verifyheap", args); -} - -DECLARE_API(AnalyzeOOM) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - - return ExecuteCommand("analyzeoom", args); -} - -DECLARE_API(VerifyObj) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - - return ExecuteCommand("verifyobj", args); -} - -DECLARE_API(ListNearObj) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - - return ExecuteCommand("listnearobj", args); -} - -DECLARE_API(GCHeapStat) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - - return ExecuteCommand("gcheapstat", args); -} - /**********************************************************************\ * Routine Description: * * * @@ -3665,7 +3559,7 @@ DECLARE_API(SyncBlk) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -3915,21 +3809,6 @@ DECLARE_API(RCWCleanupList) } #endif // FEATURE_COMINTEROP -/**********************************************************************\ -* Routine Description: * -* * -* This function is called to dump the contents of the finalizer * -* queue. * -* * -\**********************************************************************/ -DECLARE_API(FinalizeQueue) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - - return ExecuteCommand("finalizequeue", args); -} - enum { // These are the values set in m_dwTransientFlags. // Note that none of these flags survive a prejit save/restore. @@ -4011,12 +3890,12 @@ DECLARE_API(DumpModule) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } if (nArg != 1) { ExtOut("Usage: DumpModule [-mt] \n"); - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -4182,7 +4061,7 @@ DECLARE_API(DumpDomain) if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -4303,7 +4182,7 @@ DECLARE_API(DumpAssembly) if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -4849,7 +4728,7 @@ DECLARE_API(ThreadState) DECLARE_API(Threads) { - INIT_API(); + INIT_API_PROBE_MANAGED("clrthreads"); BOOL bPrintSpecialThreads = FALSE; BOOL bPrintLiveThreadsOnly = FALSE; @@ -4865,7 +4744,7 @@ DECLARE_API(Threads) }; if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL)) { - return Status; + return E_INVALIDARG; } if (bSwitchToManagedExceptionThread) @@ -5857,10 +5736,9 @@ BOOL CheckCLRNotificationEvent(DEBUG_LAST_EVENT_INFO_EXCEPTION* pdle) return FALSE; } - // The new DAC based interface doesn't exists so ask the debugger for the last exception - // information. NOTE: this function doesn't work on xplat version when the coreclr symbols - // have been stripped. + // The new DAC based interface doesn't exists so ask the debugger for the last exception information. +#ifdef HOST_WINDOWS ULONG Type, ProcessId, ThreadId; ULONG ExtraInformationUsed; Status = g_ExtControl->GetLastEventInformation( @@ -5883,8 +5761,10 @@ BOOL CheckCLRNotificationEvent(DEBUG_LAST_EVENT_INFO_EXCEPTION* pdle) { return FALSE; } - return TRUE; +#else + return FALSE; +#endif } HRESULT HandleCLRNotificationEvent() @@ -5960,7 +5840,7 @@ DECLARE_API(SOSHandleCLRN) HRESULT HandleRuntimeLoadedNotification(IDebugClient* client) { - INIT_API(); + INIT_API_EFN(); EnableModuleLoadUnloadCallbacks(); return g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "sxe -c \"!SOSHandleCLRN\" clrn", 0); } @@ -5969,13 +5849,13 @@ HRESULT HandleRuntimeLoadedNotification(IDebugClient* client) HRESULT HandleExceptionNotification(ILLDBServices *client) { - INIT_API(); + INIT_API_EFN(); return HandleCLRNotificationEvent(); } HRESULT HandleRuntimeLoadedNotification(ILLDBServices *client) { - INIT_API(); + INIT_API_EFN(); EnableModuleLoadUnloadCallbacks(); return g_ExtServices->SetExceptionCallback(HandleExceptionNotification); } @@ -6026,7 +5906,7 @@ DECLARE_API(bpmd) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } bool fBadParam = false; @@ -6358,20 +6238,6 @@ DECLARE_API(bpmd) return Status; } -/**********************************************************************\ -* Routine Description: * -* * -* This function is called to dump the managed threadpool * -* * -\**********************************************************************/ -DECLARE_API(ThreadPool) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - - return ExecuteCommand("threadpool", args); -} - DECLARE_API(FindAppDomain) { INIT_API(); @@ -6392,7 +6258,7 @@ DECLARE_API(FindAppDomain) if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -6642,7 +6508,7 @@ BOOL traverseEh(UINT clauseIndex,UINT totalClauses,DACEHInfo *pEHInfo,LPVOID tok DECLARE_API(EHInfo) { - INIT_API(); + INIT_API_PROBE_MANAGED("ehinfo"); MINIDUMP_NOT_SUPPORTED(); DWORD_PTR dwStartAddr = NULL; @@ -6661,7 +6527,7 @@ DECLARE_API(EHInfo) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg) || (0 == nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -6723,7 +6589,7 @@ DECLARE_API(EHInfo) \**********************************************************************/ DECLARE_API(GCInfo) { - INIT_API(); + INIT_API_PROBE_MANAGED("gcinfo"); MINIDUMP_NOT_SUPPORTED(); TADDR taStartAddr = NULL; @@ -6741,7 +6607,7 @@ DECLARE_API(GCInfo) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg) || (0 == nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -7048,7 +6914,7 @@ DECLARE_API(u) }; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg) || (nArg < 1)) { - return Status; + return E_INVALIDARG; } // symlines will be non-zero only if SYMOPT_LOAD_LINES was set in the symbol options ULONG symlines = 0; @@ -7552,10 +7418,8 @@ HRESULT GetIntermediateLangMap(BOOL bIL, const DacpCodeHeaderData& codeHeaderDat \**********************************************************************/ DECLARE_API(DumpLog) { - INIT_API_NO_RET_ON_FAILURE(); - + INIT_API_NO_RET_ON_FAILURE("dumplog"); MINIDUMP_NOT_SUPPORTED(); - _ASSERTE(g_pRuntime != nullptr); // Not supported on desktop runtime @@ -7583,7 +7447,7 @@ DECLARE_API(DumpLog) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } if (nArg > 0 && sFileName.data != NULL) { @@ -8047,7 +7911,7 @@ extern char sccsid[]; \**********************************************************************/ DECLARE_API(EEVersion) { - INIT_API_NO_RET_ON_FAILURE(); + INIT_API_NO_RET_ON_FAILURE("eeversion"); static const int fileVersionBufferSize = 1024; ArrayHolder fileVersionBuffer = new char[fileVersionBufferSize]; @@ -8138,39 +8002,31 @@ DECLARE_API(EEVersion) \**********************************************************************/ DECLARE_API(SOSStatus) { - INIT_API_NOEE(); + INIT_API_NOEE_PROBE_MANAGED("sosstatus"); - IHostServices* hostServices = GetHostServices(); - if (hostServices != nullptr) + BOOL bReset = FALSE; + CMDOption option[] = + { // name, vptr, type, hasValue + {"-reset", &bReset, COBOOL, FALSE}, + {"--reset", &bReset, COBOOL, FALSE}, + {"-r", &bReset, COBOOL, FALSE}, + }; + if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL)) { - Status = hostServices->DispatchCommand("sosstatus", args); + return E_INVALIDARG; } - else + if (bReset) { - BOOL bReset = FALSE; - CMDOption option[] = - { // name, vptr, type, hasValue - {"-reset", &bReset, COBOOL, FALSE}, - {"--reset", &bReset, COBOOL, FALSE}, - {"-r", &bReset, COBOOL, FALSE}, - }; - if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL)) - { - return Status; - } - if (bReset) + ITarget* target = GetTarget(); + if (target != nullptr) { - ITarget* target = GetTarget(); - if (target != nullptr) - { - target->Flush(); - } - ExtOut("Internal cached state reset\n"); - return S_OK; + target->Flush(); } - Target::DisplayStatus(); + ExtOut("Internal cached state reset\n"); + return S_OK; } - return Status; + Target::DisplayStatus(); + return S_OK; } #ifndef FEATURE_PAL @@ -8484,13 +8340,13 @@ DECLARE_API(Token2EE) size_t nArg; if (!GetCMDOption(args,option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } if (nArg!=2) { ExtOut("Usage: %stoken2ee module_name mdToken\n", SOSPrefix); ExtOut(" You can pass * for module_name to search all modules.\n"); - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -8556,7 +8412,7 @@ DECLARE_API(Token2EE) \**********************************************************************/ DECLARE_API(Name2EE) { - INIT_API(); + INIT_API_PROBE_MANAGED("name2ee"); MINIDUMP_NOT_SUPPORTED(); StringHolder DllName, TypeName; @@ -8576,7 +8432,7 @@ DECLARE_API(Name2EE) if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -8619,7 +8475,7 @@ DECLARE_API(Name2EE) ExtOut(" use * for module_name to search all loaded modules\n"); ExtOut("Examples: %sname2ee mscorlib.dll System.String.ToString\n", SOSPrefix); ExtOut(" %sname2ee *!System.String\n", SOSPrefix); - return Status; + return E_INVALIDARG; } int numModule; @@ -8674,43 +8530,9 @@ DECLARE_API(Name2EE) return Status; } - -DECLARE_API(PathTo) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - - return ExecuteCommand("pathto", args); -} - - -/**********************************************************************\ -* Routine Description: * -* * -* This function finds all roots (on stack or in handles) for a * -* given object. * -* * -\**********************************************************************/ -DECLARE_API(GCRoot) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - - return ExecuteCommand("gcroot", args); -} - -DECLARE_API(GCWhere) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - - return ExecuteCommand("gcwhere", args); -} - - DECLARE_API(FindRoots) { - INIT_API_EXT(); + INIT_API(); MINIDUMP_NOT_SUPPORTED(); if (IsDumpFile()) @@ -8736,7 +8558,7 @@ DECLARE_API(FindRoots) }; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -8938,8 +8760,10 @@ class GCHandlesImpl {"/d", &mDML, COBOOL, FALSE}, }; - if (!GetCMDOption(args,option,ARRAY_SIZE(option),NULL,0,NULL)) + if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL)) + { sos::Throw("Failed to parse command line arguments."); + } if (type != NULL) { if (_stricmp(type, "Pinned") == 0) @@ -9325,7 +9149,7 @@ DECLARE_API(GetCodeTypeFlags) size_t nArg; if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } size_t preg = 1; // by default @@ -9335,7 +9159,7 @@ DECLARE_API(GetCodeTypeFlags) if (preg > 19) { ExtOut("Pseudo-register number must be between 0 and 19\n"); - return Status; + return E_INVALIDARG; } } @@ -9433,19 +9257,19 @@ DECLARE_API(StopOnException) size_t nArg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } if (IsDumpFile()) { ExtOut("Live debugging session required\n"); - return Status; + return E_INVALIDARG; } if (nArg < 1 || nArg > 2) { ExtOut("usage: StopOnException [-derived] [-create | -create2] \n"); ExtOut(" []\n"); ExtOut("ex: StopOnException -create System.OutOfMemoryException 1\n"); - return Status; + return E_INVALIDARG; } size_t preg = 1; // by default @@ -9455,7 +9279,7 @@ DECLARE_API(StopOnException) if (preg > 19) { ExtOut("Pseudo-register number must be between 0 and 19\n"); - return Status; + return E_INVALIDARG; } } @@ -9540,20 +9364,6 @@ DECLARE_API(StopOnException) return Status; } -/**********************************************************************\ -* Routine Description: * -* * -* This function finds the size of an object or all roots. * -* * -\**********************************************************************/ -DECLARE_API(ObjSize) -{ - INIT_API_EXT(); - MINIDUMP_NOT_SUPPORTED(); - - return ExecuteCommand("objsize", args); -} - #ifndef FEATURE_PAL // For FEATURE_PAL, MEMORY_BASIC_INFORMATION64 doesn't exist yet. TODO? DECLARE_API(GCHandleLeaks) @@ -9579,7 +9389,7 @@ DECLARE_API(GCHandleLeaks) if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -11581,7 +11391,7 @@ DECLARE_API(Watch) }; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } if(addExpression.data != NULL || aExpression.data != NULL) @@ -11670,7 +11480,7 @@ DECLARE_API(Watch) DECLARE_API(ClrStack) { - INIT_API(); + INIT_API_PROBE_MANAGED("clrstack"); BOOL bAll = FALSE; BOOL bParams = FALSE; @@ -11714,7 +11524,7 @@ DECLARE_API(ClrStack) }; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } EnableDMLHolder dmlHolder(dml); @@ -11784,7 +11594,7 @@ BOOL IsMemoryInfoAvailable() return TRUE; } -DECLARE_API( VMMap ) +DECLARE_API(VMMap) { INIT_API(); @@ -11798,30 +11608,21 @@ DECLARE_API( VMMap ) } return Status; -} // DECLARE_API( vmmap ) +} #endif // FEATURE_PAL DECLARE_API(SOSFlush) { - INIT_API_NOEE(); + INIT_API_NOEE_PROBE_MANAGED("sosflush"); - IHostServices* hostServices = GetHostServices(); - if (hostServices != nullptr) - { - Status = hostServices->DispatchCommand("sosflush", args); - } - else + ITarget* target = GetTarget(); + if (target != nullptr) { - ITarget* target = GetTarget(); - if (target != nullptr) - { - target->Flush(); - } - ExtOut("Internal cached state reset\n"); - return S_OK; + target->Flush(); } - return Status; + ExtOut("Internal cached state reset\n"); + return S_OK; } #ifndef FEATURE_PAL @@ -11866,16 +11667,16 @@ DECLARE_API(SaveModule) size_t nArg; if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } if (nArg != 2) { ExtOut("Usage: SaveModule
\n"); - return Status; + return E_INVALIDARG; } if (moduleAddr == 0) { ExtOut ("Invalid arg\n"); - return Status; + return E_INVALIDARG; } char* ptr = Location.data; @@ -12056,7 +11857,7 @@ DECLARE_API(dbgout) if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL)) { - return Status; + return E_INVALIDARG; } Output::SetDebugOutputEnabled(!bOff); @@ -12531,7 +12332,7 @@ HRESULT CALLBACK _EFN_StackTrace( size_t uiSizeOfContext, DWORD Flags) { - INIT_API(); + INIT_API_EFN(); Status = ImplementEFNStackTraceTry(client, wszTextOut, puiTextLength, pTransitionContexts, puiTransitionContextCount, @@ -12829,7 +12630,7 @@ DECLARE_API(VerifyStackTrace) if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL,0,NULL)) { - return Status; + return E_INVALIDARG; } if (bVerifyManagedExcepStack) @@ -13035,7 +12836,7 @@ DECLARE_API(SaveState) size_t nArg; if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg)) { - return E_FAIL; + return E_INVALIDARG; } if(nArg == 0) @@ -13075,7 +12876,7 @@ DECLARE_API(SuppressJitOptimization) size_t nArg; if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg)) { - return E_FAIL; + return E_INVALIDARG; } if (nArg == 1 && (_stricmp(onOff.data, "On") == 0)) @@ -13404,7 +13205,7 @@ _EFN_GetManagedExcepStack( ULONG cbString ) { - INIT_API(); + INIT_API_EFN(); ArrayHolder tmpStr = new NOTHROW WCHAR[cbString]; if (tmpStr == NULL) @@ -13435,7 +13236,7 @@ _EFN_GetManagedExcepStackW( ULONG cchString ) { - INIT_API(); + INIT_API_EFN(); return ImplementEFNGetManagedExcepStack(StackObjAddr, wszStackString, cchString); } @@ -13451,7 +13252,7 @@ _EFN_GetManagedObjectName( ULONG cbName ) { - INIT_API (); + INIT_API_EFN(); if (!sos::IsObject(objAddr, false)) { @@ -13480,7 +13281,7 @@ _EFN_GetManagedObjectFieldInfo( PULONG pOffset ) { - INIT_API(); + INIT_API_EFN(); DacpObjectData objData; LPWSTR fieldName = (LPWSTR)alloca(mdNameLen * sizeof(WCHAR)); @@ -13539,7 +13340,7 @@ DECLARE_API(VerifyGMT) if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg)) { - return Status; + return E_INVALIDARG; } } ULONG64 managedThread; @@ -13564,7 +13365,7 @@ _EFN_GetManagedThread( ULONG osThreadId, PULONG64 pManagedThread) { - INIT_API(); + INIT_API_EFN(); _ASSERTE(pManagedThread != nullptr); *pManagedThread = 0; @@ -13622,7 +13423,7 @@ DECLARE_API(SetHostRuntime) size_t narg; if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &narg)) { - return E_FAIL; + return E_INVALIDARG; } if (narg > 0 || bNetCore || bNetFx || bNone) { @@ -13688,41 +13489,31 @@ DECLARE_API(SetHostRuntime) // DECLARE_API(SetClrPath) { - INIT_API_NOEE(); + INIT_API_NODAC_PROBE_MANAGED("setclrpath"); - IHostServices* hostServices = GetHostServices(); - if (hostServices != nullptr) + StringHolder runtimeModulePath; + CMDValue arg[] = { - return hostServices->DispatchCommand("setclrpath", args); + {&runtimeModulePath.data, COSTRING}, + }; + size_t narg; + if (!GetCMDOption(args, nullptr, 0, arg, ARRAY_SIZE(arg), &narg)) + { + return E_FAIL; } - else + if (narg > 0) { - INIT_API_EE(); - - StringHolder runtimeModulePath; - CMDValue arg[] = - { - {&runtimeModulePath.data, COSTRING}, - }; - size_t narg; - if (!GetCMDOption(args, nullptr, 0, arg, ARRAY_SIZE(arg), &narg)) + std::string fullPath; + if (!GetAbsolutePath(runtimeModulePath.data, fullPath)) { + ExtErr("Invalid runtime directory %s\n", fullPath.c_str()); return E_FAIL; } - if (narg > 0) - { - std::string fullPath; - if (!GetAbsolutePath(runtimeModulePath.data, fullPath)) - { - ExtErr("Invalid runtime directory %s\n", fullPath.c_str()); - return E_FAIL; - } - g_pRuntime->SetRuntimeDirectory(fullPath.c_str()); - } - const char* runtimeDirectory = g_pRuntime->GetRuntimeDirectory(); - if (runtimeDirectory != nullptr) { - ExtOut("Runtime module directory: %s\n", runtimeDirectory); - } + g_pRuntime->SetRuntimeDirectory(fullPath.c_str()); + } + const char* runtimeDirectory = g_pRuntime->GetRuntimeDirectory(); + if (runtimeDirectory != nullptr) { + ExtOut("Runtime module directory: %s\n", runtimeDirectory); } return S_OK; } @@ -13732,129 +13523,46 @@ DECLARE_API(SetClrPath) // DECLARE_API(runtimes) { - INIT_API_NOEE(); + INIT_API_NOEE_PROBE_MANAGED("runtimes"); - IHostServices* hostServices = GetHostServices(); - if (hostServices != nullptr) + BOOL bNetFx = FALSE; + BOOL bNetCore = FALSE; + CMDOption option[] = + { // name, vptr, type, hasValue + {"-netfx", &bNetFx, COBOOL, FALSE}, + {"-netcore", &bNetCore, COBOOL, FALSE}, + }; + if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL)) { - Status = hostServices->DispatchCommand("runtimes", args); + return E_INVALIDARG; } - else + if (bNetCore || bNetFx) { - BOOL bNetFx = FALSE; - BOOL bNetCore = FALSE; - CMDOption option[] = - { // name, vptr, type, hasValue - {"-netfx", &bNetFx, COBOOL, FALSE}, - {"-netcore", &bNetCore, COBOOL, FALSE}, - }; - if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL)) - { - return Status; - } - if (bNetCore || bNetFx) - { #ifndef FEATURE_PAL - if (IsWindowsTarget()) - { - PCSTR name = bNetFx ? "desktop .NET Framework" : ".NET Core"; - if (!Target::SwitchRuntime(bNetFx)) - { - ExtErr("The %s runtime is not loaded\n", name); - return E_FAIL; - } - ExtOut("Switched to %s runtime successfully\n", name); - } - else -#endif + if (IsWindowsTarget()) + { + PCSTR name = bNetFx ? "desktop .NET Framework" : ".NET Core"; + if (!Target::SwitchRuntime(bNetFx)) { - ExtErr("The '-netfx' and '-netcore' options are only supported on Windows targets\n"); - return E_FAIL; + ExtErr("The %s runtime is not loaded\n", name); + return E_INVALIDARG; } + ExtOut("Switched to %s runtime successfully\n", name); } else +#endif { - Target::DisplayStatus(); + ExtErr("The '-netfx' and '-netcore' options are only supported on Windows targets\n"); + return E_INVALIDARG; } } - return Status; -} - -#ifdef HOST_WINDOWS -// -// Sets the symbol server path. -// -DECLARE_API(SetSymbolServer) -{ - INIT_API_EXT(); - return ExecuteCommand("setsymbolserver", args); -} - -// -// Dumps the managed assemblies -// -DECLARE_API(clrmodules) -{ - INIT_API_EXT(); - return ExecuteCommand("clrmodules", args); -} - -// -// Dumps async stacks -// -DECLARE_API(DumpAsync) -{ - INIT_API_EXT(); - return ExecuteCommand("dumpasync", args); -} - -// -// Enables and disables managed extension logging -// -DECLARE_API(logging) -{ - INIT_API_EXT(); - return ExecuteCommand("logging", args); -} - -typedef HRESULT (*PFN_COMMAND)(PDEBUG_CLIENT client, PCSTR args); - -// -// Executes managed extension commands -// -DECLARE_API(ext) -{ - INIT_API_EXT(); - - if (args == nullptr || strlen(args) <= 0) - { - args = "Help"; - } - std::string arguments(args); - size_t pos = arguments.find(' '); - std::string commandName = arguments.substr(0, pos); - if (pos != std::string::npos) - { - arguments = arguments.substr(pos + 1); - } else { - arguments.clear(); - } - Status = ExecuteCommand(commandName.c_str(), arguments.c_str()); - if (Status == E_NOTIMPL) - { - PFN_COMMAND commandFunc = (PFN_COMMAND)GetProcAddress(g_hInstance, commandName.c_str()); - if (commandFunc != nullptr) - { - Status = (*commandFunc)(client, arguments.c_str()); - } + Target::DisplayStatus(); } return Status; } -#endif // HOST_WINDOWS - void PrintHelp (__in_z LPCSTR pszCmdName) { static LPSTR pText = NULL; @@ -13959,7 +13667,7 @@ void PrintHelp (__in_z LPCSTR pszCmdName) \**********************************************************************/ DECLARE_API(Help) { - INIT_API_EXT(); + INIT_API_NOEE_PROBE_MANAGED("help"); StringHolder commandName; CMDValue arg[] = @@ -13976,15 +13684,6 @@ DECLARE_API(Help) if (nArg == 1) { - IHostServices* hostServices = GetHostServices(); - if (hostServices != nullptr) - { - if (hostServices->DisplayHelp(commandName.data) == S_OK) - { - return S_OK; - } - } - // Convert commandName to lower-case LPSTR curChar = commandName.data; while (*curChar != '\0') @@ -14006,12 +13705,6 @@ DECLARE_API(Help) else { PrintHelp ("contents"); - IHostServices* hostServices = GetHostServices(); - if (hostServices != nullptr) - { - ExtOut("\n"); - hostServices->DisplayHelp(nullptr); - } } return S_OK; diff --git a/src/SOS/Strike/util.h b/src/SOS/Strike/util.h index 50467dd8ad..8eb3f60e52 100644 --- a/src/SOS/Strike/util.h +++ b/src/SOS/Strike/util.h @@ -1778,8 +1778,6 @@ CLRDATA_ADDRESS GetAppDomain(CLRDATA_ADDRESS objPtr); BOOL IsMTForFreeObj(DWORD_PTR pMT); -HRESULT ExecuteCommand(PCSTR commandName, PCSTR args); - enum ARGTYPE {COBOOL,COSIZE_T,COHEX,COSTRING}; struct CMDOption { diff --git a/src/SOS/extensions/CMakeLists.txt b/src/SOS/extensions/CMakeLists.txt index faff991739..bb4f7bf47f 100644 --- a/src/SOS/extensions/CMakeLists.txt +++ b/src/SOS/extensions/CMakeLists.txt @@ -2,10 +2,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) include(configure.cmake) -if(WIN32) - add_definitions(-MT) -endif(WIN32) - add_definitions(-DPAL_STDCPP_COMPAT) include_directories(${ROOT_DIR}/src/SOS/inc) diff --git a/src/SOS/extensions/hostcoreclr.cpp b/src/SOS/extensions/hostcoreclr.cpp index db940bbf5b..025d6a3f9e 100644 --- a/src/SOS/extensions/hostcoreclr.cpp +++ b/src/SOS/extensions/hostcoreclr.cpp @@ -85,6 +85,8 @@ namespace RuntimeHostingConstants "DOTNET_ROOT_ARM"; #elif defined(HOST_ARM64) "DOTNET_ROOT_ARM64"; +#elif defined(HOST_RISCV64) + "DOTNET_ROOT_RISCV64"; #else "Error"; #error Hosting layer doesn't support target arch @@ -105,6 +107,8 @@ namespace RuntimeHostingConstants "/etc/dotnet/install_location_arm"; #elif defined(HOST_ARM64) "/etc/dotnet/install_location_arm64"; +#elif defined(HOST_RISCV64) + "/etc/dotnet/install_location_riscv64"; #else "ERROR"; #error Hosting layer doesn't support target arch diff --git a/src/SOS/inc/debuggerservices.h b/src/SOS/inc/debuggerservices.h index cc39b44ca6..1ec815f4a0 100644 --- a/src/SOS/inc/debuggerservices.h +++ b/src/SOS/inc/debuggerservices.h @@ -166,6 +166,17 @@ IDebuggerServices : public IUnknown virtual HRESULT STDMETHODCALLTYPE AddModuleSymbol( void* param, const char* symbolFileName) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetLastEventInformation( + PULONG type, + PULONG processId, + PULONG threadId, + PVOID extraInformation, + ULONG extraInformationSize, + PULONG extraInformationUsed, + PSTR description, + ULONG descriptionSize, + PULONG descriptionUsed) = 0; }; #ifdef __cplusplus diff --git a/src/SOS/inc/hostservices.h b/src/SOS/inc/hostservices.h index 788f6f3538..74f1815609 100644 --- a/src/SOS/inc/hostservices.h +++ b/src/SOS/inc/hostservices.h @@ -82,14 +82,6 @@ IHostServices : public IUnknown PCSTR commandName, PCSTR arguments) = 0; - /// - /// Displays the help for a managed extension command - /// - /// - /// error code - virtual HRESULT STDMETHODCALLTYPE DisplayHelp( - PCSTR commandName) = 0; - /// /// Uninitialize the extension infrastructure /// diff --git a/src/SOS/inc/specialdiaginfo.h b/src/SOS/inc/specialdiaginfo.h new file mode 100644 index 0000000000..9dfb1e9145 --- /dev/null +++ b/src/SOS/inc/specialdiaginfo.h @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ****************************************************************************** +// WARNING!!!: This code is also used by createdump in the runtime repo. +// See: https://github.com/dotnet/runtime/blob/main/src/coreclr/debug/createdump/specialdiaginfo.h +// ****************************************************************************** + +// This is a special memory region added to ELF and MachO dumps that contains extra diagnostics +// information like the exception record for a crash for a NativeAOT app. The exception record +// contains the pointer to the JSON formatted crash info. + +#define SPECIAL_DIAGINFO_SIGNATURE "DIAGINFOHEADER" +#define SPECIAL_DIAGINFO_VERSION 1 + +#ifdef __APPLE__ +const uint64_t SpecialDiagInfoAddress = 0x7fffffff10000000; +#else +#if TARGET_64BIT +const uint64_t SpecialDiagInfoAddress = 0x00007ffffff10000; +#else +const uint64_t SpecialDiagInfoAddress = 0x7fff1000; +#endif +#endif + +struct SpecialDiagInfoHeader +{ + char Signature[16]; + int32_t Version; + uint64_t ExceptionRecordAddress; +}; diff --git a/src/SOS/lldbplugin/CMakeLists.txt b/src/SOS/lldbplugin/CMakeLists.txt index 8dd487fd97..49936d30b4 100644 --- a/src/SOS/lldbplugin/CMakeLists.txt +++ b/src/SOS/lldbplugin/CMakeLists.txt @@ -50,6 +50,13 @@ elseif(CLR_CMAKE_HOST_ARCH_MIPS64) add_definitions(-DDBG_TARGET_WIN64=1) add_definitions(-DBIT64) SET(REQUIRE_LLDBPLUGIN false) +elseif(CLR_CMAKE_HOST_ARCH_RISCV64) + add_definitions(-D_TARGET_RISCV64_=1) + add_definitions(-DDBG_TARGET_64BIT=1) + add_definitions(-DDBG_TARGET_RISCV64=1) + add_definitions(-DDBG_TARGET_WIN64=1) + add_definitions(-DBIT64) + SET(REQUIRE_LLDBPLUGIN false) endif() if(NOT $ENV{LLVM_HOME} STREQUAL "") diff --git a/src/SOS/lldbplugin/services.cpp b/src/SOS/lldbplugin/services.cpp index e23dc1e382..1ecc095710 100644 --- a/src/SOS/lldbplugin/services.cpp +++ b/src/SOS/lldbplugin/services.cpp @@ -471,12 +471,6 @@ LLDBServices::Execute( return status <= lldb::eReturnStatusSuccessContinuingResult ? S_OK : E_FAIL; } -// PAL raise exception function and exception record pointer variable name -// See coreclr\src\pal\src\exception\seh-unwind.cpp for the details. This -// function depends on RtlpRaisException not being inlined or optimized. -#define FUNCTION_NAME "RtlpRaiseException" -#define VARIABLE_NAME "ExceptionRecord" - HRESULT LLDBServices::GetLastEventInformation( PULONG type, @@ -489,8 +483,7 @@ LLDBServices::GetLastEventInformation( ULONG descriptionSize, PULONG descriptionUsed) { - if (extraInformationSize < sizeof(DEBUG_LAST_EVENT_INFO_EXCEPTION) || - type == NULL || processId == NULL || threadId == NULL || extraInformationUsed == NULL) + if (type == NULL || processId == NULL || threadId == NULL) { return E_INVALIDARG; } @@ -498,10 +491,25 @@ LLDBServices::GetLastEventInformation( *type = DEBUG_EVENT_EXCEPTION; *processId = 0; *threadId = 0; - *extraInformationUsed = sizeof(DEBUG_LAST_EVENT_INFO_EXCEPTION); + + if (extraInformationUsed != nullptr) + { + *extraInformationUsed = sizeof(DEBUG_LAST_EVENT_INFO_EXCEPTION); + } + + if (extraInformation == nullptr) + { + return S_OK; + } + + if (extraInformationSize < sizeof(DEBUG_LAST_EVENT_INFO_EXCEPTION)) + { + return E_INVALIDARG; + } DEBUG_LAST_EVENT_INFO_EXCEPTION *pdle = (DEBUG_LAST_EVENT_INFO_EXCEPTION *)extraInformation; pdle->FirstChance = 1; + lldb::SBError error; lldb::SBProcess process = GetCurrentProcess(); if (!process.IsValid()) @@ -518,47 +526,31 @@ LLDBServices::GetLastEventInformation( *processId = GetProcessId(process); *threadId = GetThreadId(thread); - // Enumerate each stack frame at the special "throw" - // breakpoint and find the raise exception function - // with the exception record parameter. - int numFrames = thread.GetNumFrames(); - for (int i = 0; i < numFrames; i++) + SpecialDiagInfoHeader header; + size_t read = process.ReadMemory(SpecialDiagInfoAddress, &header, sizeof(header), error); + if (error.Fail() || read != sizeof(header)) { - lldb::SBFrame frame = thread.GetFrameAtIndex(i); - if (!frame.IsValid()) - { - break; - } - - const char *functionName = frame.GetFunctionName(); - if (functionName == NULL || strncmp(functionName, FUNCTION_NAME, sizeof(FUNCTION_NAME) - 1) != 0) - { - continue; - } - - lldb::SBValue exValue = frame.FindVariable(VARIABLE_NAME); - if (!exValue.IsValid()) - { - break; - } - - lldb::SBError error; - ULONG64 pExceptionRecord = exValue.GetValueAsUnsigned(error); - if (error.Fail()) - { - break; - } - - process.ReadMemory(pExceptionRecord, &pdle->ExceptionRecord, sizeof(pdle->ExceptionRecord), error); - if (error.Fail()) - { - break; - } - - return S_OK; + Output(DEBUG_OUTPUT_WARNING, "Special diagnostics info read failed\n"); + return E_FAIL; + } + if (strncmp(header.Signature, SPECIAL_DIAGINFO_SIGNATURE, sizeof(SPECIAL_DIAGINFO_SIGNATURE)) != 0) + { + Output(DEBUG_OUTPUT_WARNING, "Special diagnostics info signature invalid\n"); + return E_FAIL; + } + if (header.Version < SPECIAL_DIAGINFO_VERSION || header.ExceptionRecordAddress == 0) + { + Output(DEBUG_OUTPUT_WARNING, "No exception record in special diagnostics info\n"); + return E_FAIL; + } + read = process.ReadMemory(header.ExceptionRecordAddress, &pdle->ExceptionRecord, sizeof(pdle->ExceptionRecord), error); + if (error.Fail() || read != sizeof(pdle->ExceptionRecord)) + { + Output(DEBUG_OUTPUT_WARNING, "Exception record in special diagnostics info read failed\n"); + return E_FAIL; } - return E_FAIL; + return S_OK; } HRESULT diff --git a/src/SOS/lldbplugin/soscommand.cpp b/src/SOS/lldbplugin/soscommand.cpp index 7f79ec8bcb..95847b9bce 100644 --- a/src/SOS/lldbplugin/soscommand.cpp +++ b/src/SOS/lldbplugin/soscommand.cpp @@ -157,10 +157,12 @@ sosCommandInitialize(lldb::SBDebugger debugger) g_services->AddCommand("ext", new sosCommand(nullptr), "Executes various coreclr debugging commands. Use the syntax 'sos '. For more information, see 'soshelp'."); g_services->AddManagedCommand("analyzeoom", "Provides a stack trace of managed code only."); g_services->AddCommand("bpmd", new sosCommand("bpmd"), "Creates a breakpoint at the specified managed method in the specified module."); + g_services->AddManagedCommand("assemblies", "Lists the managed modules in the process."); g_services->AddManagedCommand("clrmodules", "Lists the managed modules in the process."); g_services->AddCommand("clrstack", new sosCommand("ClrStack"), "Provides a stack trace of managed code only."); g_services->AddCommand("clrthreads", new sosCommand("Threads"), "Lists the managed threads running."); g_services->AddCommand("clru", new sosCommand("u"), "Displays an annotated disassembly of a managed method."); + g_services->AddManagedCommand("crashinfo", "Displays the Native AOT crash info."); g_services->AddCommand("dbgout", new sosCommand("dbgout"), "Enables/disables (-off) internal SOS logging."); g_services->AddCommand("dumpalc", new sosCommand("DumpALC"), "Displays details about a collectible AssemblyLoadContext to which the specified object is loaded."); g_services->AddCommand("dumparray", new sosCommand("DumpArray"), "Displays details about a managed array."); @@ -203,12 +205,12 @@ sosCommandInitialize(lldb::SBDebugger debugger) g_services->AddCommand("histroot", new sosCommand("HistRoot"), "Displays information related to both promotions and relocations of the specified root."); g_services->AddCommand("histstats", new sosCommand("HistStats"), "Displays stress log stats."); g_services->AddCommand("ip2md", new sosCommand("IP2MD"), "Displays the MethodDesc structure at the specified address in code that has been JIT-compiled."); - g_services->AddCommand("listnearobj", new sosCommand("ListNearObj"), "Displays the object preceding and succeeding the specified address."); + g_services->AddManagedCommand("listnearobj", "Displays the object preceding and succeeding the specified address."); g_services->AddManagedCommand("loadsymbols", "Loads the .NET Core native module symbols."); g_services->AddManagedCommand("logging", "Enables/disables internal SOS logging."); g_services->AddCommand("name2ee", new sosCommand("Name2EE"), "Displays the MethodTable structure and EEClass structure for the specified type or method in the specified module."); g_services->AddManagedCommand("objsize", "Displays the size of the specified object."); - g_services->AddCommand("pathto", new sosCommand("PathTo"), "Displays the GC path from to ."); + g_services->AddManagedCommand("pathto", "Displays the GC path from to ."); g_services->AddCommand("pe", new sosCommand("PrintException"), "Displays and formats fields of any object derived from the Exception class at the specified address."); g_services->AddCommand("printexception", new sosCommand("PrintException"), "Displays and formats fields of any object derived from the Exception class at the specified address."); g_services->AddCommand("runtimes", new sosCommand("runtimes"), "Lists the runtimes in the target or change the default runtime."); @@ -224,5 +226,6 @@ sosCommandInitialize(lldb::SBDebugger debugger) g_services->AddCommand("token2ee", new sosCommand("token2ee"), "Displays the MethodTable structure and MethodDesc structure for the specified token and module."); g_services->AddManagedCommand("verifyheap", "Checks the GC heap for signs of corruption."); g_services->AddManagedCommand("verifyobj", "Checks the object that is passed as an argument for signs of corruption."); + g_services->AddManagedCommand("traverseheap", "Writes out heap information to a file in a format understood by the CLR Profiler."); return true; } diff --git a/src/SOS/lldbplugin/sosplugin.h b/src/SOS/lldbplugin/sosplugin.h index ad3b2395e1..4f387de027 100644 --- a/src/SOS/lldbplugin/sosplugin.h +++ b/src/SOS/lldbplugin/sosplugin.h @@ -9,6 +9,7 @@ #include "lldbservices.h" #include "extensions.h" #include "dbgtargetcontext.h" +#include "specialdiaginfo.h" #include "specialthreadinfo.h" #include "services.h" diff --git a/src/SOS/runcommand/CMakeLists.txt b/src/SOS/runcommand/CMakeLists.txt index 469d802405..c9b492de8a 100644 --- a/src/SOS/runcommand/CMakeLists.txt +++ b/src/SOS/runcommand/CMakeLists.txt @@ -7,9 +7,6 @@ include_directories("$ENV{VSInstallDir}/DIA SDK/include") add_definitions(-DUSE_STL) -#use static crt -add_definitions(-MT) - set(RUNCOMMAND_SOURCES runcommand.cpp ) diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index a2a623d0a6..04159229ab 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -6,45 +6,31 @@ using System.CommandLine; using System.CommandLine.IO; using System.CommandLine.Rendering; +using System.ComponentModel; using System.Diagnostics; -using System.Diagnostics.Tracing; -using System.Globalization; -using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Diagnostics.Monitoring; using Microsoft.Diagnostics.Monitoring.EventPipe; using Microsoft.Diagnostics.NETCore.Client; using Microsoft.Diagnostics.Tools.Counters.Exporters; -using Microsoft.Diagnostics.Tracing; using Microsoft.Internal.Common.Utils; namespace Microsoft.Diagnostics.Tools.Counters { - public class CounterMonitor + internal class CounterMonitor : ICountersLogger { private const int BufferDelaySecs = 1; - private const string SharedSessionId = "SHARED"; // This should be identical to the one used by dotnet-monitor in MetricSourceConfiguration.cs - private static HashSet inactiveSharedSessions = new(StringComparer.OrdinalIgnoreCase); - - private string _sessionId; private int _processId; - private int _interval; private CounterSet _counterList; - private CancellationToken _ct; private IConsole _console; private ICounterRenderer _renderer; private string _output; private bool _pauseCmdSet; - private readonly TaskCompletionSource _shouldExit; - private bool _resumeRuntime; + private readonly TaskCompletionSource _shouldExit; private DiagnosticsClient _diagnosticsClient; - private EventPipeSession _session; - private readonly string _clientId; - private int _maxTimeSeries; - private int _maxHistograms; - private TimeSpan _duration; + private MetricsPipelineSettings _settings; private class ProviderEventState { @@ -57,77 +43,7 @@ private class ProviderEventState public CounterMonitor() { _pauseCmdSet = false; - _clientId = Guid.NewGuid().ToString(); - - _shouldExit = new TaskCompletionSource(); - } - - private void DynamicAllMonitor(TraceEvent obj) - { - if (_shouldExit.Task.IsCompleted) - { - return; - } - - lock (this) - { - // If we are paused, ignore the event. - // There's a potential race here between the two tasks but not a huge deal if we miss by one event. - _renderer.ToggleStatus(_pauseCmdSet); - - // If a session received a MultipleSessionsConfiguredIncorrectlyError, ignore future shared events - if (obj.ProviderName == "System.Diagnostics.Metrics" && !inactiveSharedSessions.Contains(_clientId)) - { - if (obj.EventName == "BeginInstrumentReporting") - { - HandleBeginInstrumentReporting(obj); - } - if (obj.EventName == "HistogramValuePublished") - { - HandleHistogram(obj); - } - else if (obj.EventName == "GaugeValuePublished") - { - HandleGauge(obj); - } - else if (obj.EventName == "CounterRateValuePublished") - { - HandleCounterRate(obj); - } - else if (obj.EventName == "UpDownCounterRateValuePublished") - { - HandleUpDownCounterValue(obj); - } - else if (obj.EventName == "TimeSeriesLimitReached") - { - HandleTimeSeriesLimitReached(obj); - } - else if (obj.EventName == "HistogramLimitReached") - { - HandleHistogramLimitReached(obj); - } - else if (obj.EventName == "Error") - { - HandleError(obj); - } - else if (obj.EventName == "ObservableInstrumentCallbackError") - { - HandleObservableInstrumentCallbackError(obj); - } - else if (obj.EventName == "MultipleSessionsNotSupportedError") - { - HandleMultipleSessionsNotSupportedError(obj); - } - else if (obj.EventName == "MultipleSessionsConfiguredIncorrectlyError") - { - HandleMultipleSessionsConfiguredIncorrectlyError(obj); - } - } - else if (obj.EventName == "EventCounters") - { - HandleDiagnosticCounter(obj); - } - } + _shouldExit = new TaskCompletionSource(); } private void MeterInstrumentEventObserved(string meterName, DateTime timestamp) @@ -147,255 +63,16 @@ private void MeterInstrumentEventObserved(string meterName, DateTime timestamp) } } - private void HandleBeginInstrumentReporting(TraceEvent obj) - { - string sessionId = (string)obj.PayloadValue(0); - string meterName = (string)obj.PayloadValue(1); - // string instrumentName = (string)obj.PayloadValue(3); - if (sessionId != _sessionId) - { - return; - } - MeterInstrumentEventObserved(meterName, obj.TimeStamp); - } - - private void HandleCounterRate(TraceEvent obj) - { - string sessionId = (string)obj.PayloadValue(0); - string meterName = (string)obj.PayloadValue(1); - //string meterVersion = (string)obj.PayloadValue(2); - string instrumentName = (string)obj.PayloadValue(3); - string unit = (string)obj.PayloadValue(4); - string tags = (string)obj.PayloadValue(5); - string rateText = (string)obj.PayloadValue(6); - if (sessionId != _sessionId || !Filter(meterName, instrumentName)) - { - return; - } - MeterInstrumentEventObserved(meterName, obj.TimeStamp); - - // the value might be an empty string indicating no measurement was provided this collection interval - if (double.TryParse(rateText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double rate)) - { - CounterPayload payload = new RatePayload(meterName, instrumentName, null, unit, tags, rate, _interval, obj.TimeStamp); - _renderer.CounterPayloadReceived(payload, _pauseCmdSet); - } - - } - - private void HandleGauge(TraceEvent obj) - { - string sessionId = (string)obj.PayloadValue(0); - string meterName = (string)obj.PayloadValue(1); - //string meterVersion = (string)obj.PayloadValue(2); - string instrumentName = (string)obj.PayloadValue(3); - string unit = (string)obj.PayloadValue(4); - string tags = (string)obj.PayloadValue(5); - string lastValueText = (string)obj.PayloadValue(6); - if (sessionId != _sessionId || !Filter(meterName, instrumentName)) - { - return; - } - MeterInstrumentEventObserved(meterName, obj.TimeStamp); - - // the value might be an empty string indicating no measurement was provided this collection interval - if (double.TryParse(lastValueText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double lastValue)) - { - CounterPayload payload = new GaugePayload(meterName, instrumentName, null, unit, tags, lastValue, obj.TimeStamp); - _renderer.CounterPayloadReceived(payload, _pauseCmdSet); - } - else - { - // for observable instruments we assume the lack of data is meaningful and remove it from the UI - CounterPayload payload = new RatePayload(meterName, instrumentName, null, unit, tags, 0, _interval, obj.TimeStamp); - _renderer.CounterStopped(payload); - } - } - - private void HandleUpDownCounterValue(TraceEvent obj) - { - if (obj.Version < 1) // Version 1 added the value field. - { - return; - } - - string sessionId = (string)obj.PayloadValue(0); - string meterName = (string)obj.PayloadValue(1); - //string meterVersion = (string)obj.PayloadValue(2); - string instrumentName = (string)obj.PayloadValue(3); - string unit = (string)obj.PayloadValue(4); - string tags = (string)obj.PayloadValue(5); - //string rateText = (string)obj.PayloadValue(6); // Not currently using rate for UpDownCounters. - string valueText = (string)obj.PayloadValue(7); - if (sessionId != _sessionId || !Filter(meterName, instrumentName)) - { - return; - } - MeterInstrumentEventObserved(meterName, obj.TimeStamp); - - // the value might be an empty string indicating no measurement was provided this collection interval - if (double.TryParse(valueText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) - { - // UpDownCounter reports the value, not the rate - this is different than how Counter behaves, and is thus treated as a gauge. - CounterPayload payload = new GaugePayload(meterName, instrumentName, null, unit, tags, value, obj.TimeStamp); - _renderer.CounterPayloadReceived(payload, _pauseCmdSet); - } - else - { - // for observable instruments we assume the lack of data is meaningful and remove it from the UI - CounterPayload payload = new RatePayload(meterName, instrumentName, null, unit, tags, 0, _interval, obj.TimeStamp); - _renderer.CounterStopped(payload); - } - } - - private void HandleHistogram(TraceEvent obj) - { - string sessionId = (string)obj.PayloadValue(0); - string meterName = (string)obj.PayloadValue(1); - //string meterVersion = (string)obj.PayloadValue(2); - string instrumentName = (string)obj.PayloadValue(3); - string unit = (string)obj.PayloadValue(4); - string tags = (string)obj.PayloadValue(5); - string quantilesText = (string)obj.PayloadValue(6); - if (sessionId != _sessionId || !Filter(meterName, instrumentName)) - { - return; - } - MeterInstrumentEventObserved(meterName, obj.TimeStamp); - KeyValuePair[] quantiles = ParseQuantiles(quantilesText); - foreach ((double key, double val) in quantiles) - { - CounterPayload payload = new PercentilePayload(meterName, instrumentName, null, unit, AppendQuantile(tags, $"Percentile={key * 100}"), val, obj.TimeStamp); - _renderer.CounterPayloadReceived(payload, _pauseCmdSet); - } - } - - private void HandleHistogramLimitReached(TraceEvent obj) - { - string sessionId = (string)obj.PayloadValue(0); - if (sessionId != _clientId) - { - return; - } - _renderer.SetErrorText( - $"Warning: Histogram tracking limit ({_maxHistograms}) reached. Not all data is being shown." + Environment.NewLine + - "The limit can be changed with --maxHistograms but will use more memory in the target process." - ); - } - - private void HandleTimeSeriesLimitReached(TraceEvent obj) - { - string sessionId = (string)obj.PayloadValue(0); - if (sessionId != _sessionId) - { - return; - } - _renderer.SetErrorText( - $"Warning: Time series tracking limit ({_maxTimeSeries}) reached. Not all data is being shown." + Environment.NewLine + - "The limit can be changed with --maxTimeSeries but will use more memory in the target process." - ); - } - - private void HandleError(TraceEvent obj) - { - string sessionId = (string)obj.PayloadValue(0); - string error = (string)obj.PayloadValue(1); - if (sessionId != _sessionId) - { - return; - } - _renderer.SetErrorText( - "Error reported from target process:" + Environment.NewLine + - error - ); - _shouldExit.TrySetResult((int)ReturnCode.TracingError); - } - - private void HandleObservableInstrumentCallbackError(TraceEvent obj) - { - string sessionId = (string)obj.PayloadValue(0); - string error = (string)obj.PayloadValue(1); - if (sessionId != _sessionId) - { - return; - } - _renderer.SetErrorText( - "Exception thrown from an observable instrument callback in the target process:" + Environment.NewLine + - error - ); - } - - private void HandleMultipleSessionsNotSupportedError(TraceEvent obj) - { - string runningSessionId = (string)obj.PayloadValue(0); - if (runningSessionId == _sessionId) - { - // If our session is the one that is running then the error is not for us, - // it is for some other session that came later - return; - } - _renderer.SetErrorText( - "Error: Another metrics collection session is already in progress for the target process." + Environment.NewLine + - "Concurrent sessions are not supported."); - _shouldExit.TrySetResult((int)ReturnCode.SessionCreationError); - } - - private void HandleMultipleSessionsConfiguredIncorrectlyError(TraceEvent obj) - { - if (TraceEventExtensions.TryCreateSharedSessionConfiguredIncorrectlyMessage(obj, _clientId, out string message)) - { - _renderer.SetErrorText(message); - inactiveSharedSessions.Add(_clientId); - _shouldExit.TrySetResult((int)ReturnCode.SessionCreationError); - } - } - - private static KeyValuePair[] ParseQuantiles(string quantileList) - { - string[] quantileParts = quantileList.Split(';', StringSplitOptions.RemoveEmptyEntries); - List> quantiles = new(); - foreach (string quantile in quantileParts) - { - string[] keyValParts = quantile.Split('=', StringSplitOptions.RemoveEmptyEntries); - if (keyValParts.Length != 2) - { - continue; - } - if (!double.TryParse(keyValParts[0], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double key)) - { - continue; - } - if (!double.TryParse(keyValParts[1], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double val)) - { - continue; - } - quantiles.Add(new KeyValuePair(key, val)); - } - return quantiles.ToArray(); - } - - private static string AppendQuantile(string tags, string quantile) => string.IsNullOrEmpty(tags) ? quantile : $"{tags},{quantile}"; - - private void HandleDiagnosticCounter(TraceEvent obj) + private void HandleDiagnosticCounter(ICounterPayload payload) { - IDictionary payloadVal = (IDictionary)(obj.PayloadValue(0)); - IDictionary payloadFields = (IDictionary)(payloadVal["Payload"]); - - // If it's not a counter we asked for, ignore it. - string name = payloadFields["Name"].ToString(); - if (!_counterList.Contains(obj.ProviderName, name)) - { - return; - } - // init providerEventState if this is the first time we've seen an event from this provider - if (!_providerEventStates.TryGetValue(obj.ProviderName, out ProviderEventState providerState)) + if (!_providerEventStates.TryGetValue(payload.Provider, out ProviderEventState providerState)) { providerState = new ProviderEventState() { - FirstReceiveTimestamp = obj.TimeStamp + FirstReceiveTimestamp = payload.Timestamp }; - _providerEventStates.Add(obj.ProviderName, providerState); + _providerEventStates.Add(payload.Provider, providerState); } // we give precedence to instrument events over diagnostic counter events. If we are seeing @@ -405,42 +82,35 @@ private void HandleDiagnosticCounter(TraceEvent obj) return; } - CounterPayload payload; - if (payloadFields["CounterType"].Equals("Sum")) - { - payload = new RatePayload( - obj.ProviderName, - name, - payloadFields["DisplayName"].ToString(), - payloadFields["DisplayUnits"].ToString(), - null, - (double)payloadFields["Increment"], - _interval, - obj.TimeStamp); - } - else - { - payload = new GaugePayload( - obj.ProviderName, - name, - payloadFields["DisplayName"].ToString(), - payloadFields["DisplayUnits"].ToString(), - null, - (double)payloadFields["Mean"], - obj.TimeStamp); - } - // If we saw the first event for this provider recently then a duplicate instrument event may still be // coming. We'll buffer this event for a while and then render it if it remains unduplicated for // a while. // This is all best effort, if we do show the DiagnosticCounter event and then an instrument event shows up - // later the renderer may obsserve some odd behavior like changes in the counter metadata, oddly timed reporting + // later the renderer may observe some odd behavior like changes in the counter metadata, oddly timed reporting // intervals, or counters that stop reporting. // I'm gambling this is good enough that the behavior will never be seen in practice, but if it is we could // either adjust the time delay or try to improve how the renderers handle it. - if (providerState.FirstReceiveTimestamp + TimeSpan.FromSeconds(BufferDelaySecs) >= obj.TimeStamp) + if (providerState.FirstReceiveTimestamp + TimeSpan.FromSeconds(BufferDelaySecs) >= payload.Timestamp) + { + _bufferedEvents.Enqueue((CounterPayload)payload); + } + else { - _bufferedEvents.Enqueue(payload); + CounterPayloadReceived((CounterPayload)payload); + } + } + + private void CounterPayloadReceived(CounterPayload payload) + { + if (payload is AggregatePercentilePayload aggregatePayload) + { + foreach (Quantile quantile in aggregatePayload.Quantiles) + { + (double key, double val) = quantile; + PercentilePayload percentilePayload = new(payload.Provider, payload.Name, payload.DisplayName, payload.Unit, AppendQuantile(payload.Metadata, $"Percentile={key * 100}"), val, payload.Timestamp); + _renderer.CounterPayloadReceived(percentilePayload, _pauseCmdSet); + } + } else { @@ -448,6 +118,8 @@ private void HandleDiagnosticCounter(TraceEvent obj) } } + private static string AppendQuantile(string tags, string quantile) => string.IsNullOrEmpty(tags) ? quantile : $"{tags},{quantile}"; + // when receiving DiagnosticCounter events we may have buffered them to wait for // duplicate instrument events. If we've waited long enough then we should remove // them from the buffer and render them. @@ -459,7 +131,7 @@ private void HandleBufferedEvents() while (_bufferedEvents.Count != 0) { CounterPayload payload = _bufferedEvents.Peek(); - ProviderEventState providerEventState = _providerEventStates[payload.ProviderName]; + ProviderEventState providerEventState = _providerEventStates[payload.Provider]; if (providerEventState.InstrumentEventObserved) { _bufferedEvents.Dequeue(); @@ -467,7 +139,7 @@ private void HandleBufferedEvents() else if (providerEventState.FirstReceiveTimestamp + TimeSpan.FromSeconds(BufferDelaySecs) < now) { _bufferedEvents.Dequeue(); - _renderer.CounterPayloadReceived(payload, _pauseCmdSet); + CounterPayloadReceived((CounterPayload)payload); } else { @@ -481,37 +153,7 @@ private void HandleBufferedEvents() } } - private void StopMonitor() - { - try - { - _session?.Stop(); - } - catch (EndOfStreamException ex) - { - // If the app we're monitoring exits abruptly, this may throw in which case we just swallow the exception and exit gracefully. - Debug.WriteLine($"[ERROR] {ex}"); - } - // We may time out if the process ended before we sent StopTracing command. We can just exit in that case. - catch (TimeoutException) - { - } - // On Unix platforms, we may actually get a PNSE since the pipe is gone with the process, and Runtime Client Library - // does not know how to distinguish a situation where there is no pipe to begin with, or where the process has exited - // before dotnet-counters and got rid of a pipe that once existed. - // Since we are catching this in StopMonitor() we know that the pipe once existed (otherwise the exception would've - // been thrown in StartMonitor directly) - catch (PlatformNotSupportedException) - { - } - // On non-abrupt exits, the socket may be already closed by the runtime and we won't be able to send a stop request through it. - catch (ServerNotAvailableException) - { - } - _renderer.Stop(); - } - - public async Task Monitor( + public async Task Monitor( CancellationToken ct, List counter_list, string counters, @@ -535,7 +177,7 @@ public async Task Monitor( ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries)); if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ValidateArgumentsForAttach(processId, name, diagnosticPort, out _processId)) { - return (int)ReturnCode.ArgumentError; + return ReturnCode.ArgumentError; } ct.Register(() => _shouldExit.TrySetResult((int)ReturnCode.Ok)); @@ -546,7 +188,7 @@ public async Task Monitor( bool useAnsi = vTerm.IsEnabled; if (holder == null) { - return (int)ReturnCode.Ok; + return ReturnCode.Ok; } try { @@ -554,38 +196,48 @@ public async Task Monitor( // the launch command may misinterpret app arguments as the old space separated // provider list so we need to ignore it in that case _counterList = ConfigureCounters(counters, _processId != 0 ? counter_list : null); - _ct = ct; - _interval = refreshInterval; - _maxHistograms = maxHistograms; - _maxTimeSeries = maxTimeSeries; _renderer = new ConsoleWriter(useAnsi); _diagnosticsClient = holder.Client; - _resumeRuntime = resumeRuntime; - _duration = duration; - int ret = await Start().ConfigureAwait(false); + _settings = new MetricsPipelineSettings(); + _settings.Duration = duration == TimeSpan.Zero ? Timeout.InfiniteTimeSpan : duration; + _settings.MaxHistograms = maxHistograms; + _settings.MaxTimeSeries = maxTimeSeries; + _settings.CounterIntervalSeconds = refreshInterval; + _settings.ResumeRuntime = resumeRuntime; + _settings.CounterGroups = GetEventPipeProviders(); + + bool useSharedSession = false; + if (_diagnosticsClient.GetProcessInfo().TryGetProcessClrVersion(out Version version)) + { + useSharedSession = version.Major >= 8 ? true : false; + } + _settings.UseSharedSession = useSharedSession; + + ReturnCode ret; + MetricsPipeline eventCounterPipeline = new(holder.Client, _settings, new[] { this }); + await using (eventCounterPipeline.ConfigureAwait(false)) + { + ret = await Start(eventCounterPipeline, ct).ConfigureAwait(false); + } ProcessLauncher.Launcher.Cleanup(); return ret; } catch (OperationCanceledException) { - try - { - _session.Stop(); - } - catch (Exception) { } // Swallow all exceptions for now. + //Cancellation token should automatically stop the session console.Out.WriteLine($"Complete"); - return (int)ReturnCode.Ok; + return ReturnCode.Ok; } } } catch (CommandLineErrorException e) { console.Error.WriteLine(e.Message); - return (int)ReturnCode.ArgumentError; + return ReturnCode.ArgumentError; } } - public async Task Collect( + public async Task Collect( CancellationToken ct, List counter_list, string counters, @@ -611,7 +263,7 @@ public async Task Collect( ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries)); if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ValidateArgumentsForAttach(processId, name, diagnosticPort, out _processId)) { - return (int)ReturnCode.ArgumentError; + return ReturnCode.ArgumentError; } ct.Register(() => _shouldExit.TrySetResult((int)ReturnCode.Ok)); @@ -629,17 +281,19 @@ public async Task Collect( // the launch command may misinterpret app arguments as the old space separated // provider list so we need to ignore it in that case _counterList = ConfigureCounters(counters, _processId != 0 ? counter_list : null); - _ct = ct; - _interval = refreshInterval; - _maxHistograms = maxHistograms; - _maxTimeSeries = maxTimeSeries; + _settings = new MetricsPipelineSettings(); + _settings.Duration = duration == TimeSpan.Zero ? Timeout.InfiniteTimeSpan : duration; + _settings.MaxHistograms = maxHistograms; + _settings.MaxTimeSeries = maxTimeSeries; + _settings.CounterIntervalSeconds = refreshInterval; + _settings.ResumeRuntime = resumeRuntime; + _settings.CounterGroups = GetEventPipeProviders(); _output = output; _diagnosticsClient = holder.Client; - _duration = duration; if (_output.Length == 0) { _console.Error.WriteLine("Output cannot be an empty string"); - return (int)ReturnCode.ArgumentError; + return ReturnCode.ArgumentError; } if (format == CountersExportFormat.csv) { @@ -663,27 +317,29 @@ public async Task Collect( else { _console.Error.WriteLine($"The output format {format} is not a valid output format."); - return (int)ReturnCode.ArgumentError; + return ReturnCode.ArgumentError; } - _resumeRuntime = resumeRuntime; - int ret = await Start().ConfigureAwait(false); + + ReturnCode ret; + MetricsPipeline eventCounterPipeline = new(holder.Client, _settings, new[] { this }); + await using (eventCounterPipeline.ConfigureAwait(false)) + { + ret = await Start(pipeline: eventCounterPipeline, ct).ConfigureAwait(false); + } + return ret; } catch (OperationCanceledException) { - try - { - _session.Stop(); - } - catch (Exception) { } // session.Stop() can throw if target application already stopped before we send the stop command. - return (int)ReturnCode.Ok; + //Cancellation token should automatically stop the session + return ReturnCode.Ok; } } } catch (CommandLineErrorException e) { console.Error.WriteLine(e.Message); - return (int)ReturnCode.ArgumentError; + return ReturnCode.ArgumentError; } } @@ -833,85 +489,21 @@ private static void ParseCounterProvider(string providerText, CounterSet counter } } - private EventPipeProvider[] GetEventPipeProviders() - { - // EventSources support EventCounter based metrics directly - IEnumerable eventCounterProviders = _counterList.Providers.Select( - providerName => new EventPipeProvider(providerName, EventLevel.Error, 0, new Dictionary() - {{ "EventCounterIntervalSec", _interval.ToString() }})); - - //System.Diagnostics.Metrics EventSource supports the new Meter/Instrument APIs - const long TimeSeriesValues = 0x2; - StringBuilder metrics = new(); - foreach (string provider in _counterList.Providers) + private EventPipeCounterGroup[] GetEventPipeProviders() => + _counterList.Providers.Select(provider => new EventPipeCounterGroup { - if (metrics.Length != 0) - { - metrics.Append(','); - } - if (_counterList.IncludesAllCounters(provider)) - { - metrics.Append(provider); - } - else - { - string[] providerCounters = _counterList.GetCounters(provider).Select(counter => $"{provider}\\{counter}").ToArray(); - metrics.Append(string.Join(',', providerCounters)); - } - } + ProviderName = provider, + CounterNames = _counterList.GetCounters(provider).ToArray() + }).ToArray(); - // Shared Session Id was added in 8.0 - older runtimes will not properly support it. - _sessionId = Guid.NewGuid().ToString(); - if (_diagnosticsClient.GetProcessInfo().TryGetProcessClrVersion(out Version version)) - { - _sessionId = version.Major >= 8 ? SharedSessionId : _sessionId; - } - - EventPipeProvider metricsEventSourceProvider = - new("System.Diagnostics.Metrics", EventLevel.Informational, TimeSeriesValues, - new Dictionary() - { - { "SessionId", _sessionId }, - { "Metrics", metrics.ToString() }, - { "RefreshInterval", _interval.ToString() }, - { "MaxTimeSeries", _maxTimeSeries.ToString() }, - { "MaxHistograms", _maxHistograms.ToString() }, - { "ClientId", _clientId } - } - ); - - return eventCounterProviders.Append(metricsEventSourceProvider).ToArray(); - } - - private bool Filter(string meterName, string instrumentName) - { - return _counterList.GetCounters(meterName).Contains(instrumentName) || _counterList.IncludesAllCounters(meterName); - } - - private Task Start() + private async Task Start(MetricsPipeline pipeline, CancellationToken token) { - EventPipeProvider[] providers = GetEventPipeProviders(); _renderer.Initialize(); - - Task monitorTask = new(() => { + Task monitorTask = new(async () => { try { - _session = _diagnosticsClient.StartEventPipeSession(providers, false, 10); - if (_resumeRuntime) - { - try - { - _diagnosticsClient.ResumeRuntime(); - } - catch (UnsupportedCommandException) - { - // Noop if the command is unknown since the target process is most likely a 3.1 app. - } - } - EventPipeEventSource source = new(_session.EventStream); - source.Dynamic.All += DynamicAllMonitor; - _renderer.EventPipeSourceConnected(); - source.Process(); + Task runAsyncTask = await pipeline.StartAsync(token).ConfigureAwait(false); + await runAsyncTask.ConfigureAwait(false); } catch (DiagnosticsClientException ex) { @@ -928,15 +520,8 @@ private Task Start() }); monitorTask.Start(); - bool shouldStopAfterDuration = _duration != default(TimeSpan); - Stopwatch durationStopwatch = null; - if (shouldStopAfterDuration) - { - durationStopwatch = Stopwatch.StartNew(); - } - - while (!_shouldExit.Task.Wait(250)) + while (!_shouldExit.Task.Wait(250, token)) { HandleBufferedEvents(); if (!Console.IsInputRedirected && Console.KeyAvailable) @@ -955,16 +540,107 @@ private Task Start() _pauseCmdSet = false; } } + } + + try + { + await pipeline.StopAsync(token).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } + catch (PipelineException) + { + } + + return await _shouldExit.Task.ConfigureAwait(false); + } + + void ICountersLogger.Log(ICounterPayload payload) + { + if (_shouldExit.Task.IsCompleted) + { + return; + } - if (shouldStopAfterDuration && durationStopwatch.Elapsed >= _duration) + lock (this) + { + // If we are paused, ignore the event. + // There's a potential race here between the two tasks but not a huge deal if we miss by one event. + _renderer.ToggleStatus(_pauseCmdSet); + if (payload is ErrorPayload errorPayload) { - durationStopwatch.Stop(); - break; + // Several of the error messages used by Dotnet are specific to the tool; + // the error messages found in errorPayload.ErrorMessage are not tool-specific. + // This replaces the generic error messages with specific ones as-needed. + string errorMessage = string.Empty; + switch (errorPayload.EventType) + { + case EventType.HistogramLimitError: + errorMessage = $"Warning: Histogram tracking limit ({_settings.MaxHistograms}) reached. Not all data is being shown." + Environment.NewLine + + "The limit can be changed with --maxHistograms but will use more memory in the target process."; + break; + case EventType.TimeSeriesLimitError: + errorMessage = $"Warning: Time series tracking limit ({_settings.MaxTimeSeries}) reached. Not all data is being shown." + Environment.NewLine + + "The limit can be changed with --maxTimeSeries but will use more memory in the target process."; + break; + case EventType.ErrorTargetProcess: + case EventType.MultipleSessionsNotSupportedError: + case EventType.MultipleSessionsConfiguredIncorrectlyError: + case EventType.ObservableInstrumentCallbackError: + default: + errorMessage = errorPayload.ErrorMessage; + break; + } + + _renderer.SetErrorText(errorMessage); + + if (errorPayload.EventType.IsSessionStartupError()) + { + _shouldExit.TrySetResult(ReturnCode.SessionCreationError); + } + else if (errorPayload.EventType.IsTracingError()) + { + _shouldExit.TrySetResult(ReturnCode.TracingError); + } + else if (errorPayload.EventType.IsNonFatalError()) + { + // Don't need to exit for NonFatalError + } + else + { + _shouldExit.TrySetResult(ReturnCode.UnknownError); + } + } + else if (payload is CounterEndedPayload counterEnded) + { + _renderer.CounterStopped(counterEnded); + } + else if (payload.IsMeter) + { + MeterInstrumentEventObserved(payload.Provider, payload.Timestamp); + if (payload.EventType.IsValuePublishedEvent()) + { + CounterPayloadReceived((CounterPayload)payload); + } + } + else + { + HandleDiagnosticCounter(payload); } } + } + + public Task PipelineStarted(CancellationToken token) + { + _renderer.EventPipeSourceConnected(); + return Task.CompletedTask; + } - StopMonitor(); - return _shouldExit.Task; + public Task PipelineStopped(CancellationToken token) + { + _renderer.Stop(); + return Task.CompletedTask; } } } diff --git a/src/Tools/dotnet-counters/CounterPayload.cs b/src/Tools/dotnet-counters/CounterPayload.cs deleted file mode 100644 index a7863bd143..0000000000 --- a/src/Tools/dotnet-counters/CounterPayload.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; - -namespace Microsoft.Diagnostics.Tools.Counters -{ - public class CounterPayload - { - public CounterPayload(string providerName, string name, string displayName, string displayUnits, string tags, double value, DateTime timestamp, string type) - { - ProviderName = providerName; - Name = name; - Tags = tags; - Value = value; - Timestamp = timestamp; - CounterType = type; - } - - public string ProviderName { get; private set; } - public string Name { get; private set; } - public double Value { get; private set; } - public virtual string DisplayName { get; protected set; } - public string CounterType { get; private set; } - public DateTime Timestamp { get; private set; } - public string Tags { get; private set; } - } - - internal class GaugePayload : CounterPayload - { - public GaugePayload(string providerName, string name, string displayName, string displayUnits, string tags, double value, DateTime timestamp) : - base(providerName, name, displayName, displayUnits, tags, value, timestamp, "Metric") - { - // In case these properties are not provided, set them to appropriate values. - string counterName = string.IsNullOrEmpty(displayName) ? name : displayName; - DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName; - } - } - - internal class RatePayload : CounterPayload - { - public RatePayload(string providerName, string name, string displayName, string displayUnits, string tags, double value, double intervalSecs, DateTime timestamp) : - base(providerName, name, displayName, displayUnits, tags, value, timestamp, "Rate") - { - // In case these properties are not provided, set them to appropriate values. - string counterName = string.IsNullOrEmpty(displayName) ? name : displayName; - string unitsName = string.IsNullOrEmpty(displayUnits) ? "Count" : displayUnits; - string intervalName = intervalSecs.ToString() + " sec"; - DisplayName = $"{counterName} ({unitsName} / {intervalName})"; - } - } - - internal class PercentilePayload : CounterPayload - { - public PercentilePayload(string providerName, string name, string displayName, string displayUnits, string tags, double val, DateTime timestamp) : - base(providerName, name, displayName, displayUnits, tags, val, timestamp, "Metric") - { - // In case these properties are not provided, set them to appropriate values. - string counterName = string.IsNullOrEmpty(displayName) ? name : displayName; - DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName; - } - } -} diff --git a/src/Tools/dotnet-counters/CounterPayloadExtensions.cs b/src/Tools/dotnet-counters/CounterPayloadExtensions.cs new file mode 100644 index 0000000000..974e054c96 --- /dev/null +++ b/src/Tools/dotnet-counters/CounterPayloadExtensions.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.Monitoring.EventPipe; + +namespace Microsoft.Diagnostics.Tools.Counters +{ + internal static class CounterPayloadExtensions + { + public static string GetDisplay(this ICounterPayload counterPayload) + { + if (!counterPayload.IsMeter) + { + string unit = counterPayload.Unit == "count" ? "Count" : counterPayload.Unit; + if (counterPayload.CounterType == CounterType.Rate) + { + return $"{counterPayload.DisplayName} ({unit} / {counterPayload.Series} sec)"; + } + if (!string.IsNullOrEmpty(counterPayload.Unit)) + { + return $"{counterPayload.DisplayName} ({unit})"; + } + } + + return $"{counterPayload.DisplayName}"; + } + } +} diff --git a/src/Tools/dotnet-counters/Exporters/CSVExporter.cs b/src/Tools/dotnet-counters/Exporters/CSVExporter.cs index cdf760edb4..eb4399de96 100644 --- a/src/Tools/dotnet-counters/Exporters/CSVExporter.cs +++ b/src/Tools/dotnet-counters/Exporters/CSVExporter.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.IO; using System.Text; +using Microsoft.Diagnostics.Monitoring.EventPipe; namespace Microsoft.Diagnostics.Tools.Counters.Exporters { @@ -70,11 +71,11 @@ public void CounterPayloadReceived(CounterPayload payload, bool _) builder .Append(payload.Timestamp.ToString()).Append(',') - .Append(payload.ProviderName).Append(',') - .Append(payload.DisplayName); - if (!string.IsNullOrEmpty(payload.Tags)) + .Append(payload.Provider).Append(',') + .Append(payload.GetDisplay()); + if (!string.IsNullOrEmpty(payload.Metadata)) { - builder.Append('[').Append(payload.Tags.Replace(',', ';')).Append(']'); + builder.Append('[').Append(payload.Metadata.Replace(',', ';')).Append(']'); } builder.Append(',') .Append(payload.CounterType).Append(',') diff --git a/src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs b/src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs index 76f06931f7..8795ba4e73 100644 --- a/src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs +++ b/src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using Microsoft.Diagnostics.Monitoring.EventPipe; namespace Microsoft.Diagnostics.Tools.Counters.Exporters { @@ -12,7 +13,7 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters /// ConsoleWriter is an implementation of ICounterRenderer for rendering the counter values in real-time /// to the console. This is the renderer for the `dotnet-counters monitor` command. /// - public class ConsoleWriter : ICounterRenderer + internal class ConsoleWriter : ICounterRenderer { /// Information about an observed provider. private class ObservedProvider @@ -257,9 +258,9 @@ public void CounterPayloadReceived(CounterPayload payload, bool pauseCmdSet) return; } - string providerName = payload.ProviderName; + string providerName = payload.Provider; string name = payload.Name; - string tags = payload.Tags; + string tags = payload.Metadata; bool redraw = false; if (!_providers.TryGetValue(providerName, out ObservedProvider provider)) @@ -270,7 +271,7 @@ public void CounterPayloadReceived(CounterPayload payload, bool pauseCmdSet) if (!provider.Counters.TryGetValue(name, out ObservedCounter counter)) { - string displayName = payload.DisplayName; + string displayName = payload.GetDisplay(); provider.Counters[name] = counter = new ObservedCounter(displayName); _maxNameLength = Math.Max(_maxNameLength, displayName.Length); if (tags != null) @@ -313,9 +314,9 @@ public void CounterStopped(CounterPayload payload) { lock (_lock) { - string providerName = payload.ProviderName; + string providerName = payload.Provider; string counterName = payload.Name; - string tags = payload.Tags; + string tags = payload.Metadata; if (!_providers.TryGetValue(providerName, out ObservedProvider provider)) { diff --git a/src/Tools/dotnet-counters/Exporters/ICounterRenderer.cs b/src/Tools/dotnet-counters/Exporters/ICounterRenderer.cs index d8389b2ec7..2bd4ca254f 100644 --- a/src/Tools/dotnet-counters/Exporters/ICounterRenderer.cs +++ b/src/Tools/dotnet-counters/Exporters/ICounterRenderer.cs @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Diagnostics.Monitoring.EventPipe; + namespace Microsoft.Diagnostics.Tools.Counters.Exporters { - public interface ICounterRenderer + internal interface ICounterRenderer { void Initialize(); void EventPipeSourceConnected(); diff --git a/src/Tools/dotnet-counters/Exporters/JSONExporter.cs b/src/Tools/dotnet-counters/Exporters/JSONExporter.cs index 8abadbbeb7..ec571ad7ba 100644 --- a/src/Tools/dotnet-counters/Exporters/JSONExporter.cs +++ b/src/Tools/dotnet-counters/Exporters/JSONExporter.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.IO; using System.Text; +using Microsoft.Diagnostics.Monitoring.EventPipe; namespace Microsoft.Diagnostics.Tools.Counters.Exporters { @@ -72,10 +73,10 @@ public void CounterPayloadReceived(CounterPayload payload, bool _) } builder .Append("{ \"timestamp\": \"").Append(DateTime.Now.ToString("u")).Append("\", ") - .Append(" \"provider\": \"").Append(JsonEscape(payload.ProviderName)).Append("\", ") - .Append(" \"name\": \"").Append(JsonEscape(payload.DisplayName)).Append("\", ") - .Append(" \"tags\": \"").Append(JsonEscape(payload.Tags)).Append("\", ") - .Append(" \"counterType\": \"").Append(JsonEscape(payload.CounterType)).Append("\", ") + .Append(" \"provider\": \"").Append(JsonEscape(payload.Provider)).Append("\", ") + .Append(" \"name\": \"").Append(JsonEscape(payload.GetDisplay())).Append("\", ") + .Append(" \"tags\": \"").Append(JsonEscape(payload.Metadata)).Append("\", ") + .Append(" \"counterType\": \"").Append(JsonEscape(payload.CounterType.ToString())).Append("\", ") .Append(" \"value\": ").Append(payload.Value.ToString(CultureInfo.InvariantCulture)).Append(" },"); } } diff --git a/src/Tools/dotnet-counters/Program.cs b/src/Tools/dotnet-counters/Program.cs index ec79a2e6ef..d1fde42e6f 100644 --- a/src/Tools/dotnet-counters/Program.cs +++ b/src/Tools/dotnet-counters/Program.cs @@ -21,7 +21,7 @@ public enum CountersExportFormat { csv, json }; internal static class Program { - private delegate Task CollectDelegate( + private delegate Task CollectDelegate( CancellationToken ct, List counter_list, string counters, @@ -37,7 +37,7 @@ private delegate Task CollectDelegate( int maxTimeSeries, TimeSpan duration); - private delegate Task MonitorDelegate( + private delegate Task MonitorDelegate( CancellationToken ct, List counter_list, string counters, diff --git a/src/Tools/dotnet-dsrouter/ADBTcpRouterFactory.cs b/src/Tools/dotnet-dsrouter/ADBTcpRouterFactory.cs index 5177e6b307..4cb9284ead 100644 --- a/src/Tools/dotnet-dsrouter/ADBTcpRouterFactory.cs +++ b/src/Tools/dotnet-dsrouter/ADBTcpRouterFactory.cs @@ -13,64 +13,64 @@ namespace Microsoft.Diagnostics.Tools.DiagnosticsServerRouter { internal static class ADBCommandExec { - public static bool AdbAddPortForward(int port, ILogger logger) + public static bool AdbAddPortForward(int localPort, int remotePort, bool rethrow, ILogger logger) { bool ownsPortForward = false; - if (!RunAdbCommandInternal($"forward --list", $"tcp:{port}", 0, logger)) + if (!RunAdbCommandInternal($"forward --list", $"tcp:{localPort}", 0, rethrow, logger)) { - ownsPortForward = RunAdbCommandInternal($"forward tcp:{port} tcp:{port}", "", 0, logger); + ownsPortForward = RunAdbCommandInternal($"forward tcp:{localPort} tcp:{remotePort}", "", 0, rethrow, logger); if (!ownsPortForward) { - logger?.LogError($"Failed setting up port forward for tcp:{port}."); + logger?.LogError($"Failed setting up port forward for host tcp:{localPort} <-> device tcp:{remotePort}."); } } return ownsPortForward; } - public static bool AdbAddPortReverse(int port, ILogger logger) + public static bool AdbAddPortReverse(int localPort, int remotePort, bool rethrow, ILogger logger) { bool ownsPortForward = false; - if (!RunAdbCommandInternal($"reverse --list", $"tcp:{port}", 0, logger)) + if (!RunAdbCommandInternal($"reverse --list", $"tcp:{remotePort}", 0, rethrow, logger)) { - ownsPortForward = RunAdbCommandInternal($"reverse tcp:{port} tcp:{port}", "", 0, logger); + ownsPortForward = RunAdbCommandInternal($"reverse tcp:{remotePort} tcp:{localPort}", "", 0, rethrow, logger); if (!ownsPortForward) { - logger?.LogError($"Failed setting up port forward for tcp:{port}."); + logger?.LogError($"Failed setting up port forward for host tcp:{localPort} <-> device tcp:{remotePort}."); } } return ownsPortForward; } - public static void AdbRemovePortForward(int port, bool ownsPortForward, ILogger logger) + public static void AdbRemovePortForward(int localPort, int remotePort, bool ownsPortForward, bool rethrow, ILogger logger) { if (ownsPortForward) { - if (!RunAdbCommandInternal($"forward --remove tcp:{port}", "", 0, logger)) + if (!RunAdbCommandInternal($"forward --remove tcp:{localPort}", "", 0, rethrow, logger)) { - logger?.LogError($"Failed removing port forward for tcp:{port}."); + logger?.LogError($"Failed setting up port forward for host tcp:{localPort} <-> device tcp:{remotePort}."); } } } - public static void AdbRemovePortReverse(int port, bool ownsPortForward, ILogger logger) + public static void AdbRemovePortReverse(int localPort, int remotePort, bool ownsPortForward, bool rethrow, ILogger logger) { if (ownsPortForward) { - if (!RunAdbCommandInternal($"reverse --remove tcp:{port}", "", 0, logger)) + if (!RunAdbCommandInternal($"reverse --remove tcp:{remotePort}", "", 0, rethrow, logger)) { - logger?.LogError($"Failed removing port forward for tcp:{port}."); + logger?.LogError($"Failed setting up port forward for host tcp:{localPort} <-> device tcp:{remotePort}."); } } } - public static bool RunAdbCommandInternal(string command, string expectedOutput, int expectedExitCode, ILogger logger) + public static bool RunAdbCommandInternal(string command, string expectedOutput, int expectedExitCode, bool rethrow, ILogger logger) { string sdkRoot = Environment.GetEnvironmentVariable("ANDROID_SDK_ROOT"); string adbTool = "adb"; if (!string.IsNullOrEmpty(sdkRoot)) { - adbTool = sdkRoot + Path.DirectorySeparatorChar + "platform-tools" + Path.DirectorySeparatorChar + adbTool; + adbTool = Path.Combine(sdkRoot, "platform-tools", adbTool); } logger?.LogDebug($"Executing {adbTool} {command}."); @@ -94,7 +94,11 @@ public static bool RunAdbCommandInternal(string command, string expectedOutput, } catch (Exception ex) { - logger.LogError($"Failed executing {adbTool} {command}. Error: {ex.Message}."); + logger.LogError($"Failed executing {adbTool} {command}. Error: {ex.Message}"); + if (rethrow) + { + throw ex; + } } if (processStartedResult) @@ -116,10 +120,7 @@ public static bool RunAdbCommandInternal(string command, string expectedOutput, { logger.LogError($"stderr: {stderr.TrimEnd()}"); } - } - if (processStartedResult) - { process.WaitForExit(); expectedExitCodeResult = (expectedExitCode != -1) ? (process.ExitCode == expectedExitCode) : true; } @@ -130,7 +131,8 @@ public static bool RunAdbCommandInternal(string command, string expectedOutput, internal sealed class ADBTcpServerRouterFactory : TcpServerRouterFactory { - private readonly int _port; + private readonly int _localPort; + private readonly int _remotePort; private bool _ownsPortReverse; private Task _portReverseTask; private CancellationTokenSource _portReverseTaskCancelToken; @@ -143,13 +145,32 @@ public static TcpServerRouterFactory CreateADBInstance(string tcpServer, int run public ADBTcpServerRouterFactory(string tcpServer, int runtimeTimeoutMs, ILogger logger) : base(tcpServer, runtimeTimeoutMs, logger) { - _port = new IpcTcpSocketEndPoint(tcpServer).EndPoint.Port; + _localPort = new IpcTcpSocketEndPoint(tcpServer).EndPoint.Port; + _remotePort = _localPort - 1; + + if (_remotePort <= 0) + { + throw new ArgumentException($"Invalid local/remote TCP endpoint ports {_localPort}/{_remotePort}."); + } } public override void Start() { // Enable port reverse. - _ownsPortReverse = ADBCommandExec.AdbAddPortReverse(_port, Logger); + try + { + _ownsPortReverse = ADBCommandExec.AdbAddPortReverse(_localPort, _remotePort, true, Logger); + } + catch + { + _ownsPortReverse = false; + Logger.LogError("Failed setting up adb port reverse." + + " This might lead to problems communicating with Android application." + + " Make sure env variable ANDROID_SDK_ROOT is set and points to an Android SDK." + + $" Executing with unknown adb status for port {_localPort}."); + base.Start(); + return; + } _portReverseTaskCancelToken = new CancellationTokenSource(); _portReverseTask = Task.Run(async () => { @@ -157,7 +178,7 @@ public override void Start() while (await timer.WaitForNextTickAsync(_portReverseTaskCancelToken.Token).ConfigureAwait(false) && !_portReverseTaskCancelToken.Token.IsCancellationRequested) { // Make sure reverse port configuration is still active. - if (ADBCommandExec.AdbAddPortReverse(_port, Logger) && !_ownsPortReverse) + if (ADBCommandExec.AdbAddPortReverse(_localPort, _remotePort, false, Logger) && !_ownsPortReverse) { _ownsPortReverse = true; } @@ -179,14 +200,15 @@ public override async Task Stop() catch { } // Disable port reverse. - ADBCommandExec.AdbRemovePortReverse(_port, _ownsPortReverse, Logger); + ADBCommandExec.AdbRemovePortReverse(_localPort, _remotePort, _ownsPortReverse, false, Logger); _ownsPortReverse = false; } } internal sealed class ADBTcpClientRouterFactory : TcpClientRouterFactory { - private readonly int _port; + private readonly int _localPort; + private readonly int _remotePort; private bool _ownsPortForward; private Task _portForwardTask; private CancellationTokenSource _portForwardTaskCancelToken; @@ -199,13 +221,31 @@ public static TcpClientRouterFactory CreateADBInstance(string tcpClient, int run public ADBTcpClientRouterFactory(string tcpClient, int runtimeTimeoutMs, ILogger logger) : base(tcpClient, runtimeTimeoutMs, logger) { - _port = new IpcTcpSocketEndPoint(tcpClient).EndPoint.Port; + _localPort = new IpcTcpSocketEndPoint(tcpClient).EndPoint.Port; + _remotePort = _localPort - 1; + + if (_remotePort <= 0) + { + throw new ArgumentException($"Invalid local/remote TCP endpoint ports {_localPort}/{_remotePort}."); + } } public override void Start() { // Enable port forwarding. - _ownsPortForward = ADBCommandExec.AdbAddPortForward(_port, _logger); + try + { + _ownsPortForward = ADBCommandExec.AdbAddPortForward(_localPort, _remotePort, true, Logger); + } + catch + { + _ownsPortForward = false; + Logger.LogError("Failed setting up adb port forward." + + " This might lead to problems communicating with Android application." + + " Make sure env variable ANDROID_SDK_ROOT is set and points to an Android SDK." + + $" Executing with unknown adb status for port {_localPort}."); + return; + } _portForwardTaskCancelToken = new CancellationTokenSource(); _portForwardTask = Task.Run(async () => { @@ -213,7 +253,7 @@ public override void Start() while (await timer.WaitForNextTickAsync(_portForwardTaskCancelToken.Token).ConfigureAwait(false) && !_portForwardTaskCancelToken.Token.IsCancellationRequested) { // Make sure forward port configuration is still active. - if (ADBCommandExec.AdbAddPortForward(_port, _logger) && !_ownsPortForward) + if (ADBCommandExec.AdbAddPortForward(_localPort, _remotePort, false, Logger) && !_ownsPortForward) { _ownsPortForward = true; } @@ -231,7 +271,7 @@ public override void Stop() catch { } // Disable port forwarding. - ADBCommandExec.AdbRemovePortForward(_port, _ownsPortForward, _logger); + ADBCommandExec.AdbRemovePortForward(_localPort, _remotePort, _ownsPortForward, false, Logger); _ownsPortForward = false; } } diff --git a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs index 5b83ceda81..213c801990 100644 --- a/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs +++ b/src/Tools/dotnet-dsrouter/DiagnosticsServerRouterCommands.cs @@ -72,27 +72,8 @@ protected SpecificRunnerBase(LogLevel logLevel) LogLevel = logLevel; } - protected SpecificRunnerBase(string logLevel) : this(ParseLogLevel(logLevel)) - { - } - public abstract void ConfigureLauncher(CancellationToken cancellationToken); - protected static LogLevel ParseLogLevel(string verbose) - { - LogLevel logLevel = LogLevel.Information; - if (string.Equals(verbose, "debug", StringComparison.OrdinalIgnoreCase)) - { - logLevel = LogLevel.Debug; - } - else if (string.Equals(verbose, "trace", StringComparison.OrdinalIgnoreCase)) - { - logLevel = LogLevel.Trace; - } - - return logLevel; - } - // The basic run loop: configure logging and the launcher, then create the router and run it until it exits or the user interrupts public async Task CommonRunLoop(Func> createRouterTask, CancellationToken token) { @@ -103,9 +84,11 @@ public async Task CommonRunLoop(Func routerTask = createRouterTask(logger, Launcher, linkedCancelToken); @@ -147,13 +130,13 @@ await Task.WhenAny(routerTask, Task.Delay( private sealed class IpcClientTcpServerRunner : SpecificRunnerBase { - public IpcClientTcpServerRunner(string verbose) : base(verbose) { } + public IpcClientTcpServerRunner(LogLevel logLevel) : base(logLevel) { } public override void ConfigureLauncher(CancellationToken cancellationToken) { Launcher.SuspendProcess = true; Launcher.ConnectMode = true; - Launcher.Verbose = LogLevel != LogLevel.Information; + Launcher.Verbose = LogLevel < LogLevel.Information; Launcher.CommandToken = cancellationToken; } @@ -169,9 +152,11 @@ public override ILoggerFactory ConfigureLogging() public async Task RunIpcClientTcpServerRouter(CancellationToken token, string ipcClient, string tcpServer, int runtimeTimeout, string verbose, string forwardPort) { - checkLoopbackOnly(tcpServer); + LogLevel logLevel = ParseLogLevel(verbose); - IpcClientTcpServerRunner runner = new(verbose); + checkLoopbackOnly(tcpServer, logLevel); + + IpcClientTcpServerRunner runner = new(logLevel); return await runner.CommonRunLoop((logger, launcherCallbacks, linkedCancelToken) => { NetServerRouterFactory.CreateInstanceDelegate tcpServerRouterFactory = ChooseTcpServerRouterFactory(forwardPort, logger); @@ -183,22 +168,24 @@ public async Task RunIpcClientTcpServerRouter(CancellationToken token, stri private sealed class IpcServerTcpServerRunner : SpecificRunnerBase { - public IpcServerTcpServerRunner(string verbose) : base(verbose) { } + public IpcServerTcpServerRunner(LogLevel logLevel) : base(logLevel) { } public override void ConfigureLauncher(CancellationToken cancellationToken) { Launcher.SuspendProcess = false; Launcher.ConnectMode = true; - Launcher.Verbose = LogLevel != LogLevel.Information; + Launcher.Verbose = LogLevel < LogLevel.Information; Launcher.CommandToken = cancellationToken; } } public async Task RunIpcServerTcpServerRouter(CancellationToken token, string ipcServer, string tcpServer, int runtimeTimeout, string verbose, string forwardPort) { - checkLoopbackOnly(tcpServer); + LogLevel logLevel = ParseLogLevel(verbose); + + checkLoopbackOnly(tcpServer, logLevel); - IpcServerTcpServerRunner runner = new(verbose); + IpcServerTcpServerRunner runner = new(logLevel); return await runner.CommonRunLoop((logger, launcherCallbacks, linkedCancelToken) => { NetServerRouterFactory.CreateInstanceDelegate tcpServerRouterFactory = ChooseTcpServerRouterFactory(forwardPort, logger); @@ -215,20 +202,20 @@ public async Task RunIpcServerTcpServerRouter(CancellationToken token, stri private sealed class IpcServerTcpClientRunner : SpecificRunnerBase { - public IpcServerTcpClientRunner(string verbose) : base(verbose) { } + public IpcServerTcpClientRunner(LogLevel logLevel) : base(logLevel) { } public override void ConfigureLauncher(CancellationToken cancellationToken) { Launcher.SuspendProcess = false; Launcher.ConnectMode = false; - Launcher.Verbose = LogLevel != LogLevel.Information; + Launcher.Verbose = LogLevel < LogLevel.Information; Launcher.CommandToken = cancellationToken; } } public async Task RunIpcServerTcpClientRouter(CancellationToken token, string ipcServer, string tcpClient, int runtimeTimeout, string verbose, string forwardPort) { - IpcServerTcpClientRunner runner = new(verbose); + IpcServerTcpClientRunner runner = new(ParseLogLevel(verbose)); return await runner.CommonRunLoop((logger, launcherCallbacks, linkedCancelToken) => { TcpClientRouterFactory.CreateInstanceDelegate tcpClientRouterFactory = ChooseTcpClientRouterFactory(forwardPort, logger); @@ -244,20 +231,20 @@ public async Task RunIpcServerTcpClientRouter(CancellationToken token, stri private sealed class IpcClientTcpClientRunner : SpecificRunnerBase { - public IpcClientTcpClientRunner(string verbose) : base(verbose) { } + public IpcClientTcpClientRunner(LogLevel logLevel) : base(logLevel) { } public override void ConfigureLauncher(CancellationToken cancellationToken) { Launcher.SuspendProcess = true; Launcher.ConnectMode = false; - Launcher.Verbose = LogLevel != LogLevel.Information; + Launcher.Verbose = LogLevel < LogLevel.Information; Launcher.CommandToken = cancellationToken; } } public async Task RunIpcClientTcpClientRouter(CancellationToken token, string ipcClient, string tcpClient, int runtimeTimeout, string verbose, string forwardPort) { - IpcClientTcpClientRunner runner = new(verbose); + IpcClientTcpClientRunner runner = new(ParseLogLevel(verbose)); return await runner.CommonRunLoop((logger, launcherCallbacks, linkedCancelToken) => { TcpClientRouterFactory.CreateInstanceDelegate tcpClientRouterFactory = ChooseTcpClientRouterFactory(forwardPort, logger); @@ -268,20 +255,20 @@ public async Task RunIpcClientTcpClientRouter(CancellationToken token, stri private sealed class IpcServerWebSocketServerRunner : SpecificRunnerBase { - public IpcServerWebSocketServerRunner(string verbose) : base(verbose) { } + public IpcServerWebSocketServerRunner(LogLevel logLevel) : base(logLevel) { } public override void ConfigureLauncher(CancellationToken cancellationToken) { Launcher.SuspendProcess = false; Launcher.ConnectMode = true; - Launcher.Verbose = LogLevel != LogLevel.Information; + Launcher.Verbose = LogLevel < LogLevel.Information; Launcher.CommandToken = cancellationToken; } } public async Task RunIpcServerWebSocketServerRouter(CancellationToken token, string ipcServer, string webSocket, int runtimeTimeout, string verbose) { - IpcServerWebSocketServerRunner runner = new(verbose); + IpcServerWebSocketServerRunner runner = new(ParseLogLevel(verbose)); WebSocketServer.WebSocketServerImpl server = new(runner.LogLevel); @@ -311,20 +298,20 @@ public async Task RunIpcServerWebSocketServerRouter(CancellationToken token private sealed class IpcClientWebSocketServerRunner : SpecificRunnerBase { - public IpcClientWebSocketServerRunner(string verbose) : base(verbose) { } + public IpcClientWebSocketServerRunner(LogLevel logLevel) : base(logLevel) { } public override void ConfigureLauncher(CancellationToken cancellationToken) { Launcher.SuspendProcess = true; Launcher.ConnectMode = true; - Launcher.Verbose = LogLevel != LogLevel.Information; + Launcher.Verbose = LogLevel < LogLevel.Information; Launcher.CommandToken = cancellationToken; } } public async Task RunIpcClientWebSocketServerRouter(CancellationToken token, string ipcClient, string webSocket, int runtimeTimeout, string verbose) { - IpcClientWebSocketServerRunner runner = new(verbose); + IpcClientWebSocketServerRunner runner = new(ParseLogLevel(verbose)); WebSocketServer.WebSocketServerImpl server = new(runner.LogLevel); @@ -347,28 +334,44 @@ public async Task RunIpcClientWebSocketServerRouter(CancellationToken token } } - public async Task RunIpcServerIOSSimulatorRouter(CancellationToken token, int runtimeTimeout, string verbose) + public async Task RunIpcServerIOSSimulatorRouter(CancellationToken token, int runtimeTimeout, string verbose, bool info) { - logDiagnosticPortsConfiguration("ios simulator", "127.0.0.1:9000", false, verbose); - return await RunIpcServerTcpServerRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "").ConfigureAwait(false); + if (info || ParseLogLevel(verbose) <= LogLevel.Information) + { + logRouterUsageInfo("ios simulator", "127.0.0.1:9000", true); + } + + return await RunIpcServerTcpClientRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "").ConfigureAwait(false); } - public async Task RunIpcServerIOSRouter(CancellationToken token, int runtimeTimeout, string verbose) + public async Task RunIpcServerIOSRouter(CancellationToken token, int runtimeTimeout, string verbose, bool info) { - logDiagnosticPortsConfiguration("ios device", "127.0.0.1:9000", true, verbose); + if (info || ParseLogLevel(verbose) <= LogLevel.Information) + { + logRouterUsageInfo("ios device", "127.0.0.1:9000", true); + } + return await RunIpcServerTcpClientRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "iOS").ConfigureAwait(false); } - public async Task RunIpcServerAndroidEmulatorRouter(CancellationToken token, int runtimeTimeout, string verbose) + public async Task RunIpcServerAndroidEmulatorRouter(CancellationToken token, int runtimeTimeout, string verbose, bool info) { - logDiagnosticPortsConfiguration("android emulator", "10.0.2.2:9000", false, verbose); + if (info || ParseLogLevel(verbose) <= LogLevel.Information) + { + logRouterUsageInfo("android emulator", "10.0.2.2:9000", false); + } + return await RunIpcServerTcpServerRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "").ConfigureAwait(false); } - public async Task RunIpcServerAndroidRouter(CancellationToken token, int runtimeTimeout, string verbose) + public async Task RunIpcServerAndroidRouter(CancellationToken token, int runtimeTimeout, string verbose, bool info) { - logDiagnosticPortsConfiguration("android emulator", "127.0.0.1:9000", false, verbose); - return await RunIpcServerTcpServerRouter(token, "", "127.0.0.1:9000", runtimeTimeout, verbose, "Android").ConfigureAwait(false); + if (info || ParseLogLevel(verbose) <= LogLevel.Information) + { + logRouterUsageInfo("android device", "127.0.0.1:9000", false); + } + + return await RunIpcServerTcpServerRouter(token, "", "127.0.0.1:9001", runtimeTimeout, verbose, "Android").ConfigureAwait(false); } private static string GetDefaultIpcServerPath(ILogger logger) @@ -436,26 +439,71 @@ private static NetServerRouterFactory.CreateInstanceDelegate ChooseTcpServerRout return tcpServerRouterFactory; } - private static void logDiagnosticPortsConfiguration(string deviceName, string deviceTcpIpAddress, bool deviceListenMode, string verbose) + private static LogLevel ParseLogLevel(string verbose) { - StringBuilder message = new(); - - if (!string.IsNullOrEmpty(verbose)) + LogLevel logLevel; + if (string.Equals(verbose, "none", StringComparison.OrdinalIgnoreCase)) + { + logLevel = LogLevel.None; + } + else if (string.Equals(verbose, "critical", StringComparison.OrdinalIgnoreCase)) + { + logLevel = LogLevel.Critical; + } + else if (string.Equals(verbose, "error", StringComparison.OrdinalIgnoreCase)) + { + logLevel = LogLevel.Error; + } + else if (string.Equals(verbose, "warning", StringComparison.OrdinalIgnoreCase)) + { + logLevel = LogLevel.Warning; + } + else if (string.Equals(verbose, "info", StringComparison.OrdinalIgnoreCase)) + { + logLevel = LogLevel.Information; + } + else if (string.Equals(verbose, "debug", StringComparison.OrdinalIgnoreCase)) + { + logLevel = LogLevel.Debug; + } + else if (string.Equals(verbose, "trace", StringComparison.OrdinalIgnoreCase)) + { + logLevel = LogLevel.Trace; + } + else { - deviceName = !string.IsNullOrEmpty(deviceName) ? $" on {deviceName} " : " "; - message.AppendLine($"Start an application{deviceName}with one of the following environment variables set:"); + throw new ArgumentException($"Unknown verbose log level, {verbose}"); } - string listenMode = deviceListenMode ? ",listen" : ",connect"; - message.AppendLine($"DOTNET_DiagnosticPorts={deviceTcpIpAddress},nosuspend{listenMode}"); - message.AppendLine($"DOTNET_DiagnosticPorts={deviceTcpIpAddress},suspend{listenMode}"); + return logLevel; + } + + private static void logRouterUsageInfo(string deviceName, string deviceTcpIpAddress, bool deviceListenMode) + { + StringBuilder message = new(); + string listenMode = deviceListenMode ? "listen" : "connect"; + int pid = Process.GetCurrentProcess().Id; + + message.AppendLine($"How to connect current dotnet-dsrouter pid={pid} with {deviceName} and diagnostics tooling."); + message.AppendLine($"Start an application on {deviceName} with ONE of the following environment variables set:"); + message.AppendLine("[Default Tracing]"); + message.AppendLine($"DOTNET_DiagnosticPorts={deviceTcpIpAddress},nosuspend,{listenMode}"); + message.AppendLine("[Startup Tracing]"); + message.AppendLine($"DOTNET_DiagnosticPorts={deviceTcpIpAddress},suspend,{listenMode}"); + message.AppendLine($"Run diagnotic tool connecting application on {deviceName} through dotnet-dsrouter pid={pid}:"); + message.AppendLine($"dotnet-trace collect -p {pid}"); + message.AppendLine($"See https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter for additional details and examples."); + + ConsoleColor currentColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(message.ToString()); + Console.ForegroundColor = currentColor; } - private static void checkLoopbackOnly(string tcpServer) + private static void checkLoopbackOnly(string tcpServer, LogLevel logLevel) { - if (!string.IsNullOrEmpty(tcpServer) && !DiagnosticsServerRouterRunner.isLoopbackOnly(tcpServer)) + if (logLevel != LogLevel.None && !string.IsNullOrEmpty(tcpServer) && !DiagnosticsServerRouterRunner.isLoopbackOnly(tcpServer)) { StringBuilder message = new(); diff --git a/src/Tools/dotnet-dsrouter/Program.cs b/src/Tools/dotnet-dsrouter/Program.cs index 4ed1a3ee98..bc3b53281d 100644 --- a/src/Tools/dotnet-dsrouter/Program.cs +++ b/src/Tools/dotnet-dsrouter/Program.cs @@ -27,13 +27,13 @@ internal sealed class Program private delegate Task DiagnosticsServerIpcClientWebSocketServerRouterDelegate(CancellationToken ct, string ipcClient, string webSocket, int runtimeTimeoutS, string verbose); - private delegate Task DiagnosticsServerIpcServerIOSSimulatorRouterDelegate(CancellationToken ct, int runtimeTimeoutS, string verbose); + private delegate Task DiagnosticsServerIpcServerIOSSimulatorRouterDelegate(CancellationToken ct, int runtimeTimeoutS, string verbose, bool info); - private delegate Task DiagnosticsServerIpcServerIOSRouterDelegate(CancellationToken ct, int runtimeTimeoutS, string verbose); + private delegate Task DiagnosticsServerIpcServerIOSRouterDelegate(CancellationToken ct, int runtimeTimeoutS, string verbose, bool info); - private delegate Task DiagnosticsServerIpcServerAndroidEmulatorRouterDelegate(CancellationToken ct, int runtimeTimeoutS, string verbose); + private delegate Task DiagnosticsServerIpcServerAndroidEmulatorRouterDelegate(CancellationToken ct, int runtimeTimeoutS, string verbose, bool info); - private delegate Task DiagnosticsServerIpcServerAndroidRouterDelegate(CancellationToken ct, int runtimeTimeoutS, string verbose); + private delegate Task DiagnosticsServerIpcServerAndroidRouterDelegate(CancellationToken ct, int runtimeTimeoutS, string verbose, bool info); private static Command IpcClientTcpServerRouterCommand() => new( @@ -122,7 +122,7 @@ private static Command IOSSimulatorRouterCommand() => // Handler HandlerDescriptor.FromDelegate((DiagnosticsServerIpcServerIOSSimulatorRouterDelegate)new DiagnosticsServerRouterCommands().RunIpcServerIOSSimulatorRouter).GetCommandHandler(), // Options - RuntimeTimeoutOption(), VerboseOption() + RuntimeTimeoutOption(), VerboseOption(), InfoOption() }; private static Command IOSRouterCommand() => @@ -135,7 +135,7 @@ private static Command IOSRouterCommand() => // Handler HandlerDescriptor.FromDelegate((DiagnosticsServerIpcServerIOSRouterDelegate)new DiagnosticsServerRouterCommands().RunIpcServerIOSRouter).GetCommandHandler(), // Options - RuntimeTimeoutOption(), VerboseOption() + RuntimeTimeoutOption(), VerboseOption(), InfoOption() }; private static Command AndroidEmulatorRouterCommand() => @@ -148,7 +148,7 @@ private static Command AndroidEmulatorRouterCommand() => // Handler HandlerDescriptor.FromDelegate((DiagnosticsServerIpcServerAndroidEmulatorRouterDelegate)new DiagnosticsServerRouterCommands().RunIpcServerAndroidEmulatorRouter).GetCommandHandler(), // Options - RuntimeTimeoutOption(), VerboseOption() + RuntimeTimeoutOption(), VerboseOption(), InfoOption() }; private static Command AndroidRouterCommand() => @@ -161,7 +161,7 @@ private static Command AndroidRouterCommand() => // Handler HandlerDescriptor.FromDelegate((DiagnosticsServerIpcServerAndroidRouterDelegate)new DiagnosticsServerRouterCommands().RunIpcServerAndroidRouter).GetCommandHandler(), // Options - RuntimeTimeoutOption(), VerboseOption() + RuntimeTimeoutOption(), VerboseOption(), InfoOption() }; private static Option IpcClientAddressOption() => @@ -226,9 +226,9 @@ private static Option RuntimeTimeoutOption() => private static Option VerboseOption() => new( aliases: new[] { "--verbose", "-v" }, - description: "Enable verbose logging (debug|trace)") + description: "Enable verbose logging (none|critical|error|warning|info|debug|trace)") { - Argument = new Argument(name: "verbose", getDefaultValue: () => "") + Argument = new Argument(name: "verbose", getDefaultValue: () => "info") }; private static Option ForwardPortOption() => @@ -239,13 +239,16 @@ private static Option ForwardPortOption() => Argument = new Argument(name: "forwardPort", getDefaultValue: () => "") }; + private static Option InfoOption() => + new( + aliases: new[] { "--info", "-i" }, + description: "Print info on how to use current dotnet-dsrouter instance with application and diagnostic tooling.") + { + Argument = new Argument(name: "info", getDefaultValue: () => false) + }; + private static int Main(string[] args) { - ConsoleColor currentColor = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("WARNING: dotnet-dsrouter is a development tool not intended for production environments." + Environment.NewLine); - Console.ForegroundColor = currentColor; - Parser parser = new CommandLineBuilder() .AddCommand(IpcClientTcpServerRouterCommand()) .AddCommand(IpcServerTcpServerRouterCommand()) @@ -267,6 +270,15 @@ private static int Main(string[] args) ProcessLauncher.Launcher.PrepareChildProcess(args); } + string verbose = parseResult.ValueForOption("-v"); + if (!string.Equals(verbose, "none", StringComparison.OrdinalIgnoreCase)) + { + ConsoleColor currentColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine("WARNING: dotnet-dsrouter is a development tool not intended for production environments." + Environment.NewLine); + Console.ForegroundColor = currentColor; + } + return parser.InvokeAsync(args).Result; } } diff --git a/src/Tools/dotnet-dump/Analyzer.cs b/src/Tools/dotnet-dump/Analyzer.cs index 6718677d65..2a2b5defcf 100644 --- a/src/Tools/dotnet-dump/Analyzer.cs +++ b/src/Tools/dotnet-dump/Analyzer.cs @@ -63,11 +63,11 @@ public Task Analyze(FileInfo dump_path, string[] command) _consoleService.AddCommandHistory(history); } catch (Exception ex) when - (ex is IOException or - ArgumentNullException or - UnauthorizedAccessException or - NotSupportedException or - SecurityException) + (ex is IOException + or ArgumentNullException + or UnauthorizedAccessException + or NotSupportedException + or SecurityException) { } @@ -86,9 +86,6 @@ NotSupportedException or // Add the specially handled exit command _commandService.AddCommands(typeof(ExitCommand), (services) => new ExitCommand(_consoleService.Stop)); - // Add "sos" command manually - _commandService.AddCommands(typeof(SOSCommand), (services) => new SOSCommand(_commandService, services)); - // Display any extension assembly loads on console _serviceManager.NotifyExtensionLoad.Register((Assembly assembly) => _fileLoggingConsoleService.WriteLine($"Loading extension {assembly.Location}")); _serviceManager.NotifyExtensionLoadFailure.Register((Exception ex) => _fileLoggingConsoleService.WriteLine(ex.Message)); @@ -107,6 +104,7 @@ NotSupportedException or _serviceContainer.AddService(_fileLoggingConsoleService); _serviceContainer.AddService(DiagnosticLoggingService.Instance); _serviceContainer.AddService(_commandService); + _serviceContainer.AddService(_commandService); SymbolService symbolService = new(this); _serviceContainer.AddService(symbolService); @@ -133,18 +131,24 @@ NotSupportedException or symbolService.AddCachePath(symbolService.DefaultSymbolCache); symbolService.AddDirectoryPath(Path.GetDirectoryName(dump_path.FullName)); - // Run the commands from the dotnet-dump command line + // Run the commands from the dotnet-dump command line. Any errors/exceptions from the + // command execution will be displayed and dotnet-dump exited. if (command != null) { - foreach (string cmd in command) + foreach (string commandLine in command) { - _commandService.Execute(cmd, contextService.Services); + if (!_commandService.Execute(commandLine, contextService.Services)) + { + throw new CommandNotFoundException($"{CommandNotFoundException.NotFoundMessage} '{commandLine}'"); + } if (_consoleService.Shutdown) { break; } } } + + // Now start the REPL command loop if the console isn't redirected if (!_consoleService.Shutdown && (!Console.IsOutputRedirected || Console.IsInputRedirected)) { // Start interactive command line processing @@ -153,21 +157,25 @@ NotSupportedException or _consoleService.Start((string prompt, string commandLine, CancellationToken cancellation) => { _fileLoggingConsoleService.WriteLine("{0}{1}", prompt, commandLine); - _commandService.Execute(commandLine, contextService.Services); + if (!_commandService.Execute(commandLine, contextService.Services)) + { + throw new CommandNotFoundException($"{CommandNotFoundException.NotFoundMessage} '{commandLine}'"); + } }); } } catch (Exception ex) when - (ex is ClrDiagnosticsException or - FileNotFoundException or - DirectoryNotFoundException or - UnauthorizedAccessException or - PlatformNotSupportedException or - InvalidDataException or - InvalidOperationException or - NotSupportedException) + (ex is ClrDiagnosticsException + or DiagnosticsException + or FileNotFoundException + or DirectoryNotFoundException + or UnauthorizedAccessException + or PlatformNotSupportedException + or InvalidDataException + or InvalidOperationException + or NotSupportedException) { - _fileLoggingConsoleService.WriteError($"{ex.Message}"); + _fileLoggingConsoleService.WriteLineError($"{ex.Message}"); return Task.FromResult(1); } finally @@ -186,10 +194,10 @@ InvalidOperationException or File.WriteAllLines(historyFileName, _consoleService.GetCommandHistory()); } catch (Exception ex) when - (ex is IOException or - UnauthorizedAccessException or - NotSupportedException or - SecurityException) + (ex is IOException + or UnauthorizedAccessException + or NotSupportedException + or SecurityException) { } } diff --git a/src/Tools/dotnet-dump/Commands/ReadMemoryCommand.cs b/src/Tools/dotnet-dump/Commands/ReadMemoryCommand.cs index f4017f7b47..21f6475de8 100644 --- a/src/Tools/dotnet-dump/Commands/ReadMemoryCommand.cs +++ b/src/Tools/dotnet-dump/Commands/ReadMemoryCommand.cs @@ -7,7 +7,7 @@ namespace Microsoft.Diagnostics.Tools.Dump { - [Command(Name = "readmemory", Aliases = new string[] { "d" }, Help = "Dumps memory contents.")] + [Command(Name = "d", Aliases = new string[] { "readmemory" }, Help = "Dumps memory contents.")] [Command(Name = "db", DefaultOptions = "--ascii:true --unicode:false --ascii-string:false --unicode-string:false -c:128 -l:1 -w:16", Help = "Dumps memory as bytes.")] [Command(Name = "dc", DefaultOptions = "--ascii:false --unicode:true --ascii-string:false --unicode-string:false -c:64 -l:2 -w:8", Help = "Dumps memory as chars.")] [Command(Name = "da", DefaultOptions = "--ascii:false --unicode:false --ascii-string:true --unicode-string:false -c:128 -l:1 -w:0", Help = "Dumps memory as zero-terminated byte strings.")] diff --git a/src/Tools/dotnet-dump/Commands/SOSCommand.cs b/src/Tools/dotnet-dump/Commands/SOSCommand.cs index a3c15fc79e..a2045070c4 100644 --- a/src/Tools/dotnet-dump/Commands/SOSCommand.cs +++ b/src/Tools/dotnet-dump/Commands/SOSCommand.cs @@ -9,55 +9,48 @@ namespace Microsoft.Diagnostics.Tools.Dump { - [Command(Name = "sos", Aliases = new string[] { "ext" }, Help = "Executes various SOS debugging commands.", Flags = CommandFlags.Global | CommandFlags.Manual)] + [Command(Name = "sos", Aliases = new string[] { "ext" }, Help = "Executes various SOS debugging commands.")] public class SOSCommand : CommandBase { - private readonly CommandService _commandService; - private readonly IServiceProvider _services; - private SOSHost _sosHost; + [ServiceImport] + public CommandService CommandService { get; set; } - [Argument(Name = "arguments", Help = "SOS command and arguments.")] + [ServiceImport] + public IServiceProvider Services { get; set; } + + [ServiceImport(Optional = true)] + public SOSHost SOSHost { get; set; } + + [Argument(Name = "command_and_arguments", Help = "SOS command and arguments.")] public string[] Arguments { get; set; } - public SOSCommand(CommandService commandService, IServiceProvider services) + public SOSCommand() { - _commandService = commandService; - _services = services; } public override void Invoke() { - string commandLine; - string commandName; + string command; + string arguments; if (Arguments != null && Arguments.Length > 0) { - commandLine = string.Concat(Arguments.Select((arg) => arg + " ")).Trim(); - commandName = Arguments[0]; + command = Arguments[0]; + arguments = string.Concat(Arguments.Skip(1).Select((arg) => arg + " ")).Trim(); } else { - commandLine = commandName = "help"; + command = "help"; + arguments = null; } - if (_commandService.IsCommand(commandName)) + if (CommandService.Execute(command, arguments, Services)) { - try - { - _commandService.Execute(commandLine, _services); - return; - } - catch (CommandNotSupportedException) - { - } + return; } - if (_sosHost is null) + if (SOSHost is null) { - _sosHost = _services.GetService(); - if (_sosHost is null) - { - throw new DiagnosticsException($"'{commandName}' command not found"); - } + throw new CommandNotFoundException($"{CommandNotFoundException.NotFoundMessage} '{command}'"); } - _sosHost.ExecuteCommand(commandLine); + SOSHost.ExecuteCommand(command, arguments); } } } diff --git a/src/Tools/dotnet-gcdump/DotNetHeapDump/DotNetHeapDumpGraphReader.cs b/src/Tools/dotnet-gcdump/DotNetHeapDump/DotNetHeapDumpGraphReader.cs index c26488fcef..328febb54d 100644 --- a/src/Tools/dotnet-gcdump/DotNetHeapDump/DotNetHeapDumpGraphReader.cs +++ b/src/Tools/dotnet-gcdump/DotNetHeapDump/DotNetHeapDumpGraphReader.cs @@ -117,12 +117,13 @@ internal void SetupCallbacks(MemoryGraph memoryGraph, TraceEventDispatcher sourc return; } - if (!m_moduleID2Name.ContainsKey((ulong)data.ModuleID)) + ulong moduleID = unchecked((ulong)data.ModuleID); + if (!m_moduleID2Name.ContainsKey(moduleID)) { - m_moduleID2Name[(ulong)data.ModuleID] = data.ModuleILPath; + m_moduleID2Name[moduleID] = data.ModuleILPath; } - m_log.WriteLine("Found Module {0} ID 0x{1:x}", data.ModuleILFileName, (ulong)data.ModuleID); + m_log.WriteLine("Found Module {0} ID 0x{1:x}", data.ModuleILFileName, moduleID); }; source.Clr.AddCallbackForEvents(moduleCallback); // Get module events for clr provider // TODO should not be needed if we use CAPTURE_STATE when collecting. diff --git a/src/dbgshim/debugshim.cpp b/src/dbgshim/debugshim.cpp index efddfaed60..bc480ee803 100644 --- a/src/dbgshim/debugshim.cpp +++ b/src/dbgshim/debugshim.cpp @@ -756,6 +756,8 @@ HRESULT CLRDebuggingImpl::FormatLongDacModuleName(_Inout_updates_z_(cchBuffer) W const WCHAR* pHostArch = W("arm"); #elif defined(HOST_ARM64) const WCHAR* pHostArch = W("arm64"); +#elif defined(HOST_RISCV64) + const WCHAR* pHostArch = W("riscv64"); #else _ASSERTE(!"Unknown host arch"); return E_NOTIMPL; diff --git a/src/shared/dbgutil/elfreader.cpp b/src/shared/dbgutil/elfreader.cpp index 99bf785a4d..4b5f7347e3 100644 --- a/src/shared/dbgutil/elfreader.cpp +++ b/src/shared/dbgutil/elfreader.cpp @@ -846,6 +846,8 @@ Elf64_Ehdr::Elf64_Ehdr() e_machine = EM_AARCH64; #elif defined(TARGET_LOONGARCH64) e_machine = EM_LOONGARCH; +#elif defined(TARGET_RISCV64) + e_machine = EM_RISCV; #endif e_flags = 0; e_version = 1; diff --git a/src/shared/gcdump/gcinfodecoder.cpp b/src/shared/gcdump/gcinfodecoder.cpp index 6833af4daa..c8e1bf3a39 100644 --- a/src/shared/gcdump/gcinfodecoder.cpp +++ b/src/shared/gcdump/gcinfodecoder.cpp @@ -520,13 +520,13 @@ bool GcInfoDecoder::GetIsVarArg() return m_IsVarArg; } -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_RISCV64) bool GcInfoDecoder::HasTailCalls() { _ASSERTE( m_Flags & DECODE_HAS_TAILCALLS ); return m_HasTailCalls; } -#endif // TARGET_ARM || TARGET_ARM64 +#endif // TARGET_ARM || TARGET_ARM64 || TARGET_RISCV64 bool GcInfoDecoder::WantsReportOnlyLeaf() { diff --git a/src/shared/inc/clrnt.h b/src/shared/inc/clrnt.h index e12c588700..bd541d4284 100644 --- a/src/shared/inc/clrnt.h +++ b/src/shared/inc/clrnt.h @@ -1029,4 +1029,61 @@ RtlVirtualUnwind( #endif +#ifdef TARGET_RISCV64 +#include "daccess.h" + +#define UNW_FLAG_NHANDLER 0x0 /* any handler */ +#define UNW_FLAG_EHANDLER 0x1 /* filter handler */ +#define UNW_FLAG_UHANDLER 0x2 /* unwind handler */ + +// This function returns the RVA of the end of the function (exclusive, so one byte after the actual end) +// using the unwind info on ARM64. (see ExternalAPIs\Win9CoreSystem\inc\winnt.h) +FORCEINLINE +ULONG64 +RtlpGetFunctionEndAddress ( + _In_ PT_RUNTIME_FUNCTION FunctionEntry, + _In_ ULONG64 ImageBase + ) +{ + ULONG64 FunctionLength; + + FunctionLength = FunctionEntry->UnwindData; + if ((FunctionLength & 3) != 0) { + FunctionLength = (FunctionLength >> 2) & 0x7ff; + } else { + memcpy(&FunctionLength, (void*)(ImageBase + FunctionLength), sizeof(UINT32)); + FunctionLength &= 0x3ffff; + } + + return FunctionEntry->BeginAddress + 4 * FunctionLength; +} + +#define RUNTIME_FUNCTION__BeginAddress(FunctionEntry) ((FunctionEntry)->BeginAddress) +#define RUNTIME_FUNCTION__SetBeginAddress(FunctionEntry,address) ((FunctionEntry)->BeginAddress = (address)) + +#define RUNTIME_FUNCTION__EndAddress(FunctionEntry, ImageBase) (RtlpGetFunctionEndAddress(FunctionEntry, (ULONG64)(ImageBase))) + +#define RUNTIME_FUNCTION__SetUnwindInfoAddress(prf,address) do { (prf)->UnwindData = (address); } while (0) + +typedef struct _UNWIND_INFO { + // dummy +} UNWIND_INFO, *PUNWIND_INFO; + +EXTERN_C +NTSYSAPI +PEXCEPTION_ROUTINE +NTAPI +RtlVirtualUnwind( + IN ULONG HandlerType, + IN ULONG64 ImageBase, + IN ULONG64 ControlPc, + IN PRUNTIME_FUNCTION FunctionEntry, + IN OUT PCONTEXT ContextRecord, + OUT PVOID *HandlerData, + OUT PULONG64 EstablisherFrame, + IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers OPTIONAL + ); + +#endif // TARGET_RISCV64 + #endif // CLRNT_H_ diff --git a/src/shared/inc/crosscomp.h b/src/shared/inc/crosscomp.h index 84b6f81606..01ec1ff928 100644 --- a/src/shared/inc/crosscomp.h +++ b/src/shared/inc/crosscomp.h @@ -382,6 +382,147 @@ enum #endif // TARGET_ARM64 && !HOST_ARM64 +#elif defined(HOST_AMD64) && defined(TARGET_RISCV64) // Host amd64 managing RISCV64 related code + +#ifndef CROSS_COMPILE +#define CROSS_COMPILE +#endif + +// +// Specify the number of breakpoints and watchpoints that the OS +// will track. +// + +#define RISCV64_MAX_BREAKPOINTS 8 +#define RISCV64_MAX_WATCHPOINTS 2 + +#define CONTEXT_UNWOUND_TO_CALL 0x20000000 + +typedef struct DECLSPEC_ALIGN(16) _T_CONTEXT { + + // + // Control flags. + // + + /* +0x000 */ DWORD ContextFlags; + + // + // Integer registers + // + DWORD64 R0; + DWORD64 Ra; + DWORD64 Sp; + DWORD64 Gp; + DWORD64 Tp; + DWORD64 T0; + DWORD64 T1; + DWORD64 T2; + DWORD64 Fp; + DWORD64 S1; + DWORD64 A0; + DWORD64 A1; + DWORD64 A2; + DWORD64 A3; + DWORD64 A4; + DWORD64 A5; + DWORD64 A6; + DWORD64 A7; + DWORD64 S2; + DWORD64 S3; + DWORD64 S4; + DWORD64 S5; + DWORD64 S6; + DWORD64 S7; + DWORD64 S8; + DWORD64 S9; + DWORD64 S10; + DWORD64 S11; + DWORD64 T3; + DWORD64 T4; + DWORD64 T5; + DWORD64 T6; + DWORD64 Pc; + + // + // Floating Point Registers + // + //TODO-RISCV64: support the SIMD. + ULONGLONG F[32]; + DWORD Fcsr; +} T_CONTEXT, *PT_CONTEXT; + +// _IMAGE_RISCV64_RUNTIME_FUNCTION_ENTRY (see ExternalAPIs\Win9CoreSystem\inc\winnt.h) +typedef struct _T_RUNTIME_FUNCTION { + DWORD BeginAddress; + union { + DWORD UnwindData; + struct { + DWORD Flag : 2; + DWORD FunctionLength : 11; + DWORD RegF : 3; + DWORD RegI : 4; + DWORD H : 1; + DWORD CR : 2; + DWORD FrameSize : 9; + } PackedUnwindData; + }; +} T_RUNTIME_FUNCTION, *PT_RUNTIME_FUNCTION; + +// +// Define exception dispatch context structure. +// + +typedef struct _T_DISPATCHER_CONTEXT { + DWORD64 ControlPc; + DWORD64 ImageBase; + PT_RUNTIME_FUNCTION FunctionEntry; + DWORD64 EstablisherFrame; + DWORD64 TargetPc; + PCONTEXT ContextRecord; + PEXCEPTION_ROUTINE LanguageHandler; + PVOID HandlerData; + PVOID HistoryTable; + DWORD ScopeIndex; + BOOLEAN ControlPcIsUnwound; + PBYTE NonVolatileRegisters; +} T_DISPATCHER_CONTEXT, *PT_DISPATCHER_CONTEXT; + +// +// Nonvolatile context pointer record. +// + +typedef struct _T_KNONVOLATILE_CONTEXT_POINTERS { + + PDWORD64 S1; + PDWORD64 S2; + PDWORD64 S3; + PDWORD64 S4; + PDWORD64 S5; + PDWORD64 S6; + PDWORD64 S7; + PDWORD64 S8; + PDWORD64 S9; + PDWORD64 S10; + PDWORD64 S11; + PDWORD64 Fp; + PDWORD64 Gp; + PDWORD64 Tp; + PDWORD64 Ra; + + PDWORD64 F8; + PDWORD64 F9; + PDWORD64 F18; + PDWORD64 F19; + PDWORD64 F20; + PDWORD64 F21; + PDWORD64 F22; + PDWORD64 F23; + PDWORD64 F24; + PDWORD64 F25; + PDWORD64 F26; + PDWORD64 F27; +} T_KNONVOLATILE_CONTEXT_POINTERS, *PT_KNONVOLATILE_CONTEXT_POINTERS; + #else #define T_CONTEXT CONTEXT @@ -426,6 +567,8 @@ enum #define DAC_CS_NATIVE_DATA_SIZE 96 #elif defined(TARGET_LINUX) && defined(TARGET_S390X) #define DAC_CS_NATIVE_DATA_SIZE 96 +#elif defined(TARGET_LINUX) && defined(TARGET_RISCV64) +#define DAC_CS_NATIVE_DATA_SIZE 96 #elif defined(TARGET_NETBSD) && defined(TARGET_AMD64) #define DAC_CS_NATIVE_DATA_SIZE 96 #elif defined(TARGET_NETBSD) && defined(TARGET_ARM) diff --git a/src/shared/inc/dbgtargetcontext.h b/src/shared/inc/dbgtargetcontext.h index e4fd6e4bf5..4970caed05 100644 --- a/src/shared/inc/dbgtargetcontext.h +++ b/src/shared/inc/dbgtargetcontext.h @@ -48,6 +48,8 @@ #define DTCONTEXT_IS_ARM #elif defined (TARGET_ARM64) #define DTCONTEXT_IS_ARM64 +#elif defined (TARGET_RISCV64) +#define DTCONTEXT_IS_RISCV64 #endif #if defined(DTCONTEXT_IS_X86) @@ -447,6 +449,74 @@ typedef DECLSPEC_ALIGN(16) struct { } DT_CONTEXT; +#elif defined(DTCONTEXT_IS_RISCV64) + +#define DT_CONTEXT_RISCV64 0x01000000L + +#define DT_CONTEXT_CONTROL (DT_CONTEXT_RISCV64 | 0x1L) +#define DT_CONTEXT_INTEGER (DT_CONTEXT_RISCV64 | 0x2L) +#define DT_CONTEXT_FLOATING_POINT (DT_CONTEXT_RISCV64 | 0x4L) +#define DT_CONTEXT_DEBUG_REGISTERS (DT_CONTEXT_RISCV64 | 0x8L) + +#define DT_CONTEXT_FULL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_FLOATING_POINT) +#define DT_CONTEXT_ALL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_FLOATING_POINT | DT_CONTEXT_DEBUG_REGISTERS) + +#define DT_RISCV64_MAX_BREAKPOINTS 8 +#define DT_RISCV64_MAX_WATCHPOINTS 2 + +typedef struct DECLSPEC_ALIGN(16) { + // + // Control flags. + // + + /* +0x000 */ DWORD ContextFlags; + + // + // Integer registers + // + DWORD64 R0; + DWORD64 Ra; + DWORD64 Sp; + DWORD64 Gp; + DWORD64 Tp; + DWORD64 T0; + DWORD64 T1; + DWORD64 T2; + DWORD64 Fp; + DWORD64 S1; + DWORD64 A0; + DWORD64 A1; + DWORD64 A2; + DWORD64 A3; + DWORD64 A4; + DWORD64 A5; + DWORD64 A6; + DWORD64 A7; + DWORD64 S2; + DWORD64 S3; + DWORD64 S4; + DWORD64 S5; + DWORD64 S6; + DWORD64 S7; + DWORD64 S8; + DWORD64 S9; + DWORD64 S10; + DWORD64 S11; + DWORD64 T3; + DWORD64 T4; + DWORD64 T5; + DWORD64 T6; + DWORD64 Pc; + + // + // Floating Point Registers + // + ULONGLONG F[32]; + DWORD Fcsr; +} DT_CONTEXT; + +static_assert(sizeof(DT_CONTEXT) == sizeof(T_CONTEXT), "DT_CONTEXT size must equal the T_CONTEXT size"); + #else #error Unsupported platform #endif diff --git a/src/shared/inc/gcinfodecoder.h b/src/shared/inc/gcinfodecoder.h index 13b9575630..6ccdbe4aae 100644 --- a/src/shared/inc/gcinfodecoder.h +++ b/src/shared/inc/gcinfodecoder.h @@ -212,7 +212,7 @@ enum GcInfoDecoderFlags DECODE_EDIT_AND_CONTINUE = 0x800, DECODE_REVERSE_PINVOKE_VAR = 0x1000, DECODE_RETURN_KIND = 0x2000, -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_RISCV64) DECODE_HAS_TAILCALLS = 0x4000, #endif // TARGET_ARM || TARGET_ARM64 }; @@ -231,7 +231,7 @@ enum GcInfoHeaderFlags GC_INFO_HAS_STACK_BASE_REGISTER = 0x40, #ifdef TARGET_AMD64 GC_INFO_WANTS_REPORT_ONLY_LEAF = 0x80, -#elif defined(TARGET_ARM) || defined(TARGET_ARM64) +#elif defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_RISCV64) GC_INFO_HAS_TAILCALLS = 0x80, #endif // TARGET_AMD64 GC_INFO_HAS_EDIT_AND_CONTINUE_PRESERVED_SLOTS = 0x100, @@ -536,9 +536,9 @@ class GcInfoDecoder bool HasMethodTableGenericsInstContext(); bool GetIsVarArg(); bool WantsReportOnlyLeaf(); -#if defined(TARGET_ARM) || defined(TARGET_ARM64) +#if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_RISCV64) bool HasTailCalls(); -#endif // TARGET_ARM || TARGET_ARM64 +#endif // TARGET_ARM || TARGET_ARM64 || TARGET_RISCV64 ReturnKind GetReturnKind(); UINT32 GetCodeLength(); UINT32 GetStackBaseRegister(); @@ -561,7 +561,7 @@ class GcInfoDecoder bool m_GenericSecretParamIsMT; #ifdef TARGET_AMD64 bool m_WantsReportOnlyLeaf; -#elif defined(TARGET_ARM) || defined(TARGET_ARM64) +#elif defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_RISCV64) bool m_HasTailCalls; #endif // TARGET_AMD64 INT32 m_SecurityObjectStackSlot; diff --git a/src/shared/inc/gcinfotypes.h b/src/shared/inc/gcinfotypes.h index 1dd9372900..fc409d3700 100644 --- a/src/shared/inc/gcinfotypes.h +++ b/src/shared/inc/gcinfotypes.h @@ -155,7 +155,7 @@ struct GcStackSlot // 10 RT_ByRef // 11 RT_Unset -#elif defined(TARGET_AMD64) || defined(TARGET_ARM64) +#elif defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_RISCV64) // Slim Header: @@ -772,6 +772,62 @@ void FASTCALL decodeCallPattern(int pattern, #define LIVESTATE_RLE_RUN_ENCBASE 2 #define LIVESTATE_RLE_SKIP_ENCBASE 4 +#elif defined(TARGET_RISCV64) +#ifndef TARGET_POINTER_SIZE +#define TARGET_POINTER_SIZE 8 // equal to sizeof(void*) and the managed pointer size in bytes for this target +#endif +#define NUM_NORM_CODE_OFFSETS_PER_CHUNK (64) +#define NUM_NORM_CODE_OFFSETS_PER_CHUNK_LOG2 (6) +#define NORMALIZE_STACK_SLOT(x) ((x)>>3) // GC Pointers are 8-bytes aligned +#define DENORMALIZE_STACK_SLOT(x) ((x)<<3) +#define NORMALIZE_CODE_LENGTH(x) ((x)>>2) // All Instructions are 4 bytes long +#define DENORMALIZE_CODE_LENGTH(x) ((x)<<2) +#define NORMALIZE_STACK_BASE_REGISTER(x) ((x)^8) // Encode Frame pointer X8 as zero +#define DENORMALIZE_STACK_BASE_REGISTER(x) ((x)^8) +#define NORMALIZE_SIZE_OF_STACK_AREA(x) ((x)>>3) +#define DENORMALIZE_SIZE_OF_STACK_AREA(x) ((x)<<3) +#define CODE_OFFSETS_NEED_NORMALIZATION 0 +#define NORMALIZE_CODE_OFFSET(x) (x) // Instructions are 4 bytes long, but the safe-point +#define DENORMALIZE_CODE_OFFSET(x) (x) // offsets are encoded with a -1 adjustment. +#define NORMALIZE_REGISTER(x) (x) +#define DENORMALIZE_REGISTER(x) (x) +#define NORMALIZE_NUM_SAFE_POINTS(x) (x) +#define DENORMALIZE_NUM_SAFE_POINTS(x) (x) +#define NORMALIZE_NUM_INTERRUPTIBLE_RANGES(x) (x) +#define DENORMALIZE_NUM_INTERRUPTIBLE_RANGES(x) (x) + +#define PSP_SYM_STACK_SLOT_ENCBASE 6 +#define GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE 6 +#define SECURITY_OBJECT_STACK_SLOT_ENCBASE 6 +#define GS_COOKIE_STACK_SLOT_ENCBASE 6 +#define CODE_LENGTH_ENCBASE 8 +#define SIZE_OF_RETURN_KIND_IN_SLIM_HEADER 2 +#define SIZE_OF_RETURN_KIND_IN_FAT_HEADER 4 +#define STACK_BASE_REGISTER_ENCBASE 2 +// FP encoded as 0, SP as 2?? +#define SIZE_OF_STACK_AREA_ENCBASE 3 +#define SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE 4 +#define SIZE_OF_EDIT_AND_CONTINUE_FIXED_STACK_FRAME_ENCBASE 4 +#define REVERSE_PINVOKE_FRAME_ENCBASE 6 +#define NUM_REGISTERS_ENCBASE 3 +#define NUM_STACK_SLOTS_ENCBASE 2 +#define NUM_UNTRACKED_SLOTS_ENCBASE 1 +#define NORM_PROLOG_SIZE_ENCBASE 5 +#define NORM_EPILOG_SIZE_ENCBASE 3 +#define NORM_CODE_OFFSET_DELTA_ENCBASE 3 +#define INTERRUPTIBLE_RANGE_DELTA1_ENCBASE 6 +#define INTERRUPTIBLE_RANGE_DELTA2_ENCBASE 6 +#define REGISTER_ENCBASE 3 +#define REGISTER_DELTA_ENCBASE 2 +#define STACK_SLOT_ENCBASE 6 +#define STACK_SLOT_DELTA_ENCBASE 4 +#define NUM_SAFE_POINTS_ENCBASE 3 +#define NUM_INTERRUPTIBLE_RANGES_ENCBASE 1 +#define NUM_EH_CLAUSES_ENCBASE 2 +#define POINTER_SIZE_ENCBASE 3 +#define LIVESTATE_RLE_RUN_ENCBASE 2 +#define LIVESTATE_RLE_SKIP_ENCBASE 4 + #else #ifndef TARGET_X86 diff --git a/src/shared/inc/pedecoder.h b/src/shared/inc/pedecoder.h index bb4ea867db..769c0b0116 100644 --- a/src/shared/inc/pedecoder.h +++ b/src/shared/inc/pedecoder.h @@ -81,6 +81,8 @@ inline CHECK CheckOverflow(RVA value1, COUNT_T value2) #define IMAGE_FILE_MACHINE_NATIVE IMAGE_FILE_MACHINE_ARM64 #elif defined(TARGET_S390X) #define IMAGE_FILE_MACHINE_NATIVE IMAGE_FILE_MACHINE_UNKNOWN +#elif defined(TARGET_RISCV64) +#define IMAGE_FILE_MACHINE_NATIVE IMAGE_FILE_MACHINE_RISCV64 #else #error "port me" #endif diff --git a/src/shared/inc/regdisp.h b/src/shared/inc/regdisp.h index b031b0a80b..02e67dbdd4 100644 --- a/src/shared/inc/regdisp.h +++ b/src/shared/inc/regdisp.h @@ -184,11 +184,38 @@ typedef struct _Arm64VolatileContextPointer }; } Arm64VolatileContextPointer; #endif //TARGET_ARM64 + +#if defined(TARGET_RISCV64) +typedef struct _RiscV64VolatileContextPointer +{ + PDWORD64 R0; + PDWORD64 A0; + PDWORD64 A1; + PDWORD64 A2; + PDWORD64 A3; + PDWORD64 A4; + PDWORD64 A5; + PDWORD64 A6; + PDWORD64 A7; + PDWORD64 T0; + PDWORD64 T1; + PDWORD64 T2; + PDWORD64 T3; + PDWORD64 T4; + PDWORD64 T5; + PDWORD64 T6; +} RiscV64VolatileContextPointer; +#endif + struct REGDISPLAY : public REGDISPLAY_BASE { #ifdef TARGET_ARM64 Arm64VolatileContextPointer volatileCurrContextPointers; #endif +#ifdef TARGET_RISCV64 + RiscV64VolatileContextPointer volatileCurrContextPointers; +#endif + REGDISPLAY() { // Initialize @@ -297,6 +324,8 @@ inline LPVOID GetRegdisplayReturnValue(REGDISPLAY *display) return (LPVOID)((TADDR)display->pCurrentContext->R0); #elif defined(TARGET_X86) return (LPVOID)display->pCurrentContext->Eax; +#elif defined(TARGET_RISCV64) + return (LPVOID)display->pCurrentContext->A0; #else PORTABILITY_ASSERT("GetRegdisplayReturnValue NYI for this platform (Regdisp.h)"); return NULL; @@ -352,7 +381,23 @@ inline void FillContextPointers(PT_KNONVOLATILE_CONTEXT_POINTERS pCtxPtrs, PT_CO { *(&pCtxPtrs->Edi + i) = (&pCtx->Edi + i); } -#else // TARGET_X86 +#elif defined(TARGET_RISCV64) // TARGET_X86 + *(&pCtxPtrs->S1) = &pCtx->S1; + *(&pCtxPtrs->S2) = &pCtx->S2; + *(&pCtxPtrs->S3) = &pCtx->S3; + *(&pCtxPtrs->S4) = &pCtx->S4; + *(&pCtxPtrs->S5) = &pCtx->S5; + *(&pCtxPtrs->S6) = &pCtx->S6; + *(&pCtxPtrs->S7) = &pCtx->S7; + *(&pCtxPtrs->S8) = &pCtx->S8; + *(&pCtxPtrs->S9) = &pCtx->S9; + *(&pCtxPtrs->S10) = &pCtx->S10; + *(&pCtxPtrs->S11) = &pCtx->S11; + *(&pCtxPtrs->Gp) = &pCtx->Gp; + *(&pCtxPtrs->Tp) = &pCtx->Tp; + *(&pCtxPtrs->Fp) = &pCtx->Fp; + *(&pCtxPtrs->Ra) = &pCtx->Ra; +#else // TARGET_RISCV64 PORTABILITY_ASSERT("FillContextPointers"); #endif // _TARGET_???_ (ELSE) } @@ -424,7 +469,23 @@ inline void FillRegDisplay(const PREGDISPLAY pRD, PT_CONTEXT pctx, PT_CONTEXT pC // Fill volatile context pointers. They can be used by GC in the case of the leaf frame for (int i=0; i < 18; i++) pRD->volatileCurrContextPointers.X[i] = &pctx->X[i]; -#endif // TARGET_ARM64 +#elif defined(TARGET_RISCV64) // TARGET_ARM64 + pRD->volatileCurrContextPointers.A0 = &pctx->A0; + pRD->volatileCurrContextPointers.A1 = &pctx->A1; + pRD->volatileCurrContextPointers.A2 = &pctx->A2; + pRD->volatileCurrContextPointers.A3 = &pctx->A3; + pRD->volatileCurrContextPointers.A4 = &pctx->A4; + pRD->volatileCurrContextPointers.A5 = &pctx->A5; + pRD->volatileCurrContextPointers.A6 = &pctx->A6; + pRD->volatileCurrContextPointers.A7 = &pctx->A7; + pRD->volatileCurrContextPointers.T0 = &pctx->T0; + pRD->volatileCurrContextPointers.T1 = &pctx->T1; + pRD->volatileCurrContextPointers.T2 = &pctx->T2; + pRD->volatileCurrContextPointers.T3 = &pctx->T3; + pRD->volatileCurrContextPointers.T4 = &pctx->T4; + pRD->volatileCurrContextPointers.T5 = &pctx->T5; + pRD->volatileCurrContextPointers.T6 = &pctx->T6; +#endif // TARGET_RISCV64 #ifdef DEBUG_REGDISPLAY pRD->_pThread = NULL; @@ -504,6 +565,9 @@ inline size_t * getRegAddr (unsigned regNum, PTR_CONTEXT regs) #elif defined(TARGET_ARM64) _ASSERTE(regNum < 31); return (size_t *)®s->X0 + regNum; +#elif defined(TARGET_RISCV64) + _ASSERTE(regNum < 32); + return (size_t *)®s->R0 + regNum; #else _ASSERTE(!"@TODO Port - getRegAddr (Regdisp.h)"); #endif diff --git a/src/shared/inc/switches.h b/src/shared/inc/switches.h index 072d37d69b..6a4f6d6371 100644 --- a/src/shared/inc/switches.h +++ b/src/shared/inc/switches.h @@ -31,7 +31,7 @@ #if defined(TARGET_X86) || defined(TARGET_ARM) #define USE_LAZY_PREFERRED_RANGE 0 -#elif defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_S390X) || defined(TARGET_LOONGARCH64) +#elif defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_S390X) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) #if defined(HOST_UNIX) // In PAL we have a smechanism that reserves memory on start up that is diff --git a/src/shared/inc/volatile.h b/src/shared/inc/volatile.h index e127fe25f9..9a811309ed 100644 --- a/src/shared/inc/volatile.h +++ b/src/shared/inc/volatile.h @@ -68,8 +68,8 @@ #error The Volatile type is currently only defined for Visual C++ and GNU C++ #endif -#if defined(__GNUC__) && !defined(HOST_X86) && !defined(HOST_AMD64) && !defined(HOST_ARM) && !defined(HOST_ARM64) && !defined(HOST_S390X) -#error The Volatile type is currently only defined for GCC when targeting x86, AMD64, ARM, ARM64, or S390X CPUs +#if defined(__GNUC__) && !defined(HOST_X86) && !defined(HOST_AMD64) && !defined(HOST_ARM) && !defined(HOST_ARM64) && !defined(HOST_RISCV64) && !defined(HOST_S390X) +#error The Volatile type is currently only defined for GCC when targeting x86, AMD64, ARM, ARM64, RISCV64, or S390X CPUs #endif #if defined(__GNUC__) @@ -99,6 +99,8 @@ // currently don't have a cheap way to determine the number of CPUs from this header file. Revisit this if it // turns out to be a performance issue for the uni-proc case. #define VOLATILE_MEMORY_BARRIER() MemoryBarrier() +#elif defined(HOST_RISCV64) +#define VOLATILE_MEMORY_BARRIER() asm volatile ("fence rw,rw" : : : "memory") #else // // On VC++, reorderings at the compiler and machine level are prevented by the use of the diff --git a/src/shared/pal/inc/pal.h b/src/shared/pal/inc/pal.h index 01e0851cc5..cbdc93434b 100644 --- a/src/shared/pal/inc/pal.h +++ b/src/shared/pal/inc/pal.h @@ -96,6 +96,8 @@ typedef PVOID NATIVE_LIBRARY_HANDLE; #define _M_ARM64 1 #elif defined(__s390x__) && !defined(_M_S390X) #define _M_S390X 1 +#elif defined(__riscv) && (__riscv_xlen == 64) && !defined(_M_RISCV64) +#define _M_RISCV64 1 #endif #if defined(_M_IX86) && !defined(HOST_X86) @@ -108,6 +110,8 @@ typedef PVOID NATIVE_LIBRARY_HANDLE; #define HOST_ARM64 #elif defined(_M_S390X) && !defined(HOST_S390X) #define HOST_S390X +#elif defined(_M_RISCV64) && !defined(HOST_RISCV64) +#define HOST_RISCV64 #endif #endif // !_MSC_VER @@ -1762,6 +1766,134 @@ typedef struct _KNONVOLATILE_CONTEXT_POINTERS { } KNONVOLATILE_CONTEXT_POINTERS, *PKNONVOLATILE_CONTEXT_POINTERS; +#elif defined(HOST_RISCV64) + +// Please refer to src/coreclr/pal/src/arch/riscv64/asmconstants.h +#define CONTEXT_RISCV64 0x01000000L + +#define CONTEXT_CONTROL (CONTEXT_RISCV64 | 0x1) +#define CONTEXT_INTEGER (CONTEXT_RISCV64 | 0x2) +#define CONTEXT_FLOATING_POINT (CONTEXT_RISCV64 | 0x4) +#define CONTEXT_DEBUG_REGISTERS (CONTEXT_RISCV64 | 0x8) + +#define CONTEXT_FULL (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT) + +#define CONTEXT_ALL (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS) + +#define CONTEXT_EXCEPTION_ACTIVE 0x8000000 +#define CONTEXT_SERVICE_ACTIVE 0x10000000 +#define CONTEXT_EXCEPTION_REQUEST 0x40000000 +#define CONTEXT_EXCEPTION_REPORTING 0x80000000 + +// +// This flag is set by the unwinder if it has unwound to a call +// site, and cleared whenever it unwinds through a trap frame. +// It is used by language-specific exception handlers to help +// differentiate exception scopes during dispatching. +// + +#define CONTEXT_UNWOUND_TO_CALL 0x20000000 + +// begin_ntoshvp + +// +// Specify the number of breakpoints and watchpoints that the OS +// will track. Architecturally, RISCV64 supports up to 16. In practice, +// however, almost no one implements more than 4 of each. +// + +#define RISCV64_MAX_BREAKPOINTS 8 +#define RISCV64_MAX_WATCHPOINTS 2 + +typedef struct DECLSPEC_ALIGN(16) _CONTEXT { + + // + // Control flags. + // + + /* +0x000 */ DWORD ContextFlags; + + // + // Integer registers. + // + DWORD64 R0; + DWORD64 Ra; + DWORD64 Sp; + DWORD64 Gp; + DWORD64 Tp; + DWORD64 T0; + DWORD64 T1; + DWORD64 T2; + DWORD64 Fp; + DWORD64 S1; + DWORD64 A0; + DWORD64 A1; + DWORD64 A2; + DWORD64 A3; + DWORD64 A4; + DWORD64 A5; + DWORD64 A6; + DWORD64 A7; + DWORD64 S2; + DWORD64 S3; + DWORD64 S4; + DWORD64 S5; + DWORD64 S6; + DWORD64 S7; + DWORD64 S8; + DWORD64 S9; + DWORD64 S10; + DWORD64 S11; + DWORD64 T3; + DWORD64 T4; + DWORD64 T5; + DWORD64 T6; + DWORD64 Pc; + + // + // Floating Point Registers + // + // TODO-RISCV64: support the SIMD. + ULONGLONG F[32]; + DWORD Fcsr; +} CONTEXT, *PCONTEXT, *LPCONTEXT; + +// +// Nonvolatile context pointer record. +// + +typedef struct _KNONVOLATILE_CONTEXT_POINTERS { + + PDWORD64 S1; + PDWORD64 S2; + PDWORD64 S3; + PDWORD64 S4; + PDWORD64 S5; + PDWORD64 S6; + PDWORD64 S7; + PDWORD64 S8; + PDWORD64 S9; + PDWORD64 S10; + PDWORD64 S11; + PDWORD64 Fp; + PDWORD64 Gp; + PDWORD64 Tp; + PDWORD64 Ra; + + PDWORD64 F8; + PDWORD64 F9; + PDWORD64 F18; + PDWORD64 F19; + PDWORD64 F20; + PDWORD64 F21; + PDWORD64 F22; + PDWORD64 F23; + PDWORD64 F24; + PDWORD64 F25; + PDWORD64 F26; + PDWORD64 F27; +} KNONVOLATILE_CONTEXT_POINTERS, *PKNONVOLATILE_CONTEXT_POINTERS; + #elif defined(HOST_S390X) // There is no context for s390x defined in winnt.h, @@ -1912,6 +2044,8 @@ GetThreadTimes( #define PAL_CS_NATIVE_DATA_SIZE 56 #elif defined(__sun) && defined(__x86_64__) #define PAL_CS_NATIVE_DATA_SIZE 48 +#elif defined(__linux__) && defined(__riscv) && __riscv_xlen == 64 +#define PAL_CS_NATIVE_DATA_SIZE 96 #else #warning #error PAL_CS_NATIVE_DATA_SIZE is not defined for this architecture @@ -2548,9 +2682,9 @@ BitScanReverse64( return qwMask != 0; } -FORCEINLINE void PAL_ArmInterlockedOperationBarrier() +FORCEINLINE void PAL_InterlockedOperationBarrier() { -#ifdef HOST_ARM64 +#if defined(HOST_ARM64) || defined(HOST_RISCV64) // On arm64, most of the __sync* functions generate a code sequence like: // loop: // ldaxr (load acquire exclusive) @@ -2563,7 +2697,7 @@ FORCEINLINE void PAL_ArmInterlockedOperationBarrier() // require the load to occur after the store. This memory barrier should be used following a call to a __sync* function to // prevent that reordering. Code generated for arm32 includes a 'dmb' after 'cbnz', so no issue there at the moment. __sync_synchronize(); -#endif // HOST_ARM64 +#endif } /*++ @@ -2594,7 +2728,7 @@ InterlockedIncrement( IN OUT LONG volatile *lpAddend) { LONG result = __sync_add_and_fetch(lpAddend, (LONG)1); - PAL_ArmInterlockedOperationBarrier(); + PAL_InterlockedOperationBarrier(); return result; } @@ -2607,7 +2741,7 @@ InterlockedIncrement64( IN OUT LONGLONG volatile *lpAddend) { LONGLONG result = __sync_add_and_fetch(lpAddend, (LONGLONG)1); - PAL_ArmInterlockedOperationBarrier(); + PAL_InterlockedOperationBarrier(); return result; } @@ -2639,7 +2773,7 @@ InterlockedDecrement( IN OUT LONG volatile *lpAddend) { LONG result = __sync_sub_and_fetch(lpAddend, (LONG)1); - PAL_ArmInterlockedOperationBarrier(); + PAL_InterlockedOperationBarrier(); return result; } @@ -2655,7 +2789,7 @@ InterlockedDecrement64( IN OUT LONGLONG volatile *lpAddend) { LONGLONG result = __sync_sub_and_fetch(lpAddend, (LONGLONG)1); - PAL_ArmInterlockedOperationBarrier(); + PAL_InterlockedOperationBarrier(); return result; } @@ -2690,7 +2824,7 @@ InterlockedExchange( IN LONG Value) { LONG result = __atomic_exchange_n(Target, Value, __ATOMIC_ACQ_REL); - PAL_ArmInterlockedOperationBarrier(); + PAL_InterlockedOperationBarrier(); return result; } @@ -2704,7 +2838,7 @@ InterlockedExchange64( IN LONGLONG Value) { LONGLONG result = __atomic_exchange_n(Target, Value, __ATOMIC_ACQ_REL); - PAL_ArmInterlockedOperationBarrier(); + PAL_InterlockedOperationBarrier(); return result; } @@ -2746,7 +2880,7 @@ InterlockedCompareExchange( Destination, /* The pointer to a variable whose value is to be compared with. */ Comperand, /* The value to be compared */ Exchange /* The value to be stored */); - PAL_ArmInterlockedOperationBarrier(); + PAL_InterlockedOperationBarrier(); return result; } @@ -2769,7 +2903,7 @@ InterlockedCompareExchange64( Destination, /* The pointer to a variable whose value is to be compared with. */ Comperand, /* The value to be compared */ Exchange /* The value to be stored */); - PAL_ArmInterlockedOperationBarrier(); + PAL_InterlockedOperationBarrier(); return result; } @@ -2800,7 +2934,7 @@ InterlockedExchangeAdd( IN LONG Value) { LONG result = __sync_fetch_and_add(Addend, Value); - PAL_ArmInterlockedOperationBarrier(); + PAL_InterlockedOperationBarrier(); return result; } @@ -2814,7 +2948,7 @@ InterlockedExchangeAdd64( IN LONGLONG Value) { LONGLONG result = __sync_fetch_and_add(Addend, Value); - PAL_ArmInterlockedOperationBarrier(); + PAL_InterlockedOperationBarrier(); return result; } @@ -2828,7 +2962,7 @@ InterlockedAnd( IN LONG Value) { LONG result = __sync_fetch_and_and(Destination, Value); - PAL_ArmInterlockedOperationBarrier(); + PAL_InterlockedOperationBarrier(); return result; } @@ -2842,7 +2976,7 @@ InterlockedOr( IN LONG Value) { LONG result = __sync_fetch_and_or(Destination, Value); - PAL_ArmInterlockedOperationBarrier(); + PAL_InterlockedOperationBarrier(); return result; } @@ -2914,6 +3048,9 @@ YieldProcessor() "nop"); #elif defined(HOST_ARM) || defined(HOST_ARM64) __asm__ __volatile__( "yield"); +#elif defined(HOST_RISCV64) + // TODO-RISCV64-CQ: When Zihintpause is supported, replace with `pause` instruction. + __asm__ __volatile__(".word 0x0100000f"); #else return; #endif diff --git a/src/shared/pal/inc/rt/intsafe.h b/src/shared/pal/inc/rt/intsafe.h index 2b607e3a31..23e8969cf8 100644 --- a/src/shared/pal/inc/rt/intsafe.h +++ b/src/shared/pal/inc/rt/intsafe.h @@ -31,7 +31,7 @@ #define LODWORD(_qw) ((ULONG)(_qw)) #if defined(MIDL_PASS) || defined(RC_INVOKED) || defined(_M_CEE_PURE) \ - || defined(_M_AMD64) || defined(__ARM_ARCH) || defined(_M_S390X) + || defined(_M_AMD64) || defined(__ARM_ARCH) || defined(_M_S390X) || defined(_M_RISCV64) #ifndef UInt32x32To64 #define UInt32x32To64(a, b) ((unsigned __int64)((ULONG)(a)) * (unsigned __int64)((ULONG)(b))) diff --git a/src/shared/pal/inc/rt/ntimage.h b/src/shared/pal/inc/rt/ntimage.h index f722da0e4b..a1ac7c2f99 100644 --- a/src/shared/pal/inc/rt/ntimage.h +++ b/src/shared/pal/inc/rt/ntimage.h @@ -244,6 +244,7 @@ typedef struct _IMAGE_FILE_HEADER { #define IMAGE_FILE_MACHINE_ARM64 0xAA64 // ARM64 Little-Endian #define IMAGE_FILE_MACHINE_CEE 0xC0EE #define IMAGE_FILE_MACHINE_LOONGARCH64 0x6264 // LOONGARCH64. +#define IMAGE_FILE_MACHINE_RISCV64 0x5064 // RISCV64 // // Directory format. @@ -1036,6 +1037,12 @@ typedef IMAGE_RELOCATION UNALIGNED *PIMAGE_RELOCATION; #define IMAGE_REL_LOONGARCH64_PC 0x0003 #define IMAGE_REL_LOONGARCH64_JIR 0x0004 +// +// RISCV64 relocation types +// +#define IMAGE_REL_RISCV64_PC 0x0003 +#define IMAGE_REL_RISCV64_JALR 0x0004 + // // CEF relocation types. // diff --git a/src/shared/pal/inc/rt/palrt.h b/src/shared/pal/inc/rt/palrt.h index 0855f5991c..033e523c35 100644 --- a/src/shared/pal/inc/rt/palrt.h +++ b/src/shared/pal/inc/rt/palrt.h @@ -1167,7 +1167,7 @@ typedef struct _DISPATCHER_CONTEXT { DWORD Reserved; } DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT; -#elif defined(HOST_ARM64) +#elif defined(HOST_ARM64) || defined(HOST_RISCV64) typedef struct _DISPATCHER_CONTEXT { ULONG64 ControlPc; diff --git a/src/shared/pal/inc/unixasmmacros.inc b/src/shared/pal/inc/unixasmmacros.inc index a1bad6300f..593d6089a4 100644 --- a/src/shared/pal/inc/unixasmmacros.inc +++ b/src/shared/pal/inc/unixasmmacros.inc @@ -43,6 +43,8 @@ #include "unixasmmacrosarm.inc" #elif defined(HOST_ARM64) #include "unixasmmacrosarm64.inc" +#elif defined(HOST_RISCV64) +#include "unixasmmacrosriscv64.inc" #elif defined(HOST_S390X) #include "unixasmmacross390x.inc" #endif diff --git a/src/shared/pal/inc/unixasmmacrosriscv64.inc b/src/shared/pal/inc/unixasmmacrosriscv64.inc new file mode 100644 index 0000000000..56da9af150 --- /dev/null +++ b/src/shared/pal/inc/unixasmmacrosriscv64.inc @@ -0,0 +1,341 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +.macro NESTED_ENTRY Name, Section, Handler + LEAF_ENTRY \Name, \Section + .ifnc \Handler, NoHandler + .cfi_personality 0x1B, C_FUNC(\Handler) // 0x1B == DW_EH_PE_pcrel | DW_EH_PE_sdata4 + .endif +.endm + +.macro NESTED_END Name, Section + LEAF_END \Name, \Section +.endm + +.macro PATCH_LABEL Name + .global C_FUNC(\Name) +C_FUNC(\Name): +.endm + +.macro LEAF_ENTRY Name, Section + .global C_FUNC(\Name) + .type \Name, %function +C_FUNC(\Name): + .cfi_startproc +.endm + +.macro LEAF_END Name, Section + .size \Name, .-\Name + .cfi_endproc +.endm + +.macro LEAF_END_MARKED Name, Section +C_FUNC(\Name\()_End): + .global C_FUNC(\Name\()_End) + LEAF_END \Name, \Section + // make sure this symbol gets its own address + nop +.endm + +.macro PREPARE_EXTERNAL_VAR Name, HelperReg + lla \HelperReg, \Name +.endm + +.macro PROLOG_STACK_ALLOC Size + addi sp, sp, -\Size + .cfi_adjust_cfa_offset \Size +.endm + +.macro EPILOG_STACK_FREE Size + addi sp, sp, \Size + .cfi_adjust_cfa_offset -\Size +.endm + +.macro EPILOG_STACK_RESTORE + ori sp, fp, 0 + .cfi_restore sp +.endm + +.macro PROLOG_SAVE_REG reg, ofs + sd \reg, \ofs(sp) + .cfi_rel_offset \reg, \ofs +.endm + +.macro PROLOG_SAVE_REG_PAIR reg1, reg2, ofs, __def_cfa_save=0 + sd \reg1, \ofs(sp) + sd \reg2, (\ofs+8)(sp) + .cfi_rel_offset \reg1, \ofs + .cfi_rel_offset \reg2, \ofs + 8 + .if (\__def_cfa_save == 1) + addi fp, sp, 0 + .cfi_def_cfa_register fp + .endif +.endm + +.macro PROLOG_SAVE_REG_PAIR_INDEXED reg1, reg2, ssize, __def_cfa_save=1 + addi sp, sp, -\ssize + .cfi_adjust_cfa_offset \ssize + + sd \reg1, 0(sp) + sd \reg2, 8(sp) + + .cfi_rel_offset \reg1, 0 + .cfi_rel_offset \reg2, 8 + .if (\__def_cfa_save == 1) + addi fp, sp, 0 + .cfi_def_cfa_register fp + .endif +.endm + +.macro EPILOG_RESTORE_REG reg, ofs + ld \reg, (\ofs)(sp) + .cfi_restore \reg +.endm + +.macro EPILOG_RESTORE_REG_PAIR reg1, reg2, ofs + ld \reg2, (\ofs+8)(sp) + ld \reg1, (\ofs)(sp) + .cfi_restore \reg2 + .cfi_restore \reg1 +.endm + +.macro EPILOG_RESTORE_REG_PAIR_INDEXED reg1, reg2, ssize + ld \reg2, 8(sp) + ld \reg1, 0(sp) + .cfi_restore \reg2 + .cfi_restore \reg1 + + addi sp, sp, \ssize + .cfi_adjust_cfa_offset -\ssize +.endm + +.macro EPILOG_RETURN + ret +.endm + +.macro EMIT_BREAKPOINT + ebreak +.endm + +.macro EPILOG_BRANCH Target + j \Target +.endm + +.macro EPILOG_BRANCH_REG reg + jr \reg +.endm + +//----------------------------------------------------------------------------- +// The Following sets of SAVE_*_REGISTERS expect the memory to be reserved and +// base address to be passed in $reg +// + +// Reserve 64 bytes of memory before calling SAVE_CALLEESAVED_REGISTERS +.macro SAVE_CALLEESAVED_REGISTERS reg, ofs + PROLOG_SAVE_REG_PAIR s1, s2, \ofs + 16 + PROLOG_SAVE_REG_PAIR s3, s4, \ofs + 32 + PROLOG_SAVE_REG_PAIR s5, s6, \ofs + 48 + PROLOG_SAVE_REG_PAIR s7, s8, \ofs + 64 + PROLOG_SAVE_REG_PAIR s9, s10, \ofs + 80 + PROLOG_SAVE_REG_PAIR s11, tp \ofs + 96 + PROLOG_SAVE_REG gp, \ofs + 112 +.endm + +// Reserve 64 bytes of memory before calling SAVE_ARGUMENT_REGISTERS +.macro SAVE_ARGUMENT_REGISTERS reg, ofs + sd a0, (\ofs)(\reg) + sd a1, (\ofs + 8)(\reg) + sd a2, (\ofs + 16)(\reg) + sd a3, (\ofs + 24)(\reg) + sd a4, (\ofs + 32)(\reg) + sd a5, (\ofs + 40)(\reg) + sd a6, (\ofs + 48)(\reg) + sd a7, (\ofs + 56)(\reg) +.endm + +// Reserve 64 bytes of memory before calling SAVE_FLOAT_ARGUMENT_REGISTERS +.macro SAVE_FLOAT_ARGUMENT_REGISTERS reg, ofs + fsd fa0, (\ofs)(\reg) + fsd fa1, (\ofs + 8)(\reg) + fsd fa2, (\ofs + 16)(\reg) + fsd fa3, (\ofs + 24)(\reg) + fsd fa4, (\ofs + 32)(\reg) + fsd fa5, (\ofs + 40)(\reg) + fsd fa6, (\ofs + 48)(\reg) + fsd fa7, (\ofs + 56)(\reg) +.endm + +// Reserve 64 bytes of memory before calling SAVE_FLOAT_CALLEESAVED_REGISTERS +.macro SAVE_FLOAT_CALLEESAVED_REGISTERS reg, ofs +// TODO RISCV NYI + sw ra, 0(zero) +.endm + +.macro RESTORE_CALLEESAVED_REGISTERS reg, ofs + EPILOG_RESTORE_REG gp \ofs + 112 + EPILOG_RESTORE_REG_PAIR s11, tp \ofs + 96 + EPILOG_RESTORE_REG_PAIR s9, s10, \ofs + 80 + EPILOG_RESTORE_REG_PAIR s7, s8, \ofs + 64 + EPILOG_RESTORE_REG_PAIR s5, s6, \ofs + 48 + EPILOG_RESTORE_REG_PAIR s3, s4, \ofs + 32 + EPILOG_RESTORE_REG_PAIR s1, s2, \ofs + 16 +.endm + +.macro RESTORE_ARGUMENT_REGISTERS reg, ofs + ld a0, (\ofs)(\reg) + ld a1, (\ofs + 8)(\reg) + ld a2, (\ofs + 16)(\reg) + ld a3, (\ofs + 24)(\reg) + ld a4, (\ofs + 32)(\reg) + ld a5, (\ofs + 40)(\reg) + ld a6, (\ofs + 48)(\reg) + ld a7, (\ofs + 56)(\reg) +.endm + +.macro RESTORE_FLOAT_ARGUMENT_REGISTERS reg, ofs + fld fa0, (\ofs)(\reg) + fld fa1, (\ofs + 8)(\reg) + fld fa2, (\ofs + 16)(\reg) + fld fa3, (\ofs + 24)(\reg) + fld fa4, (\ofs + 32)(\reg) + fld fa5, (\ofs + 40)(\reg) + fld fa6, (\ofs + 48)(\reg) + fld fa7, (\ofs + 56)(\reg) +.endm + +.macro RESTORE_FLOAT_CALLEESAVED_REGISTERS reg, ofs +// TODO RISCV NYI + sw ra, 0(zero) +.endm + +//----------------------------------------------------------------------------- +// Define the prolog for a TransitionBlock-based method. This macro should be called first in the method and +// comprises the entire prolog.The locals must be 8 byte aligned +// +// Save_argument_registers: +// GPR_a7 +// GPR_a6 +// GPR_a5 +// GPR_a4 +// GPR_a3 +// GPR_a2 +// GPR_a1 +// GPR_a0 +// +// General Registers: +// GPR_tp +// GPR_s8 +// GPR_s7 +// GPR_s6 +// GPR_s5 +// GPR_s4 +// GPR_s3 +// GPR_s2 +// GPR_s1 +// GPR_s0 +// GPR_ra +// GPR_fp +// +// Float Point: +// FPR_f27 / fs11 +// FPR_f26 / fs10 +// FPR_f25 / fs9 +// FPR_f24 / fs8 +// FPR_f23 / fs7 +// FPR_f22 / fs6 +// FPR_f21 / fs5 +// FPR_f20 / fs4 +// FPR_f19 / fs3 +// FPR_f18 / fs2 +// FPR_f9 / fs1 +// FPR_f8 / fs0 +// Extra: +// +.macro PROLOG_WITH_TRANSITION_BLOCK extraParameters = 0, extraLocals = 0, SaveFPRegs = 1 + __PWTB_SaveFPArgs = \SaveFPRegs + + __PWTB_FloatArgumentRegisters = \extraLocals + + .if ((__PWTB_FloatArgumentRegisters % 16) != 0) + __PWTB_FloatArgumentRegisters = __PWTB_FloatArgumentRegisters + 8 + .endif + + __PWTB_TransitionBlock = __PWTB_FloatArgumentRegisters + + .if (__PWTB_SaveFPArgs == 1) + __PWTB_TransitionBlock = __PWTB_TransitionBlock + SIZEOF__FloatArgumentRegisters + .endif + + + __PWTB_CalleeSavedRegisters = __PWTB_TransitionBlock + __PWTB_ArgumentRegisters = __PWTB_TransitionBlock + 120 + + // Including fp, ra, s1-s11, tp, gp, and (a0-a7)arguments. (1+1+11+1+1)*8 + 8*8. + __PWTB_StackAlloc = __PWTB_TransitionBlock + 120 + 64 + PROLOG_STACK_ALLOC __PWTB_StackAlloc + PROLOG_SAVE_REG_PAIR fp, ra, __PWTB_CalleeSavedRegisters, 1 + + // First, Spill argument registers. + SAVE_ARGUMENT_REGISTERS sp, __PWTB_ArgumentRegisters + + // Then, Spill callee saved registers. sp=r2. + SAVE_CALLEESAVED_REGISTERS sp, __PWTB_CalleeSavedRegisters + + // saving is f10-17. + .if (__PWTB_SaveFPArgs == 1) + SAVE_FLOAT_ARGUMENT_REGISTERS sp, __PWTB_FloatArgumentRegisters + .endif + +.endm + +.macro EPILOG_WITH_TRANSITION_BLOCK_RETURN +// TODO RISCV NYI + sw ra, 0(zero) +.endm + + +//----------------------------------------------------------------------------- +// Provides a matching epilog to PROLOG_WITH_TRANSITION_BLOCK and ends by preparing for tail-calling. +// Since this is a tail call argument registers are restored. +// +.macro EPILOG_WITH_TRANSITION_BLOCK_TAILCALL + .if (__PWTB_SaveFPArgs == 1) + RESTORE_FLOAT_ARGUMENT_REGISTERS sp, __PWTB_FloatArgumentRegisters + .endif + + RESTORE_CALLEESAVED_REGISTERS sp, __PWTB_CalleeSavedRegisters + + RESTORE_ARGUMENT_REGISTERS sp, __PWTB_ArgumentRegisters + + EPILOG_RESTORE_REG_PAIR fp, ra, __PWTB_CalleeSavedRegisters + + EPILOG_STACK_FREE __PWTB_StackAlloc +.endm + +// ------------------------------------------------------------------ +// Macro to generate Redirection Stubs +// +// $reason : reason for redirection +// Eg. GCThreadControl +// NOTE: If you edit this macro, make sure you update GetCONTEXTFromRedirectedStubStackFrame. +// This function is used by both the personality routine and the debugger to retrieve the original CONTEXT. +.macro GenerateRedirectedHandledJITCaseStub reason +// TODO RISCV NYI + sw ra, 0(zero) +.endm + +//----------------------------------------------------------------------------- +// Macro used to check (in debug builds only) whether the stack is 16-bytes aligned (a requirement before calling +// out into C++/OS code). Invoke this directly after your prolog (if the stack frame size is fixed) or directly +// before a call (if you have a frame pointer and a dynamic stack). A breakpoint will be invoked if the stack +// is misaligned. +// +.macro CHECK_STACK_ALIGNMENT + +#ifdef _DEBUG + andi t4, sp, 0xf + beq t4, zero, 0f + EMIT_BREAKPOINT +0: +#endif +.endm diff --git a/src/shared/pal/prebuilt/inc/cordebug.h b/src/shared/pal/prebuilt/inc/cordebug.h index df87698471..5feeaff766 100644 --- a/src/shared/pal/prebuilt/inc/cordebug.h +++ b/src/shared/pal/prebuilt/inc/cordebug.h @@ -1475,7 +1475,9 @@ enum CorDebugPlatform CORDB_PLATFORM_POSIX_AMD64 = ( CORDB_PLATFORM_WINDOWS_ARM64 + 1 ) , CORDB_PLATFORM_POSIX_X86 = ( CORDB_PLATFORM_POSIX_AMD64 + 1 ) , CORDB_PLATFORM_POSIX_ARM = ( CORDB_PLATFORM_POSIX_X86 + 1 ) , - CORDB_PLATFORM_POSIX_ARM64 = ( CORDB_PLATFORM_POSIX_ARM + 1 ) + CORDB_PLATFORM_POSIX_ARM64 = ( CORDB_PLATFORM_POSIX_ARM + 1 ) , + CORDB_PLATFORM_POSIX_LOONGARCH64 = ( CORDB_PLATFORM_POSIX_ARM64 + 1 ) , + CORDB_PLATFORM_POSIX_RISCV64 = ( CORDB_PLATFORM_POSIX_LOONGARCH64 + 1 ) } CorDebugPlatform; @@ -9191,7 +9193,72 @@ enum CorDebugRegister REGISTER_ARM64_V28 = ( REGISTER_ARM64_V27 + 1 ) , REGISTER_ARM64_V29 = ( REGISTER_ARM64_V28 + 1 ) , REGISTER_ARM64_V30 = ( REGISTER_ARM64_V29 + 1 ) , - REGISTER_ARM64_V31 = ( REGISTER_ARM64_V30 + 1 ) + REGISTER_ARM64_V31 = ( REGISTER_ARM64_V30 + 1 ) , + REGISTER_RISCV64_PC = 0, + REGISTER_RISCV64_RA = ( REGISTER_RISCV64_PC + 1), + REGISTER_RISCV64_SP = ( REGISTER_RISCV64_RA + 1), + REGISTER_RISCV64_GP = ( REGISTER_RISCV64_SP + 1), + REGISTER_RISCV64_TP = ( REGISTER_RISCV64_GP + 1 ), + REGISTER_RISCV64_T0 = ( REGISTER_RISCV64_TP + 1 ), + REGISTER_RISCV64_T1 = ( REGISTER_RISCV64_T0 + 1 ), + REGISTER_RISCV64_T2 = ( REGISTER_RISCV64_T1 + 1 ), + REGISTER_RISCV64_FP = ( REGISTER_RISCV64_T2 + 1 ), + REGISTER_RISCV64_S1 = ( REGISTER_RISCV64_FP + 1 ), + REGISTER_RISCV64_A0 = ( REGISTER_RISCV64_S1 + 1 ), + REGISTER_RISCV64_A1 = ( REGISTER_RISCV64_A0 + 1 ), + REGISTER_RISCV64_A2 = ( REGISTER_RISCV64_A1 + 1 ), + REGISTER_RISCV64_A3 = ( REGISTER_RISCV64_A2 + 1 ), + REGISTER_RISCV64_A4 = ( REGISTER_RISCV64_A3 + 1 ), + REGISTER_RISCV64_A5 = ( REGISTER_RISCV64_A4 + 1 ), + REGISTER_RISCV64_A6 = ( REGISTER_RISCV64_A5 + 1 ), + REGISTER_RISCV64_A7 = ( REGISTER_RISCV64_A6 + 1 ), + REGISTER_RISCV64_S2 = ( REGISTER_RISCV64_A7 + 1 ), + REGISTER_RISCV64_S3 = ( REGISTER_RISCV64_S2 + 1 ), + REGISTER_RISCV64_S4 = ( REGISTER_RISCV64_S3 + 1 ), + REGISTER_RISCV64_S5 = ( REGISTER_RISCV64_S4 + 1 ), + REGISTER_RISCV64_S6 = ( REGISTER_RISCV64_S5 + 1 ), + REGISTER_RISCV64_S7 = ( REGISTER_RISCV64_S6 + 1 ), + REGISTER_RISCV64_S8 = ( REGISTER_RISCV64_S7 + 1 ), + REGISTER_RISCV64_S9 = ( REGISTER_RISCV64_S8 + 1 ), + REGISTER_RISCV64_S10 = ( REGISTER_RISCV64_S9 + 1 ), + REGISTER_RISCV64_S11 = ( REGISTER_RISCV64_S10 + 1 ), + REGISTER_RISCV64_T3 = ( REGISTER_RISCV64_S11 + 1 ), + REGISTER_RISCV64_T4 = ( REGISTER_RISCV64_T3 + 1 ), + REGISTER_RISCV64_T5 = ( REGISTER_RISCV64_T4 + 1 ), + REGISTER_RISCV64_T6 = ( REGISTER_RISCV64_T5 + 1 ), + REGISTER_RISCV64_F0 = ( REGISTER_RISCV64_T6 + 1 ), + REGISTER_RISCV64_F1 = ( REGISTER_RISCV64_F0 + 1 ), + REGISTER_RISCV64_F2 = ( REGISTER_RISCV64_F1 + 1 ), + REGISTER_RISCV64_F3 = ( REGISTER_RISCV64_F2 + 1 ), + REGISTER_RISCV64_F4 = ( REGISTER_RISCV64_F3 + 1 ), + REGISTER_RISCV64_F5 = ( REGISTER_RISCV64_F4 + 1 ), + REGISTER_RISCV64_F6 = ( REGISTER_RISCV64_F5 + 1 ), + REGISTER_RISCV64_F7 = ( REGISTER_RISCV64_F6 + 1 ), + REGISTER_RISCV64_F8 = ( REGISTER_RISCV64_F7 + 1 ), + REGISTER_RISCV64_F9 = ( REGISTER_RISCV64_F8 + 1 ), + REGISTER_RISCV64_F10 = ( REGISTER_RISCV64_F9 + 1 ), + REGISTER_RISCV64_F11 = ( REGISTER_RISCV64_F10 + 1 ), + REGISTER_RISCV64_F12 = ( REGISTER_RISCV64_F11 + 1 ), + REGISTER_RISCV64_F13 = ( REGISTER_RISCV64_F12 + 1 ), + REGISTER_RISCV64_F14 = ( REGISTER_RISCV64_F13 + 1 ), + REGISTER_RISCV64_F15 = ( REGISTER_RISCV64_F14 + 1 ), + REGISTER_RISCV64_F16 = ( REGISTER_RISCV64_F15 + 1 ), + REGISTER_RISCV64_F17 = ( REGISTER_RISCV64_F16 + 1 ), + REGISTER_RISCV64_F18 = ( REGISTER_RISCV64_F17 + 1 ), + REGISTER_RISCV64_F19 = ( REGISTER_RISCV64_F18 + 1 ), + REGISTER_RISCV64_F20 = ( REGISTER_RISCV64_F19 + 1 ), + REGISTER_RISCV64_F21 = ( REGISTER_RISCV64_F20 + 1 ), + REGISTER_RISCV64_F22 = ( REGISTER_RISCV64_F21 + 1 ), + REGISTER_RISCV64_F23 = ( REGISTER_RISCV64_F22 + 1 ), + REGISTER_RISCV64_F24 = ( REGISTER_RISCV64_F23 + 1 ), + REGISTER_RISCV64_F25 = ( REGISTER_RISCV64_F24 + 1 ), + REGISTER_RISCV64_F26 = ( REGISTER_RISCV64_F25 + 1 ), + REGISTER_RISCV64_F27 = ( REGISTER_RISCV64_F26 + 1 ), + REGISTER_RISCV64_F28 = ( REGISTER_RISCV64_F27 + 1 ), + REGISTER_RISCV64_F29 = ( REGISTER_RISCV64_F28 + 1 ), + REGISTER_RISCV64_F30 = ( REGISTER_RISCV64_F29 + 1 ), + REGISTER_RISCV64_F31 = ( REGISTER_RISCV64_F30 + 1 ), + REGISTER_RISCV64_X0 = ( REGISTER_RISCV64_F31 + 1 ), // TODO-RISCV64-CQ: Add X0 for an use in debug. Need to check. } CorDebugRegister; diff --git a/src/shared/pal/src/CMakeLists.txt b/src/shared/pal/src/CMakeLists.txt index 453433e13b..7ae9cb393d 100644 --- a/src/shared/pal/src/CMakeLists.txt +++ b/src/shared/pal/src/CMakeLists.txt @@ -42,6 +42,8 @@ elseif(CLR_CMAKE_HOST_ARCH_I386) set(PAL_ARCH_SOURCES_DIR i386) elseif(CLR_CMAKE_HOST_ARCH_S390X) set(PAL_ARCH_SOURCES_DIR s390x) +elseif(CLR_CMAKE_HOST_ARCH_RISCV64) + set(PAL_ARCH_SOURCES_DIR riscv64) endif() if(CLR_CMAKE_TARGET_OSX) diff --git a/src/shared/pal/src/arch/riscv64/asmconstants.h b/src/shared/pal/src/arch/riscv64/asmconstants.h new file mode 100644 index 0000000000..97ae37f0c3 --- /dev/null +++ b/src/shared/pal/src/arch/riscv64/asmconstants.h @@ -0,0 +1,135 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __PAL_RISCV64_ASMCONSTANTS_H__ +#define __PAL_RISCV64_ASMCONSTANTS_H__ + +// https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/2d865a2964fe06bfc569ab00c74e152b582ed764/riscv-dwarf.adoc +// https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/2d865a2964fe06bfc569ab00c74e152b582ed764/riscv-cc.adoc + +#define CONTEXT_RISCV64 0x01000000L + +#define CONTEXT_CONTROL_BIT (0) +#define CONTEXT_INTEGER_BIT (1) +#define CONTEXT_FLOATING_POINT_BIT (2) +#define CONTEXT_DEBUG_REGISTERS_BIT (3) + +#define CONTEXT_CONTROL (CONTEXT_RISCV64 | (1L << CONTEXT_CONTROL_BIT)) +#define CONTEXT_INTEGER (CONTEXT_RISCV64 | (1 << CONTEXT_INTEGER_BIT)) +#define CONTEXT_FLOATING_POINT (CONTEXT_RISCV64 | (1 << CONTEXT_FLOATING_POINT_BIT)) +#define CONTEXT_DEBUG_REGISTERS (CONTEXT_RISCV64 | (1 << CONTEXT_DEBUG_REGISTERS_BIT)) + +#define CONTEXT_FULL (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT) + +#define SIZEOF_RISCV64_GPR 8 +#define SIZEOF_RISCV64_FPR 8 + +#define CONTEXT_ContextFlags 0 +#define CONTEXT_X0 CONTEXT_ContextFlags + SIZEOF_RISCV64_GPR // hardwired zero +#define CONTEXT_X1 CONTEXT_X0 + SIZEOF_RISCV64_GPR +#define CONTEXT_X2 CONTEXT_X1 + SIZEOF_RISCV64_GPR +#define CONTEXT_X3 CONTEXT_X2 + SIZEOF_RISCV64_GPR +#define CONTEXT_X4 CONTEXT_X3 + SIZEOF_RISCV64_GPR +#define CONTEXT_X5 CONTEXT_X4 + SIZEOF_RISCV64_GPR +#define CONTEXT_X6 CONTEXT_X5 + SIZEOF_RISCV64_GPR +#define CONTEXT_X7 CONTEXT_X6 + SIZEOF_RISCV64_GPR +#define CONTEXT_X8 CONTEXT_X7 + SIZEOF_RISCV64_GPR +#define CONTEXT_X9 CONTEXT_X8 + SIZEOF_RISCV64_GPR +#define CONTEXT_X10 CONTEXT_X9 + SIZEOF_RISCV64_GPR +#define CONTEXT_X11 CONTEXT_X10 + SIZEOF_RISCV64_GPR +#define CONTEXT_X12 CONTEXT_X11 + SIZEOF_RISCV64_GPR +#define CONTEXT_X13 CONTEXT_X12 + SIZEOF_RISCV64_GPR +#define CONTEXT_X14 CONTEXT_X13 + SIZEOF_RISCV64_GPR +#define CONTEXT_X15 CONTEXT_X14 + SIZEOF_RISCV64_GPR +#define CONTEXT_X16 CONTEXT_X15 + SIZEOF_RISCV64_GPR +#define CONTEXT_X17 CONTEXT_X16 + SIZEOF_RISCV64_GPR +#define CONTEXT_X18 CONTEXT_X17 + SIZEOF_RISCV64_GPR +#define CONTEXT_X19 CONTEXT_X18 + SIZEOF_RISCV64_GPR +#define CONTEXT_X20 CONTEXT_X19 + SIZEOF_RISCV64_GPR +#define CONTEXT_X21 CONTEXT_X20 + SIZEOF_RISCV64_GPR +#define CONTEXT_X22 CONTEXT_X21 + SIZEOF_RISCV64_GPR +#define CONTEXT_X23 CONTEXT_X22 + SIZEOF_RISCV64_GPR +#define CONTEXT_X24 CONTEXT_X23 + SIZEOF_RISCV64_GPR +#define CONTEXT_X25 CONTEXT_X24 + SIZEOF_RISCV64_GPR +#define CONTEXT_X26 CONTEXT_X25 + SIZEOF_RISCV64_GPR +#define CONTEXT_X27 CONTEXT_X26 + SIZEOF_RISCV64_GPR +#define CONTEXT_X28 CONTEXT_X27 + SIZEOF_RISCV64_GPR +#define CONTEXT_X29 CONTEXT_X28 + SIZEOF_RISCV64_GPR +#define CONTEXT_X30 CONTEXT_X29 + SIZEOF_RISCV64_GPR +#define CONTEXT_X31 CONTEXT_X30 + SIZEOF_RISCV64_GPR + +#define CONTEXT_Pc CONTEXT_X31 + SIZEOF_RISCV64_GPR +#define CONTEXT_FPU_OFFSET CONTEXT_Pc + SIZEOF_RISCV64_GPR +#define CONTEXT_Ra CONTEXT_X1 +#define CONTEXT_Sp CONTEXT_X2 +#define CONTEXT_Gp CONTEXT_X3 +#define CONTEXT_Tp CONTEXT_X4 +#define CONTEXT_Fp CONTEXT_X8 + +#define CONTEXT_S0 CONTEXT_X8 +#define CONTEXT_S1 CONTEXT_X9 +#define CONTEXT_S2 CONTEXT_X18 +#define CONTEXT_S3 CONTEXT_X19 +#define CONTEXT_S4 CONTEXT_X20 +#define CONTEXT_S5 CONTEXT_X21 +#define CONTEXT_S6 CONTEXT_X22 +#define CONTEXT_S7 CONTEXT_X23 +#define CONTEXT_S8 CONTEXT_X24 +#define CONTEXT_S9 CONTEXT_X25 +#define CONTEXT_S10 CONTEXT_X26 +#define CONTEXT_S11 CONTEXT_X27 + +#define CONTEXT_A0 CONTEXT_X10 +#define CONTEXT_A1 CONTEXT_X11 +#define CONTEXT_A2 CONTEXT_X12 +#define CONTEXT_A3 CONTEXT_X13 +#define CONTEXT_A4 CONTEXT_X14 +#define CONTEXT_A5 CONTEXT_X15 +#define CONTEXT_A6 CONTEXT_X16 +#define CONTEXT_A7 CONTEXT_X17 + +#define CONTEXT_T0 CONTEXT_X5 +#define CONTEXT_T1 CONTEXT_X6 +#define CONTEXT_T2 CONTEXT_X7 +#define CONTEXT_T3 CONTEXT_X28 +#define CONTEXT_T4 CONTEXT_X29 +#define CONTEXT_T5 CONTEXT_X30 +#define CONTEXT_T6 CONTEXT_X31 + +#define CONTEXT_F0 0 +#define CONTEXT_F1 CONTEXT_F0 + SIZEOF_RISCV64_FPR +#define CONTEXT_F2 CONTEXT_F1 + SIZEOF_RISCV64_FPR +#define CONTEXT_F3 CONTEXT_F2 + SIZEOF_RISCV64_FPR +#define CONTEXT_F4 CONTEXT_F3 + SIZEOF_RISCV64_FPR +#define CONTEXT_F5 CONTEXT_F4 + SIZEOF_RISCV64_FPR +#define CONTEXT_F6 CONTEXT_F5 + SIZEOF_RISCV64_FPR +#define CONTEXT_F7 CONTEXT_F6 + SIZEOF_RISCV64_FPR +#define CONTEXT_F8 CONTEXT_F7 + SIZEOF_RISCV64_FPR +#define CONTEXT_F9 CONTEXT_F8 + SIZEOF_RISCV64_FPR +#define CONTEXT_F10 CONTEXT_F9 + SIZEOF_RISCV64_FPR +#define CONTEXT_F11 CONTEXT_F10 + SIZEOF_RISCV64_FPR +#define CONTEXT_F12 CONTEXT_F11 + SIZEOF_RISCV64_FPR +#define CONTEXT_F13 CONTEXT_F12 + SIZEOF_RISCV64_FPR +#define CONTEXT_F14 CONTEXT_F13 + SIZEOF_RISCV64_FPR +#define CONTEXT_F15 CONTEXT_F14 + SIZEOF_RISCV64_FPR +#define CONTEXT_F16 CONTEXT_F15 + SIZEOF_RISCV64_FPR +#define CONTEXT_F17 CONTEXT_F16 + SIZEOF_RISCV64_FPR +#define CONTEXT_F18 CONTEXT_F17 + SIZEOF_RISCV64_FPR +#define CONTEXT_F19 CONTEXT_F18 + SIZEOF_RISCV64_FPR +#define CONTEXT_F20 CONTEXT_F19 + SIZEOF_RISCV64_FPR +#define CONTEXT_F21 CONTEXT_F20 + SIZEOF_RISCV64_FPR +#define CONTEXT_F22 CONTEXT_F21 + SIZEOF_RISCV64_FPR +#define CONTEXT_F23 CONTEXT_F22 + SIZEOF_RISCV64_FPR +#define CONTEXT_F24 CONTEXT_F23 + SIZEOF_RISCV64_FPR +#define CONTEXT_F25 CONTEXT_F24 + SIZEOF_RISCV64_FPR +#define CONTEXT_F26 CONTEXT_F25 + SIZEOF_RISCV64_FPR +#define CONTEXT_F27 CONTEXT_F26 + SIZEOF_RISCV64_FPR +#define CONTEXT_F28 CONTEXT_F27 + SIZEOF_RISCV64_FPR +#define CONTEXT_F29 CONTEXT_F28 + SIZEOF_RISCV64_FPR +#define CONTEXT_F30 CONTEXT_F29 + SIZEOF_RISCV64_FPR +#define CONTEXT_F31 CONTEXT_F30 + SIZEOF_RISCV64_FPR + +#define CONTEXT_FLOAT_CONTROL_OFFSET CONTEXT_F31+SIZEOF_RISCV64_FPR +#define CONTEXT_Size ((CONTEXT_FPU_OFFSET + 8 + 8 + 0xf) & ~0xf) + +#endif diff --git a/src/shared/pal/src/arch/riscv64/context2.S b/src/shared/pal/src/arch/riscv64/context2.S new file mode 100644 index 0000000000..6bc3970cb7 --- /dev/null +++ b/src/shared/pal/src/arch/riscv64/context2.S @@ -0,0 +1,242 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// +// Implementation of _CONTEXT_CaptureContext for the RISCV64 platform. +// This function is processor dependent. It is used by exception handling, +// and is always apply to the current thread. +// + +#include "unixasmmacros.inc" +#include "asmconstants.h" + +// Incoming: +// a0: Context* +// a1: Exception* +// +LEAF_ENTRY RtlRestoreContext, _TEXT +#ifdef HAS_ASAN +#pragma error("TODO-RISCV64-CQ: unimplemented on RISCV64 yet") +#endif + + mv t4, a0 + lw t1, CONTEXT_ContextFlags(t4) + andi t1, t1, 0x1 << CONTEXT_FLOATING_POINT_BIT + beqz t1, LOCAL_LABEL(No_Restore_CONTEXT_FLOATING_POINT) + + //64-bits FPR. + addi t0, t4, CONTEXT_FPU_OFFSET + + fld f0, (CONTEXT_F0)(t0) + fld f1, (CONTEXT_F1)(t0) + fld f2, (CONTEXT_F2)(t0) + fld f3, (CONTEXT_F3)(t0) + fld f4, (CONTEXT_F4)(t0) + fld f5, (CONTEXT_F5)(t0) + fld f6, (CONTEXT_F6)(t0) + fld f7, (CONTEXT_F7)(t0) + fld f8, (CONTEXT_F8)(t0) + fld f9, (CONTEXT_F9)(t0) + fld f10, (CONTEXT_F10)(t0) + fld f11, (CONTEXT_F11)(t0) + fld f12, (CONTEXT_F12)(t0) + fld f13, (CONTEXT_F13)(t0) + fld f14, (CONTEXT_F14)(t0) + fld f15, (CONTEXT_F15)(t0) + fld f16, (CONTEXT_F16)(t0) + fld f17, (CONTEXT_F17)(t0) + fld f18, (CONTEXT_F18)(t0) + fld f19, (CONTEXT_F19)(t0) + fld f20, (CONTEXT_F20)(t0) + fld f21, (CONTEXT_F21)(t0) + fld f22, (CONTEXT_F22)(t0) + fld f23, (CONTEXT_F23)(t0) + fld f24, (CONTEXT_F24)(t0) + fld f25, (CONTEXT_F25)(t0) + fld f26, (CONTEXT_F26)(t0) + fld f27, (CONTEXT_F27)(t0) + fld f28, (CONTEXT_F28)(t0) + fld f29, (CONTEXT_F29)(t0) + fld f30, (CONTEXT_F30)(t0) + fld f31, (CONTEXT_F31)(t0) + + lw t1, (CONTEXT_FLOAT_CONTROL_OFFSET)(t0) + fscsr x0, t1 + +LOCAL_LABEL(No_Restore_CONTEXT_FLOATING_POINT): + + lw t1, CONTEXT_ContextFlags(t4) + andi t1, t1, 0x1 << CONTEXT_INTEGER_BIT + beqz t1, LOCAL_LABEL(No_Restore_CONTEXT_INTEGER) + + ld tp, (CONTEXT_Tp)(a0) + ld gp, (CONTEXT_Gp)(a0) + ld a1, (CONTEXT_A1)(a0) + ld a2, (CONTEXT_A2)(a0) + ld a3, (CONTEXT_A3)(a0) + ld a4, (CONTEXT_A4)(a0) + ld a5, (CONTEXT_A5)(a0) + ld a6, (CONTEXT_A6)(a0) + ld a7, (CONTEXT_A7)(a0) + ld t0, (CONTEXT_T0)(a0) + ld t1, (CONTEXT_T1)(a0) + ld t2, (CONTEXT_T2)(a0) + ld t3, (CONTEXT_T3)(a0) + ld t5, (CONTEXT_T5)(a0) + ld t6, (CONTEXT_T6)(a0) + + ld s1, (CONTEXT_S1)(a0) + ld s2, (CONTEXT_S2)(a0) + ld s3, (CONTEXT_S3)(a0) + ld s4, (CONTEXT_S4)(a0) + ld s5, (CONTEXT_S5)(a0) + ld s6, (CONTEXT_S6)(a0) + ld s7, (CONTEXT_S7)(a0) + ld s8, (CONTEXT_S8)(a0) + ld s9, (CONTEXT_S9)(a0) + ld s10, (CONTEXT_S10)(a0) + ld s11, (CONTEXT_S11)(a0) + + ld a0, (CONTEXT_A0)(a0) + +LOCAL_LABEL(No_Restore_CONTEXT_INTEGER): + + lw t1, CONTEXT_ContextFlags(t4) + andi t1, t1, 0x1 << CONTEXT_CONTROL_BIT + beqz t1, LOCAL_LABEL(No_Restore_CONTEXT_CONTROL) + + ld ra, (CONTEXT_Ra)(t4) + ld fp, (CONTEXT_Fp)(t4) + ld sp, (CONTEXT_Sp)(t4) + ld t1, (CONTEXT_Pc)(t4) // Since we cannot control $pc directly, we're going to corrupt t1 + ld t4, (CONTEXT_T4)(t4) + jr t1 + +LOCAL_LABEL(No_Restore_CONTEXT_CONTROL): + ld t4, (CONTEXT_T4)(t4) + ret +LEAF_END RtlRestoreContext, _TEXT + +// Incoming: +// a0: Context* + +LEAF_ENTRY RtlCaptureContext, _TEXT + PROLOG_STACK_ALLOC 16 + sd t1, 0(sp) + li t1, CONTEXT_FULL + sw t1, CONTEXT_ContextFlags(a0) + ld t1, 0(sp) + EPILOG_STACK_FREE 16 + tail CONTEXT_CaptureContext +LEAF_END RtlCaptureContext, _TEXT + +// Incoming: +// a0: Context* +// + +LEAF_ENTRY CONTEXT_CaptureContext, _TEXT + PROLOG_STACK_ALLOC 24 + sd t0, 0(sp) + sd t1, 8(sp) + sd t3, 16(sp) + + lw t1, CONTEXT_ContextFlags(a0) + li t0, CONTEXT_CONTROL + and t3, t1, t0 + bne t3, t0, LOCAL_LABEL(Done_CONTEXT_CONTROL) + + addi t0, sp, 24 + sd fp, CONTEXT_Fp(a0) + sd t0, CONTEXT_Sp(a0) + sd ra, CONTEXT_Ra(a0) + sd ra, CONTEXT_Pc(a0) + +LOCAL_LABEL(Done_CONTEXT_CONTROL): + + li t0, CONTEXT_INTEGER + and t3, t1, t0 + bne t3, t0, LOCAL_LABEL(Done_CONTEXT_INTEGER) + + ld t0, 0(sp) + ld t1, 8(sp) + ld t3, 16(sp) + + sd tp, (CONTEXT_Tp)(a0) + sd gp, (CONTEXT_Gp)(a0) + sd a0, (CONTEXT_A0)(a0) + sd a1, (CONTEXT_A1)(a0) + sd a2, (CONTEXT_A2)(a0) + sd a3, (CONTEXT_A3)(a0) + sd a4, (CONTEXT_A4)(a0) + sd a5, (CONTEXT_A5)(a0) + sd a6, (CONTEXT_A6)(a0) + sd a7, (CONTEXT_A7)(a0) + sd t0, (CONTEXT_T0)(a0) + sd t1, (CONTEXT_T1)(a0) + sd t2, (CONTEXT_T2)(a0) + sd t3, (CONTEXT_T3)(a0) + sd t4, (CONTEXT_T4)(a0) + sd t5, (CONTEXT_T5)(a0) + sd t6, (CONTEXT_T6)(a0) + + sd s1, (CONTEXT_S1)(a0) + sd s2, (CONTEXT_S2)(a0) + sd s3, (CONTEXT_S3)(a0) + sd s4, (CONTEXT_S4)(a0) + sd s5, (CONTEXT_S5)(a0) + sd s6, (CONTEXT_S6)(a0) + sd s7, (CONTEXT_S7)(a0) + sd s8, (CONTEXT_S8)(a0) + sd s9, (CONTEXT_S9)(a0) + sd s10, (CONTEXT_S10)(a0) + sd s11, (CONTEXT_S11)(a0) + +LOCAL_LABEL(Done_CONTEXT_INTEGER): + lw t1, CONTEXT_ContextFlags(a0) + + li t0, CONTEXT_FLOATING_POINT + and t3, t1, t0 + bne t3, t0, LOCAL_LABEL(Done_CONTEXT_FLOATING_POINT) + + addi a0, a0, CONTEXT_FPU_OFFSET + + fsd f0, (CONTEXT_F0)(a0) + fsd f1, (CONTEXT_F1)(a0) + fsd f2, (CONTEXT_F2)(a0) + fsd f3, (CONTEXT_F3)(a0) + fsd f4, (CONTEXT_F4)(a0) + fsd f5, (CONTEXT_F5)(a0) + fsd f6, (CONTEXT_F6)(a0) + fsd f7, (CONTEXT_F7)(a0) + fsd f8, (CONTEXT_F8)(a0) + fsd f9, (CONTEXT_F9)(a0) + fsd f10, (CONTEXT_F10)(a0) + fsd f11, (CONTEXT_F11)(a0) + fsd f12, (CONTEXT_F12)(a0) + fsd f13, (CONTEXT_F13)(a0) + fsd f14, (CONTEXT_F14)(a0) + fsd f15, (CONTEXT_F15)(a0) + fsd f16, (CONTEXT_F16)(a0) + fsd f17, (CONTEXT_F17)(a0) + fsd f18, (CONTEXT_F18)(a0) + fsd f19, (CONTEXT_F19)(a0) + fsd f20, (CONTEXT_F20)(a0) + fsd f21, (CONTEXT_F21)(a0) + fsd f22, (CONTEXT_F22)(a0) + fsd f23, (CONTEXT_F23)(a0) + fsd f24, (CONTEXT_F24)(a0) + fsd f25, (CONTEXT_F25)(a0) + fsd f26, (CONTEXT_F26)(a0) + fsd f27, (CONTEXT_F27)(a0) + fsd f28, (CONTEXT_F28)(a0) + fsd f29, (CONTEXT_F29)(a0) + fsd f30, (CONTEXT_F30)(a0) + fsd f31, (CONTEXT_F31)(a0) + + frcsr t0 + sd t0, (CONTEXT_FLOAT_CONTROL_OFFSET)(a0) + +LOCAL_LABEL(Done_CONTEXT_FLOATING_POINT): + + EPILOG_STACK_FREE 24 + ret +LEAF_END CONTEXT_CaptureContext, _TEXT diff --git a/src/shared/pal/src/arch/riscv64/debugbreak.S b/src/shared/pal/src/arch/riscv64/debugbreak.S new file mode 100644 index 0000000000..69cb82b86c --- /dev/null +++ b/src/shared/pal/src/arch/riscv64/debugbreak.S @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "unixasmmacros.inc" + +LEAF_ENTRY DBG_DebugBreak, _TEXT + EMIT_BREAKPOINT +LEAF_END_MARKED DBG_DebugBreak, _TEXT diff --git a/src/shared/pal/src/arch/riscv64/processor.cpp b/src/shared/pal/src/arch/riscv64/processor.cpp new file mode 100644 index 0000000000..ab4b84febd --- /dev/null +++ b/src/shared/pal/src/arch/riscv64/processor.cpp @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/*++ + + + +Module Name: + + processor.cpp + +Abstract: + + Implementation of processor related functions for the ARM64 + platform. These functions are processor dependent. + + + +--*/ + +#include "pal/palinternal.h" diff --git a/src/shared/pal/src/misc/sysinfo.cpp b/src/shared/pal/src/misc/sysinfo.cpp index 19f9c86fd4..4e08089d96 100644 --- a/src/shared/pal/src/misc/sysinfo.cpp +++ b/src/shared/pal/src/misc/sysinfo.cpp @@ -470,12 +470,17 @@ GlobalMemoryStatusEx( #endif // __APPLE__ } +#ifndef TARGET_RISCV64 // There is no API to get the total virtual address space size on // Unix, so we use a constant value representing 128TB, which is // the approximate size of total user virtual address space on // the currently supported Unix systems. - static const UINT64 _128TB = (1ull << 47); - lpBuffer->ullTotalVirtual = _128TB; + static const UINT64 VMSize = (1ull << 47); +#else // TARGET_RISCV64 + // For RISC-V Linux Kernel SV39 virtual memory limit is 256gb. + static const UINT64 VMSize = (1ull << 38); +#endif // TARGET_RISCV64 + lpBuffer->ullTotalVirtual = VMSize; lpBuffer->ullAvailVirtual = lpBuffer->ullAvailPhys; LOGEXIT("GlobalMemoryStatusEx returns %d\n", fRetVal); diff --git a/src/tests/CommonTestRunner/TestRunnerUtilities.cs b/src/tests/CommonTestRunner/TestRunnerUtilities.cs new file mode 100644 index 0000000000..7cbc88b264 --- /dev/null +++ b/src/tests/CommonTestRunner/TestRunnerUtilities.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Diagnostics.TestHelpers; +using Xunit.Abstractions; +using TestRunner = Microsoft.Diagnostics.CommonTestRunner.TestRunner; + +namespace CommonTestRunner +{ + public static class TestRunnerUtilities + { + public static async Task StartProcess(TestConfiguration config, string testArguments, ITestOutputHelper outputHelper, int testProcessTimeout = 60_000) + { + TestRunner runner = await TestRunner.Create(config, outputHelper, "EventPipeTracee", testArguments).ConfigureAwait(true); + await runner.Start(testProcessTimeout).ConfigureAwait(true); + return runner; + } + + public static async Task ExecuteCollection( + Func executeCollection, + TestRunner testRunner, + CancellationToken token) + { + Task collectionTask = executeCollection(token); + await ExecuteCollection(collectionTask, testRunner, token).ConfigureAwait(false); + } + + public static async Task ExecuteCollection( + Task collectionTask, + TestRunner testRunner, + CancellationToken token, + Func waitForPipeline = null) + { + // Begin event production + testRunner.WakeupTracee(); + + // Wait for event production to be done + testRunner.WaitForSignal(); + + try + { + if (waitForPipeline != null) + { + await waitForPipeline(token).ConfigureAwait(false); + } + + await collectionTask.ConfigureAwait(true); + } + finally + { + // Signal for debuggee that it's ok to end/move on. + testRunner.WakeupTracee(); + } + } + } +} diff --git a/src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs b/src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs index e3c2b99cdf..a1c54f93d4 100644 --- a/src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs +++ b/src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs @@ -331,7 +331,7 @@ private string DownloadModule(string moduleName, uint timeStamp, uint sizeOfImag Assert.True(timeStamp != 0 && sizeOfImage != 0); SymbolStoreKey key = PEFileKeyGenerator.GetKey(moduleName, timeStamp, sizeOfImage); Assert.NotNull(key); - string downloadedPath = SymbolService.DownloadFile(key); + string downloadedPath = SymbolService.DownloadFile(key.Index, key.FullPathName); Assert.NotNull(downloadedPath); return downloadedPath; } @@ -368,7 +368,7 @@ private string DownloadModule(string moduleName, byte[] buildId) key = MachOFileKeyGenerator.GetKeys(KeyTypeFlags.IdentityKey, moduleName, buildId, symbolFile: false, symbolFileName: null).SingleOrDefault(); } Assert.NotNull(key); - string downloadedPath = SymbolService.DownloadFile(key); + string downloadedPath = SymbolService.DownloadFile(key.Index, key.FullPathName); Assert.NotNull(downloadedPath); return downloadedPath; } diff --git a/src/tests/EventPipeTracee/CustomMetrics.cs b/src/tests/EventPipeTracee/CustomMetrics.cs new file mode 100644 index 0000000000..d16b036f8a --- /dev/null +++ b/src/tests/EventPipeTracee/CustomMetrics.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; +using Constants = DotnetCounters.UnitTests.TestConstants; + +namespace EventPipeTracee +{ + internal sealed class CustomMetrics : IDisposable + { + private Meter _meter; + private Counter _counter; + private Histogram _histogram; + + public CustomMetrics() + { + _meter = new(Constants.TestMeterName); + _counter = _meter.CreateCounter(Constants.TestCounter, "dollars"); + _histogram = _meter.CreateHistogram(Constants.TestHistogram, "feet"); + // consider adding gauge/etc. here + } + + public void IncrementCounter(int v = 1) + { + _counter.Add(v); + } + + public void RecordHistogram(float v = 10.0f) + { + KeyValuePair tags = new(Constants.TagKey, Constants.TagValue); + _histogram.Record(v, tags); + } + + public void Dispose() => _meter?.Dispose(); + } +} diff --git a/src/tests/EventPipeTracee/EventPipeTracee.csproj b/src/tests/EventPipeTracee/EventPipeTracee.csproj index b94bb2b3cd..f61b62ceda 100644 --- a/src/tests/EventPipeTracee/EventPipeTracee.csproj +++ b/src/tests/EventPipeTracee/EventPipeTracee.csproj @@ -1,10 +1,14 @@ - + Exe $(BuildProjectFramework) net6.0;net7.0;net8.0 + + + + diff --git a/src/tests/EventPipeTracee/Program.cs b/src/tests/EventPipeTracee/Program.cs index 0ede793dff..1decd119a0 100644 --- a/src/tests/EventPipeTracee/Program.cs +++ b/src/tests/EventPipeTracee/Program.cs @@ -6,6 +6,8 @@ using System.Diagnostics; using System.IO.Pipes; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -15,7 +17,7 @@ internal static class Program { private const string AppLoggerCategoryName = "AppLoggerCategory"; - public static int Main(string[] args) + public static async Task Main(string[] args) { int pid = Process.GetCurrentProcess().Id; string pipeServerName = args.Length > 0 ? args[0] : null; @@ -29,6 +31,10 @@ public static int Main(string[] args) bool spinWait10 = args.Length > 2 && "SpinWait10".Equals(args[2], StringComparison.Ordinal); string loggerCategory = args[1]; + bool diagMetrics = args.Any("DiagMetrics".Equals); + + Console.WriteLine($"{pid} EventPipeTracee: DiagMetrics {diagMetrics}"); + Console.WriteLine($"{pid} EventPipeTracee: start process"); Console.Out.Flush(); @@ -54,13 +60,36 @@ public static int Main(string[] args) Console.WriteLine($"{pid} EventPipeTracee: {DateTime.UtcNow} Awaiting start"); Console.Out.Flush(); + using CustomMetrics metrics = diagMetrics ? new CustomMetrics() : null; + // Wait for server to send something int input = pipeStream.ReadByte(); Console.WriteLine($"{pid} {DateTime.UtcNow} Starting test body '{input}'"); Console.Out.Flush(); - TestBodyCore(customCategoryLogger, appCategoryLogger); + CancellationTokenSource recordMetricsCancellationTokenSource = new(); + + if (diagMetrics) + { + _ = Task.Run(async () => { + + // Recording a single value appeared to cause test flakiness due to a race + // condition with the timing of when dotnet-counters starts collecting and + // when these values are published. Publishing values repeatedly bypasses this problem. + while (!recordMetricsCancellationTokenSource.Token.IsCancellationRequested) + { + recordMetricsCancellationTokenSource.Token.ThrowIfCancellationRequested(); + + metrics.IncrementCounter(); + metrics.RecordHistogram(10.0f); + await Task.Delay(1000).ConfigureAwait(true); + } + + }).ConfigureAwait(true); + } + + await TestBodyCore(customCategoryLogger, appCategoryLogger).ConfigureAwait(false); Console.WriteLine($"{pid} EventPipeTracee: signal end of test data"); Console.Out.Flush(); @@ -87,22 +116,56 @@ public static int Main(string[] args) // Wait for server to send something input = pipeStream.ReadByte(); + recordMetricsCancellationTokenSource.Cancel(); + Console.WriteLine($"{pid} EventPipeTracee {DateTime.UtcNow} Ending remote test process '{input}'"); return 0; } // TODO At some point we may want parameters to choose different test bodies. - private static void TestBodyCore(ILogger customCategoryLogger, ILogger appCategoryLogger) + private static async Task TestBodyCore(ILogger customCategoryLogger, ILogger appCategoryLogger) { - //Json data is always converted to strings for ActivityStart events. - using (IDisposable scope = customCategoryLogger.BeginScope(new Dictionary { + TaskCompletionSource secondSetScopes = new(TaskCreationOptions.RunContinuationsAsynchronously); + TaskCompletionSource firstFinishedLogging = new(TaskCreationOptions.RunContinuationsAsynchronously); + TaskCompletionSource secondFinishedLogging = new(TaskCreationOptions.RunContinuationsAsynchronously); + + Task firstTask = Task.Run(async () => { + using (IDisposable scope = customCategoryLogger.BeginScope(new Dictionary { { "IntValue", "5" }, { "BoolValue", "true" }, { "StringValue", "test" } }.ToList())) - { - customCategoryLogger.LogInformation("Some warning message with {Arg}", 6); - } + { + // Await for the other task to add its scopes. + await secondSetScopes.Task.ConfigureAwait(false); + + customCategoryLogger.LogInformation("Some warning message with {Arg}", 6); + + // Signal other task to log + firstFinishedLogging.SetResult(); + + // Do not dispose scopes until the other task is done + await secondFinishedLogging.Task.ConfigureAwait(false); + } + }); + Task secondTask = Task.Run(async () => { + using (IDisposable scope = customCategoryLogger.BeginScope(new Dictionary { + { "IntValue", "6" }, + { "BoolValue", "false" }, + { "StringValue", "string" } }.ToList())) + { + // Signal that we added our scopes and wait for the other task to log + secondSetScopes.SetResult(); + await firstFinishedLogging.Task.ConfigureAwait(false); + customCategoryLogger.LogInformation("Some other message with {Arg}", 7); + secondFinishedLogging.SetResult(); + } + }); + + await firstTask.ConfigureAwait(false); + await secondTask.ConfigureAwait(false); + + //Json data is always converted to strings for ActivityStart events. customCategoryLogger.LogWarning(new EventId(7, "AnotherEventId"), "Another message"); appCategoryLogger.LogInformation("Information message."); diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/CommandServiceTests.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/CommandServiceTests.cs new file mode 100644 index 0000000000..34f018693f --- /dev/null +++ b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/CommandServiceTests.cs @@ -0,0 +1,132 @@ +using Microsoft.Diagnostics.DebugServices.Implementation; +using Microsoft.Diagnostics.TestHelpers; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Xunit; +using Xunit.Abstractions; +using Xunit.Extensions; + +[assembly: SuppressMessage("Performance", "CA1825:Avoid zero-length array allocations.", Justification = "")] + +namespace Microsoft.Diagnostics.DebugServices.UnitTests +{ + public class CommandServiceTests : IDisposable + { + private const string ListenerName = "CommandServiceTests"; + + private static IEnumerable _configurations; + + /// + /// Get the first test asset dump. It doesn't matter which one. + /// + /// + public static IEnumerable GetConfiguration() + { + return _configurations ??= TestRunConfiguration.Instance.Configurations + .Where((config) => config.AllSettings.ContainsKey("DumpFile")) + .Take(1) + .Select(c => new[] { c }) + .ToImmutableArray(); + } + + ITestOutputHelper Output { get; set; } + + public CommandServiceTests(ITestOutputHelper output) + { + Output = output; + LoggingListener.EnableListener(output, ListenerName); + } + + void IDisposable.Dispose() => Trace.Listeners.Remove(ListenerName); + + [SkippableTheory, MemberData(nameof(GetConfiguration))] + public void CommandServiceTest1(TestConfiguration config) + { + using TestDump testDump = new(config); + + CaptureConsoleService consoleService = new(); + testDump.ServiceContainer.AddService(consoleService); + + CommandService commandService = new(); + testDump.ServiceContainer.AddService(commandService); + + // Add all the test commands + commandService.AddCommands(typeof(TestCommand1).Assembly); + + // See if the test commands exists + Assert.Contains(commandService.Commands, ((string name, string help, IEnumerable aliases) cmd) => cmd.name == "testcommand"); + + // Invoke only TestCommand1 + TestCommand1.FilterValue = true; + TestCommand1.Invoked = false; + TestCommand2.FilterValue = false; + TestCommand2.Invoked = false; + TestCommand3.FilterValue = false; + TestCommand3.Invoked = false; + Assert.True(commandService.Execute("testcommand", testDump.Target.Services)); + Assert.True(TestCommand1.Invoked); + Assert.False(TestCommand2.Invoked); + Assert.False(TestCommand3.Invoked); + + // Check for TestCommand1 help + string help1 = commandService.GetDetailedHelp("testcommand", testDump.Target.Services, consoleWidth: int.MaxValue); + Assert.NotNull(help1); + Output.WriteLine(help1); + Assert.Contains("Test command #1", help1); + + // Invoke only TestCommand2 + TestCommand1.FilterValue = false; + TestCommand1.Invoked = false; + TestCommand2.FilterValue = true; + TestCommand2.Invoked = false; + TestCommand3.FilterValue = false; + TestCommand3.Invoked = false; + Assert.True(commandService.Execute("testcommand", testDump.Target.Services)); + Assert.False(TestCommand1.Invoked); + Assert.True(TestCommand2.Invoked); + Assert.False(TestCommand3.Invoked); + + // Invoke only TestCommand3 + + TestCommand1.FilterValue = false; + TestCommand1.Invoked = false; + TestCommand2.FilterValue = false; + TestCommand2.Invoked = false; + TestCommand3.FilterValue = true; + TestCommand3.Invoked = false; + Assert.True(commandService.Execute("testcommand", "--foo 23", testDump.Target.Services)); + Assert.False(TestCommand1.Invoked); + Assert.False(TestCommand2.Invoked); + Assert.True(TestCommand3.Invoked); + + // Check for TestCommand3 help + string help3 = commandService.GetDetailedHelp("testcommand", testDump.Target.Services, consoleWidth: int.MaxValue); + Assert.NotNull(help3); + Output.WriteLine(help3); + Assert.Contains("Test command #3", help3); + + // Invoke none of the test commands + TestCommand1.FilterValue = false; + TestCommand1.Invoked = false; + TestCommand2.FilterValue = false; + TestCommand2.Invoked = false; + TestCommand3.FilterValue = false; + TestCommand3.Invoked = false; + try + { + Assert.False(commandService.Execute("testcommand", testDump.Target.Services)); + } + catch (DiagnosticsException ex) + { + Assert.Matches("Test command #2 filter", ex.Message); + } + Assert.False(TestCommand1.Invoked); + Assert.False(TestCommand2.Invoked); + Assert.False(TestCommand3.Invoked); + } + } +} diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/DebugServicesTests.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/DebugServicesTests.cs index 040ff1d8aa..fb7ba82b99 100644 --- a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/DebugServicesTests.cs +++ b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/DebugServicesTests.cs @@ -30,11 +30,11 @@ public class DebugServicesTests : IDisposable public static IEnumerable GetConfigurations() { - _configurations ??= TestRunConfiguration.Instance.Configurations + return _configurations ??= TestRunConfiguration.Instance.Configurations .Where((config) => config.AllSettings.ContainsKey("DumpFile")) - .Select((config) => CreateHost(config)) - .Select((host) => new[] { host }).ToImmutableArray(); - return _configurations; + .Select(CreateHost) + .Select((host) => new[] { host }) + .ToImmutableArray(); } private static TestHost CreateHost(TestConfiguration config) @@ -116,6 +116,15 @@ public void ModuleTests(TestHost host) } Assert.NotNull(module); + if (OS.Kind != OSKind.Windows) + { + // Skip managed modules when running on Linux/OSX because of the 6.0 injection activation issue in the DAC + if (moduleData.TryGetValue("IsManaged", out bool isManaged) && isManaged) + { + continue; + } + } + if (host.Target.Host.HostType != HostType.Lldb) { // Check that the resulting module matches the test data @@ -264,6 +273,11 @@ public void RuntimeTests(TestHost host) { throw new SkipTestException("Not supported on Alpine Linux"); } + // Disable running on Linux/OSX because of the 6.0 injection activation issue in the DAC + if (OS.Kind != OSKind.Windows) + { + throw new SkipTestException("Not supported on Linux"); + } IRuntimeService runtimeService = host.Target.Services.GetService(); Assert.NotNull(runtimeService); diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestCommands.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestCommands.cs new file mode 100644 index 0000000000..920190d72d --- /dev/null +++ b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/TestCommands.cs @@ -0,0 +1,75 @@ +// 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 Xunit; + +namespace Microsoft.Diagnostics.DebugServices.UnitTests +{ + [Command(Name = "testcommand", Help = "Test command #1")] + public class TestCommand1 : CommandBase + { + public static bool FilterValue; + public static bool Invoked; + + [ServiceImport] + public ITarget Target { get; set; } + + [Argument(Name = "FileName", Help = "Test argument.")] + public string FileName { get; set; } + + public override void Invoke() + { + Assert.NotNull(Target); + Invoked = true; + } + + [FilterInvoke] + public bool FilterInvoke() => FilterValue; + } + + [Command(Name = "testcommand", Help = "Test command #2")] + public class TestCommand2 : CommandBase + { + public static bool FilterValue; + public static bool Invoked; + + [ServiceImport] + public ITarget Target { get; set; } + + [Option(Name = "--foo", Help = "Test option.")] + public int Foo { get; set; } + + public override void Invoke() + { + Assert.NotNull(Target); + Invoked = true; + } + + [FilterInvoke(Message = "Test command #2 filter")] + public bool FilterInvoke() => FilterValue; + } + + [Command(Name = "testcommand", Help = "Test command #3")] + public class TestCommand3 : CommandBase + { + public static bool FilterValue; + public static bool Invoked; + + [ServiceImport] + public ITarget Target { get; set; } + + [Option(Name = "--foo", Help = "Test option.")] + public int Foo { get; set; } + + public override void Invoke() + { + Assert.NotNull(Target); + Invoked = true; + } + + [FilterInvoke] + public static bool FilterInvoke() => FilterValue; + } +} diff --git a/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventCounterPipelineUnitTests.cs b/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventCounterPipelineUnitTests.cs index 46ae594632..e7b4ae3752 100644 --- a/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventCounterPipelineUnitTests.cs +++ b/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventCounterPipelineUnitTests.cs @@ -57,11 +57,11 @@ public TestMetricsLogger(IDictionary> expectedCounte public IEnumerable Metrics => _metrics.Values; - public void Log(ICounterPayload metric) + public void Log(ICounterPayload payload) { - string key = CreateKey(metric); + string key = CreateKey(payload); - _metrics[key] = metric; + _metrics[key] = payload; // Complete the task source if the last expected key was removed. if (_expectedCounters.Remove(key) && _expectedCounters.Count == 0) diff --git a/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventCounterTriggerTests.cs b/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventCounterTriggerTests.cs index 9634999aa0..b55ed5a64a 100644 --- a/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventCounterTriggerTests.cs +++ b/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventCounterTriggerTests.cs @@ -477,7 +477,7 @@ public ICounterPayload CreateNext(double value) // Add some variance between -5 and 5 milliseconds to simulate "real" timestamp _lastTimestamp = _lastTimestamp.Value.AddMilliseconds((10 * _random.NextDouble()) - 5); - return new CounterPayload( + return new EventCounterPayload( _lastTimestamp.Value, EventCounterConstants.RuntimeProviderName, EventCounterConstants.CpuUsageCounterName, @@ -486,6 +486,7 @@ public ICounterPayload CreateNext(double value) value, CounterType.Metric, actualInterval, + (int)_intervalSeconds, null); } } diff --git a/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventLogsPipelineUnitTests.cs b/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventLogsPipelineUnitTests.cs index dfe8753b1f..c449efc5d1 100644 --- a/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventLogsPipelineUnitTests.cs +++ b/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/EventLogsPipelineUnitTests.cs @@ -263,6 +263,18 @@ private static void ValidateLoggerRemoteCategoryInformationMessage(StreamReader Assert.Equal(string.Empty, result.EventName); Validate(result.Scopes, ("BoolValue", "true"), ("StringValue", "test"), ("IntValue", "5")); Validate(result.Arguments, ("Arg", "6")); + + message = reader.ReadLine(); + Assert.NotNull(message); + + result = JsonSerializer.Deserialize(message); + Assert.Equal("Some other message with 7", result.Message); + Assert.Equal(LoggerRemoteTestName, result.Category); + Assert.Equal("Information", result.LogLevel); + Assert.Equal(0, result.EventId); + Assert.Equal(string.Empty, result.EventName); + Validate(result.Scopes, ("BoolValue", "false"), ("StringValue", "string"), ("IntValue", "6")); + Validate(result.Arguments, ("Arg", "7")); } private static void ValidateLoggerRemoteCategoryWarningMessage(StreamReader reader) diff --git a/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/GlobMatcherTests.cs b/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/GlobMatcherTests.cs index 5e013b9776..88317be47b 100644 --- a/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/GlobMatcherTests.cs +++ b/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/GlobMatcherTests.cs @@ -50,7 +50,7 @@ public void TestGlobs() { if (!matcher.Match(value)) { - Assert.True(false, $"Expected {value} to match pattern {keyValuePair.Key}"); + Assert.Fail($"Expected {value} to match pattern {keyValuePair.Key}"); }; } @@ -58,7 +58,7 @@ public void TestGlobs() { if (matcher.Match(value)) { - Assert.False(true, $"Expected {value} to not match pattern {keyValuePair.Key}"); + Assert.Fail($"Expected {value} to not match pattern {keyValuePair.Key}"); } } } diff --git a/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/PipelineTestUtilities.cs b/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/PipelineTestUtilities.cs index 70f72ecb49..ffd52880e4 100644 --- a/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/PipelineTestUtilities.cs +++ b/src/tests/Microsoft.Diagnostics.Monitoring.EventPipe/PipelineTestUtilities.cs @@ -4,6 +4,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using CommonTestRunner; using Microsoft.Diagnostics.TestHelpers; using Xunit.Abstractions; using TestRunner = Microsoft.Diagnostics.CommonTestRunner.TestRunner; @@ -16,9 +17,7 @@ internal static class PipelineTestUtilities public static async Task StartProcess(TestConfiguration config, string testArguments, ITestOutputHelper outputHelper, int testProcessTimeout = 60_000) { - TestRunner runner = await TestRunner.Create(config, outputHelper, "EventPipeTracee", testArguments); - await runner.Start(testProcessTimeout); - return runner; + return await TestRunnerUtilities.StartProcess(config, testArguments, outputHelper, testProcessTimeout); } public static async Task ExecutePipelineWithTracee( @@ -75,14 +74,7 @@ private static async Task ExecutePipelineWithTracee( { Task runTask = await startPipelineAsync(pipeline, token); - // Begin event production - testRunner.WakeupTracee(); - - // Wait for event production to be done - testRunner.WaitForSignal(); - - try - { + Func waitForPipeline = async (cancellationToken) => { // Optionally wait on caller before allowing the pipeline to stop. if (null != waitTaskSource) { @@ -96,15 +88,9 @@ private static async Task ExecutePipelineWithTracee( //Signal for the pipeline to stop await pipeline.StopAsync(token); + }; - //After a pipeline is stopped, we should expect the RunTask to eventually finish - await runTask; - } - finally - { - // Signal for debugee that's ok to end/move on. - testRunner.WakeupTracee(); - } + await TestRunnerUtilities.ExecuteCollection(runTask, testRunner, token, waitForPipeline); } } } diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/EventPipeSessionTests.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/EventPipeSessionTests.cs index 89d29b8b64..9bfeb9c9ec 100644 --- a/src/tests/Microsoft.Diagnostics.NETCore.Client/EventPipeSessionTests.cs +++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/EventPipeSessionTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.Tracing; +using System.Threading; using System.Threading.Tasks; using Microsoft.Diagnostics.TestHelpers; using Microsoft.Diagnostics.Tracing; @@ -92,7 +93,7 @@ private async Task EventPipeSessionStreamTestCore(TestConfiguration config, bool EventPipeEventSource source = new(session.EventStream); source.Dynamic.All += (TraceEvent obj) => { runner.WriteLine("Got an event"); - evntCnt += 1; + Interlocked.Increment(ref evntCnt); }; try { @@ -111,6 +112,7 @@ private async Task EventPipeSessionStreamTestCore(TestConfiguration config, bool runner.WriteLine("Waiting for stream Task"); streamTask.Wait(10000); runner.WriteLine("Done waiting for stream Task"); + Assert.True(evntCnt > 0); } } diff --git a/src/tests/TestExtension/TestCommands.cs b/src/tests/TestExtension/TestCommands.cs new file mode 100644 index 0000000000..9e7d6b0a41 --- /dev/null +++ b/src/tests/TestExtension/TestCommands.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Diagnostics.DebugServices; + +namespace TestExtension +{ + [Command(Name = "clrstack", Help = "Test command #1")] + public class TestCommand1 : CommandBase + { + [ServiceImport] + public ITarget Target { get; set; } + + [Argument(Name = "FileName", Help = "Test argument.")] + public string FileName { get; set; } + + public override void Invoke() + { + if (Target is null) + { + throw new ArgumentNullException(nameof(Target)); + } + WriteLine("Test command #1 invoked"); + } + + [FilterInvoke] + public bool FilterInvoke() => true; + } + + [Command(Name = "dumpheap", Help = "Test command #2")] + public class TestCommand2 : CommandBase + { + [ServiceImport] + public ITarget Target { get; set; } + + [Option(Name = "--foo", Help = "Test option.")] + public int Foo { get; set; } + + public override void Invoke() + { + if (Target is null) + { + throw new ArgumentNullException(nameof(Target)); + } + WriteLine("Test command #2 invoked"); + } + + [FilterInvoke] + public bool FilterInvoke() => true; + } + + [Command(Name = "dumpheap", Help = "Test command #3")] + public class TestCommand3 : CommandBase + { + [ServiceImport] + public ITarget Target { get; set; } + + [Option(Name = "--foo", Help = "Test option.")] + public int Foo { get; set; } + + public override void Invoke() + { + if (Target is null) + { + throw new ArgumentNullException(nameof(Target)); + } + WriteLine("Test command #3 invoked"); + } + + [FilterInvoke] + public bool FilterInvoke() => false; + } + + [Command(Name = "assemblies", Help = "Test command #4")] + public class TestCommand4 : CommandBase + { + [ServiceImport] + public ITarget Target { get; set; } + + [Option(Name = "--foo", Help = "Test option.")] + public int Foo { get; set; } + + public override void Invoke() + { + if (Target is null) + { + throw new ArgumentNullException(nameof(Target)); + } + WriteLine("Test command #4 invoked"); + } + + [FilterInvoke] + public static bool FilterInvoke() => true; + } + + [Command(Name = "ip2md", Help = "Test command #5")] + public class TestCommand5 : CommandBase + { + [ServiceImport] + public ITarget Target { get; set; } + + [Option(Name = "--bar", Help = "Test option #5.")] + public int Foo { get; set; } + + public override void Invoke() + { + if (Target is null) + { + throw new ArgumentNullException(nameof(Target)); + } + WriteLine("Test command #5 invoked"); + } + + [FilterInvoke] + public static bool FilterInvoke() => false; + } +} diff --git a/src/tests/TestExtension/TestExtension.csproj b/src/tests/TestExtension/TestExtension.csproj new file mode 100644 index 0000000000..3db8fe9c15 --- /dev/null +++ b/src/tests/TestExtension/TestExtension.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/src/tests/dotnet-counters/CSVExporterTests.cs b/src/tests/dotnet-counters/CSVExporterTests.cs index fca3d71c75..342d367480 100644 --- a/src/tests/dotnet-counters/CSVExporterTests.cs +++ b/src/tests/dotnet-counters/CSVExporterTests.cs @@ -7,6 +7,7 @@ using System.Linq; using Microsoft.Diagnostics.Tools.Counters; using Microsoft.Diagnostics.Tools.Counters.Exporters; +using Microsoft.Diagnostics.Monitoring.EventPipe; using Xunit; namespace DotnetCounters.UnitTests @@ -25,7 +26,7 @@ public void IncrementingCounterTest() DateTime start = DateTime.Now; for (int i = 0; i < 100; i++) { - exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", "", "", i, 1, start + TimeSpan.FromSeconds(i)), false); + exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", string.Empty, string.Empty, i, 1, start + TimeSpan.FromSeconds(i)), false); } exporter.Stop(); @@ -36,11 +37,7 @@ public void IncrementingCounterTest() List lines = File.ReadLines(fileName).ToList(); Assert.Equal(101, lines.Count); // should be 101 including the headers - string[] headerTokens = lines[0].Split(','); - Assert.Equal("Provider", headerTokens[1]); - Assert.Equal("Counter Name", headerTokens[2]); - Assert.Equal("Counter Type", headerTokens[3]); - Assert.Equal("Mean/Increment", headerTokens[4]); + ValidateHeaderTokens(lines[0]); for (int i = 1; i < lines.Count; i++) { @@ -67,7 +64,7 @@ public void CounterTest() DateTime start = DateTime.Now; for (int i = 0; i < 10; i++) { - exporter.CounterPayloadReceived(new GaugePayload("myProvider", "counterOne", "Counter One", "", "", i, start + TimeSpan.FromSeconds(i)), false); + exporter.CounterPayloadReceived(new GaugePayload("myProvider", "counterOne", "Counter One", string.Empty, null, i, start + TimeSpan.FromSeconds(i)), false); } exporter.Stop(); @@ -78,12 +75,7 @@ public void CounterTest() List lines = File.ReadLines(fileName).ToList(); Assert.Equal(11, lines.Count); // should be 11 including the headers - string[] headerTokens = lines[0].Split(','); - Assert.Equal("Provider", headerTokens[1]); - Assert.Equal("Counter Name", headerTokens[2]); - Assert.Equal("Counter Type", headerTokens[3]); - Assert.Equal("Mean/Increment", headerTokens[4]); - + ValidateHeaderTokens(lines[0]); for (int i = 1; i < lines.Count; i++) { @@ -110,7 +102,7 @@ public void DifferentDisplayRateTest() DateTime start = DateTime.Now; for (int i = 0; i < 100; i++) { - exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", "", "", i, 60, start + TimeSpan.FromSeconds(i)), false); + exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", string.Empty, null, i, 60, start + TimeSpan.FromSeconds(i)), false); } exporter.Stop(); @@ -121,11 +113,7 @@ public void DifferentDisplayRateTest() List lines = File.ReadLines(fileName).ToList(); Assert.Equal(101, lines.Count); // should be 101 including the headers - string[] headerTokens = lines[0].Split(','); - Assert.Equal("Provider", headerTokens[1]); - Assert.Equal("Counter Name", headerTokens[2]); - Assert.Equal("Counter Type", headerTokens[3]); - Assert.Equal("Mean/Increment", headerTokens[4]); + ValidateHeaderTokens(lines[0]); for (int i = 1; i < lines.Count; i++) { @@ -152,7 +140,7 @@ public void DisplayUnitsTest() DateTime start = DateTime.Now; for (int i = 0; i < 100; i++) { - exporter.CounterPayloadReceived(new RatePayload("myProvider", "allocRateGen", "Allocation Rate Gen", "MB", "", i, 60, start + TimeSpan.FromSeconds(i)), false); + exporter.CounterPayloadReceived(new RatePayload("myProvider", "allocRateGen", "Allocation Rate Gen", "MB", string.Empty, i, 60, start + TimeSpan.FromSeconds(i)), false); } exporter.Stop(); @@ -163,11 +151,7 @@ public void DisplayUnitsTest() List lines = File.ReadLines(fileName).ToList(); Assert.Equal(101, lines.Count); // should be 101 including the headers - string[] headerTokens = lines[0].Split(','); - Assert.Equal("Provider", headerTokens[1]); - Assert.Equal("Counter Name", headerTokens[2]); - Assert.Equal("Counter Type", headerTokens[3]); - Assert.Equal("Mean/Increment", headerTokens[4]); + ValidateHeaderTokens(lines[0]); for (int i = 1; i < lines.Count; i++) { @@ -205,11 +189,7 @@ public void TagsTest() List lines = File.ReadLines(fileName).ToList(); Assert.Equal(101, lines.Count); // should be 101 including the headers - string[] headerTokens = lines[0].Split(','); - Assert.Equal("Provider", headerTokens[1]); - Assert.Equal("Counter Name", headerTokens[2]); - Assert.Equal("Counter Type", headerTokens[3]); - Assert.Equal("Mean/Increment", headerTokens[4]); + ValidateHeaderTokens(lines[0]); for (int i = 1; i < lines.Count; i++) { @@ -247,11 +227,7 @@ public void PercentilesTest() List lines = File.ReadLines(fileName).ToList(); Assert.Equal(101, lines.Count); // should be 101 including the headers - string[] headerTokens = lines[0].Split(','); - Assert.Equal("Provider", headerTokens[1]); - Assert.Equal("Counter Name", headerTokens[2]); - Assert.Equal("Counter Type", headerTokens[3]); - Assert.Equal("Mean/Increment", headerTokens[4]); + ValidateHeaderTokens(lines[0]); for (int i = 1; i < lines.Count; i++) { @@ -268,5 +244,14 @@ public void PercentilesTest() File.Delete(fileName); } } + + internal static void ValidateHeaderTokens(string headerLine) + { + string[] headerTokens = headerLine.Split(','); + Assert.Equal("Provider", headerTokens[TestConstants.ProviderIndex]); + Assert.Equal("Counter Name", headerTokens[TestConstants.CounterNameIndex]); + Assert.Equal("Counter Type", headerTokens[TestConstants.CounterTypeIndex]); + Assert.Equal("Mean/Increment", headerTokens[TestConstants.ValueIndex]); + } } } diff --git a/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs b/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs new file mode 100644 index 0000000000..b2c38cbd4c --- /dev/null +++ b/src/tests/dotnet-counters/CounterMonitorPayloadTests.cs @@ -0,0 +1,336 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.IO; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using CommonTestRunner; +using Microsoft.Diagnostics.TestHelpers; +using Microsoft.Diagnostics.Tools.Counters; +using Xunit; +using Xunit.Abstractions; +using Xunit.Extensions; +using TestRunner = Microsoft.Diagnostics.CommonTestRunner.TestRunner; +using Constants = DotnetCounters.UnitTests.TestConstants; + +namespace DotnetCounters.UnitTests +{ + /// + /// Tests the behavior of CounterMonitor's Collect command. + /// + public class CounterMonitorPayloadTests + { + private enum CounterTypes + { + Metric, Rate + } + + private ITestOutputHelper _outputHelper; + private static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(2); + private static readonly string SystemRuntimeName = "System.Runtime"; + private static readonly string TagStart = "["; + + private static HashSet ExpectedCounterTypes = new() { CounterTypes.Metric, CounterTypes.Rate }; + + public CounterMonitorPayloadTests(ITestOutputHelper outputHelper) + { + _outputHelper = outputHelper; + } + + [SkippableTheory, MemberData(nameof(Configurations))] + public async Task TestCounterMonitorCustomMetricsJSON(TestConfiguration configuration) + { + CheckRuntimeOS(); + CheckFramework(configuration); + + List metricComponents = await GetCounterTraceJSON(configuration, new List { Constants.TestMeterName }); + + ValidateCustomMetrics(metricComponents, CountersExportFormat.json); + } + + [SkippableTheory, MemberData(nameof(Configurations))] + public async Task TestCounterMonitorCustomMetricsCSV(TestConfiguration configuration) + { + CheckRuntimeOS(); + CheckFramework(configuration); + + List metricComponents = await GetCounterTraceCSV(configuration, new List { Constants.TestMeterName }); + + ValidateCustomMetrics(metricComponents, CountersExportFormat.csv); + } + + [SkippableTheory, MemberData(nameof(Configurations))] + public async Task TestCounterMonitorSystemRuntimeMetricsJSON(TestConfiguration configuration) + { + CheckRuntimeOS(); + + List metricComponents = await GetCounterTraceJSON(configuration, new List { SystemRuntimeName }); + + ValidateSystemRuntimeMetrics(metricComponents); + } + + [SkippableTheory, MemberData(nameof(Configurations))] + public async Task TestCounterMonitorSystemRuntimeMetricsCSV(TestConfiguration configuration) + { + CheckRuntimeOS(); + + List metricComponents = await GetCounterTraceCSV(configuration, new List { SystemRuntimeName }); + + ValidateSystemRuntimeMetrics(metricComponents); + } + + private void ValidateSystemRuntimeMetrics(List metricComponents) + { + string[] ExpectedProviders = { "System.Runtime" }; + Assert.Equal(ExpectedProviders, metricComponents.Select(c => c.ProviderName).ToHashSet()); + + // Could also just check the number of counter names + HashSet expectedCounterNames = new() + { + "CPU Usage (%)", + "Working Set (MB)", + "GC Heap Size (MB)", + "Gen 0 GC Count (Count / 1 sec)", + "Gen 1 GC Count (Count / 1 sec)", + "Gen 2 GC Count (Count / 1 sec)", + "ThreadPool Thread Count", + "Monitor Lock Contention Count (Count / 1 sec)", + "ThreadPool Queue Length", + "ThreadPool Completed Work Item Count (Count / 1 sec)", + "Allocation Rate (B / 1 sec)", + "Number of Active Timers", + "GC Fragmentation (%)", + "GC Committed Bytes (MB)", + "Exception Count (Count / 1 sec)", + "% Time in GC since last GC (%)", + "Gen 0 Size (B)", + "Gen 1 Size (B)", + "Gen 2 Size (B)", + "LOH Size (B)", + "POH (Pinned Object Heap) Size (B)", + "Number of Assemblies Loaded", + "IL Bytes Jitted (B)", + "Number of Methods Jitted", + "Time spent in JIT (ms / 1 sec)" + }; + + Assert.Subset(metricComponents.Select(c => c.CounterName).ToHashSet(), expectedCounterNames); + + Assert.Equal(ExpectedCounterTypes, metricComponents.Select(c => c.CounterType).ToHashSet()); + } + + private async Task> GetCounterTraceJSON(TestConfiguration configuration, List counterList) + { + string path = Path.ChangeExtension(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()), "json"); + + Func> createMetricComponents = () => + { + using FileStream metricsFile = File.OpenRead(path); + JSONCounterTrace trace = JsonSerializer.Deserialize(metricsFile, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + var providers = trace.events.Select(e => e.provider).ToList(); + var counterNames = trace.events.Select(e => e.name).ToList(); + var counterTypes = trace.events.Select(e => e.counterType).ToList(); + var tags = trace.events.Select(e => e.tags).ToList(); + var values = trace.events.Select(e => e.value).ToList(); + + return CreateMetricComponents(providers, counterNames, counterTypes, tags, values); + }; + + return await GetCounterTrace(configuration, counterList, path, CountersExportFormat.json, createMetricComponents); + } + + private async Task> GetCounterTraceCSV(TestConfiguration configuration, List counterList) + { + string path = Path.ChangeExtension(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()), "csv"); + + Func> createMetricComponents = () => + { + List lines = File.ReadLines(path).ToList(); + CSVExporterTests.ValidateHeaderTokens(lines[0]); + lines.RemoveAt(0); // Trim the header + + IEnumerable splitLines = lines.Select(l => l.Split(",")); + + var providers = splitLines.Select(line => line[Constants.ProviderIndex]).ToList(); + var countersList = splitLines.Select(line => line[Constants.CounterNameIndex]).ToList(); + var counterNames = countersList.Select(counter => counter.Split(TagStart)[0]).ToList(); + var counterTypes = splitLines.Select(line => line[Constants.CounterTypeIndex]).ToList(); + var tags = GetCSVTags(countersList); + var values = GetCSVValues(splitLines); + + return CreateMetricComponents(providers, counterNames, counterTypes, tags, values); + }; + + return await GetCounterTrace(configuration, counterList, path, CountersExportFormat.csv, createMetricComponents); + } + + private List CreateMetricComponents(List providers, List counterNames, List counterTypes, List tags, List values) + { + List metricComponents = new(providers.Count()); + + for (int index = 0; index < providers.Count(); ++index) + { + CounterTypes type; + Enum.TryParse(counterTypes[index], out type); + + metricComponents.Add(new MetricComponents() + { + ProviderName = providers[index], + CounterName = counterNames[index], + CounterType = type, + Tags = tags[index], + Value = values[index] + }); + } + + return metricComponents; + } + + private async Task> GetCounterTrace(TestConfiguration configuration, List counterList, string path, CountersExportFormat exportFormat, Func> CreateMetricComponents) + { + try + { + CounterMonitor monitor = new CounterMonitor(); + + using CancellationTokenSource source = new CancellationTokenSource(DefaultTimeout); + + await using var testRunner = await TestRunnerUtilities.StartProcess(configuration, "TestCounterMonitor DiagMetrics", _outputHelper); + + await TestRunnerUtilities.ExecuteCollection((ct) => { + return Task.Run(async () => + await monitor.Collect( + ct: ct, + counter_list: counterList, + counters: null, + console: new TestConsole(), + processId: testRunner.Pid, + refreshInterval: 1, + format: exportFormat, + output: path, + name: null, + diagnosticPort: null, + resumeRuntime: false, + maxHistograms: 10, + maxTimeSeries: 1000, + duration: TimeSpan.FromSeconds(10))); + }, testRunner, source.Token); + + return CreateMetricComponents(); + } + finally + { + try + { + File.Delete(path); + } + catch { } + } + } + + private void ValidateCustomMetrics(List metricComponents, CountersExportFormat format) + { + // Currently not validating timestamp due to https://github.com/dotnet/diagnostics/issues/3905 + + HashSet expectedProviders = new() { Constants.TestMeterName }; + Assert.Equal(expectedProviders, metricComponents.Select(c => c.ProviderName).ToHashSet()); + + HashSet expectedCounterNames = new() { Constants.TestHistogramName, Constants.TestCounterName }; + Assert.Equal(expectedCounterNames, metricComponents.Select(c => c.CounterName).ToHashSet()); + + Assert.Equal(ExpectedCounterTypes, metricComponents.Select(c => c.CounterType).ToHashSet()); + + string tagSeparator = format == CountersExportFormat.csv ? ";" : ","; + string tag = Constants.TagKey + "=" + Constants.TagValue + tagSeparator + Constants.PercentileKey + "="; + HashSet expectedTags = new() { $"{tag}{Constants.Quantile50}", $"{tag}{Constants.Quantile95}", $"{tag}{Constants.Quantile99}" }; + Assert.Equal(expectedTags, metricComponents.Where(c => c.CounterName == Constants.TestHistogramName).Select(c => c.Tags).Distinct()); + Assert.Empty(metricComponents.Where(c => c.CounterName == Constants.TestCounterName).Where(c => c.Tags != string.Empty)); + + var actualCounterValues = metricComponents.Where(c => c.CounterName == Constants.TestCounterName).Select(c => c.Value); + + Assert.NotEmpty(actualCounterValues); + double histogramValue = Assert.Single(metricComponents.Where(c => c.CounterName == Constants.TestHistogramName).Select(c => c.Value).Distinct()); + Assert.Equal(10, histogramValue); + } + + private List GetCSVTags(List countersList) + { + var tags = countersList.Select(counter => { + var split = counter.Split(TagStart); + return split.Length > 1 ? split[1].Remove(split[1].Length - 1) : string.Empty; + }).ToList(); + + return tags; + } + + private List GetCSVValues(IEnumerable splitLines) + { + return splitLines.Select(line => { + return double.TryParse(line[Constants.ValueIndex], out double val) ? val : -1; + }).ToList(); + } + + private void CheckFramework(TestConfiguration configuration) + { + if (configuration.RuntimeFrameworkVersionMajor < 8) + { + throw new SkipTestException("Not supported on < .NET 8.0"); + } + } + + private void CheckRuntimeOS() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + throw new SkipTestException("Test instability on OSX"); + } + } + + public static IEnumerable Configurations => TestRunner.Configurations; + + private sealed class MetricComponents + { + public string ProviderName { get; set; } + public string CounterName { get; set; } + public double Value { get; set; } + public string Tags { get; set; } + public CounterTypes CounterType { get; set; } + } + + private sealed class TestConsole : IConsole + { + private readonly TestStandardStreamWriter _outWriter; + private readonly TestStandardStreamWriter _errorWriter; + + private sealed class TestStandardStreamWriter : IStandardStreamWriter + { + private StringWriter _writer = new(); + public void Write(string value) => _writer.Write(value); + public void WriteLine(string value) => _writer.WriteLine(value); + } + + public TestConsole() + { + _outWriter = new TestStandardStreamWriter(); + _errorWriter = new TestStandardStreamWriter(); + } + + public IStandardStreamWriter Out => _outWriter; + + public bool IsOutputRedirected => true; + + public IStandardStreamWriter Error => _errorWriter; + + public bool IsErrorRedirected => true; + + public bool IsInputRedirected => false; + } + } +} diff --git a/src/tests/dotnet-counters/DotnetCounters.UnitTests.csproj b/src/tests/dotnet-counters/DotnetCounters.UnitTests.csproj index 06c1878141..00f6e91c0d 100644 --- a/src/tests/dotnet-counters/DotnetCounters.UnitTests.csproj +++ b/src/tests/dotnet-counters/DotnetCounters.UnitTests.csproj @@ -6,6 +6,8 @@ + + diff --git a/src/tests/dotnet-counters/JSONExporterTests.cs b/src/tests/dotnet-counters/JSONExporterTests.cs index 379f699be3..d4daadbbcb 100644 --- a/src/tests/dotnet-counters/JSONExporterTests.cs +++ b/src/tests/dotnet-counters/JSONExporterTests.cs @@ -6,7 +6,9 @@ using Microsoft.Diagnostics.Tools.Counters; using Microsoft.Diagnostics.Tools.Counters.Exporters; using Newtonsoft.Json; +using Microsoft.Diagnostics.Monitoring.EventPipe; using Xunit; +using System.Collections.Generic; #pragma warning disable CA1507 // Use nameof to express symbol names @@ -26,7 +28,7 @@ public void IncrementingCounterTest() DateTime start = DateTime.Now; for (int i = 0; i < 10; i++) { - exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", "", "", 1, 1, start + TimeSpan.FromSeconds(i)), false); + exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", string.Empty, string.Empty, 1, 1, start + TimeSpan.FromSeconds(i)), false); } exporter.Stop(); @@ -57,7 +59,7 @@ public void CounterTest() DateTime start = DateTime.Now; for (int i = 0; i < 10; i++) { - exporter.CounterPayloadReceived(new GaugePayload("myProvider", "counterOne", "Counter One", "", "", 1, start + TimeSpan.FromSeconds(i)), false); + exporter.CounterPayloadReceived(new GaugePayload("myProvider", "counterOne", "Counter One", string.Empty, string.Empty, 1, start + TimeSpan.FromSeconds(i)), false); } exporter.Stop(); @@ -88,7 +90,7 @@ public void DisplayUnitsTest() DateTime start = DateTime.Now; for (int i = 0; i < 20; i++) { - exporter.CounterPayloadReceived(new GaugePayload("myProvider", "heapSize", "Heap Size", "MB", "", i, start + TimeSpan.FromSeconds(i)), false); + exporter.CounterPayloadReceived(new GaugePayload("myProvider", "heapSize", "Heap Size", "MB", string.Empty, i, start + TimeSpan.FromSeconds(i)), false); } exporter.Stop(); @@ -122,7 +124,7 @@ public void ValidJSONFormatTest() DateTime start = DateTime.Now; for (int i = 0; i < 20; i++) { - exporter.CounterPayloadReceived(new RatePayload("myProvider", "heapSize", "Heap Size", "MB", "", 0, 60, start + TimeSpan.FromSeconds(i)), false); + exporter.CounterPayloadReceived(new RatePayload("myProvider", "heapSize", "Heap Size", "MB", string.Empty, 0, 60, start + TimeSpan.FromSeconds(i)), false); } exporter.Stop(); @@ -210,7 +212,7 @@ public void PercentilesTest() DateTime start = DateTime.Now; for (int i = 0; i < 10; i++) { - exporter.CounterPayloadReceived(new PercentilePayload("myProvider", "counterOne", "Counter One", "", "f=abc,Percentile=50", 1, start + TimeSpan.FromSeconds(i)), false); + exporter.CounterPayloadReceived(new PercentilePayload("myProvider", "counterOne", "Counter One", "", "f=abc,Percentile=50", 1, start + TimeSpan.FromSeconds(1)), false); } exporter.Stop(); diff --git a/src/tests/dotnet-counters/TestConstants.cs b/src/tests/dotnet-counters/TestConstants.cs new file mode 100644 index 0000000000..4e9912d2e6 --- /dev/null +++ b/src/tests/dotnet-counters/TestConstants.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace DotnetCounters.UnitTests +{ + public static class TestConstants + { + public const string TestCounter = "TestCounter"; + public const string TestCounterName = TestCounter + " (dollars / 1 sec)"; + public const string TestHistogram = "TestHistogram"; + public const string TestHistogramName = TestHistogram + " (feet)"; + public const string PercentileKey = "Percentile"; + public const string TagKey = "TestTag"; + public const string TagValue = "5"; + public const string TestMeterName = "TestMeter"; + public const string Quantile50 = "50"; + public const string Quantile95 = "95"; + public const string Quantile99 = "99"; + + public const int ProviderIndex = 1; + public const int CounterNameIndex = 2; + public const int CounterTypeIndex = 3; + public const int ValueIndex = 4; + } +} diff --git a/src/tests/eventpipe/ContentionEvents.cs b/src/tests/eventpipe/ContentionEvents.cs index 7aa13645b3..4d5faa1f73 100644 --- a/src/tests/eventpipe/ContentionEvents.cs +++ b/src/tests/eventpipe/ContentionEvents.cs @@ -70,9 +70,9 @@ await RemoteTestExecutorHelper.RunTestCaseAsync(() => { Func> _DoesTraceContainEvents = (source) => { int ContentionStartEvents = 0; - source.Clr.ContentionStart += (eventData) => ContentionStartEvents += 1; + source.Clr.ContentionStart += (eventData) => Interlocked.Increment(ref ContentionStartEvents); int ContentionStopEvents = 0; - source.Clr.ContentionStop += (eventData) => ContentionStopEvents += 1; + source.Clr.ContentionStop += (eventData) => Interlocked.Increment(ref ContentionStopEvents); return () => { Logger.logger.Log("Event counts validation"); Logger.logger.Log("ContentionStartEvents: " + ContentionStartEvents); diff --git a/src/tests/eventpipe/LoaderEvents.cs b/src/tests/eventpipe/LoaderEvents.cs index 86656eb99b..7297391a7e 100644 --- a/src/tests/eventpipe/LoaderEvents.cs +++ b/src/tests/eventpipe/LoaderEvents.cs @@ -7,6 +7,7 @@ using System.IO; using System.Reflection; using System.Runtime.Loader; +using System.Threading; using EventPipe.UnitTests.Common; using Microsoft.Diagnostics.NETCore.Client; using Microsoft.Diagnostics.Tracing; @@ -79,13 +80,13 @@ void GetAssemblyPath() Func> _DoesTraceContainEvents = (source) => { int LoaderAssemblyLoadEvents = 0; int LoaderAssemblyUnloadEvents = 0; - source.Clr.LoaderAssemblyLoad += (eventData) => LoaderAssemblyLoadEvents += 1; - source.Clr.LoaderAssemblyUnload += (eventData) => LoaderAssemblyUnloadEvents += 1; + source.Clr.LoaderAssemblyLoad += (eventData) => Interlocked.Increment(ref LoaderAssemblyLoadEvents); + source.Clr.LoaderAssemblyUnload += (eventData) => Interlocked.Increment(ref LoaderAssemblyUnloadEvents); int LoaderModuleLoadEvents = 0; int LoaderModuleUnloadEvents = 0; - source.Clr.LoaderModuleLoad += (eventData) => LoaderModuleLoadEvents += 1; - source.Clr.LoaderModuleUnload += (eventData) => LoaderModuleUnloadEvents += 1; + source.Clr.LoaderModuleLoad += (eventData) => Interlocked.Increment(ref LoaderModuleLoadEvents); + source.Clr.LoaderModuleUnload += (eventData) => Interlocked.Increment(ref LoaderModuleUnloadEvents); return () => { Logger.logger.Log("Event counts validation"); diff --git a/src/tests/eventpipe/MethodEvents.cs b/src/tests/eventpipe/MethodEvents.cs index ee47168e4d..7e78fe717c 100644 --- a/src/tests/eventpipe/MethodEvents.cs +++ b/src/tests/eventpipe/MethodEvents.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.Tracing; +using System.Threading; using EventPipe.UnitTests.Common; using Microsoft.Diagnostics.NETCore.Client; using Microsoft.Diagnostics.Tracing; @@ -66,21 +67,22 @@ await RemoteTestExecutorHelper.RunTestCaseAsync(() => { } }; - Func> _DoesTraceContainEvents = (source) => { - int MethodLoadVerboseEvents = 0; - int MethodUnloadVerboseEvents = 0; - source.Clr.MethodLoadVerbose += (eventData) => MethodLoadVerboseEvents += 1; - source.Clr.MethodUnloadVerbose += (eventData) => MethodUnloadVerboseEvents += 1; + int MethodLoadVerboseEvents = 0; + int MethodUnloadVerboseEvents = 0; + int MethodJittingStartedEvents = 0; - int MethodJittingStartedEvents = 0; - source.Clr.MethodJittingStarted += (eventData) => MethodJittingStartedEvents += 1; + Func> _DoesTraceContainEvents = (source) => { + source.Clr.MethodLoadVerbose += (eventData) => Interlocked.Increment(ref MethodUnloadVerboseEvents); + source.Clr.MethodUnloadVerbose += (eventData) => Interlocked.Increment(ref MethodUnloadVerboseEvents); + source.Clr.MethodJittingStarted += (eventData) => Interlocked.Increment(ref MethodJittingStartedEvents); return () => { Logger.logger.Log("Event counts validation"); Logger.logger.Log("MethodLoadVerboseEvents: " + MethodLoadVerboseEvents); Logger.logger.Log("MethodUnloadVerboseEvents: " + MethodUnloadVerboseEvents); - //MethodUnloadVerboseEvents not stable, ignore the verification - bool MethodVerboseResult = MethodLoadVerboseEvents >= 1 && MethodUnloadVerboseEvents >= 0; + //MethodLoadVerboseEvents doesn't seem to ever get incremented, ignore the verification + //bool MethodVerboseResult = MethodLoadVerboseEvents >= 1 && MethodUnloadVerboseEvents >= 1; + bool MethodVerboseResult = MethodUnloadVerboseEvents >= 1; Logger.logger.Log("MethodVerboseResult check: " + MethodVerboseResult); Logger.logger.Log("MethodJittingStartedEvents: " + MethodJittingStartedEvents); diff --git a/src/tests/eventpipe/ThreadPoolEvents.cs b/src/tests/eventpipe/ThreadPoolEvents.cs index 4b97bf06c4..c1a1676196 100644 --- a/src/tests/eventpipe/ThreadPoolEvents.cs +++ b/src/tests/eventpipe/ThreadPoolEvents.cs @@ -50,7 +50,9 @@ await RemoteTestExecutorHelper.RunTestCaseAsync(() => { taskArray[i] = Task.Run(() => TestTask()); } +#pragma warning disable xUnit1031 // Do not use blocking task operations in test method Task.WaitAll(taskArray); +#pragma warning restore xUnit1031 // Do not use blocking task operations in test method }; void TestTask() @@ -64,8 +66,8 @@ void TestTask() int ThreadPoolWorkerThreadAdjustmentSampleEvents = 0; int ThreadPoolWorkerThreadAdjustmentAdjustmentEvents = 0; - source.Clr.ThreadPoolWorkerThreadAdjustmentSample += (eventData) => ThreadPoolWorkerThreadAdjustmentSampleEvents += 1; - source.Clr.ThreadPoolWorkerThreadAdjustmentAdjustment += (eventData) => ThreadPoolWorkerThreadAdjustmentAdjustmentEvents += 1; + source.Clr.ThreadPoolWorkerThreadAdjustmentSample += (eventData) => Interlocked.Increment(ref ThreadPoolWorkerThreadAdjustmentSampleEvents); + source.Clr.ThreadPoolWorkerThreadAdjustmentAdjustment += (eventData) => Interlocked.Increment(ref ThreadPoolWorkerThreadAdjustmentAdjustmentEvents); return () => { Logger.logger.Log("Event counts validation");