From: Mike McLaughlin Date: Thu, 20 Jun 2019 21:08:55 +0000 (-0700) Subject: Remove SOS and lldb plugin (#25220) X-Git-Tag: accepted/tizen/unified/20190813.215958~40^2~99 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=6b9a3d3a87825b1a34bd8f114c9b181ce75b3b2e;p=platform%2Fupstream%2Fcoreclr.git Remove SOS and lldb plugin (#25220) * Remove SOS and lldb plugin Issue: https://github.com/dotnet/coreclr/issues/24092 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index ab5c441..21f2031 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,7 +158,6 @@ if(CLR_CMAKE_PLATFORM_UNIX AND NOT DEFINED CLR_CROSS_COMPONENTS_BUILD) endif() if(CLR_CMAKE_PLATFORM_UNIX) - add_subdirectory(src/ToolBox/SOS/lldbplugin) add_subdirectory(src/pal) add_subdirectory(src/coreclr/hosts) add_subdirectory(src/ildasm/unixcoreclrloader) diff --git a/Documentation/building/buildinglldb.md b/Documentation/building/buildinglldb.md deleted file mode 100644 index 5c4b775..0000000 --- a/Documentation/building/buildinglldb.md +++ /dev/null @@ -1,87 +0,0 @@ -Building LLDB -============= - -1. Clone the llvm, clang, and lldb repos like this: - - llvm - | - `-- tools - | - +-- clang - | - `-- lldb - - ``` - cd $HOME - git clone http://llvm.org/git/llvm.git - cd $HOME/llvm/tools - git clone http://llvm.org/git/clang.git - git clone http://llvm.org/git/lldb.git - ``` - -2. Checkout the "release_39" branches in llvm/clang/lldb: - - ``` - cd $HOME/llvm - git checkout release_39 - cd $HOME/llvm/tools/clang - git checkout release_39 - cd $HOME/llvm/tools/lldb - git checkout release_39 - ``` - -3. Install the prerequisites: - - For Linux (Debian or Ubuntu): - ``` - sudo apt-get install build-essential subversion swig python2.7-dev libedit-dev libncurses5-dev - ``` - - For OSX, the latest Xcode needs to be installed and I use Homebrew to install the rest: - ``` - brew install python swig doxygen ocaml - ``` - - There may be more prerequisites required, when building the cmake files it should let - you know if there are any I missed. - - See http://lldb.llvm.org/build.html for more details on these preliminaries. - -4. If building on OSX, carefully following the signing directions (before you build) - here: $HOME/llvm/tools/lldb/docs/code-signing.txt. Even though those build directions - say to use Xcode to build lldb, I never got it to work, but cmake/make works. - -5. Building the cmake files (you can build either debug or release or both). - - For debug: - ``` - mkdir -p $HOME/build/debug - cd $HOME/build/debug - cmake -DCMAKE_BUILD_TYPE=debug $HOME/llvm - ``` - For release: - ``` - mkdir -p $HOME/build/release - cd $HOME/build/release - cmake -DCMAKE_BUILD_TYPE=release $HOME/llvm - ``` -6. Build lldb (release was picked in this example, but can be replaced with "debug"): - ``` - cd $HOME/build/release/tools/lldb - make -j16 - ``` - When you build with -j16 (parallel build with 16 jobs), sometimes it fails. Just start again with just make. - - For OS X, building in remote ssh shell won't sign properly, use a terminal window on the machine itself. - -7. To use the newly built lldb and to build the coreclr SOS plugin for it, set these environment variables in your .profile: - ``` - export LLDB_INCLUDE_DIR=$HOME/llvm/tools/lldb/include - export LLDB_LIB_DIR=$HOME/build/release/lib - PATH=$HOME/build/release/bin:$PATH - ``` - For OS X also set: - ``` - export LLDB_DEBUGSERVER_PATH=$HOME/build/release/bin/debugserver - ``` - It also seems to be necessary to run lldb as superuser e.g. `sudo -E $HOME/build/release/bin/lldb` (the -E is necessary so the above debug server environment variable is passed) if using a remote ssh, but it isn't necessary if run it in a local terminal session. diff --git a/Documentation/building/debugging-instructions.md b/Documentation/building/debugging-instructions.md index 86468b6..f99b498 100644 --- a/Documentation/building/debugging-instructions.md +++ b/Documentation/building/debugging-instructions.md @@ -1,7 +1,9 @@ Debugging CoreCLR ================= -These instructions will lead you through debugging CoreCLR on Windows and Linux. They will be expanded to support macOS when we have good instructions for that. +These instructions will lead you through debugging CoreCLR. + +SOS has moved to the diagnostics repo. For more information on SOS, installation and how to use it click [here](https://github.com/dotnet/diagnostics#net-core-diagnostics-repo). Debugging CoreCLR on Windows ============================ @@ -24,29 +26,25 @@ Steps 1-8 only need to be done once, and then (9) can be repeated whenever you w ### Using SOS with windbg or cdb on Windows ### -If you know the path of the `sos.dll` for the version of your runtime, load it like `.load c:\path\to\sos\sos.dll`. Use can use the `lm` command to find the path of the "coreclr.dll" module. `.loadby sos coreclr` should also work. - -For more information on SOS commands click [here](https://msdn.microsoft.com/en-us/library/bb190764(v=vs.110).aspx). +See the SOS installation instructions [here](https://github.com/dotnet/diagnostics/blob/master/documentation/installing-sos-windows-instructions.md). -Debugging CoreCLR on OS X -========================== +For more information on SOS commands click [here](https://github.com/dotnet/diagnostics/blob/master/documentation/sos-debugging-extension-windows.md). -To use lldb on OS X, you first need to build it and the SOS plugin on the machine you intend to use it. See the instructions in [building lldb](buildinglldb.md). The rest of instructions on how to use lldb for Linux on are the same. +Debugging CoreCLR on Linux and macOS +==================================== -Debugging CoreCLR on Linux -========================== +See the SOS installation instructions [here](https://github.com/dotnet/diagnostics/blob/master/documentation/installing-sos-windows-instructions.md). After SOS is installed, it will automatically be loaded by lldb. -Only lldb is supported by the SOS plugin. gdb can be used to debug the coreclr code but with no SOS support. Visual Studio 2015 RTM remote debugging isn't currently supported. +Only lldb is supported by SOS. Gdb can be used to debug the coreclr code but with no SOS support. 1. Perform a build of the coreclr repo. 2. Install the corefx managed assemblies to the binaries directory. 3. cd to build's binaries: `cd ~/coreclr/bin/Product/Linux.x64.Debug` -4. Start lldb (the version the plugin was built with, currently 3.9): `lldb-3.9 corerun HelloWorld.exe linux` -5. Now at the lldb command prompt, load SOS plugin: `plugin load libsosplugin.so` +4. Start lldb: `lldb-3.9 corerun HelloWorld.exe linux` 6. Launch program: `process launch -s` 7. To stop annoying breaks on SIGUSR1/SIGUSR2 signals used by the runtime run: `process handle -s false SIGUSR1 SIGUSR2` 8. Get to a point where coreclr is initialized by setting a breakpoint (i.e. `breakpoint set -n LoadLibraryExW` and then `process continue`) or stepping into the runtime. -9. Run a SOS command like `sos ClrStack` or `sos VerifyHeap`. The command name is case sensitive. +9. Run a SOS command like `clrstack` or `sos VerifyHeap`. The command name is case sensitive. You can combine steps 4-8 and pass everything on the lldb command line: @@ -56,120 +54,10 @@ For .NET Core version 1.x and 2.0.x, libsosplugin.so is built for and will only **Note:** _corerun_ is a simple host that does not support resolving NuGet dependencies. It relies on libraries being locatable via the `CORE_LIBRARIES` environment variable or present in the same directory as the corerun executable. The instructions above are equally applicable to the _dotnet_ host, however - e.g. for step 4 `lldb-3.9 dotnet bin/Debug/netcoreapp2.1/MvcApplication.dll` will let you debug _MvcApplication_ in the same manner. -### SOS commands ### - -This is the full list of commands currently supported by SOS. lldb is case-sensitive, unlike windbg. - - Type "soshelp " for detailed info on that function. - - Object Inspection Examining code and stacks - ----------------------------- ----------------------------- - DumpObj (dumpobj) Threads (clrthreads) - DumpArray ThreadState - DumpStackObjects (dso) IP2MD (ip2md) - DumpHeap (dumpheap) u (clru) - DumpVC DumpStack (dumpstack) - GCRoot (gcroot) EEStack (eestack) - PrintException (pe) ClrStack (clrstack) - GCInfo - EHInfo - bpmd (bpmd) - - Examining CLR data structures Diagnostic Utilities - ----------------------------- ----------------------------- - DumpDomain VerifyHeap - EEHeap (eeheap) FindAppDomain - Name2EE (name2ee) DumpLog (dumplog) - DumpMT (dumpmt) CreateDump (createdump) - DumpClass (dumpclass) - DumpMD (dumpmd) - Token2EE - DumpModule (dumpmodule) - DumpAssembly - DumpRuntimeTypes - DumpIL (dumpil) - DumpSig - DumpSigElem - - Examining the GC history Other - ----------------------------- ----------------------------- - HistInit (histinit) FAQ - HistRoot (histroot) Help (soshelp) - HistObj (histobj) - HistObjFind (histobjfind) - HistClear (histclear) - -### Aliases ### - -By default you can reach all the SOS commands by using: _sos [command\_name]_ -However the common commands have been aliased so that you don't need the SOS prefix: - - bpmd -> sos bpmd - clrstack -> sos ClrStack - clrthreads -> sos Threads - clru -> sos U - createdump -> sos CreateDump - dso -> sos DumpStackObjects - dumpclass -> sos DumpClass - dumpheap -> sos DumpHeap - dumpil -> sos DumpIL - dumplog -> sos DumpLog - dumpmd -> sos DumpMD - dumpmodule -> sos DumpModule - dumpmt -> sos DumpMT - dumpobj -> sos DumpObj - dumpstack -> sos DumpStack - eeheap -> sos EEHeap - eestack -> sos EEStack - gcroot -> sos GCRoot - histinit -> sos HistInit - histroot -> sos HistRoot - histobj -> sos HistObj - histobjfind -> sos HistObjFind - histclear -> sos HistClear - ip2md -> sos IP2MD - name2ee -> sos Name2EE - pe -> sos PrintException - soshelp -> sos Help - ### Debugging core dumps with lldb -It is also possible to debug .NET Core crash dumps using lldb and SOS. In order to do this, you need all of the following: - -- The crash dump file. We have a service called "Dumpling" which collects, uploads, and archives crash dump files during all of our CI jobs and official builds. -- On Linux, there is a utility called `createdump` (see [doc](https://github.com/dotnet/coreclr/blob/master/Documentation/botr/xplat-minidump-generation.md#configurationpolicy)) that can be setup to generate core dumps when a managed app throws an unhandled exception or faults. -- To get matching runtime and symbol binaries for the core dump use the symbol downloader CLI extension: - - Install the [.NET Core 2.1 SDK](https://www.microsoft.com/net/download/). - - Install the symbol downloader extension: `dotnet tool install -g dotnet-symbol`. Make sure you are not in any project directory with a NuGet.Config that doesn't include nuget.org as a source. - - Run `dotnet symbol coredump` to download the runtime binaries and symbols. - - Check out the coreclr and corefx repositories at the appropriate commit for the appropriate source. - - For further information see: [dotnet-symbol](https://github.com/dotnet/symstore/blob/master/src/dotnet-symbol/README.md). -- lldb version 3.9. The SOS plugin (i.e. libsosplugin.so) provided is now built for lldb 3.9. In order to install lldb 3.9 just run the following commands: -``` -~$ echo "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.9 main" | sudo tee /etc/apt/sources.list.d/llvm.list -~$ wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key | sudo apt-key add - -~$ sudo apt-get update -~$ sudo apt-get install lldb-3.9 -``` - -Once you have everything listed above, you are ready to start debugging. You need to specify an extra parameter to lldb in order for it to correctly resolve the symbols for libcoreclr.so. Use a command like this: - -``` -lldb-3.9 -O "settings set target.exec-search-paths " -o "plugin load " --core -``` - -- ``: The path containing libcoreclr.so.dbg, as well as the rest of the runtime and framework assemblies. -- ``: The path to the core dump you are attempting to debug. -- ``: The path to the dotnet or corerun executable, potentially in the `` folder. -- ``: The path to libsosplugin.so, should be in the `` folder. - -lldb should start debugging successfully at this point. You should see stack traces with resolved symbols for libcoreclr.so. At this point, you can run `plugin load `, and begin using SOS commands, as above. - -##### Example - -``` -lldb-3.9 -O "settings set target.exec-search-paths /home/parallels/Downloads/System.Drawing.Common.Tests/home/helixbot/dotnetbuild/work/2a74cf82-3018-4e08-9e9a-744bb492869e/Payload/shared/Microsoft.NETCore.App/9.9.9/" -o "plugin load /home/parallels/Downloads/System.Drawing.Common.Tests/home/helixbot/dotnetbuild/work/2a74cf82-3018-4e08-9e9a-744bb492869e/Payload/shared/Microsoft.NETCore.App/9.9.9/libsosplugin.so" --core /home/parallels/Downloads/System.Drawing.Common.Tests/home/helixbot/dotnetbuild/work/2a74cf82-3018-4e08-9e9a-744bb492869e/Work/f6414a62-9b41-4144-baed-756321e3e075/Unzip/core /home/parallels/Downloads/System.Drawing.Common.Tests/home/helixbot/dotnetbuild/work/2a74cf82-3018-4e08-9e9a-744bb492869e/Payload/shared/Microsoft.NETCore.App/9.9.9/dotnet -``` +See this [link](https://github.com/dotnet/diagnostics/blob/master/documentation/debugging-coredump.md) in the diagnostics repo. + Disabling Managed Attach/Debugging ================================== diff --git a/build.cmd b/build.cmd index 395c823..94f1f13 100644 --- a/build.cmd +++ b/build.cmd @@ -88,7 +88,6 @@ set __UnprocessedBuildArgs= set __CommonMSBuildArgs= set __BuildCoreLib=1 -set __BuildSOS=1 set __BuildNative=1 set __BuildCrossArchNative=0 set __SkipCrossArchNative=0 @@ -166,11 +165,11 @@ if [!__PassThroughArgs!]==[] ( set __PassThroughArgs=%__PassThroughArgs% %1 ) -if /i "%1" == "-freebsdmscorlib" (set __BuildSOS=0&set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=FreeBSD&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) -if /i "%1" == "-linuxmscorlib" (set __BuildSOS=0&set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=Linux&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) -if /i "%1" == "-netbsdmscorlib" (set __BuildSOS=0&set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=NetBSD&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) -if /i "%1" == "-osxmscorlib" (set __BuildSOS=0&set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=OSX&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) -if /i "%1" == "-windowsmscorlib" (set __BuildSOS=0&set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=Windows_NT&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "-freebsdmscorlib" (set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=FreeBSD&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "-linuxmscorlib" (set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=Linux&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "-netbsdmscorlib" (set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=NetBSD&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "-osxmscorlib" (set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=OSX&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "-windowsmscorlib" (set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=Windows_NT&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%1" == "-nativemscorlib" (set __BuildNativeCoreLib=1&set __BuildCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%1" == "-configureonly" (set __ConfigureOnly=1&set __BuildNative=1&set __BuildNativeCoreLib=0&set __BuildCoreLib=0&set __BuildTests=0&set __BuildPackages=0&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%1" == "-skipconfigure" (set __SkipConfigure=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) @@ -196,11 +195,11 @@ if /i "%1" == "-OfficialBuildId" (set __OfficialBuildIdArg=/p:OfficialBuildI REM TODO these are deprecated remove them eventually REM don't add more, use the - syntax instead -if /i "%1" == "freebsdmscorlib" (set __BuildSOS=0&set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=FreeBSD&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) -if /i "%1" == "linuxmscorlib" (set __BuildSOS=0&set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=Linux&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) -if /i "%1" == "netbsdmscorlib" (set __BuildSOS=0&set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=NetBSD&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) -if /i "%1" == "osxmscorlib" (set __BuildSOS=0&set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=OSX&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) -if /i "%1" == "windowsmscorlib" (set __BuildSOS=0&set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=Windows_NT&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "freebsdmscorlib" (set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=FreeBSD&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "linuxmscorlib" (set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=Linux&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "netbsdmscorlib" (set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=NetBSD&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "osxmscorlib" (set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=OSX&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "windowsmscorlib" (set __BuildNativeCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set __BuildOS=Windows_NT&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%1" == "nativemscorlib" (set __BuildNativeCoreLib=1&set __BuildCoreLib=0&set __BuildNative=0&set __BuildTests=0&set __BuildPackages=0&set __BuildManagedTools=0&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%1" == "configureonly" (set __ConfigureOnly=1&set __BuildNative=1&set __BuildNativeCoreLib=0&set __BuildCoreLib=0&set __BuildTests=0&set __BuildPackages=0&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) if /i "%1" == "skipconfigure" (set __SkipConfigure=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) @@ -614,10 +613,6 @@ if %__BuildCoreLib% EQU 1 ( set __ExtraBuildArgs= - if "%__BuildSOS%" == "0" ( - set __ExtraBuildArgs=!__ExtraBuildArgs! /p:SkipSOS=true - ) - if "%__BuildManagedTools%" == "1" ( set __ExtraBuildArgs=!__ExtraBuildArgs! /p:BuildManagedTools=true ) diff --git a/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.FreeBSD.Microsoft.NETCore.Runtime.CoreCLR.props b/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.FreeBSD.Microsoft.NETCore.Runtime.CoreCLR.props index fcf0d78..3219da6 100644 --- a/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.FreeBSD.Microsoft.NETCore.Runtime.CoreCLR.props +++ b/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.FreeBSD.Microsoft.NETCore.Runtime.CoreCLR.props @@ -6,12 +6,11 @@ - - - + + runtimes\$(PackageTargetRuntime)\native + - diff --git a/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.Linux.Microsoft.NETCore.Runtime.CoreCLR.props b/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.Linux.Microsoft.NETCore.Runtime.CoreCLR.props index d74974d..a046f0b 100644 --- a/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.Linux.Microsoft.NETCore.Runtime.CoreCLR.props +++ b/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.Linux.Microsoft.NETCore.Runtime.CoreCLR.props @@ -5,7 +5,6 @@ <_PlatformDoesNotSupportCreatedump Condition="'$(_runtimeOSFamily)' == 'tizen'">true <_PlatformDoesNotSupportEventTrace Condition="'$(_runtimeOSFamily)' == 'tizen'">true <_PlatformDoesNotSupportEventTrace Condition="'$(Platform)' == 'x86'">true - <_PlatformDoesNotSupportSosPlugin Condition="'$(_runtimeOSFamily)' == 'android'">true @@ -13,13 +12,12 @@ - - - + + runtimes\$(PackageTargetRuntime)\native + - diff --git a/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.OSX.Microsoft.NETCore.Runtime.CoreCLR.props b/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.OSX.Microsoft.NETCore.Runtime.CoreCLR.props index 9a64197..7fa9c74 100644 --- a/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.OSX.Microsoft.NETCore.Runtime.CoreCLR.props +++ b/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.OSX.Microsoft.NETCore.Runtime.CoreCLR.props @@ -5,11 +5,11 @@ - + + runtimes\$(PackageTargetRuntime)\native + - - diff --git a/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.Windows_NT.Microsoft.NETCore.Runtime.CoreCLR.props b/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.Windows_NT.Microsoft.NETCore.Runtime.CoreCLR.props index 58912b8..3e00690 100644 --- a/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.Windows_NT.Microsoft.NETCore.Runtime.CoreCLR.props +++ b/src/.nuget/Microsoft.NETCore.Runtime.CoreCLR/runtime.Windows_NT.Microsoft.NETCore.Runtime.CoreCLR.props @@ -18,15 +18,15 @@ - + + runtimes\$(PackageTargetRuntime)\native + - - @@ -37,12 +37,10 @@ - + runtimes\$(PackageTargetRuntime)\native\%(FileName)$(LongNameSuffix)%(Extension) - tools\$(CrossTargetComponentFolder)_$(Platform)\%(FileName)$(CrossTargetLongNameSuffix)%(Extension) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f4542b3..1b3c90e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,8 +20,6 @@ if(CLR_CMAKE_PLATFORM_UNIX) add_subdirectory(debug/createdump) endif(CLR_CMAKE_PLATFORM_LINUX) - add_subdirectory(ToolBox/SOS/Strike) - # Include the dummy c++ include files include_directories("pal/inc/rt/cpp") diff --git a/src/ToolBox/SOS/CMakeLists.txt b/src/ToolBox/SOS/CMakeLists.txt index 5ecf760..56d38c4 100644 --- a/src/ToolBox/SOS/CMakeLists.txt +++ b/src/ToolBox/SOS/CMakeLists.txt @@ -2,5 +2,6 @@ if(WIN32) if (CMAKE_GENERATOR MATCHES "Visual Studio .*") add_subdirectory(DacTableGen) endif() - add_subdirectory(Strike) endif(WIN32) + +_install(FILES SOS_README.md DESTINATION .) diff --git a/src/ToolBox/SOS/NETCore/SOS.NETCore.csproj b/src/ToolBox/SOS/NETCore/SOS.NETCore.csproj deleted file mode 100644 index 5e888a2..0000000 --- a/src/ToolBox/SOS/NETCore/SOS.NETCore.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - netcoreapp2.0 - SOS.NETCore - true - ;1591;1701 - .NET Core SOS - $(BinDir) - false - - - - - - diff --git a/src/ToolBox/SOS/NETCore/SOS.NETCore.sln b/src/ToolBox/SOS/NETCore/SOS.NETCore.sln deleted file mode 100644 index 0106883..0000000 --- a/src/ToolBox/SOS/NETCore/SOS.NETCore.sln +++ /dev/null @@ -1,22 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SOS.NETCore", "SOS.NETCore.csproj", "{20513BA2-A156-4A17-4C70-5AC2DBD4F833}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|Any CPU.Build.0 = Debug|Any CPU - {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|Any CPU.ActiveCfg = Release|Any CPU - {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/ToolBox/SOS/NETCore/SymbolReader.cs b/src/ToolBox/SOS/NETCore/SymbolReader.cs deleted file mode 100644 index 7a4bb52..0000000 --- a/src/ToolBox/SOS/NETCore/SymbolReader.cs +++ /dev/null @@ -1,782 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; -using System.Reflection.PortableExecutable; -using System.Runtime.InteropServices; - -namespace SOS -{ - internal class SymbolReader - { - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct DebugInfo - { - public int lineNumber; - public int ilOffset; - public string fileName; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct LocalVarInfo - { - public int startOffset; - public int endOffset; - public string name; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct MethodDebugInfo - { - public IntPtr points; - public int size; - public IntPtr locals; - public int localsSize; - - } - - /// - /// Read memory callback - /// - /// number of bytes read or 0 for error - internal unsafe delegate int ReadMemoryDelegate(ulong address, byte* buffer, int count); - - private sealed class OpenedReader : IDisposable - { - public readonly MetadataReaderProvider Provider; - public readonly MetadataReader Reader; - - public OpenedReader(MetadataReaderProvider provider, MetadataReader reader) - { - Debug.Assert(provider != null); - Debug.Assert(reader != null); - - Provider = provider; - Reader = reader; - } - - public void Dispose() => Provider.Dispose(); - } - - /// - /// Stream implementation to read debugger target memory for in-memory PDBs - /// - private class TargetStream : Stream - { - readonly ulong _address; - readonly ReadMemoryDelegate _readMemory; - - public override long Position { get; set; } - public override long Length { get; } - public override bool CanSeek { get { return true; } } - public override bool CanRead { get { return true; } } - public override bool CanWrite { get { return false; } } - - public TargetStream(ulong address, int size, ReadMemoryDelegate readMemory) - : base() - { - _address = address; - _readMemory = readMemory; - Length = size; - Position = 0; - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (Position + count > Length) - { - throw new ArgumentOutOfRangeException(); - } - unsafe - { - fixed (byte* p = &buffer[offset]) - { - int read = _readMemory(_address + (ulong)Position, p, count); - Position += read; - return read; - } - } - } - - public override long Seek(long offset, SeekOrigin origin) - { - switch (origin) - { - case SeekOrigin.Begin: - Position = offset; - break; - case SeekOrigin.End: - Position = Length + offset; - break; - case SeekOrigin.Current: - Position += offset; - break; - } - return Position; - } - - public override void Flush() - { - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - } - - /// - /// Quick fix for Path.GetFileName which incorrectly handles Windows-style paths on Linux - /// - /// File path to be processed - /// Last component of path - private static string GetFileName(string pathName) - { - int pos = pathName.LastIndexOfAny(new char[] { '/', '\\'}); - if (pos < 0) - return pathName; - return pathName.Substring(pos + 1); - } - - /// - /// Checks availability of debugging information for given assembly. - /// - /// - /// File path of the assembly or null if the module is in-memory or dynamic (generated by Reflection.Emit) - /// - /// type of in-memory PE layout, if true, file based layout otherwise, loaded layout - /// - /// Loaded PE image address or zero if the module is dynamic (generated by Reflection.Emit). - /// Dynamic modules have their PDBs (if any) generated to an in-memory stream - /// (pointed to by and ). - /// - /// loaded PE image size - /// in memory PDB address or zero - /// in memory PDB size - /// Symbol reader handle or zero if error - internal static IntPtr LoadSymbolsForModule(string assemblyPath, bool isFileLayout, ulong loadedPeAddress, int loadedPeSize, - ulong inMemoryPdbAddress, int inMemoryPdbSize, ReadMemoryDelegate readMemory) - { - try - { - TargetStream peStream = null; - if (assemblyPath == null && loadedPeAddress != 0) - { - peStream = new TargetStream(loadedPeAddress, loadedPeSize, readMemory); - } - TargetStream pdbStream = null; - if (inMemoryPdbAddress != 0) - { - pdbStream = new TargetStream(inMemoryPdbAddress, inMemoryPdbSize, readMemory); - } - OpenedReader openedReader = GetReader(assemblyPath, isFileLayout, peStream, pdbStream); - if (openedReader != null) - { - GCHandle gch = GCHandle.Alloc(openedReader); - return GCHandle.ToIntPtr(gch); - } - } - catch - { - } - return IntPtr.Zero; - } - - /// - /// Cleanup and dispose of symbol reader handle - /// - /// symbol reader handle returned by LoadSymbolsForModule - internal static void Dispose(IntPtr symbolReaderHandle) - { - Debug.Assert(symbolReaderHandle != IntPtr.Zero); - try - { - GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); - ((OpenedReader)gch.Target).Dispose(); - gch.Free(); - } - catch - { - } - } - - /// - /// Returns method token and IL offset for given source line number. - /// - /// symbol reader handle returned by LoadSymbolsForModule - /// source file name and path - /// source line number - /// method token return - /// IL offset return - /// true if information is available - internal static bool ResolveSequencePoint(IntPtr symbolReaderHandle, string filePath, int lineNumber, out int methodToken, out int ilOffset) - { - Debug.Assert(symbolReaderHandle != IntPtr.Zero); - methodToken = 0; - ilOffset = 0; - - GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); - MetadataReader reader = ((OpenedReader)gch.Target).Reader; - - try - { - string fileName = GetFileName(filePath); - foreach (MethodDebugInformationHandle methodDebugInformationHandle in reader.MethodDebugInformation) - { - MethodDebugInformation methodDebugInfo = reader.GetMethodDebugInformation(methodDebugInformationHandle); - SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints(); - foreach (SequencePoint point in sequencePoints) - { - string sourceName = reader.GetString(reader.GetDocument(point.Document).Name); - if (point.StartLine == lineNumber && GetFileName(sourceName) == fileName) - { - methodToken = MetadataTokens.GetToken(methodDebugInformationHandle.ToDefinitionHandle()); - ilOffset = point.Offset; - return true; - } - } - } - } - catch - { - } - return false; - } - - /// - /// Returns source line number and source file name for given IL offset and method token. - /// - /// symbol reader handle returned by LoadSymbolsForModule - /// method token - /// IL offset - /// source line number return - /// source file name return - /// true if information is available - internal static bool GetLineByILOffset(IntPtr symbolReaderHandle, int methodToken, long ilOffset, out int lineNumber, out IntPtr fileName) - { - lineNumber = 0; - fileName = IntPtr.Zero; - - string sourceFileName = null; - - if (!GetSourceLineByILOffset(symbolReaderHandle, methodToken, ilOffset, out lineNumber, out sourceFileName)) - { - return false; - } - fileName = Marshal.StringToBSTR(sourceFileName); - sourceFileName = null; - return true; - } - - /// - /// Helper method to return source line number and source file name for given IL offset and method token. - /// - /// symbol reader handle returned by LoadSymbolsForModule - /// method token - /// IL offset - /// source line number return - /// source file name return - /// true if information is available - private static bool GetSourceLineByILOffset(IntPtr symbolReaderHandle, int methodToken, long ilOffset, out int lineNumber, out string fileName) - { - Debug.Assert(symbolReaderHandle != IntPtr.Zero); - lineNumber = 0; - fileName = null; - - GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); - MetadataReader reader = ((OpenedReader)gch.Target).Reader; - - try - { - Handle handle = MetadataTokens.Handle(methodToken); - if (handle.Kind != HandleKind.MethodDefinition) - return false; - - MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); - if (methodDebugHandle.IsNil) - return false; - - MethodDebugInformation methodDebugInfo = reader.GetMethodDebugInformation(methodDebugHandle); - SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints(); - - SequencePoint nearestPoint = sequencePoints.GetEnumerator().Current; - foreach (SequencePoint point in sequencePoints) - { - if (point.Offset < ilOffset) - { - nearestPoint = point; - } - else - { - if (point.Offset == ilOffset) - nearestPoint = point; - - if (nearestPoint.StartLine == 0 || nearestPoint.StartLine == SequencePoint.HiddenLine) - return false; - - lineNumber = nearestPoint.StartLine; - fileName = reader.GetString(reader.GetDocument(nearestPoint.Document).Name); - return true; - } - } - } - catch - { - } - return false; - } - - /// - /// Returns local variable name for given local index and IL offset. - /// - /// symbol reader handle returned by LoadSymbolsForModule - /// method token - /// local variable index - /// local variable name return - /// true if name has been found - internal static bool GetLocalVariableName(IntPtr symbolReaderHandle, int methodToken, int localIndex, out IntPtr localVarName) - { - localVarName = IntPtr.Zero; - - string localVar = null; - if (!GetLocalVariableByIndex(symbolReaderHandle, methodToken, localIndex, out localVar)) - return false; - - localVarName = Marshal.StringToBSTR(localVar); - localVar = null; - return true; - } - - /// - /// Helper method to return local variable name for given local index and IL offset. - /// - /// symbol reader handle returned by LoadSymbolsForModule - /// method token - /// local variable index - /// local variable name return - /// true if name has been found - internal static bool GetLocalVariableByIndex(IntPtr symbolReaderHandle, int methodToken, int localIndex, out string localVarName) - { - Debug.Assert(symbolReaderHandle != IntPtr.Zero); - localVarName = null; - - GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); - MetadataReader reader = ((OpenedReader)gch.Target).Reader; - - try - { - Handle handle = MetadataTokens.Handle(methodToken); - if (handle.Kind != HandleKind.MethodDefinition) - return false; - - MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); - LocalScopeHandleCollection localScopes = reader.GetLocalScopes(methodDebugHandle); - foreach (LocalScopeHandle scopeHandle in localScopes) - { - LocalScope scope = reader.GetLocalScope(scopeHandle); - LocalVariableHandleCollection localVars = scope.GetLocalVariables(); - foreach (LocalVariableHandle varHandle in localVars) - { - LocalVariable localVar = reader.GetLocalVariable(varHandle); - if (localVar.Index == localIndex) - { - if (localVar.Attributes == LocalVariableAttributes.DebuggerHidden) - return false; - - localVarName = reader.GetString(localVar.Name); - return true; - } - } - } - } - catch - { - } - return false; - } - internal static bool GetLocalsInfoForMethod(string assemblyPath, int methodToken, out List locals) - { - locals = null; - - OpenedReader openedReader = GetReader(assemblyPath, isFileLayout: true, peStream: null, pdbStream: null); - if (openedReader == null) - return false; - - using (openedReader) - { - try - { - Handle handle = MetadataTokens.Handle(methodToken); - if (handle.Kind != HandleKind.MethodDefinition) - return false; - - locals = new List(); - - MethodDebugInformationHandle methodDebugHandle = - ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); - LocalScopeHandleCollection localScopes = openedReader.Reader.GetLocalScopes(methodDebugHandle); - foreach (LocalScopeHandle scopeHandle in localScopes) - { - LocalScope scope = openedReader.Reader.GetLocalScope(scopeHandle); - LocalVariableHandleCollection localVars = scope.GetLocalVariables(); - foreach (LocalVariableHandle varHandle in localVars) - { - LocalVariable localVar = openedReader.Reader.GetLocalVariable(varHandle); - if (localVar.Attributes == LocalVariableAttributes.DebuggerHidden) - continue; - LocalVarInfo info = new LocalVarInfo(); - info.startOffset = scope.StartOffset; - info.endOffset = scope.EndOffset; - info.name = openedReader.Reader.GetString(localVar.Name); - locals.Add(info); - } - } - } - catch - { - return false; - } - } - return true; - - } - /// - /// Returns source name, line numbers and IL offsets for given method token. - /// - /// file path of the assembly - /// method token - /// structure with debug information return - /// true if information is available - /// used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs - internal static bool GetInfoForMethod(string assemblyPath, int methodToken, ref MethodDebugInfo debugInfo) - { - try - { - List points = null; - List locals = null; - - if (!GetDebugInfoForMethod(assemblyPath, methodToken, out points)) - { - return false; - } - - if (!GetLocalsInfoForMethod(assemblyPath, methodToken, out locals)) - { - return false; - } - var structSize = Marshal.SizeOf(); - - debugInfo.size = points.Count; - var ptr = debugInfo.points; - - foreach (var info in points) - { - Marshal.StructureToPtr(info, ptr, false); - ptr = (IntPtr)(ptr.ToInt64() + structSize); - } - - structSize = Marshal.SizeOf(); - - debugInfo.localsSize = locals.Count; - ptr = debugInfo.locals; - - foreach (var info in locals) - { - Marshal.StructureToPtr(info, ptr, false); - ptr = (IntPtr)(ptr.ToInt64() + structSize); - } - - return true; - } - catch - { - } - return false; - } - - /// - /// Helper method to return source name, line numbers and IL offsets for given method token. - /// - /// file path of the assembly - /// method token - /// list of debug information for each sequence point return - /// true if information is available - /// used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs - private static bool GetDebugInfoForMethod(string assemblyPath, int methodToken, out List points) - { - points = null; - - OpenedReader openedReader = GetReader(assemblyPath, isFileLayout: true, peStream: null, pdbStream: null); - if (openedReader == null) - return false; - - using (openedReader) - { - try - { - Handle handle = MetadataTokens.Handle(methodToken); - if (handle.Kind != HandleKind.MethodDefinition) - return false; - - points = new List(); - MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); - MethodDebugInformation methodDebugInfo = openedReader.Reader.GetMethodDebugInformation(methodDebugHandle); - SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints(); - - foreach (SequencePoint point in sequencePoints) - { - - DebugInfo debugInfo = new DebugInfo(); - debugInfo.lineNumber = point.StartLine; - debugInfo.fileName = openedReader.Reader.GetString(openedReader.Reader.GetDocument(point.Document).Name); - debugInfo.ilOffset = point.Offset; - points.Add(debugInfo); - } - } - catch - { - return false; - } - } - return true; - } - - /// - /// Returns the portable PDB reader for the assembly path - /// - /// file path of the assembly or null if the module is in-memory or dynamic - /// type of in-memory PE layout, if true, file based layout otherwise, loaded layout - /// optional in-memory PE stream - /// optional in-memory PDB stream - /// reader/provider wrapper instance - /// - /// Assumes that neither PE image nor PDB loaded into memory can be unloaded or moved around. - /// - private static OpenedReader GetReader(string assemblyPath, bool isFileLayout, Stream peStream, Stream pdbStream) - { - return (pdbStream != null) ? TryOpenReaderForInMemoryPdb(pdbStream) : TryOpenReaderFromAssembly(assemblyPath, isFileLayout, peStream); - } - - private static OpenedReader TryOpenReaderForInMemoryPdb(Stream pdbStream) - { - Debug.Assert(pdbStream != null); - - byte[] buffer = new byte[sizeof(uint)]; - if (pdbStream.Read(buffer, 0, sizeof(uint)) != sizeof(uint)) - { - return null; - } - uint signature = BitConverter.ToUInt32(buffer, 0); - - // quick check to avoid throwing exceptions below in common cases: - const uint ManagedMetadataSignature = 0x424A5342; - if (signature != ManagedMetadataSignature) - { - // not a Portable PDB - return null; - } - - OpenedReader result = null; - MetadataReaderProvider provider = null; - try - { - pdbStream.Position = 0; - provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); - result = new OpenedReader(provider, provider.GetMetadataReader()); - } - catch (Exception e) when (e is BadImageFormatException || e is IOException) - { - return null; - } - finally - { - if (result == null) - { - provider?.Dispose(); - } - } - - return result; - } - - private static OpenedReader TryOpenReaderFromAssembly(string assemblyPath, bool isFileLayout, Stream peStream) - { - if (assemblyPath == null && peStream == null) - return null; - - PEStreamOptions options = isFileLayout ? PEStreamOptions.Default : PEStreamOptions.IsLoadedImage; - if (peStream == null) - { - peStream = TryOpenFile(assemblyPath); - if (peStream == null) - return null; - - options = PEStreamOptions.Default; - } - - try - { - using (var peReader = new PEReader(peStream, options)) - { - DebugDirectoryEntry codeViewEntry, embeddedPdbEntry; - ReadPortableDebugTableEntries(peReader, out codeViewEntry, out embeddedPdbEntry); - - // First try .pdb file specified in CodeView data (we prefer .pdb file on disk over embedded PDB - // since embedded PDB needs decompression which is less efficient than memory-mapping the file). - if (codeViewEntry.DataSize != 0) - { - var result = TryOpenReaderFromCodeView(peReader, codeViewEntry, assemblyPath); - if (result != null) - { - return result; - } - } - - // if it failed try Embedded Portable PDB (if available): - if (embeddedPdbEntry.DataSize != 0) - { - return TryOpenReaderFromEmbeddedPdb(peReader, embeddedPdbEntry); - } - } - } - catch (Exception e) when (e is BadImageFormatException || e is IOException) - { - // nop - } - - return null; - } - - private static void ReadPortableDebugTableEntries(PEReader peReader, out DebugDirectoryEntry codeViewEntry, out DebugDirectoryEntry embeddedPdbEntry) - { - // See spec: https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PE-COFF.md - - codeViewEntry = default(DebugDirectoryEntry); - embeddedPdbEntry = default(DebugDirectoryEntry); - - foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory()) - { - if (entry.Type == DebugDirectoryEntryType.CodeView) - { - const ushort PortableCodeViewVersionMagic = 0x504d; - if (entry.MinorVersion != PortableCodeViewVersionMagic) - { - continue; - } - - codeViewEntry = entry; - } - else if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb) - { - embeddedPdbEntry = entry; - } - } - } - - private static OpenedReader TryOpenReaderFromCodeView(PEReader peReader, DebugDirectoryEntry codeViewEntry, string assemblyPath) - { - OpenedReader result = null; - MetadataReaderProvider provider = null; - try - { - var data = peReader.ReadCodeViewDebugDirectoryData(codeViewEntry); - - string pdbPath = data.Path; - if (assemblyPath != null) - { - try - { - pdbPath = Path.Combine(Path.GetDirectoryName(assemblyPath), GetFileName(pdbPath)); - } - catch - { - // invalid characters in CodeView path - return null; - } - } - - var pdbStream = TryOpenFile(pdbPath); - if (pdbStream == null) - { - return null; - } - - provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); - var reader = provider.GetMetadataReader(); - - // Validate that the PDB matches the assembly version - if (data.Age == 1 && new BlobContentId(reader.DebugMetadataHeader.Id) == new BlobContentId(data.Guid, codeViewEntry.Stamp)) - { - result = new OpenedReader(provider, reader); - } - } - catch (Exception e) when (e is BadImageFormatException || e is IOException) - { - return null; - } - finally - { - if (result == null) - { - provider?.Dispose(); - } - } - - return result; - } - - private static OpenedReader TryOpenReaderFromEmbeddedPdb(PEReader peReader, DebugDirectoryEntry embeddedPdbEntry) - { - OpenedReader result = null; - MetadataReaderProvider provider = null; - - try - { - // TODO: We might want to cache this provider globally (across stack traces), - // since decompressing embedded PDB takes some time. - provider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry); - result = new OpenedReader(provider, provider.GetMetadataReader()); - } - catch (Exception e) when (e is BadImageFormatException || e is IOException) - { - return null; - } - finally - { - if (result == null) - { - provider?.Dispose(); - } - } - - return result; - } - - private static Stream TryOpenFile(string path) - { - if (!File.Exists(path)) - { - return null; - } - try - { - return File.OpenRead(path); - } - catch - { - return null; - } - } - } -} diff --git a/src/ToolBox/SOS/SOSAndICorDebug.md b/src/ToolBox/SOS/SOSAndICorDebug.md deleted file mode 100644 index 7398f63..0000000 --- a/src/ToolBox/SOS/SOSAndICorDebug.md +++ /dev/null @@ -1,81 +0,0 @@ -SOS & ICorDebug --------------------- - -## Introduction - -The purpose of this doc is to capture the work and learnings produced by the Dev 11 MQ item to integrate the public ICorDebug* interfaces (mscordbi.dll) into the SOS implementation. -Note: Use of the name “windbg” in this document is meant to actually indicate all members of the ntsd family of debuggers. - - -## MSCORDBI.DLL Distribution - -### What’s checked in - suboptimal - -For MQ, SOS activates mscordbi.dll via the “typical” mechanism of calling into mscoree.dll to get the ICLRDebugging interface, on which SOS calls OpenVirtualProcess. -Although this works, it is not ideal. -It assumes the CLR you want to debug has been formally installed onto the debugging machine (complete with mscoree.dll in your system32 / syswow64 directory). - -### Recommendation -Optimally, everything windbg needs in order to debug the CLR should be automagically downloaded to the user’s box, including DAC, mscordbi.dll, and even SOS itself, with no need at all for mscoree.dll or mscoreei.dll. - -### Download -Today, DAC is already automagically downloaded via the symbol server, with support for that hard-coded into the debugger: Windbg takes file information of the CLR.dll in the debuggee or dump that would normally be used to ask the symbol server for CLR.pdb, and instead asks for the DAC dll (using CLR.dll’s file info and the DAC dll’s file name). -This same hack could be applied to download the appropriate mscordbi.dll, and even SOS.dll. - - -This requires work from the following parties: - - -* **CLR & Windows debugging teams** agree on a design whereby windbg could automatically (or receive a command to) locate and download the appropriate mscordbi.dll and SOS.dll much as it does today to locate DAC. -Extensions like SOS should then be able to access mscordbi.dll via a windbg API, much like the windbg API that exposes the private DAC interface. - * The above is intentionally way-vague. - Extra work needs to be done to determine a good way for windbg & extensions to cooperate and access mscordbi functionality. -* **Windows debugging team** implements the above -* **CLR Servicing Team** must alter their process to ensure mscordbi.dll and SOS.dll are properly indexed on the symbol server upon each CLR release, just as with DAC. - - -### Activation -Once DAC, SOS, and mscordbi are all on the same machine, we need a way for SOS to get an ICorDebugProcess from mscordbi that does not require mscoree.dll on the box. -Since the necessary mscordbi is already available as per above, there is no need for SOS to go through mscoree or mscoreei as it is today. -Since mscoreei.dll eventually just calls OpenVirtualProcessImpl() from mscordbi.dll, one could imagine that SOS could simply call mscordbi’s OpenVirtualProcessImpl() directly. - -* CLR Team would need to export OpenVirtualProcessImpl, or the equivalent, from mscordbi.dll directly. -* CLR Team would modify SOS to use OpenVirtualProcessImpl rather than the current scheme that relies on mscoree.dll. -* Since CLR Team owns both ends of this interface (i.e., implementation and consumer of OpenVirtualProcessImpl), and since we already have a scheme to exchange version numbers in OpenVirtualProcessImpl, there should not be versioning problems. -* However, we might prefer to have windbg (instead of SOS) call OpenVirtualProcessImpl(). -Examples why: - * The “Download” section above talks about SOS “accessing” mscordbi. - This is vague, and could either mean that SOS calls into mscordbi’ s OpenVirtualProcessImpl() directly to get an ICorDebugProcess OR SOS uses a new, structured windbg API which would call (on SOS’s behalf) into mscordbi to provide an ICorDebugProcess. - The latter may be a more natural way for an extension to get access to the “right” ICorDebugProcess, particularly if multiple mscordbi.dll’s end up on the symbol path, or for in-proc SxS scenarios. - * Perhaps windbg could someday use mscordbi directly to aid in managed debugging, rather than the private DAC interface. -* If we do choose to have windbg (instead of SOS) call OpenVirtualProcessImpl, then we’d need to give some more thought to OpenVirtualProcessImpl and versioning, since we no longer control the consumer end of that relationship. - -## !ClrStack –i [-p] - -This checkin uses !clrstack as the prototype for activating and consuming the public dbgapi. -This section briefly describes how that works. -If “-i” is specified, then !clrstack defers to ClrStackFromPublicInterface() to do its work. -Today in SOS, the private DAC interface is initialized at the beginning of each command, and released at the end of each command. -ClrStackFromPublicInterface() follows suit by initializing the public interface at the top, via ICLRDebugging, thus setting g_pCorDebugProcess appropriately (see “What’s checked in – suboptimal” above). -Note: currently, g_pCorDebugProcess is not released but hey, prototype code. -ClrStackFromPublicInterface then uses g_pCorDebugProcess to grab the other public interfaces necessary to do a stackwalk. -If “-p” is specified, ClrStackFromPublicInterface will also use ICorDebug* to get parameter info. -Caveats -To pretty-print the function names, there are several metadata-consuming chunks of code in SOS. -I picked the one that required the least amount of private DAC gunk. -However, that code appears not to be well-exercised. -See comments in the code for more details. -The code in !clrstack –i to print the stack is only mildly tested. -There may well be cases where the stack doesn’t look so great or where managed frames appear unordered with respect to the internal (explicit EE) Frames. -See comments in the code for more details. - -## Mac - -Nope. -There are several silly reasons why the above is #ifdef’d out on the Mac. -The work necessary to enable this on the Mac may well be straightforward. -Some of the reasons the Mac compile failed for me: - -* Need psapi.h for GetModuleInformation and structures it uses. The equivalents need to be found (or implemented!) for the Mac. -* No IID defined for IDebugAdvanced3. (Possibly just a matter of manually rebuilding the right GUID lib for the Mac, if you can find it.) -* Couldn’t find some CLR-specific headers on the Mac build, like metahost.h. diff --git a/src/ToolBox/SOS/SOS_README.md b/src/ToolBox/SOS/SOS_README.md new file mode 100644 index 0000000..298d766 --- /dev/null +++ b/src/ToolBox/SOS/SOS_README.md @@ -0,0 +1,5 @@ +SOS and other diagnostic tools now ship of band and work with any version of the .NET Core runtime. + +SOS has moved to the diagnostics repo here: https://github.com/dotnet/diagnostics.git. + +Instructions to install SOS: https://github.com/dotnet/diagnostics#installing-sos. diff --git a/src/ToolBox/SOS/Strike/ApolloNative.rc b/src/ToolBox/SOS/Strike/ApolloNative.rc deleted file mode 100644 index ba320af..0000000 --- a/src/ToolBox/SOS/Strike/ApolloNative.rc +++ /dev/null @@ -1,10 +0,0 @@ -// 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. - -#define FX_VER_FILEDESCRIPTION_STR "Microsoft NTSD extension for .NET Runtime\0" - -#include -#include - -DOCUMENTATION TEXT DISCARDABLE "apollososdocs.txt" diff --git a/src/ToolBox/SOS/Strike/CMakeLists.txt b/src/ToolBox/SOS/Strike/CMakeLists.txt deleted file mode 100644 index 1b4d746..0000000 --- a/src/ToolBox/SOS/Strike/CMakeLists.txt +++ /dev/null @@ -1,230 +0,0 @@ -# Set the RPATH of sos so that it can find dependencies without needing to set LD_LIBRARY_PATH -# For more information: http://www.cmake.org/Wiki/CMake_RPATH_handling. -if (CORECLR_SET_RPATH) - set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) - if(CLR_CMAKE_PLATFORM_DARWIN) - set(CMAKE_INSTALL_RPATH "@loader_path") - else() - set(CMAKE_INSTALL_RPATH "\$ORIGIN") - endif(CLR_CMAKE_PLATFORM_DARWIN) -endif (CORECLR_SET_RPATH) - -if(CLR_CMAKE_PLATFORM_ARCH_AMD64) - add_definitions(-DSOS_TARGET_AMD64) - add_definitions(-D_TARGET_WIN64_) - add_definitions(-DDBG_TARGET_64BIT) - add_definitions(-DDBG_TARGET_WIN64) - if(WIN32) - add_definitions(-DSOS_TARGET_ARM64) - endif(WIN32) - remove_definitions(-D_TARGET_ARM64_) - add_definitions(-D_TARGET_AMD64_) - add_definitions(-DDBG_TARGET_AMD64) -elseif(CLR_CMAKE_PLATFORM_ARCH_I386) - add_definitions(-DSOS_TARGET_X86) - add_definitions(-D_TARGET_X86_) - add_definitions(-DDBG_TARGET_32BIT) - if(WIN32) - add_definitions(-DSOS_TARGET_ARM) - endif(WIN32) -elseif(CLR_CMAKE_PLATFORM_ARCH_ARM) - add_definitions(-DSOS_TARGET_ARM) - add_definitions(-D_TARGET_WIN32_) - add_definitions(-D_TARGET_ARM_) - add_definitions(-DDBG_TARGET_32BIT) -elseif(CLR_CMAKE_PLATFORM_ARCH_ARM64) - add_definitions(-DSOS_TARGET_ARM64) - add_definitions(-D_TARGET_WIN64_) - add_definitions(-DDBG_TARGET_64BIT) - add_definitions(-DDBG_TARGET_WIN64) -endif() - -add_definitions(-DSTRIKE) - -remove_definitions(-DUNICODE) -remove_definitions(-D_UNICODE) - -include_directories(BEFORE ${VM_DIR}) -include_directories(${CLR_DIR}/src/gcdump) -include_directories(${CLR_DIR}/src/debug/shim) -include_directories(${CLR_DIR}/src/coreclr/hosts/unixcoreruncommon) -include_directories(${CLR_DIR}/src/coreclr/hosts/inc) - -if(WIN32) - include_directories(inc) - include_directories("$ENV{VSInstallDir}/DIA SDK/include") - - add_definitions(-DUSE_STL) - - #use static crt - add_definitions(-MT) - - set(SOS_SOURCES - disasm.cpp - dllsext.cpp - eeheap.cpp - EventCallbacks.cpp - ExpressionNode.cpp - exts.cpp - gchist.cpp - gcroot.cpp - metadata.cpp - sildasm.cpp - sos.cpp - stressLogDump.cpp - strike.cpp - util.cpp - vm.cpp - WatchCmd.cpp - Native.rc - ) - - add_definitions(-DFX_VER_INTERNALNAME_STR=SOS.dll) - - #Preprocess exports definition file - preprocess_def_file(${CMAKE_CURRENT_SOURCE_DIR}/sos.def ${CMAKE_CURRENT_BINARY_DIR}/sos.def) - list(APPEND SOS_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/sos.def) - - set(SOS_LIBRARY - corguids - debugshim - dbgutil - ${STATIC_MT_CRT_LIB} - ${STATIC_MT_CPP_LIB} - ${STATIC_MT_VCRT_LIB} - kernel32.lib - user32.lib - ole32.lib - oleaut32.lib - dbghelp.lib - uuid.lib - version.lib - dbgeng.lib - advapi32.lib - psapi.lib - ntdll.lib - ) -else(WIN32) - add_definitions(-DPAL_STDCPP_COMPAT) - if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wno-null-arithmetic) - elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - add_compile_options($<$:-Wno-conversion-null>) - add_compile_options(-Wno-pointer-arith) - endif() - add_compile_options(-Wno-format) - - include_directories(BEFORE xplat) - include_directories(BEFORE ../lldbplugin/inc) - - add_compile_options(-fPIC) - - set(SOS_SOURCES - disasm.cpp - datatarget.cpp - eeheap.cpp - exts.cpp - gchist.cpp - gcroot.cpp - metadata.cpp - sildasm.cpp - stressLogDump.cpp - strike.cpp - sos.cpp - util.cpp - ) - - set(SOS_LIBRARY - corguids - debugshim - dbgutil - # share the PAL in the dac module - mscordaccore - palrt - unixcoreruncommon - ) - - set(DEF_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/sos_unixexports.src) - set(EXPORTS_FILE ${CMAKE_CURRENT_BINARY_DIR}/sos.exports) - generate_exports_file(${DEF_SOURCES} ${EXPORTS_FILE}) -endif(WIN32) - -if(CLR_CMAKE_PLATFORM_LINUX) - list(APPEND - SOS_LIBRARY - createdump_lib - ) - add_definitions(-DCREATE_DUMP_SUPPORTED) -endif(CLR_CMAKE_PLATFORM_LINUX) - -if(CLR_CMAKE_PLATFORM_ARCH_AMD64) - set(SOS_SOURCES_ARCH - disasmX86.cpp - ) - if(WIN32) - list(APPEND - SOS_SOURCES_ARCH - disasmARM64.cpp - ) - endif(WIN32) -elseif(CLR_CMAKE_PLATFORM_ARCH_I386) - set(SOS_SOURCES_ARCH - disasmX86.cpp - ) - if(WIN32) - list(APPEND - SOS_SOURCES_ARCH - disasmARM.cpp - ) - endif(WIN32) -elseif(CLR_CMAKE_PLATFORM_ARCH_ARM) - set(SOS_SOURCES_ARCH - disasmARM.cpp - ) -elseif(CLR_CMAKE_PLATFORM_ARCH_ARM64) - set(SOS_SOURCES_ARCH - disasmARM64.cpp - ) -endif() - -list(APPEND SOS_SOURCES ${SOS_SOURCES_ARCH}) - -# Add the DAC PAL export mapping file -if(CLR_CMAKE_PLATFORM_LINUX) - list(APPEND SOS_SOURCES ${PAL_REDEFINES_FILE}) -endif(CLR_CMAKE_PLATFORM_LINUX) - -if(CLR_CMAKE_PLATFORM_LINUX OR CLR_CMAKE_PLATFORM_FREEBSD OR CLR_CMAKE_PLATFORM_NETBSD) - # Add linker exports file option - set(EXPORTS_LINKER_OPTION -Wl,--version-script=${EXPORTS_FILE}) -endif(CLR_CMAKE_PLATFORM_LINUX OR CLR_CMAKE_PLATFORM_FREEBSD OR CLR_CMAKE_PLATFORM_NETBSD) - -if(CLR_CMAKE_PLATFORM_DARWIN) - # Add linker exports file option - set(EXPORTS_LINKER_OPTION -Wl,-exported_symbols_list,${EXPORTS_FILE}) -endif(CLR_CMAKE_PLATFORM_DARWIN) - -add_library_clr(sos SHARED ${SOS_SOURCES}) - -if(CLR_CMAKE_PLATFORM_LINUX) - add_dependencies(sos pal_redefines_file) -endif(CLR_CMAKE_PLATFORM_LINUX) - -if(CLR_CMAKE_PLATFORM_UNIX) - add_custom_target(sos_exports DEPENDS ${EXPORTS_FILE}) - add_dependencies(sos sos_exports) - - set_property(TARGET sos APPEND_STRING PROPERTY LINK_FLAGS ${EXPORTS_LINKER_OPTION}) - set_property(TARGET sos APPEND_STRING PROPERTY LINK_DEPENDS ${EXPORTS_FILE}) - - add_dependencies(sos mscordaccore) -endif(CLR_CMAKE_PLATFORM_UNIX) - -target_link_libraries(sos ${SOS_LIBRARY}) - -# add the install targets -install_clr(sos) - -if(NOT WIN32) - _install(FILES sosdocsunix.txt DESTINATION .) -endif(NOT WIN32) diff --git a/src/ToolBox/SOS/Strike/EventCallbacks.cpp b/src/ToolBox/SOS/Strike/EventCallbacks.cpp deleted file mode 100644 index 0066dfa..0000000 --- a/src/ToolBox/SOS/Strike/EventCallbacks.cpp +++ /dev/null @@ -1,160 +0,0 @@ -// 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. - -#include "EventCallbacks.h" - -EventCallbacks::EventCallbacks(IDebugClient* pDebugClient) : m_refCount(1), m_pDebugClient(pDebugClient) -{ -} - -EventCallbacks::~EventCallbacks() -{ - if(m_pDebugClient != NULL) - m_pDebugClient->Release(); -} - - // IUnknown implementation -HRESULT __stdcall EventCallbacks::QueryInterface(REFIID riid, VOID** ppInterface) -{ - if(riid == __uuidof(IDebugEventCallbacks)) - { - *ppInterface = static_cast(this); - AddRef(); - return S_OK; - } - else if(riid == __uuidof(IUnknown)) - { - *ppInterface = static_cast(this); - AddRef(); - return S_OK; - } - else - { - return E_NOINTERFACE; - } -} - -ULONG __stdcall EventCallbacks::AddRef() -{ - return InterlockedIncrement((volatile LONG *) &m_refCount); -} - -ULONG __stdcall EventCallbacks::Release() -{ - ULONG count = InterlockedDecrement((volatile LONG *) &m_refCount); - if(count == 0) - { - delete this; - } - return count; -} - -// IDebugEventCallbacks implementation -HRESULT __stdcall EventCallbacks::Breakpoint(PDEBUG_BREAKPOINT bp) -{ - return DEBUG_STATUS_NO_CHANGE; -} - -HRESULT __stdcall EventCallbacks::ChangeDebuggeeState(ULONG Flags, ULONG64 Argument) -{ - return DEBUG_STATUS_NO_CHANGE; -} - -HRESULT __stdcall EventCallbacks::ChangeEngineState(ULONG Flags, ULONG64 Argument) -{ - return DEBUG_STATUS_NO_CHANGE; -} -HRESULT __stdcall EventCallbacks::ChangeSymbolState(ULONG Flags, ULONG64 Argument) -{ - return DEBUG_STATUS_NO_CHANGE; -} -HRESULT __stdcall EventCallbacks::CreateProcess(ULONG64 ImageFileHandle, - ULONG64 Handle, - ULONG64 BaseOffset, - ULONG ModuleSize, - PCSTR ModuleName, - PCSTR ImageName, - ULONG CheckSum, - ULONG TimeDateStamp, - ULONG64 InitialThreadHandle, - ULONG64 ThreadDataOffset, - ULONG64 StartOffset) -{ - return DEBUG_STATUS_NO_CHANGE; -} - -HRESULT __stdcall EventCallbacks::CreateThread(ULONG64 Handle, - ULONG64 DataOffset, - ULONG64 StartOffset) -{ - return DEBUG_STATUS_NO_CHANGE; -} - -HRESULT __stdcall EventCallbacks::Exception(PEXCEPTION_RECORD64 Exception, ULONG FirstChance) -{ - return DEBUG_STATUS_NO_CHANGE; -} - -HRESULT __stdcall EventCallbacks::ExitProcess(ULONG ExitCode) -{ - UninitCorDebugInterface(); - return DEBUG_STATUS_NO_CHANGE; -} - -HRESULT __stdcall EventCallbacks::ExitThread(ULONG ExitCode) -{ - return DEBUG_STATUS_NO_CHANGE; -} - -HRESULT __stdcall EventCallbacks::GetInterestMask(PULONG Mask) -{ - *Mask = DEBUG_EVENT_LOAD_MODULE | DEBUG_EVENT_EXIT_PROCESS; - return S_OK; -} - -extern BOOL g_fAllowJitOptimization; - -HRESULT __stdcall EventCallbacks::LoadModule(ULONG64 ImageFileHandle, - ULONG64 BaseOffset, - ULONG ModuleSize, - PCSTR ModuleName, - PCSTR ImageName, - ULONG CheckSum, - ULONG TimeDateStamp) -{ - HRESULT handleEventStatus = DEBUG_STATUS_NO_CHANGE; - ExtQuery(m_pDebugClient); - - if (ModuleName != NULL && _stricmp(ModuleName, MAIN_CLR_MODULE_NAME_A) == 0) - { - // if we don't want the JIT to optimize, we should also disable optimized NGEN images - if(!g_fAllowJitOptimization) - { - // if we aren't succesful SetNGENCompilerFlags will print relevant error messages - // and then we need to stop the debugger so the user can intervene if desired - if(FAILED(SetNGENCompilerFlags(CORDEBUG_JIT_DISABLE_OPTIMIZATION))) - { - handleEventStatus = DEBUG_STATUS_BREAK; - } - } - } - - ExtRelease(); - return handleEventStatus; -} - -HRESULT __stdcall EventCallbacks::SessionStatus(ULONG Status) -{ - return DEBUG_STATUS_NO_CHANGE; -} - -HRESULT __stdcall EventCallbacks::SystemError(ULONG Error, ULONG Level) -{ - return DEBUG_STATUS_NO_CHANGE; -} - -HRESULT __stdcall EventCallbacks::UnloadModule(PCSTR ImageBaseName, ULONG64 BaseOffset) -{ - return DEBUG_STATUS_NO_CHANGE; -} diff --git a/src/ToolBox/SOS/Strike/EventCallbacks.h b/src/ToolBox/SOS/Strike/EventCallbacks.h deleted file mode 100644 index acd5e41..0000000 --- a/src/ToolBox/SOS/Strike/EventCallbacks.h +++ /dev/null @@ -1,68 +0,0 @@ -// 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. - -#ifndef __EventCallbacks__ -#define __EventCallbacks__ - -#include "exts.h" - -// A set of callbacks that are registered with windbg whenever SOS is loaded -// Right now these callbacks only act on the module load event for CLR, but -// feel free to add other event hooks as needed -// -// TODO: we should probably be using these callbacks to hook clrnotify exceptions -// rather than attaching a user handler on the clrn event. That handler is both -// visible to the user and could be accidentally erased by them. -class EventCallbacks : IDebugEventCallbacks -{ -public: - EventCallbacks(IDebugClient* pDebugClient); - ~EventCallbacks(); - - // IUnknown implementation - HRESULT __stdcall QueryInterface(REFIID riid, VOID** ppInterface); - ULONG __stdcall AddRef(); - ULONG __stdcall Release(); - - // IDebugEventCallbacks implementation - HRESULT __stdcall Breakpoint(PDEBUG_BREAKPOINT bp); - HRESULT __stdcall ChangeDebuggeeState(ULONG Flags, ULONG64 Argument); - HRESULT __stdcall ChangeEngineState(ULONG Flags, ULONG64 Argument); - HRESULT __stdcall ChangeSymbolState(ULONG Flags, ULONG64 Argument); - HRESULT __stdcall CreateProcess(ULONG64 ImageFileHandle, - ULONG64 Handle, - ULONG64 BaseOffset, - ULONG ModuleSize, - PCSTR ModuleName, - PCSTR ImageName, - ULONG CheckSum, - ULONG TimeDateStamp, - ULONG64 InitialThreadHandle, - ULONG64 ThreadDataOffset, - ULONG64 StartOffset); - HRESULT __stdcall CreateThread(ULONG64 Handle, - ULONG64 DataOffset, - ULONG64 StartOffset); - HRESULT __stdcall Exception(PEXCEPTION_RECORD64 Exception, ULONG FirstChance); - HRESULT __stdcall ExitProcess(ULONG ExitCode); - HRESULT __stdcall ExitThread(ULONG ExitCode); - HRESULT __stdcall GetInterestMask(PULONG Mask); - HRESULT __stdcall LoadModule(ULONG64 ImageFileHandle, - ULONG64 BaseOffset, - ULONG ModuleSize, - PCSTR ModuleName, - PCSTR ImageName, - ULONG CheckSum, - ULONG TimeDateStamp); - HRESULT __stdcall SessionStatus(ULONG Status); - HRESULT __stdcall SystemError(ULONG Error, ULONG Level); - HRESULT __stdcall UnloadModule(PCSTR ImageBaseName, ULONG64 BaseOffset); - - -private: - volatile ULONG m_refCount; - IDebugClient* m_pDebugClient; -}; - -#endif diff --git a/src/ToolBox/SOS/Strike/ExpressionNode.cpp b/src/ToolBox/SOS/Strike/ExpressionNode.cpp deleted file mode 100644 index b61dcad..0000000 --- a/src/ToolBox/SOS/Strike/ExpressionNode.cpp +++ /dev/null @@ -1,2175 +0,0 @@ -// 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. - -#include "ExpressionNode.h" - - -#ifndef IfFailRet -#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0) -#endif - -// Returns the complete expression being evaluated to get the value for this node -// The returned pointer is a string interior to this object - once you release -// all references to this object the string is invalid. -WCHAR* ExpressionNode::GetAbsoluteExpression() { return pAbsoluteExpression; } - -// Returns the sub expression that logically indicates how the parent expression -// was built upon to reach this node. This relative value has no purpose other -// than an identifier and to convey UI meaning to the user. At present typical values -// are the name of type, a local, a parameter, a field, an array index, or '' -// for a baseclass casting operation -// The returned pointer is a string interior to this object - once you release -// all references to this object the string is invalid. -WCHAR* ExpressionNode::GetRelativeExpression() { return pRelativeExpression; } - -// Returns a text representation of the type of value that this node refers to -// It is possible this node doesn't evaluate to anything and therefore has no -// type -// The returned pointer is a string interior to this object - once you release -// all references to this object the string is invalid. -WCHAR* ExpressionNode::GetTypeName() { PopulateType(); return pTypeName; } - -// Returns a text representation of the value for this node. It is possible that -// this node doesn't evaluate to anything and therefore has no value text. -// The returned pointer is a string interior to this object - once you release -// all references to this object the string is invalid. -WCHAR* ExpressionNode::GetTextValue() { PopulateTextValue(); return pTextValue; } - -// If there is any error during the evaluation of this node's expression, it is -// returned here. -// The returned pointer is a string interior to this object - once you release -// all references to this object the string is invalid. -WCHAR* ExpressionNode::GetErrorMessage() { return pErrorMessage; } - -// Factory function for creating the expression node at the root of a tree -HRESULT ExpressionNode::CreateExpressionNode(__in_z WCHAR* pExpression, ExpressionNode** ppExpressionNode) -{ - *ppExpressionNode = NULL; - HRESULT Status = CreateExpressionNodeHelper(pExpression, - pExpression, - 0, - NULL, - NULL, - NULL, - 0, - NULL, - ppExpressionNode); - if(FAILED(Status) && *ppExpressionNode == NULL) - { - - WCHAR pErrorMessage[MAX_ERROR]; - _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Error 0x%x while parsing expression", Status); - *ppExpressionNode = new ExpressionNode(pExpression, pErrorMessage); - Status = S_OK; - if(*ppExpressionNode == NULL) - Status = E_OUTOFMEMORY; - } - return Status; -} - -// Performs recursive expansion within the tree for nodes that are along the path to varToExpand. -// Expansion involves calculating a set of child expressions from the current expression via -// field dereferencing, array index dereferencing, or casting to a base type. -// For example if a tree was rooted with expression 'foo.bar' and varToExpand is '(Baz)foo.bar[9]' -// then 'foo.bar', 'foo.bar[9]', and '(Baz)foo.bar[9]' nodes would all be expanded. -HRESULT ExpressionNode::Expand(__in_z WCHAR* varToExpand) -{ - if(!ShouldExpandVariable(varToExpand)) - return S_FALSE; - if(pValue == NULL && pTypeCast == NULL) - return S_OK; - - // if the node evaluates to a type, then the children are static fields of the type - if(pValue == NULL) - return ExpandFields(NULL, varToExpand); - - // If the value is a null reference there is nothing to expand - HRESULT Status = S_OK; - BOOL isNull = TRUE; - ToRelease pInnerValue; - IfFailRet(DereferenceAndUnboxValue(pValue, &pInnerValue, &isNull)); - if(isNull) - { - return S_OK; - } - - - CorElementType corElemType; - IfFailRet(pValue->GetType(&corElemType)); - if (corElemType == ELEMENT_TYPE_SZARRAY) - { - //If its an array, add children representing the indexed elements - return ExpandSzArray(pInnerValue, varToExpand); - } - else if(corElemType == ELEMENT_TYPE_CLASS || corElemType == ELEMENT_TYPE_VALUETYPE) - { - // If its a class or struct (not counting string, array, or object) then add children representing - // the fields. - return ExpandFields(pInnerValue, varToExpand); - } - else - { - // nothing else expands - return S_OK; - } -} - -// Standard depth first search tree traversal pattern with a callback -VOID ExpressionNode::DFSVisit(ExpressionNodeVisitorCallback pFunc, VOID* pUserData, int depth) -{ - pFunc(this, depth, pUserData); - ExpressionNode* pCurChild = pChild; - while(pCurChild != NULL) - { - pCurChild->DFSVisit(pFunc, pUserData, depth+1); - pCurChild = pCurChild->pNextSibling; - } -} - - -// Creates a new expression with a given debuggee value and frame -ExpressionNode::ExpressionNode(__in_z const WCHAR* pExpression, ICorDebugValue* pValue, ICorDebugILFrame* pFrame) -{ - Init(pValue, NULL, pFrame); - _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression); - _snwprintf_s(pRelativeExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression); -} - -// Creates a new expression that has an error and no value -ExpressionNode::ExpressionNode(__in_z const WCHAR* pExpression, __in_z const WCHAR* pErrorMessage) -{ - Init(NULL, NULL, NULL); - _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression); - _snwprintf_s(pRelativeExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression); - _snwprintf_s(this->pErrorMessage, MAX_ERROR, _TRUNCATE, L"%s", pErrorMessage); -} - -// Creates a new child expression -ExpressionNode::ExpressionNode(__in_z const WCHAR* pParentExpression, ChildKind ck, __in_z const WCHAR* pRelativeExpression, ICorDebugValue* pValue, ICorDebugType* pType, ICorDebugILFrame* pFrame, UVCP_CONSTANT pDefaultValue, ULONG cchDefaultValue) -{ - Init(pValue, pType, pFrame); - if(ck == ChildKind_BaseClass) - { - _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pParentExpression); - } - else - { - _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, ck == ChildKind_Field ? L"%s.%s" : L"%s[%s]", pParentExpression, pRelativeExpression); - } - _snwprintf_s(this->pRelativeExpression, MAX_EXPRESSION, _TRUNCATE, ck == ChildKind_Index ? L"[%s]" : L"%s", pRelativeExpression); - this->pDefaultValue = pDefaultValue; - this->cchDefaultValue = cchDefaultValue; -} - -// Common member initialization for the constructors -VOID ExpressionNode::Init(ICorDebugValue* pValue, ICorDebugType* pTypeCast, ICorDebugILFrame* pFrame) -{ - this->pValue = pValue; - this->pTypeCast = pTypeCast; - this->pILFrame = pFrame; - pChild = NULL; - pNextSibling = NULL; - pTextValue[0] = 0; - pErrorMessage[0] = 0; - pAbsoluteExpression[0] = 0; - pRelativeExpression[0] = 0; - pTypeName[0] = 0; - - pDefaultValue = NULL; - cchDefaultValue = 0; - - // The ToRelease holders don't automatically AddRef - if(pILFrame != NULL) - pILFrame->AddRef(); - if(pTypeCast != NULL) - pTypeCast->AddRef(); - if(pValue != NULL) - { - pValue->AddRef(); - PopulateMetaDataImport(); - } -} - -// Retreves the correct IMetaDataImport for the type represented in this node and stores it -// in pMD. -HRESULT ExpressionNode::PopulateMetaDataImport() -{ - if(pMD != NULL) - return S_OK; - - HRESULT Status = S_OK; - ToRelease pType; - if(pTypeCast != NULL) - { - pType = pTypeCast; - pType->AddRef(); - } - else - { - BOOL isNull; - ToRelease pInnerValue; - IfFailRet(DereferenceAndUnboxValue(pValue, &pInnerValue, &isNull)); - ToRelease pValue2; - IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); - - IfFailRet(pValue2->GetExactType(&pType)); - - // for array, pointer, and byref types we can't directly get a class, we must unwrap first - CorElementType et; - IfFailRet(pType->GetType(&et)); - while(et == ELEMENT_TYPE_ARRAY || et == ELEMENT_TYPE_SZARRAY || et == ELEMENT_TYPE_BYREF || et == ELEMENT_TYPE_PTR) - { - pType->GetFirstTypeParameter(&pType); - IfFailRet(pType->GetType(&et)); - } - } - ToRelease pClass; - IfFailRet(pType->GetClass(&pClass)); - ToRelease pModule; - IfFailRet(pClass->GetModule(&pModule)); - ToRelease pMDUnknown; - IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); - IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); - return Status; -} - -// Determines the string representation of pType and stores it in typeName -HRESULT ExpressionNode::CalculateTypeName(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen) -{ - HRESULT Status = S_OK; - - CorElementType corElemType; - IfFailRet(pType->GetType(&corElemType)); - - switch (corElemType) - { - //List of unsupported CorElementTypes: - //ELEMENT_TYPE_END = 0x0, - //ELEMENT_TYPE_VAR = 0x13, // a class type variable VAR - //ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST ... - //ELEMENT_TYPE_TYPEDBYREF = 0x16, // TYPEDREF (it takes no args) a typed referece to some other type - //ELEMENT_TYPE_MVAR = 0x1e, // a method type variable MVAR - //ELEMENT_TYPE_CMOD_REQD = 0x1F, // required C modifier : E_T_CMOD_REQD - //ELEMENT_TYPE_CMOD_OPT = 0x20, // optional C modifier : E_T_CMOD_OPT - //ELEMENT_TYPE_INTERNAL = 0x21, // INTERNAL - //ELEMENT_TYPE_MAX = 0x22, // first invalid element type - //ELEMENT_TYPE_MODIFIER = 0x40, - //ELEMENT_TYPE_SENTINEL = 0x01 | ELEMENT_TYPE_MODIFIER, // sentinel for varargs - //ELEMENT_TYPE_PINNED = 0x05 | ELEMENT_TYPE_MODIFIER, - default: - swprintf_s(typeName, typeNameLen, L"(Unhandled CorElementType: 0x%x)\0", corElemType); - break; - - case ELEMENT_TYPE_VALUETYPE: - case ELEMENT_TYPE_CLASS: - { - //Defaults in case we fail... - if(corElemType == ELEMENT_TYPE_VALUETYPE) swprintf_s(typeName, typeNameLen, L"struct\0"); - else swprintf_s(typeName, typeNameLen, L"class\0"); - - mdTypeDef typeDef; - ToRelease pClass; - if(SUCCEEDED(pType->GetClass(&pClass)) && SUCCEEDED(pClass->GetToken(&typeDef))) - { - ToRelease pModule; - IfFailRet(pClass->GetModule(&pModule)); - - ToRelease pMDUnknown; - ToRelease pMD; - IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); - IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); - - if(SUCCEEDED(NameForToken_s(TokenFromRid(typeDef, mdtTypeDef), pMD, g_mdName, mdNameLen, false))) - swprintf_s(typeName, typeNameLen, L"%s\0", g_mdName); - } - AddGenericArgs(pType, typeName, typeNameLen); - } - break; - case ELEMENT_TYPE_VOID: - swprintf_s(typeName, typeNameLen, L"void\0"); - break; - case ELEMENT_TYPE_BOOLEAN: - swprintf_s(typeName, typeNameLen, L"bool\0"); - break; - case ELEMENT_TYPE_CHAR: - swprintf_s(typeName, typeNameLen, L"char\0"); - break; - case ELEMENT_TYPE_I1: - swprintf_s(typeName, typeNameLen, L"signed byte\0"); - break; - case ELEMENT_TYPE_U1: - swprintf_s(typeName, typeNameLen, L"byte\0"); - break; - case ELEMENT_TYPE_I2: - swprintf_s(typeName, typeNameLen, L"short\0"); - break; - case ELEMENT_TYPE_U2: - swprintf_s(typeName, typeNameLen, L"unsigned short\0"); - break; - case ELEMENT_TYPE_I4: - swprintf_s(typeName, typeNameLen, L"int\0"); - break; - case ELEMENT_TYPE_U4: - swprintf_s(typeName, typeNameLen, L"unsigned int\0"); - break; - case ELEMENT_TYPE_I8: - swprintf_s(typeName, typeNameLen, L"long\0"); - break; - case ELEMENT_TYPE_U8: - swprintf_s(typeName, typeNameLen, L"unsigned long\0"); - break; - case ELEMENT_TYPE_R4: - swprintf_s(typeName, typeNameLen, L"float\0"); - break; - case ELEMENT_TYPE_R8: - swprintf_s(typeName, typeNameLen, L"double\0"); - break; - case ELEMENT_TYPE_OBJECT: - swprintf_s(typeName, typeNameLen, L"object\0"); - break; - case ELEMENT_TYPE_STRING: - swprintf_s(typeName, typeNameLen, L"string\0"); - break; - case ELEMENT_TYPE_I: - swprintf_s(typeName, typeNameLen, L"IntPtr\0"); - break; - case ELEMENT_TYPE_U: - swprintf_s(typeName, typeNameLen, L"UIntPtr\0"); - break; - case ELEMENT_TYPE_SZARRAY: - case ELEMENT_TYPE_ARRAY: - case ELEMENT_TYPE_BYREF: - case ELEMENT_TYPE_PTR: - { - // get a name for the type we are building from - ToRelease pFirstParameter; - if(SUCCEEDED(pType->GetFirstTypeParameter(&pFirstParameter))) - CalculateTypeName(pFirstParameter, typeName, typeNameLen); - else - swprintf_s(typeName, typeNameLen, L"\0"); - - // append the appropriate [], *, & - switch(corElemType) - { - case ELEMENT_TYPE_SZARRAY: - wcsncat_s(typeName, typeNameLen, L"[]", typeNameLen); - return S_OK; - case ELEMENT_TYPE_ARRAY: - { - ULONG32 rank = 0; - pType->GetRank(&rank); - wcsncat_s(typeName, typeNameLen, L"[", typeNameLen); - for(ULONG32 i = 0; i < rank - 1; i++) - { - // todo- could we print out exact boundaries? - wcsncat_s(typeName, typeNameLen, L",", typeNameLen); - } - wcsncat_s(typeName, typeNameLen, L"]", typeNameLen); - } - return S_OK; - case ELEMENT_TYPE_BYREF: - wcsncat_s(typeName, typeNameLen, L"&", typeNameLen); - return S_OK; - case ELEMENT_TYPE_PTR: - wcsncat_s(typeName, typeNameLen, L"*", typeNameLen); - return S_OK; - } - } - break; - case ELEMENT_TYPE_FNPTR: - swprintf_s(typeName, typeNameLen, L"*(...)"); - break; - case ELEMENT_TYPE_TYPEDBYREF: - swprintf_s(typeName, typeNameLen, L"typedbyref"); - break; - } - return S_OK; -} - - -// Appends angle brackets and the generic argument list to a type name -HRESULT ExpressionNode::AddGenericArgs(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen) -{ - bool isFirst = true; - ToRelease pTypeEnum; - if(SUCCEEDED(pType->EnumerateTypeParameters(&pTypeEnum))) - { - ULONG numTypes = 0; - ToRelease pCurrentTypeParam; - - while(SUCCEEDED(pTypeEnum->Next(1, &pCurrentTypeParam, &numTypes))) - { - if(numTypes == 0) break; - - if(isFirst) - { - isFirst = false; - wcsncat_s(typeName, typeNameLen, L"<", typeNameLen); - } - else wcsncat_s(typeName, typeNameLen, L",", typeNameLen); - - WCHAR typeParamName[mdNameLen]; - typeParamName[0] = L'\0'; - CalculateTypeName(pCurrentTypeParam, typeParamName, mdNameLen); - wcsncat_s(typeName, typeNameLen, typeParamName, typeNameLen); - } - if(!isFirst) - wcsncat_s(typeName, typeNameLen, L">", typeNameLen); - } - - return S_OK; -} - -// Determines the text name for the type of this node and caches it -HRESULT ExpressionNode::PopulateType() -{ - HRESULT Status = S_OK; - if(pTypeName[0] != 0) - return S_OK; - - //default value - swprintf_s(pTypeName, MAX_EXPRESSION, L""); - - // if we are displaying this type as a specific sub-type, use that - if(pTypeCast != NULL) - return CalculateTypeName(pTypeCast, pTypeName, MAX_EXPRESSION); - - // if there is no value then either we succesfully already determined the type - // name, or this node has no value or type and thus no type name. - if(pValue == NULL) - return S_OK; - - // get the type from the value and then calculate a name based on that - ToRelease pType; - ToRelease pValue2; - if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugValue2, (void**) &pValue2)) && SUCCEEDED(pValue2->GetExactType(&pType))) - return CalculateTypeName(pType, pTypeName, MAX_EXPRESSION); - - return S_OK; -} - -// Node expansion helpers - -// Inserts a new child at the end of the linked list of children -// PERF: This has O(N) insert time but these lists should never be large -VOID ExpressionNode::AddChild(ExpressionNode* pNewChild) -{ - if(pChild == NULL) - pChild = pNewChild; - else - { - ExpressionNode* pCursor = pChild; - while(pCursor->pNextSibling != NULL) - pCursor = pCursor->pNextSibling; - pCursor->pNextSibling = pNewChild; - } -} - -// Helper that determines if the current node is on the path of nodes represented by -// expression varToExpand -BOOL ExpressionNode::ShouldExpandVariable(__in_z WCHAR* varToExpand) -{ - if(pAbsoluteExpression == NULL || varToExpand == NULL) return FALSE; - - // if there is a cast operation, move past it - WCHAR* pEndCast = _wcschr(varToExpand, L')'); - varToExpand = (pEndCast == NULL) ? varToExpand : pEndCast+1; - - size_t varToExpandLen = _wcslen(varToExpand); - size_t currentExpansionLen = _wcslen(pAbsoluteExpression); - if(currentExpansionLen > varToExpandLen) return FALSE; - if(currentExpansionLen < varToExpandLen && - varToExpand[currentExpansionLen] != L'.' && - varToExpand[currentExpansionLen] != L'[') - return FALSE; - if(_wcsncmp(pAbsoluteExpression, varToExpand, currentExpansionLen) != 0) return FALSE; - - return TRUE; -} - -// Expands this array node by creating child nodes with expressions refering to individual array elements -HRESULT ExpressionNode::ExpandSzArray(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand) -{ - HRESULT Status = S_OK; - ToRelease pArrayValue; - IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue)); - - ULONG32 nRank; - IfFailRet(pArrayValue->GetRank(&nRank)); - if (nRank != 1) - { - _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Multi-dimensional arrays NYI"); - return E_UNEXPECTED; - } - - ULONG32 cElements; - IfFailRet(pArrayValue->GetCount(&cElements)); - - //TODO: do we really want all the elements? This could be huge! - for (ULONG32 i=0; i < cElements; i++) - { - WCHAR index[20]; - swprintf_s(index, 20, L"%d", i); - - ToRelease pElementValue; - IfFailRet(pArrayValue->GetElementAtPosition(i, &pElementValue)); - ExpressionNode* pExpr = new ExpressionNode(pAbsoluteExpression, ChildKind_Index, index, pElementValue, NULL, pILFrame); - AddChild(pExpr); - pExpr->Expand(varToExpand); - } - return S_OK; -} - -// Expands this struct/class node by creating child nodes with expressions refering to individual field values -// and one node for the basetype value -HRESULT ExpressionNode::ExpandFields(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand) -{ - HRESULT Status = S_OK; - - mdTypeDef currentTypeDef; - ToRelease pClass; - ToRelease pType; - ToRelease pModule; - if(pTypeCast == NULL) - { - ToRelease pValue2; - IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); - IfFailRet(pValue2->GetExactType(&pType)); - } - else - { - pType = pTypeCast; - pType->AddRef(); - } - IfFailRet(pType->GetClass(&pClass)); - IfFailRet(pClass->GetModule(&pModule)); - IfFailRet(pClass->GetToken(¤tTypeDef)); - - ToRelease pMDUnknown; - ToRelease pMD; - IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); - IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); - - // If the current type has a base type that isn't object, enum, or ValueType then add a node for the base type - WCHAR baseTypeName[mdNameLen] = L"\0"; - ToRelease pBaseType; - ExpressionNode* pBaseTypeNode = NULL; - if(SUCCEEDED(pType->GetBase(&pBaseType)) && pBaseType != NULL && SUCCEEDED(CalculateTypeName(pBaseType, baseTypeName, mdNameLen))) - { - if(_wcsncmp(baseTypeName, L"System.Enum", 11) == 0) - return S_OK; - else if(_wcsncmp(baseTypeName, L"System.Object", 13) != 0 && _wcsncmp(baseTypeName, L"System.ValueType", 16) != 0) - { - pBaseTypeNode = new ExpressionNode(pAbsoluteExpression, ChildKind_BaseClass, L"", pInnerValue, pBaseType, pILFrame); - AddChild(pBaseTypeNode); - } - } - - // add nodes for all the fields in this object - ULONG numFields = 0; - HCORENUM fEnum = NULL; - mdFieldDef fieldDef; - BOOL fieldExpanded = FALSE; - while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) - { - mdTypeDef classDef = 0; - ULONG nameLen = 0; - DWORD fieldAttr = 0; - WCHAR mdName[mdNameLen]; - CorElementType fieldDefaultValueEt; - UVCP_CONSTANT pDefaultValue; - ULONG cchDefaultValue; - if(SUCCEEDED(pMD->GetFieldProps(fieldDef, &classDef, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, (DWORD*)&fieldDefaultValueEt, &pDefaultValue, &cchDefaultValue))) - { - ToRelease pFieldType; - ToRelease pFieldVal; - - // static fields (of any kind - AppDomain, thread, context, RVA) - if (fieldAttr & fdStatic) - { - pType->GetStaticFieldValue(fieldDef, pILFrame, &pFieldVal); - } - // non-static fields on an object instance - else if(pInnerValue != NULL) - { - ToRelease pObjValue; - if (SUCCEEDED(pInnerValue->QueryInterface(IID_ICorDebugObjectValue, (LPVOID*) &pObjValue))) - pObjValue->GetFieldValue(pClass, fieldDef, &pFieldVal); - } - // skip over non-static fields on static types - else - { - continue; - } - - // we didn't get a value yet and there is default value available - // need to calculate the type because there won't be a ICorDebugValue to derive it from - if(pFieldVal == NULL && pDefaultValue != NULL) - { - FindTypeFromElementType(fieldDefaultValueEt, &pFieldType); - } - - ExpressionNode* pNewChildNode = new ExpressionNode(pAbsoluteExpression, ChildKind_Field, mdName, pFieldVal, pFieldType, pILFrame, pDefaultValue, cchDefaultValue); - AddChild(pNewChildNode); - if(pNewChildNode->Expand(varToExpand) != S_FALSE) - fieldExpanded = TRUE; - } - } - pMD->CloseEnum(fEnum); - - // Only recurse to expand the base type if all of these hold: - // 1) base type exists - // 2) no field was expanded - // 3) the non-casting portion of the varToExpand doesn't match the current expression - // OR the cast exists and doesn't match - - if(pBaseTypeNode == NULL) return Status; - if(fieldExpanded) return Status; - - WCHAR* pEndCast = _wcschr(varToExpand, L')'); - WCHAR* pNonCast = (pEndCast == NULL) ? varToExpand : pEndCast+1; - if(_wcscmp(pNonCast, pAbsoluteExpression) != 0) - { - pBaseTypeNode->Expand(varToExpand); - return Status; - } - - if(varToExpand[0] == L'(' && pEndCast != NULL) - { - int cchCastTypeName = ((int)(pEndCast-1)-(int)varToExpand)/2; - PopulateType(); - if(_wcslen(pTypeName) != (cchCastTypeName) || - _wcsncmp(varToExpand+1, pTypeName, cchCastTypeName) != 0) - { - pBaseTypeNode->Expand(varToExpand); - return Status; - } - } - - return Status; -} - - -// Value Population functions - -//Helper for unwrapping values -HRESULT ExpressionNode::DereferenceAndUnboxValue(ICorDebugValue * pInputValue, ICorDebugValue** ppOutputValue, BOOL * pIsNull) -{ - HRESULT Status = S_OK; - *ppOutputValue = NULL; - if(pIsNull != NULL) *pIsNull = FALSE; - - ToRelease pReferenceValue; - Status = pInputValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue); - if (SUCCEEDED(Status)) - { - BOOL isNull = FALSE; - IfFailRet(pReferenceValue->IsNull(&isNull)); - if(!isNull) - { - ToRelease pDereferencedValue; - IfFailRet(pReferenceValue->Dereference(&pDereferencedValue)); - return DereferenceAndUnboxValue(pDereferencedValue, ppOutputValue); - } - else - { - if(pIsNull != NULL) *pIsNull = TRUE; - *ppOutputValue = pInputValue; - (*ppOutputValue)->AddRef(); - return S_OK; - } - } - - ToRelease pBoxedValue; - Status = pInputValue->QueryInterface(IID_ICorDebugBoxValue, (LPVOID*) &pBoxedValue); - if (SUCCEEDED(Status)) - { - ToRelease pUnboxedValue; - IfFailRet(pBoxedValue->GetObject(&pUnboxedValue)); - return DereferenceAndUnboxValue(pUnboxedValue, ppOutputValue); - } - *ppOutputValue = pInputValue; - (*ppOutputValue)->AddRef(); - return S_OK; -} - -// Returns TRUE if the value derives from System.Enum -BOOL ExpressionNode::IsEnum(ICorDebugValue * pInputValue) -{ - ToRelease pValue; - if(FAILED(DereferenceAndUnboxValue(pInputValue, &pValue, NULL))) return FALSE; - - WCHAR baseTypeName[mdNameLen]; - ToRelease pValue2; - ToRelease pType; - ToRelease pBaseType; - - if(FAILED(pValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2))) return FALSE; - if(FAILED(pValue2->GetExactType(&pType))) return FALSE; - if(FAILED(pType->GetBase(&pBaseType)) || pBaseType == NULL) return FALSE; - if(FAILED(CalculateTypeName(pBaseType, baseTypeName, mdNameLen))) return FALSE; - - return (_wcsncmp(baseTypeName, L"System.Enum", 11) == 0); -} - -// Calculates the value text for nodes that have enum values -HRESULT ExpressionNode::PopulateEnumValue(ICorDebugValue* pEnumValue, BYTE* enumValue) -{ - HRESULT Status = S_OK; - - mdTypeDef currentTypeDef; - ToRelease pClass; - ToRelease pValue2; - ToRelease pType; - ToRelease pModule; - IfFailRet(pEnumValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); - IfFailRet(pValue2->GetExactType(&pType)); - IfFailRet(pType->GetClass(&pClass)); - IfFailRet(pClass->GetModule(&pModule)); - IfFailRet(pClass->GetToken(¤tTypeDef)); - - ToRelease pMDUnknown; - ToRelease pMD; - IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); - IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); - - - //First, we need to figure out the underlying enum type so that we can correctly type cast the raw values of each enum constant - //We get that from the non-static field of the enum variable (I think the field is called __value or something similar) - ULONG numFields = 0; - HCORENUM fEnum = NULL; - mdFieldDef fieldDef; - CorElementType enumUnderlyingType = ELEMENT_TYPE_END; - while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) - { - DWORD fieldAttr = 0; - PCCOR_SIGNATURE pSignatureBlob = NULL; - ULONG sigBlobLength = 0; - if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, NULL, 0, NULL, &fieldAttr, &pSignatureBlob, &sigBlobLength, NULL, NULL, NULL))) - { - if((fieldAttr & fdStatic) == 0) - { - CorSigUncompressCallingConv(pSignatureBlob); - enumUnderlyingType = CorSigUncompressElementType(pSignatureBlob); - break; - } - } - } - pMD->CloseEnum(fEnum); - - - //Now that we know the underlying enum type, let's decode the enum variable into OR-ed, human readable enum contants - fEnum = NULL; - bool isFirst = true; - ULONG64 remainingValue = *((ULONG64*)enumValue); - WCHAR* pTextValueCursor = pTextValue; - DWORD cchTextValueCursor = MAX_EXPRESSION; - while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) - { - ULONG nameLen = 0; - DWORD fieldAttr = 0; - WCHAR mdName[mdNameLen]; - UVCP_CONSTANT pRawValue = NULL; - ULONG rawValueLength = 0; - if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, NULL, &pRawValue, &rawValueLength))) - { - DWORD enumValueRequiredAttributes = fdPublic | fdStatic | fdLiteral | fdHasDefault; - if((fieldAttr & enumValueRequiredAttributes) != enumValueRequiredAttributes) - continue; - - ULONG64 currentConstValue = 0; - switch (enumUnderlyingType) - { - case ELEMENT_TYPE_CHAR: - case ELEMENT_TYPE_I1: - currentConstValue = (ULONG64)(*((CHAR*)pRawValue)); - break; - case ELEMENT_TYPE_U1: - currentConstValue = (ULONG64)(*((BYTE*)pRawValue)); - break; - case ELEMENT_TYPE_I2: - currentConstValue = (ULONG64)(*((SHORT*)pRawValue)); - break; - case ELEMENT_TYPE_U2: - currentConstValue = (ULONG64)(*((USHORT*)pRawValue)); - break; - case ELEMENT_TYPE_I4: - currentConstValue = (ULONG64)(*((INT32*)pRawValue)); - break; - case ELEMENT_TYPE_U4: - currentConstValue = (ULONG64)(*((UINT32*)pRawValue)); - break; - case ELEMENT_TYPE_I8: - currentConstValue = (ULONG64)(*((LONG*)pRawValue)); - break; - case ELEMENT_TYPE_U8: - currentConstValue = (ULONG64)(*((ULONG*)pRawValue)); - break; - case ELEMENT_TYPE_I: - currentConstValue = (ULONG64)(*((int*)pRawValue)); - break; - case ELEMENT_TYPE_U: - case ELEMENT_TYPE_R4: - case ELEMENT_TYPE_R8: - // Technically U and the floating-point ones are options in the CLI, but not in the CLS or C#, so these are NYI - default: - currentConstValue = 0; - } - - if((currentConstValue == remainingValue) || ((currentConstValue != 0) && ((currentConstValue & remainingValue) == currentConstValue))) - { - remainingValue &= ~currentConstValue; - DWORD charsCopied = 0; - if(isFirst) - { - charsCopied = _snwprintf_s(pTextValueCursor, cchTextValueCursor, _TRUNCATE, L"= %s", mdName); - isFirst = false; - } - else - charsCopied = _snwprintf_s(pTextValueCursor, cchTextValueCursor, _TRUNCATE, L" | %s", mdName); - - // if an error or truncation occurred, stop copying - if(charsCopied == -1) - { - cchTextValueCursor = 0; - pTextValueCursor = NULL; - } - else - { - // charsCopied is the number of characters copied, not counting the terminating null - // this advances the cursor to point right at the terminating null so that future copies - // will concatenate the string - pTextValueCursor += charsCopied; - cchTextValueCursor -= charsCopied; - } - } - } - } - pMD->CloseEnum(fEnum); - - return Status; -} - -// Helper that caches the textual value for nodes that evaluate to a string object -HRESULT ExpressionNode::GetDebuggeeStringValue(ICorDebugValue* pInputValue, __inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer) -{ - HRESULT Status; - - ToRelease pStringValue; - IfFailRet(pInputValue->QueryInterface(IID_ICorDebugStringValue, (LPVOID*) &pStringValue)); - - ULONG32 cchValueReturned; - IfFailRet(pStringValue->GetString(cchBuffer, &cchValueReturned, wszBuffer)); - - return S_OK; -} - -// Retrieves the string value for a constant -HRESULT ExpressionNode::GetConstantStringValue(__inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer) -{ - // The string encoded in metadata isn't null-terminated - // so we need to copy it to a null terminated buffer - DWORD copyLen = cchDefaultValue; - if(copyLen > cchBuffer-1) - copyLen = cchDefaultValue; - - wcsncpy_s(wszBuffer, cchBuffer, (WCHAR*)pDefaultValue, copyLen); - return S_OK; -} - -// Helper that caches the textual value for nodes that evaluate to array objects -HRESULT ExpressionNode::PopulateSzArrayValue(ICorDebugValue* pInputValue) -{ - HRESULT Status = S_OK; - - ToRelease pArrayValue; - IfFailRet(pInputValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue)); - - ULONG32 nRank; - IfFailRet(pArrayValue->GetRank(&nRank)); - if (nRank != 1) - { - _snwprintf_s(pErrorMessage, MAX_EXPRESSION, _TRUNCATE, L"Multi-dimensional arrays NYI"); - return E_UNEXPECTED; - } - - ULONG32 cElements; - IfFailRet(pArrayValue->GetCount(&cElements)); - - if (cElements == 0) - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"(empty)"); - else if (cElements == 1) - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"(1 element)"); - else - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"(%d elements)", cElements); - - return S_OK; -} - -// Helper that caches the textual value for nodes of any type -HRESULT ExpressionNode::PopulateTextValueHelper() -{ - HRESULT Status = S_OK; - - BOOL isNull = TRUE; - ToRelease pInnerValue; - CorElementType corElemType; - ULONG32 cbSize = 0; - if(pValue != NULL) - { - IfFailRet(DereferenceAndUnboxValue(pValue, &pInnerValue, &isNull)); - - if(isNull) - { - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= null"); - return S_OK; - } - IfFailRet(pInnerValue->GetSize(&cbSize)); - IfFailRet(pInnerValue->GetType(&corElemType)); - } - else if(pDefaultValue != NULL) - { - if(pTypeCast == NULL) - { - // this shouldn't happen, but just print nothing if it does - return S_OK; - } - // This works around an irritating issue in ICorDebug. For default values - // we have to construct the ICorDebugType ourselves, however ICorDebug - // doesn't allow type construction using the correct element types. The - // caller must past CLASS or VALUETYPE even when a more specific short - // form element type is applicable. That means that later, here, we get - // back the wrong answer. To work around this we format the type as a - // string, and check it against all the known types. That allows us determine - // everything except VALUETYPE/CLASS. Thankfully that distinction is the - // one piece of data ICorDebugType will tell us if needed. - if(FAILED(GetCanonicalElementTypeForTypeName(GetTypeName(), &corElemType))) - { - pTypeCast->GetType(&corElemType); - } - - switch(corElemType) - { - case ELEMENT_TYPE_BOOLEAN: - case ELEMENT_TYPE_I1: - case ELEMENT_TYPE_U1: - cbSize = 1; - break; - - case ELEMENT_TYPE_CHAR: - case ELEMENT_TYPE_I2: - case ELEMENT_TYPE_U2: - cbSize = 2; - break; - - case ELEMENT_TYPE_I: - case ELEMENT_TYPE_U: - case ELEMENT_TYPE_I4: - case ELEMENT_TYPE_U4: - case ELEMENT_TYPE_R4: - cbSize = 4; - break; - - case ELEMENT_TYPE_I8: - case ELEMENT_TYPE_U8: - case ELEMENT_TYPE_R8: - cbSize = 8; - break; - } - } - - if (corElemType == ELEMENT_TYPE_STRING) - { - WCHAR buffer[MAX_EXPRESSION]; - buffer[0] = L'\0'; - if(pInnerValue != NULL) - GetDebuggeeStringValue(pInnerValue, buffer, MAX_EXPRESSION); - else - GetConstantStringValue(buffer, MAX_EXPRESSION); - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= \"%s\"", buffer); - } - else if (corElemType == ELEMENT_TYPE_SZARRAY) - { - return PopulateSzArrayValue(pInnerValue); - } - - - ArrayHolder rgbValue = new BYTE[cbSize]; - memset(rgbValue.GetPtr(), 0, cbSize * sizeof(BYTE)); - if(pInnerValue != NULL) - { - ToRelease pGenericValue; - IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugGenericValue, (LPVOID*) &pGenericValue)); - IfFailRet(pGenericValue->GetValue((LPVOID) &(rgbValue[0]))); - } - else - { - memcpy((LPVOID) &(rgbValue[0]), pDefaultValue, cbSize); - } - - //TODO: this should really be calculated from the type - if(pInnerValue != NULL && IsEnum(pInnerValue)) - { - Status = PopulateEnumValue(pInnerValue, rgbValue); - return Status; - } - - switch (corElemType) - { - default: - _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Unhandled CorElementType: 0x%x", corElemType); - Status = E_FAIL; - break; - - case ELEMENT_TYPE_PTR: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L""); - break; - - case ELEMENT_TYPE_FNPTR: - { - CORDB_ADDRESS addr = 0; - ToRelease pReferenceValue = NULL; - if(SUCCEEDED(pInnerValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue))) - pReferenceValue->GetValue(&addr); - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"", addr); - } - break; - - case ELEMENT_TYPE_VALUETYPE: - case ELEMENT_TYPE_CLASS: - case ELEMENT_TYPE_OBJECT: - ULONG64 pointer; - if(pInnerValue != NULL && SUCCEEDED(pInnerValue->GetAddress(&pointer))) - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"@ 0x%p", (void *) pointer); - break; - - case ELEMENT_TYPE_BOOLEAN: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %s", rgbValue[0] == 0 ? L"false" : L"true"); - break; - - case ELEMENT_TYPE_CHAR: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= '%C'", *(WCHAR *) &(rgbValue[0])); - break; - - case ELEMENT_TYPE_I1: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(char*) &(rgbValue[0])); - break; - - case ELEMENT_TYPE_U1: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(unsigned char*) &(rgbValue[0])); - break; - - case ELEMENT_TYPE_I2: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %hd", *(short*) &(rgbValue[0])); - break; - - case ELEMENT_TYPE_U2: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %hu", *(unsigned short*) &(rgbValue[0])); - break; - - case ELEMENT_TYPE_I: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(int*) &(rgbValue[0])); - break; - - case ELEMENT_TYPE_U: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %u", *(unsigned int*) &(rgbValue[0])); - break; - - case ELEMENT_TYPE_I4: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(int*) &(rgbValue[0])); - break; - - case ELEMENT_TYPE_U4: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %u", *(unsigned int*) &(rgbValue[0])); - break; - - case ELEMENT_TYPE_I8: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %I64d", *(__int64*) &(rgbValue[0])); - break; - - case ELEMENT_TYPE_U8: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %I64u", *(unsigned __int64*) &(rgbValue[0])); - break; - - case ELEMENT_TYPE_R4: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %f", (double) *(float*) &(rgbValue[0])); - break; - - case ELEMENT_TYPE_R8: - _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"%f", *(double*) &(rgbValue[0])); - break; - - // TODO: The following corElementTypes are not yet implemented here. Array - // might be interesting to add, though the others may be of rather limited use: - // ELEMENT_TYPE_ARRAY = 0x14, // MDARRAY ... ... - // - // ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST ... - } - - return Status; -} - -// Caches the textual value of this node -HRESULT ExpressionNode::PopulateTextValue() -{ - if(pErrorMessage[0] != 0) - return E_UNEXPECTED; - if(pValue == NULL && pDefaultValue == NULL) - return S_OK; - HRESULT Status = PopulateTextValueHelper(); - if(FAILED(Status) && pErrorMessage[0] == 0) - { - _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Error in PopulateTextValueHelper: 0x%x", Status); - } - return Status; -} - - -// Expression parsing and search - -//Callback that searches a frame to determine if it contains a local variable or parameter of a given name -VOID ExpressionNode::EvaluateExpressionFrameScanCallback(ICorDebugFrame* pFrame, VOID* pUserData) -{ - EvaluateExpressionFrameScanData* pData = (EvaluateExpressionFrameScanData*)pUserData; - - // we already found what we were looking for, just continue - if(pData->pFoundValue != NULL) - return; - - // if any of these fail we just continue on - // querying for ILFrame will frequently fail because many frames aren't IL - ToRelease pILFrame; - HRESULT Status = pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame); - if (FAILED(Status)) - { - return; - } - // we need to save off the first frame we find regardless of whether we find the - // local or not. We might need this frame later for static field lookup. - if(pData->pFirstFrame == NULL) - { - pData->pFirstFrame = pILFrame; - pData->pFirstFrame->AddRef(); - } - // not all IL frames map to an assembly (ex. LCG) - ToRelease pFunction; - Status = pFrame->GetFunction(&pFunction); - if (FAILED(Status)) - { - return; - } - // from here down shouldn't generally fail, but just in case - mdMethodDef methodDef; - Status = pFunction->GetToken(&methodDef); - if (FAILED(Status)) - { - return; - } - ToRelease pModule; - Status = pFunction->GetModule(&pModule); - if (FAILED(Status)) - { - return; - } - ToRelease pMDUnknown; - Status = pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown); - if (FAILED(Status)) - { - return; - } - ToRelease pMD; - Status = pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD); - if (FAILED(Status)) - { - return; - } - - pData->pFoundFrame = pILFrame; - pData->pFoundFrame->AddRef(); - // Enumerate all the parameters - EnumerateParameters(pMD, methodDef, pILFrame, EvaluateExpressionVariableScanCallback, pUserData); - // Enumerate all the locals - EnumerateLocals(pMD, methodDef, pILFrame, EvaluateExpressionVariableScanCallback, pUserData); - - // if we didn't find it in this frame then clear the frame back out - if(pData->pFoundValue == NULL) - { - pData->pFoundFrame = NULL; - } - - return; -} - -//Callback checks to see if a given local/parameter has name pName -VOID ExpressionNode::EvaluateExpressionVariableScanCallback(ICorDebugValue* pValue, __in_z WCHAR* pName, __out_z WCHAR* pErrorMessage, VOID* pUserData) -{ - EvaluateExpressionFrameScanData* pData = (EvaluateExpressionFrameScanData*)pUserData; - if(_wcscmp(pName, pData->pIdentifier) == 0) - { - // found it - pData->pFoundValue = pValue; - pValue->AddRef(); - } - return; -} - -//Factory method that recursively parses pExpression and create an ExpressionNode -// pExpression - the entire expression being parsed -// pExpressionRemainder - the portion of the expression that remains to be parsed in this -// recursive invocation -// charactersParsed - the number of characters that have been parsed from pExpression -// so far (todo: this is basically the difference between remainder and -// full expression, do we need it?) -// pParsedValue - A debuggee value that should be used as the context for interpreting -// pExpressionRemainder -// pParsedType - A debuggee type that should be used as the context for interpreting -// pExpressionRemainder. -// pParsedDefaultValue - A fixed value from metadata that should be used as context for -// interpreting pExpressionRemainder -// cchParsedDefaultValue- Size of pParsedDefaultValue -// pFrame - A debuggee IL frame that disambiguates the thread and context needed -// to evaluate a thread-static or context-static value -// ppExpressionNode - OUT - the resulting expression node -// -// -// Valid combinations of state coming into this method: -// The expression up to charactersParsed isn't recognized yet: -// pParsedValue = pParsedType = pParsedDefaultValue = NULL -// cchParsedDefaultValue = 0 -// The expression up to charactersParsed is a recognized type: -// pParsedType = -// pParsedValue = pParsedDefaultValue = NULL -// cchParsedDefaultValue = 0 -// The expression up to charactersParsed is a recognized value in the debuggee: -// pParsedValue = -// pParsedType = pParsedDefaultValue = NULL -// cchParsedDefaultValue = 0 -// The expression up to charactersParsed is a recognized default value stored in metadata: -// pParsedValue = NULL -// pParsedType = -// pParsedDefaultValue = -// cchParsedDefaultValue = -// -// -// REFACTORING NOTE: This method is very similar (but not identical) to the expansion logic -// in ExpressionNode. The primary difference is that the nodes expand all -// fields/indices whereas this function only expands along a precise route. -// If the ExpressionNode code where enhanced to support expanding precisely -// large portions of this function could be disposed of. As soon as the function -// matched the initial name it could create an ExpressionNode and then use the -// ExpressionNode expansion functions to drill down to the actual node required. -// Also need to make sure the nodes manage lifetime ok when a parent is destroyed -// but a child node is still referenced. -HRESULT ExpressionNode::CreateExpressionNodeHelper(__in_z WCHAR* pExpression, - __in_z WCHAR* pExpressionParseRemainder, - DWORD charactersParsed, - ICorDebugValue* pParsedValue, - ICorDebugType* pParsedType, - UVCP_CONSTANT pParsedDefaultValue, - ULONG cchParsedDefaultValue, - ICorDebugILFrame* pFrame, - ExpressionNode** ppExpressionNode) -{ - HRESULT Status = S_OK; - WCHAR* pExpressionCursor = pExpressionParseRemainder; - DWORD currentCharsParsed = charactersParsed; - WCHAR pIdentifier[mdNameLen]; - pIdentifier[0] = 0; - BOOL isArray = FALSE; - WCHAR pResultBuffer[MAX_EXPRESSION]; - - // Get the next name from the expression string - if(FAILED(Status = ParseNextIdentifier(&pExpressionCursor, pIdentifier, mdNameLen, pResultBuffer, MAX_EXPRESSION, ¤tCharsParsed, &isArray))) - { - *ppExpressionNode = new ExpressionNode(pExpression, pResultBuffer); - if(*ppExpressionNode == NULL) - return E_OUTOFMEMORY; - else - return S_OK; - } - - // we've gone as far as we need, nothing left to parse - if(Status == S_FALSE) - { - ToRelease pValue; - *ppExpressionNode = new ExpressionNode(pExpression, ChildKind_BaseClass, pExpression, pParsedValue, pParsedType, pFrame, pParsedDefaultValue, cchParsedDefaultValue); - if(*ppExpressionNode == NULL) - return E_OUTOFMEMORY; - else - return S_OK; - } - // if we are just starting and have no context then we need to search locals/parameters/type names - else if(pParsedValue == NULL && pParsedType == NULL) - { - // the first identifier must be a name, not an indexing expression - if(isArray) - { - *ppExpressionNode = new ExpressionNode(pExpression, L"Expression must begin with a local variable, parameter, or fully qualified type name"); - return S_OK; - } - - // scan for root on stack - EvaluateExpressionFrameScanData data; - data.pIdentifier = pIdentifier; - data.pFoundValue = NULL; - data.pFoundFrame = NULL; - data.pFirstFrame = NULL; - data.pErrorMessage = pResultBuffer; - data.cchErrorMessage = MAX_EXPRESSION; - EnumerateFrames(EvaluateExpressionFrameScanCallback, (VOID*) &data); - - if(data.pFoundValue != NULL) - { - // found the root, now recurse along the expression - return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, data.pFoundValue, NULL, NULL, 0, data.pFoundFrame, ppExpressionNode); - } - - // didn't find it - search the type table for a matching name - WCHAR pName[MAX_EXPRESSION]; - while(true) - { - wcsncpy_s(pName, MAX_EXPRESSION, pExpression, currentCharsParsed); - ToRelease pType; - if(SUCCEEDED(FindTypeByName(pName, &pType))) - return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, NULL, pType, NULL, 0, data.pFirstFrame, ppExpressionNode); - - if(FAILED(Status = ParseNextIdentifier(&pExpressionCursor, pIdentifier, mdNameLen, pResultBuffer, MAX_EXPRESSION, ¤tCharsParsed, &isArray))) - { - *ppExpressionNode = new ExpressionNode(pExpression, pResultBuffer); - return S_OK; - } - else if(Status == S_FALSE) - { - break; - } - } - - WCHAR errorMessage[MAX_ERROR]; - swprintf_s(errorMessage, MAX_ERROR, L"No expression prefix could not be matched to an existing type, parameter, or local"); - *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); - return S_OK; - } - - // we've got some context from an earlier portion of the search, now just need to continue - // by dereferencing and indexing until we reach the end of the expression - - // Figure out the type, module, and metadata from our context information - ToRelease pType; - BOOL isNull = TRUE; - ToRelease pInnerValue = NULL; - if(pParsedValue != NULL) - { - IfFailRet(DereferenceAndUnboxValue(pParsedValue, &pInnerValue, &isNull)); - - if(isNull) - { - WCHAR parsedExpression[MAX_EXPRESSION]; - wcsncpy_s(parsedExpression, MAX_EXPRESSION, pExpression, charactersParsed); - WCHAR errorMessage[MAX_ERROR]; - swprintf_s(errorMessage, MAX_ERROR, L"Dereferencing \'%s\' throws NullReferenceException", parsedExpression); - *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); - return S_OK; - } - - ToRelease pValue2; - IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); - IfFailRet(pValue2->GetExactType(&pType)); - CorElementType et; - IfFailRet(pType->GetType(&et)); - while(et == ELEMENT_TYPE_ARRAY || et == ELEMENT_TYPE_SZARRAY || et == ELEMENT_TYPE_BYREF || et == ELEMENT_TYPE_PTR) - { - pType->GetFirstTypeParameter(&pType); - IfFailRet(pType->GetType(&et)); - } - } - else - { - pType = pParsedType; - pType->AddRef(); - } - ToRelease pClass; - IfFailRet(pType->GetClass(&pClass)); - ToRelease pModule; - IfFailRet(pClass->GetModule(&pModule)); - mdTypeDef currentTypeDef; - IfFailRet(pClass->GetToken(¤tTypeDef)); - - ToRelease pMDUnknown; - ToRelease pMD; - IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); - IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); - - - // if we are searching along and this is an array index dereference - if(isArray) - { - ToRelease pArrayValue; - if(pInnerValue == NULL || FAILED(Status = pInnerValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue))) - { - WCHAR errorMessage[MAX_ERROR]; - swprintf_s(errorMessage, MAX_ERROR, L"Index notation only supported for instances of an array type"); - *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); - return S_OK; - } - - ULONG32 nRank; - IfFailRet(pArrayValue->GetRank(&nRank)); - if (nRank != 1) - { - WCHAR errorMessage[MAX_ERROR]; - swprintf_s(errorMessage, MAX_ERROR, L"Multi-dimensional arrays NYI"); - *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); - return S_OK; - } - - int index = -1; - if(swscanf_s(pIdentifier, L"%d", &index) != 1) - { - WCHAR errorMessage[MAX_ERROR]; - swprintf_s(errorMessage, MAX_ERROR, L"Failed to parse expression, missing or invalid index expression at character %d", charactersParsed+1); - *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); - return S_OK; - } - - ULONG32 cElements; - IfFailRet(pArrayValue->GetCount(&cElements)); - if(index < 0 || (ULONG32)index >= cElements) - { - WCHAR errorMessage[MAX_ERROR]; - swprintf_s(errorMessage, MAX_ERROR, L"Index is out of range for this array"); - *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); - return S_OK; - } - - ToRelease pElementValue; - IfFailRet(pArrayValue->GetElementAtPosition(index, &pElementValue)); - return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, pElementValue, NULL, NULL, 0, pFrame, ppExpressionNode); - } - // if we are searching along and this is field dereference - else - { - ToRelease pBaseType = pType; - pBaseType->AddRef(); - - while(pBaseType != NULL) - { - // get the current base type class/token/MD - ToRelease pBaseClass; - IfFailRet(pBaseType->GetClass(&pBaseClass)); - ToRelease pBaseTypeModule; - IfFailRet(pBaseClass->GetModule(&pBaseTypeModule)); - mdTypeDef baseTypeDef; - IfFailRet(pBaseClass->GetToken(&baseTypeDef)); - ToRelease pBaseTypeMDUnknown; - ToRelease pBaseTypeMD; - IfFailRet(pBaseTypeModule->GetMetaDataInterface(IID_IMetaDataImport, &pBaseTypeMDUnknown)); - IfFailRet(pBaseTypeMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pBaseTypeMD)); - - - // iterate through all fields at this level of the class hierarchy - ULONG numFields = 0; - HCORENUM fEnum = NULL; - mdFieldDef fieldDef; - while(SUCCEEDED(pMD->EnumFields(&fEnum, baseTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) - { - ULONG nameLen = 0; - DWORD fieldAttr = 0; - WCHAR mdName[mdNameLen]; - CorElementType fieldDefaultValueEt; - UVCP_CONSTANT pDefaultValue; - ULONG cchDefaultValue; - if(SUCCEEDED(pBaseTypeMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, (DWORD*)&fieldDefaultValueEt, &pDefaultValue, &cchDefaultValue)) && - _wcscmp(mdName, pIdentifier) == 0) - { - ToRelease pFieldValType = NULL; - ToRelease pFieldVal; - if (fieldAttr & fdStatic) - pBaseType->GetStaticFieldValue(fieldDef, pFrame, &pFieldVal); - else if(pInnerValue != NULL) - { - ToRelease pObjValue; - if (SUCCEEDED(pInnerValue->QueryInterface(IID_ICorDebugObjectValue, (LPVOID*) &pObjValue))) - pObjValue->GetFieldValue(pBaseClass, fieldDef, &pFieldVal); - } - - // we didn't get a value yet and there is default value available - // need to calculate the type because there won't be a ICorDebugValue to derive it from - if(pFieldVal == NULL && pDefaultValue != NULL) - { - FindTypeFromElementType(fieldDefaultValueEt, &pFieldValType); - } - else - { - // if we aren't using default value, make sure it is cleared out - pDefaultValue = NULL; - cchDefaultValue = 0; - } - - // if we still don't have a value, check if we are trying to get an instance field from a static type - if(pInnerValue == NULL && pFieldVal == NULL && pDefaultValue == NULL) - { - WCHAR pObjectTypeName[MAX_EXPRESSION]; - CalculateTypeName(pBaseType, pObjectTypeName, MAX_EXPRESSION); - WCHAR errorMessage[MAX_ERROR]; - swprintf_s(errorMessage, MAX_ERROR, L"Can not evaluate instance field \'%s\' from static type \'%s\'", pIdentifier, pObjectTypeName); - *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); - return S_OK; - } - return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, pFieldVal, pFieldValType, pDefaultValue, cchDefaultValue, pFrame, ppExpressionNode); - } - } - - //advance to next base type - ICorDebugType* pTemp = NULL; - pBaseType->GetBase(&pTemp); - pBaseType = pTemp; - } - - WCHAR pObjectTypeName[MAX_EXPRESSION]; - CalculateTypeName(pType, pObjectTypeName, MAX_EXPRESSION); - WCHAR errorMessage[MAX_ERROR]; - swprintf_s(errorMessage, MAX_ERROR, L"Field \'%s\' does not exist in type \'%s\'", pIdentifier, pObjectTypeName); - *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); - return S_OK; - } - - return Status; -} - -// Splits apart a C#-like expression and determines the first identifier in the string and updates expression to point -// at the remaining unparsed portion -HRESULT ExpressionNode::ParseNextIdentifier(__in_z WCHAR** expression, __inout_ecount(cchIdentifierName) WCHAR* identifierName, DWORD cchIdentifierName, __inout_ecount(cchErrorMessage) WCHAR* errorMessage, DWORD cchErrorMessage, DWORD* charactersParsed, BOOL* isArrayIndex) -{ - - // This algorithm is best understood as a two stage process. The first stage splits - // the expression into two chunks an identifier and a remaining expression. The second stage - // normalizes the identifier. The splitting algorithm doesn't care if identifiers are well-formed - // at all, we do some error checking in the 2nd stage though. For the splitting stage, an identifier is - // any first character, followed by as many characters as possible that aren't a '.' or a '['. - // In the 2nd stage any '.' character is removed from the front (the only place it could be) - // and enclosing braces are removed. An error is recorded if the identifier ends 0 length or the - // opening bracket isn't matched. Here is an example showing how we would parse an expression - // which is deliberately not very well formed. Each line is the result of calling this function once... it - // takes many calls to break the entire expression down. - // - // expression 1st stage identifier 2nd stage identifier - // foo.bar[18..f[1.][2][[ - // .bar[18..f[1.][2][[ foo foo - // [18..f[1.][2][[ .bar bar - // ..f[1.][2][[ [18 error no ] - // .f[1.][2][[ . error 0-length - // [1.][2][[ .f f - // .][2][[ [1 error no ] - // [2][[ .] ] (we don't error check legal CLI identifier name characters) - // [[ [2] 2 - // [ [ error no ] - // [ error no ] - - // not an error, just the end of the expression - if(*expression == NULL || **expression == 0) - return S_FALSE; - - WCHAR* expressionStart = *expression; - DWORD currentCharsParsed = *charactersParsed; - DWORD identifierLen = (DWORD) _wcscspn(expressionStart, L".["); - // if the first character was a . or [ skip over it. Note that we don't - // do this always in case the first WCHAR was part of a surrogate pair - if(identifierLen == 0) - { - identifierLen = (DWORD) _wcscspn(expressionStart+1, L".[") + 1; - } - - *expression += identifierLen; - *charactersParsed += identifierLen; - - // done with the first stage splitting, on to 2nd stage - - // a . should be followed by field name - if(*expressionStart == L'.') - { - if(identifierLen == 1) // 0-length after . - { - swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing name after character %d", currentCharsParsed+1); - return E_FAIL; - } - if(identifierLen-1 >= cchIdentifierName) - { - swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, name at character %d is too long", currentCharsParsed+2); - return E_FAIL; - } - *isArrayIndex = FALSE; - wcsncpy_s(identifierName, cchIdentifierName, expressionStart+1, identifierLen-1); - return S_OK; - } - // an open bracket should be followed by a decimal value and then a closing bracket - else if(*expressionStart == L'[') - { - if(*(expressionStart+identifierLen-1) != L']') - { - swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing or invalid index expression at character %d", currentCharsParsed+1); - return E_FAIL; - } - if(identifierLen <= 2) // 0-length between [] - { - swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing index after character %d", currentCharsParsed+1); - return E_FAIL; - } - if(identifierLen-2 >= cchIdentifierName) - { - swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, index at character %d is too large", currentCharsParsed+2); - return E_FAIL; - } - *isArrayIndex = TRUE; - wcsncpy_s(identifierName, cchIdentifierName, expressionStart+1, identifierLen-2); - return S_OK; - } - else // no '.' or '[', this is an initial name - { - if(identifierLen == 0) // 0-length - { - swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing name after character %d", currentCharsParsed+1); - return E_FAIL; - } - if(identifierLen >= cchIdentifierName) - { - swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, name at character %d is too long", currentCharsParsed+1); - return E_FAIL; - } - *isArrayIndex = FALSE; - wcsncpy_s(identifierName, cchIdentifierName, expressionStart, identifierLen); - return S_OK; - } -} - - -// Iterate through all parameters in the ILFrame calling the callback function for each of them -HRESULT ExpressionNode::EnumerateParameters(IMetaDataImport * pMD, - mdMethodDef methodDef, - ICorDebugILFrame * pILFrame, - VariableEnumCallback pCallback, - VOID* pUserData) -{ - HRESULT Status = S_OK; - - ULONG cParams = 0; - ToRelease pParamEnum; - IfFailRet(pILFrame->EnumerateArguments(&pParamEnum)); - IfFailRet(pParamEnum->GetCount(&cParams)); - DWORD methAttr = 0; - IfFailRet(pMD->GetMethodProps(methodDef, NULL, NULL, 0, NULL, &methAttr, NULL, NULL, NULL, NULL)); - for (ULONG i=0; i < cParams; i++) - { - ULONG paramNameLen = 0; - mdParamDef paramDef; - WCHAR paramName[mdNameLen] = L"\0"; - - if(i == 0 && (methAttr & mdStatic) == 0) - swprintf_s(paramName, mdNameLen, L"this\0"); - else - { - int idx = ((methAttr & mdStatic) == 0)? i : (i + 1); - if(SUCCEEDED(pMD->GetParamForMethodIndex(methodDef, idx, ¶mDef))) - pMD->GetParamProps(paramDef, NULL, NULL, paramName, mdNameLen, ¶mNameLen, NULL, NULL, NULL, NULL); - } - if(_wcslen(paramName) == 0) - swprintf_s(paramName, mdNameLen, L"param_%d\0", i); - - ToRelease pValue; - ULONG cArgsFetched; - WCHAR pErrorMessage[MAX_ERROR] = L"\0"; - HRESULT hr = pParamEnum->Next(1, &pValue, &cArgsFetched); - if (FAILED(hr)) - { - swprintf_s(pErrorMessage, MAX_ERROR, L" + (Error 0x%x retrieving parameter '%S')\n", hr, paramName); - } - if (hr == S_FALSE) - { - break; - } - pCallback(pValue, paramName, pErrorMessage, pUserData); - } - - return Status; -} - -// Enumerate all locals in the given ILFrame, calling the callback method for each of them -HRESULT ExpressionNode::EnumerateLocals(IMetaDataImport * pMD, - mdMethodDef methodDef, - ICorDebugILFrame * pILFrame, - VariableEnumCallback pCallback, - VOID* pUserData) -{ - HRESULT Status = S_OK; - ULONG cLocals = 0; - ToRelease pFunction; - ToRelease pModule; - if(SUCCEEDED(pILFrame->GetFunction(&pFunction))) - { - IfFailRet(pFunction->GetModule(&pModule)); - } - ToRelease pLocalsEnum; - IfFailRet(pILFrame->EnumerateLocalVariables(&pLocalsEnum)); - IfFailRet(pLocalsEnum->GetCount(&cLocals)); - if (cLocals > 0) - { - SymbolReader symReader; - bool symbolsAvailable = false; - if(pModule != NULL && SUCCEEDED(symReader.LoadSymbols(pMD, pModule))) - symbolsAvailable = true; - - for (ULONG i=0; i < cLocals; i++) - { - ULONG paramNameLen = 0; - WCHAR paramName[mdNameLen] = L"\0"; - WCHAR pErrorMessage[MAX_ERROR] = L"\0"; - ToRelease pValue; - HRESULT hr = S_OK; - if(symbolsAvailable) - hr = symReader.GetNamedLocalVariable(pILFrame, i, paramName, mdNameLen, &pValue); - else - { - ULONG cArgsFetched; - hr = pLocalsEnum->Next(1, &pValue, &cArgsFetched); - } - if(_wcslen(paramName) == 0) - swprintf_s(paramName, mdNameLen, L"local_%d\0", i); - - if (FAILED(hr)) - { - swprintf_s(pErrorMessage, MAX_ERROR, L" + (Error 0x%x retrieving local variable '%S')\n", hr, paramName); - } - else if (hr == S_FALSE) - { - break; - } - pCallback(pValue, paramName, pErrorMessage, pUserData); - } - } - - return Status; -} - -// Iterates over all frames on the current thread's stack, calling the callback function for each of them -HRESULT ExpressionNode::EnumerateFrames(FrameEnumCallback pCallback, VOID* pUserData) -{ - HRESULT Status = S_OK; - ToRelease pThread; - ToRelease pThread3; - ToRelease pStackWalk; - ULONG ulThreadID = 0; - g_ExtSystem->GetCurrentThreadSystemId(&ulThreadID); - - IfFailRet(g_pCorDebugProcess->GetThread(ulThreadID, &pThread)); - IfFailRet(pThread->QueryInterface(IID_ICorDebugThread3, (LPVOID *) &pThread3)); - IfFailRet(pThread3->CreateStackWalk(&pStackWalk)); - - InternalFrameManager internalFrameManager; - IfFailRet(internalFrameManager.Init(pThread3)); - - int currentFrame = -1; - - for (Status = S_OK; ; Status = pStackWalk->Next()) - { - currentFrame++; - - if (Status == CORDBG_S_AT_END_OF_STACK) - { - break; - } - IfFailRet(Status); - - if (IsInterrupt()) - { - ExtOut("\n"); - break; - } - - CROSS_PLATFORM_CONTEXT context; - ULONG32 cbContextActual; - if ((Status=pStackWalk->GetContext( - DT_CONTEXT_FULL, - sizeof(context), - &cbContextActual, - (BYTE *)&context))!=S_OK) - { - ExtOut("GetFrameContext failed: %lx\n",Status); - break; - } - - ToRelease pFrame; - IfFailRet(pStackWalk->GetFrame(&pFrame)); - if (Status == S_FALSE) - { - Status = S_OK; - continue; - } - - pCallback(pFrame, pUserData); - } - - return Status; -} - -// Determines the corresponding ICorDebugType for a given primitive type -HRESULT ExpressionNode::FindTypeFromElementType(CorElementType et, ICorDebugType** ppType) -{ - HRESULT Status; - switch (et) - { - default: - Status = E_FAIL; - break; - - case ELEMENT_TYPE_BOOLEAN: - Status = FindTypeByName(L"System.Boolean", ppType); - break; - - case ELEMENT_TYPE_CHAR: - Status = FindTypeByName(L"System.Char", ppType); - break; - - case ELEMENT_TYPE_I1: - Status = FindTypeByName(L"System.SByte", ppType); - break; - - case ELEMENT_TYPE_U1: - Status = FindTypeByName(L"System.Byte", ppType); - break; - - case ELEMENT_TYPE_I2: - Status = FindTypeByName(L"System.Short", ppType); - break; - - case ELEMENT_TYPE_U2: - Status = FindTypeByName(L"System.UShort", ppType); - break; - - case ELEMENT_TYPE_I: - Status = FindTypeByName(L"System.Int32", ppType); - break; - - case ELEMENT_TYPE_U: - Status = FindTypeByName(L"System.UInt32", ppType); - break; - - case ELEMENT_TYPE_I4: - Status = FindTypeByName(L"System.Int32", ppType); - break; - - case ELEMENT_TYPE_U4: - Status = FindTypeByName(L"System.UInt32", ppType); - break; - - case ELEMENT_TYPE_I8: - Status = FindTypeByName(L"System.Int64", ppType); - break; - - case ELEMENT_TYPE_U8: - Status = FindTypeByName(L"System.UInt64", ppType); - break; - - case ELEMENT_TYPE_R4: - Status = FindTypeByName(L"System.Single", ppType); - break; - - case ELEMENT_TYPE_R8: - Status = FindTypeByName(L"System.Double", ppType); - break; - - case ELEMENT_TYPE_OBJECT: - Status = FindTypeByName(L"System.Object", ppType); - break; - - case ELEMENT_TYPE_STRING: - Status = FindTypeByName(L"System.String", ppType); - break; - } - return Status; -} - -// Gets the appropriate element type encoding for well-known fully qualified type names -// This doesn't work for arbitrary types, just types that have CorElementType short forms. -HRESULT ExpressionNode::GetCanonicalElementTypeForTypeName(__in_z WCHAR* pTypeName, CorElementType *et) -{ - //Sadly ICorDebug deliberately prevents creating ICorDebugType instances - //that use canonical short form element types... seems like an issue to me. - - if(_wcscmp(pTypeName, L"System.String")==0) - { - *et = ELEMENT_TYPE_STRING; - } - else if(_wcscmp(pTypeName, L"System.Object")==0) - { - *et = ELEMENT_TYPE_OBJECT; - } - else if(_wcscmp(pTypeName, L"System.Void")==0) - { - *et = ELEMENT_TYPE_VOID; - } - else if(_wcscmp(pTypeName, L"System.Boolean")==0) - { - *et = ELEMENT_TYPE_BOOLEAN; - } - else if(_wcscmp(pTypeName, L"System.Char")==0) - { - *et = ELEMENT_TYPE_CHAR; - } - else if(_wcscmp(pTypeName, L"System.Byte")==0) - { - *et = ELEMENT_TYPE_U1; - } - else if(_wcscmp(pTypeName, L"System.Sbyte")==0) - { - *et = ELEMENT_TYPE_I1; - } - else if(_wcscmp(pTypeName, L"System.Int16")==0) - { - *et = ELEMENT_TYPE_I2; - } - else if(_wcscmp(pTypeName, L"System.UInt16")==0) - { - *et = ELEMENT_TYPE_U2; - } - else if(_wcscmp(pTypeName, L"System.UInt32")==0) - { - *et = ELEMENT_TYPE_U4; - } - else if(_wcscmp(pTypeName, L"System.Int32")==0) - { - *et = ELEMENT_TYPE_I4; - } - else if(_wcscmp(pTypeName, L"System.UInt64")==0) - { - *et = ELEMENT_TYPE_U8; - } - else if(_wcscmp(pTypeName, L"System.Int64")==0) - { - *et = ELEMENT_TYPE_I8; - } - else if(_wcscmp(pTypeName, L"System.Single")==0) - { - *et = ELEMENT_TYPE_R4; - } - else if(_wcscmp(pTypeName, L"System.Double")==0) - { - *et = ELEMENT_TYPE_R8; - } - else if(_wcscmp(pTypeName, L"System.IntPtr")==0) - { - *et = ELEMENT_TYPE_U; - } - else if(_wcscmp(pTypeName, L"System.UIntPtr")==0) - { - *et = ELEMENT_TYPE_I; - } - else if(_wcscmp(pTypeName, L"System.TypedReference")==0) - { - *et = ELEMENT_TYPE_TYPEDBYREF; - } - else - { - return E_FAIL; // can't tell from a name whether it should be valuetype or class - } - return S_OK; -} - -// Searches the debuggee for any ICorDebugType that matches the given fully qualified name -// This will search across all AppDomains and Assemblies -HRESULT ExpressionNode::FindTypeByName(__in_z const WCHAR* pTypeName, ICorDebugType** ppType) -{ - HRESULT Status = S_OK; - ToRelease pAppDomainEnum; - IfFailRet(g_pCorDebugProcess->EnumerateAppDomains(&pAppDomainEnum)); - DWORD count; - IfFailRet(pAppDomainEnum->GetCount(&count)); - for(DWORD i = 0; i < count; i++) - { - ToRelease pAppDomain; - DWORD countFetched = 0; - IfFailRet(pAppDomainEnum->Next(1, &pAppDomain, &countFetched)); - Status = FindTypeByName(pAppDomain, pTypeName, ppType); - if(SUCCEEDED(Status)) - break; - } - - return Status; -} - -// Searches the debuggee for any ICorDebugType that matches the given fully qualified name -// This will search across all Assemblies in the given AppDomain -HRESULT ExpressionNode::FindTypeByName(ICorDebugAppDomain* pAppDomain, __in_z const WCHAR* pTypeName, ICorDebugType** ppType) -{ - HRESULT Status = S_OK; - ToRelease pAssemblyEnum; - IfFailRet(pAppDomain->EnumerateAssemblies(&pAssemblyEnum)); - DWORD count; - IfFailRet(pAssemblyEnum->GetCount(&count)); - for(DWORD i = 0; i < count; i++) - { - ToRelease pAssembly; - DWORD countFetched = 0; - IfFailRet(pAssemblyEnum->Next(1, &pAssembly, &countFetched)); - Status = FindTypeByName(pAssembly, pTypeName, ppType); - if(SUCCEEDED(Status)) - break; - } - - return Status; -} - -// Searches the assembly for any ICorDebugType that matches the given fully qualified name -HRESULT ExpressionNode::FindTypeByName(ICorDebugAssembly* pAssembly, __in_z const WCHAR* pTypeName, ICorDebugType** ppType) -{ - HRESULT Status = S_OK; - ToRelease pModuleEnum; - IfFailRet(pAssembly->EnumerateModules(&pModuleEnum)); - DWORD count; - IfFailRet(pModuleEnum->GetCount(&count)); - for(DWORD i = 0; i < count; i++) - { - ToRelease pModule; - DWORD countFetched = 0; - IfFailRet(pModuleEnum->Next(1, &pModule, &countFetched)); - Status = FindTypeByName(pModule, pTypeName, ppType); - if(SUCCEEDED(Status)) - break; - } - - return Status; -} - -// Searches a given module for any ICorDebugType that matches the given fully qualified type name -HRESULT ExpressionNode::FindTypeByName(ICorDebugModule* pModule, __in_z const WCHAR* pTypeName, ICorDebugType** ppType) -{ - HRESULT Status = S_OK; - ToRelease pMDUnknown; - ToRelease pMD; - IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); - IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); - - // If the name contains a generic argument list, extract the type name from - // before the list - WCHAR rootName[mdNameLen]; - const WCHAR* pRootName = NULL; - int typeNameLen = (int) _wcslen(pTypeName); - int genericParamListStart = (int) _wcscspn(pTypeName, L"<"); - if(genericParamListStart != typeNameLen) - { - if(pTypeName[typeNameLen-1] != L'>' || genericParamListStart > mdNameLen) - { - return E_FAIL; // mal-formed type name - } - else - { - wcsncpy_s(rootName, mdNameLen, pTypeName, genericParamListStart); - pRootName = rootName; - } - } - else - { - pRootName = pTypeName; - } - - // Convert from name to token to ICorDebugClass - mdTypeDef typeDef; - IfFailRet(pMD->FindTypeDefByName(pRootName, NULL, &typeDef)); - DWORD flags; - ULONG nameLen; - mdToken tkExtends; - IfFailRet(pMD->GetTypeDefProps(typeDef, NULL, 0, &nameLen, &flags, &tkExtends)); - BOOL isValueType; - IfFailRet(IsTokenValueTypeOrEnum(tkExtends, pMD, &isValueType)); - CorElementType et = isValueType ? ELEMENT_TYPE_VALUETYPE : ELEMENT_TYPE_CLASS; - ToRelease pClass; - IfFailRet(pModule->GetClassFromToken(typeDef, &pClass)); - ToRelease pClass2; - IfFailRet(pClass->QueryInterface(__uuidof(ICorDebugClass2), (void**)&pClass2)); - - // Convert from class to type - if generic then recursively resolve the generic - // parameter list - ArrayHolder> typeParams = NULL; - int countTypeParams = 0; - if(genericParamListStart != typeNameLen) - { - ToRelease pAssembly; - IfFailRet(pModule->GetAssembly(&pAssembly)); - ToRelease pDomain; - IfFailRet(pAssembly->GetAppDomain(&pDomain)); - - countTypeParams = 1; - for(int i = genericParamListStart+1; i < typeNameLen; i++) - { - if(pTypeName[i] == L',') countTypeParams++; - } - typeParams = new ToRelease[countTypeParams]; - - const WCHAR* pCurName = pTypeName + genericParamListStart+1; - for(int i = 0; i < countTypeParams; i++) - { - WCHAR typeParamName[mdNameLen]; - const WCHAR* pNextComma = _wcschr(pCurName, L','); - int len = (pNextComma != NULL) ? (int)(pNextComma - pCurName) : (int)_wcslen(pCurName)-1; - if(len > mdNameLen) - return E_FAIL; - wcsncpy_s(typeParamName, mdNameLen, pCurName, len); - FindTypeByName(pDomain, typeParamName, &(typeParams[i])); - pCurName = pNextComma+1; - } - } - IfFailRet(pClass2->GetParameterizedType(et, countTypeParams, &(typeParams[0]), ppType)); - - return Status; -} - -// Checks whether the given token is or refers to type System.ValueType or System.Enum -HRESULT ExpressionNode::IsTokenValueTypeOrEnum(mdToken token, IMetaDataImport* pMetadata, BOOL* pResult) -{ - // This isn't a 100% correct check because we aren't verifying the module portion of the - // type identity. Arbitrary assemblies could define a type named System.ValueType or System.Enum. - // If that happens this code will get the answer wrong... we just assume that happens so rarely - // that it isn't worth doing all the overhead of assembly resolution to deal with - - HRESULT Status = S_OK; - CorTokenType type = (CorTokenType)(token & 0xFF000000); - - // only need enough space to hold either System.ValueType or System.Enum - //System.ValueType -> 16 characters - //System.Enum -> 11 characters - WCHAR nameBuffer[17]; - nameBuffer[0] = L'\0'; - - if(type == mdtTypeRef) - { - ULONG chTypeDef; - pMetadata->GetTypeRefProps(token, NULL, NULL, 0, &chTypeDef); - if(chTypeDef > _countof(nameBuffer)) - { - *pResult = FALSE; - return Status; - } - IfFailRet(pMetadata->GetTypeRefProps(token, NULL, nameBuffer, _countof(nameBuffer), &chTypeDef)); - } - else if(type == mdtTypeDef) - { - ULONG chTypeDef; - pMetadata->GetTypeDefProps(token, NULL, 0, &chTypeDef, NULL, NULL); - if(chTypeDef > _countof(nameBuffer)) - { - *pResult = FALSE; - return Status; - } - IfFailRet(pMetadata->GetTypeDefProps(token, nameBuffer, _countof(nameBuffer), &chTypeDef, NULL, NULL)); - } - - if(_wcscmp(nameBuffer, L"System.ValueType") == 0 || - _wcscmp(nameBuffer, L"System.Enum") == 0) - { - *pResult = TRUE; - } - else - { - *pResult = FALSE; - } - return Status; -} diff --git a/src/ToolBox/SOS/Strike/ExpressionNode.h b/src/ToolBox/SOS/Strike/ExpressionNode.h deleted file mode 100644 index 2f34e56..0000000 --- a/src/ToolBox/SOS/Strike/ExpressionNode.h +++ /dev/null @@ -1,307 +0,0 @@ -// 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. - - -#ifndef _EXPRESSION_NODE_ -#define _EXPRESSION_NODE_ - -#ifdef FEATURE_PAL -#error "This file isn't designed to build in PAL" -#endif - -#include "strike.h" -#include "sos.h" -#include "util.h" - -#define MAX_EXPRESSION 500 -#define MAX_ERROR 500 - - -// Represents one node in a tree of expressions and sub-expressions -// These nodes are used in the !watch expandable expression tree -// Each node consists of a string based C#-like expression and its -// evaluation within the current context of the debuggee. -// -// These nodes are also intended for eventual use in ClrStack -i expression tree -// but ClrStack -i hasn't yet been refactored to use them -// -// Each node can evaluate to: -// nothing - if an error occurs during expression parsing or the expression -// names don't match to anything in the debuggee -// a debuggee value - these are values that are backed in memory of the debuggee -// (ICorDebugValue objects) or build time constants which are -// stored in the assembly metadata. -// a debuggee type - instead of refering to a particular instance of a type (the -// value case above), nodes can directly refer to a type definition -// represented by an ICorDebugType object -class ExpressionNode -{ -public: - - typedef VOID (*ExpressionNodeVisitorCallback)(ExpressionNode* pExpressionNode, int depth, VOID* pUserData); - - // Returns the complete expression being evaluated to get the value for this node - // The returned pointer is a string interior to this object - once you release - // all references to this object the string is invalid. - WCHAR* GetAbsoluteExpression(); - - // Returns the sub expression that logically indicates how the parent expression - // was built upon to reach this node. This relative value has no purpose other - // than an identifier and to convey UI meaning to the user. At present typical values - // are the name of type, a local, a parameter, a field, an array index, or '' - // for a baseclass casting operation - // The returned pointer is a string interior to this object - once you release - // all references to this object the string is invalid. - WCHAR* GetRelativeExpression(); - - // Returns a text representation of the type of value that this node refers to - // It is possible this node doesn't evaluate to anything and therefore has no - // type - // The returned pointer is a string interior to this object - once you release - // all references to this object the string is invalid. - WCHAR* GetTypeName(); - - // Returns a text representation of the value for this node. It is possible that - // this node doesn't evaluate to anything and therefore has no value text. - // The returned pointer is a string interior to this object - once you release - // all references to this object the string is invalid. - WCHAR* GetTextValue(); - - // If there is any error during the evaluation of this node's expression, it is - // returned here. - // The returned pointer is a string interior to this object - once you release - // all references to this object the string is invalid. - WCHAR* GetErrorMessage(); - - // Factory function for creating the expression node at the root of a tree - static HRESULT CreateExpressionNode(__in_z WCHAR* pExpression, ExpressionNode** ppExpressionNode); - - // Performs recursive expansion within the tree for nodes that are along the path to varToExpand. - // Expansion involves calulating a set of child expressions from the current expression via - // field dereferencing, array index dereferencing, or casting to a base type. - // For example if a tree was rooted with expression 'foo.bar' and varToExpand is '(Baz)foo.bar[9]' - // then 'foo.bar', 'foo.bar[9]', and '(Baz)foo.bar[9]' nodes would all be expanded. - HRESULT Expand(__in_z WCHAR* varToExpand); - - // Standard depth first search tree traversal pattern with a callback - VOID DFSVisit(ExpressionNodeVisitorCallback pFunc, VOID* pUserData, int depth=0); - -private: - // for nodes that evaluate to a type, this is that type - // for nodes that evaluate to a debuggee value, this is the type of that - // value or one of its base types. It represents the type the value should - // displayed and expanded as. - ToRelease pTypeCast; - - // for nodes that evaluate to a memory backed debuggee value, this is that value - ToRelease pValue; - - // if this node gets expanded and it has thread-static or context-static sub-fields, - // this frame disambiguates which thread and context to use. - ToRelease pILFrame; - - // TODO: exactly which metadata is this supposed to be? try to get rid of this - ToRelease pMD; - - // PERF: this could be a lot more memory efficient - WCHAR pTextValue[MAX_EXPRESSION]; - WCHAR pErrorMessage[MAX_ERROR]; - WCHAR pAbsoluteExpression[MAX_EXPRESSION]; - WCHAR pRelativeExpression[MAX_EXPRESSION]; - WCHAR pTypeName[MAX_EXPRESSION]; - - // if this value represents a build time constant debuggee value, this is a pointer - // to the value data stored in metadata and its size - UVCP_CONSTANT pDefaultValue; - ULONG cchDefaultValue; - - // Pointer in a linked list of sibling nodes that all share the same parent - ExpressionNode* pNextSibling; - // Pointer to the first child node of this node, other children can be found - // by following the child's sibling list. - ExpressionNode* pChild; - - typedef VOID (*VariableEnumCallback)(ICorDebugValue* pValue, WCHAR* pName, WCHAR* pErrorMessage, VOID* pUserData); - typedef VOID (*FrameEnumCallback)(ICorDebugFrame* pFrame, VOID* pUserData); - - // Indicates how a child node was derived from its parent - enum ChildKind - { - ChildKind_Field, - ChildKind_Index, - ChildKind_BaseClass - }; - - // Creates a new expression with a given debuggee value and frame - ExpressionNode(__in_z const WCHAR* pExpression, ICorDebugValue* pValue, ICorDebugILFrame* pFrame); - - // Creates a new expression that has an error and no value - ExpressionNode(__in_z const WCHAR* pExpression, __in_z const WCHAR* pErrorMessage); - - // Creates a new child expression - ExpressionNode(__in_z const WCHAR* pParentExpression, ChildKind ck, __in_z const WCHAR* pRelativeExpression, ICorDebugValue* pValue, ICorDebugType* pType, ICorDebugILFrame* pFrame, UVCP_CONSTANT pDefaultValue = NULL, ULONG cchDefaultValue = 0); - - // Common member initialization for the constructors - VOID Init(ICorDebugValue* pValue, ICorDebugType* pTypeCast, ICorDebugILFrame* pFrame); - - // Retreves the correct IMetaDataImport for the type represented in this node and stores it - // in pMD. - HRESULT PopulateMetaDataImport(); - - // Determines the string representation of pType and stores it in typeName - static HRESULT CalculateTypeName(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen); - - - // Appends angle brackets and the generic argument list to a type name - static HRESULT AddGenericArgs(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen); - - // Determines the text name for the type of this node and caches it - HRESULT PopulateType(); - - // Node expansion helpers - - // Inserts a new child at the end of the linked list of children - // PERF: This has O(N) insert time but these lists should never be large - VOID AddChild(ExpressionNode* pNewChild); - - // Helper that determines if the current node is on the path of nodes represented by - // expression varToExpand - BOOL ShouldExpandVariable(__in_z WCHAR* varToExpand); - - // Expands this array node by creating child nodes with expressions refering to individual array elements - HRESULT ExpandSzArray(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand); - - // Expands this struct/class node by creating child nodes with expressions refering to individual field values - // and one node for the basetype value - HRESULT ExpandFields(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand); - - // Value Population functions - - //Helper for unwrapping values - static HRESULT DereferenceAndUnboxValue(ICorDebugValue * pInputValue, ICorDebugValue** ppOutputValue, BOOL * pIsNull = NULL); - - // Returns TRUE if the value derives from System.Enum - static BOOL IsEnum(ICorDebugValue * pInputValue); - - // Calculates the value text for nodes that have enum values - HRESULT PopulateEnumValue(ICorDebugValue* pEnumValue, BYTE* enumValue); - - // Helper that fetches the text value of a string ICorDebugValue - HRESULT GetDebuggeeStringValue(ICorDebugValue* pInputValue, __inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer); - - // Helper that fetches the text value of a string build-time literal - HRESULT GetConstantStringValue(__inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer); - - // Helper that caches the textual value for nodes that evaluate to array objects - HRESULT PopulateSzArrayValue(ICorDebugValue* pInputValue); - - // Helper that caches the textual value for nodes of any type - HRESULT PopulateTextValueHelper(); - - // Caches the textual value of this node - HRESULT PopulateTextValue(); - - - // Expression parsing and search - - // In/Out parameters for the EvaluateExpressionFrameScanCallback - typedef struct _EvaluateExpressionFrameScanData - { - WCHAR* pIdentifier; - ToRelease pFoundValue; - ToRelease pFoundFrame; - ToRelease pFirstFrame; - WCHAR* pErrorMessage; - DWORD cchErrorMessage; - } EvaluateExpressionFrameScanData; - - //Callback that searches a frame to determine if it contains a local variable or parameter of a given name - static VOID EvaluateExpressionFrameScanCallback(ICorDebugFrame* pFrame, VOID* pUserData); - - //Callback checks to see if a given local/parameter has name pName - static VOID EvaluateExpressionVariableScanCallback(ICorDebugValue* pValue, __in_z WCHAR* pName, __out_z WCHAR* pErrorMessage, VOID* pUserData); - - //Factory method that recursively parses pExpression and create an ExpressionNode - // pExpression - the entire expression being parsed - // pExpressionRemainder - the portion of the expression that remains to be parsed in this - // recursive invocation - // charactersParsed - the number of characters that have been parsed from pExpression - // so far (todo: this is basically the difference between remainder and - // full expression, do we need it?) - // pParsedValue - A debuggee value that should be used as the context for interpreting - // pExpressionRemainder - // pParsedType - A debuggee type that should be used as the context for interpreting - // pExpressionRemainder. - // pParsedDefaultValue - A fixed value from metadata that should be used as context for - // interpretting pExpressionRemainder - // cchParsedDefaultValue- Size of pParsedDefaultValue - // pFrame - A debuggee IL frame that disambiguates the thread and context needed - // to evaluate a thread-static or context-static value - // ppExpressionNode - OUT - the resulting expression node - // - // - static HRESULT CreateExpressionNodeHelper(__in_z WCHAR* pExpression, - __in_z WCHAR* pExpressionParseRemainder, - DWORD charactersParsed, - ICorDebugValue* pParsedValue, - ICorDebugType* pParsedType, - UVCP_CONSTANT pParsedDefaultValue, - ULONG cchParsedDefaultValue, - ICorDebugILFrame* pFrame, - ExpressionNode** ppExpressionNode); - - // Splits apart a C#-like expression and determines the first identifier in the string and updates expression to point - // at the remaining unparsed portion - static HRESULT ParseNextIdentifier(__in_z WCHAR** expression, - __inout_ecount(cchIdentifierName) WCHAR* identifierName, - DWORD cchIdentifierName, - __inout_ecount(cchErrorMessage) WCHAR* errorMessage, - DWORD cchErrorMessage, - DWORD* charactersParsed, - BOOL* isArrayIndex); - - - // Iterate through all parameters in the ILFrame calling the callback function for each of them - static HRESULT EnumerateParameters(IMetaDataImport * pMD, - mdMethodDef methodDef, - ICorDebugILFrame * pILFrame, - VariableEnumCallback pCallback, - VOID* pUserData); - - // Enumerate all locals in the given ILFrame, calling the callback method for each of them - static HRESULT EnumerateLocals(IMetaDataImport * pMD, - mdMethodDef methodDef, - ICorDebugILFrame * pILFrame, - VariableEnumCallback pCallback, - VOID* pUserData); - - // Iterates over all frames on the current thread's stack, calling the callback function for each of them - static HRESULT EnumerateFrames(FrameEnumCallback pCallback, VOID* pUserData); - - // Determines the corresponding ICorDebugType for a given primitive type - static HRESULT FindTypeFromElementType(CorElementType et, ICorDebugType** ppType); - - // Gets the appropriate element type encoding for well-known fully qualified type names - // This doesn't work for arbitrary types, just types that have CorElementType short forms. - static HRESULT GetCanonicalElementTypeForTypeName(__in_z WCHAR* pTypeName, CorElementType *et); - - // Searches the debuggee for any ICorDebugType that matches the given fully qualified name - // This will search across all AppDomains and Assemblies - static HRESULT FindTypeByName(__in_z const WCHAR* pTypeName, ICorDebugType** ppType); - - // Searches the debuggee for any ICorDebugType that matches the given fully qualified name - // This will search across all Assemblies in the given AppDomain - static HRESULT FindTypeByName(ICorDebugAppDomain* pAppDomain, __in_z const WCHAR* pTypeName, ICorDebugType** ppType); - - // Searches the assembly for any ICorDebugType that matches the given fully qualified name - static HRESULT FindTypeByName(ICorDebugAssembly* pAssembly, __in_z const WCHAR* pTypeName, ICorDebugType** ppType); - - // Searches a given module for any ICorDebugType that matches the given fully qualified type name - static HRESULT FindTypeByName(ICorDebugModule* pModule, __in_z const WCHAR* pTypeName, ICorDebugType** ppType); - - // Checks whether the given token is or refers to type System.ValueType or System.Enum - static HRESULT IsTokenValueTypeOrEnum(mdToken token, IMetaDataImport* pMetadata, BOOL* pResult); -}; - -#endif diff --git a/src/ToolBox/SOS/Strike/Native.rc b/src/ToolBox/SOS/Strike/Native.rc deleted file mode 100644 index 179ddfd..0000000 --- a/src/ToolBox/SOS/Strike/Native.rc +++ /dev/null @@ -1,10 +0,0 @@ -// 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. - -#define FX_VER_FILEDESCRIPTION_STR "Microsoft NTSD extension for .NET Runtime\0" - -#include -#include - -DOCUMENTATION TEXT DISCARDABLE "sosdocs.txt" diff --git a/src/ToolBox/SOS/Strike/UtilCode.h b/src/ToolBox/SOS/Strike/UtilCode.h deleted file mode 100644 index a002edc..0000000 --- a/src/ToolBox/SOS/Strike/UtilCode.h +++ /dev/null @@ -1,11 +0,0 @@ -// 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. - -// ==++== -// - -// -// ==--== -// An empty file so that gcdump.cpp does not include the one from other -// places. diff --git a/src/ToolBox/SOS/Strike/WatchCmd.cpp b/src/ToolBox/SOS/Strike/WatchCmd.cpp deleted file mode 100644 index 443f1dd..0000000 --- a/src/ToolBox/SOS/Strike/WatchCmd.cpp +++ /dev/null @@ -1,331 +0,0 @@ -// 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. - -#include "WatchCmd.h" - -#ifndef IfFailRet -#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0) -#endif - -_PersistList::~_PersistList() -{ - PersistWatchExpression* pCur = pHeadExpr; - while(pCur != NULL) - { - PersistWatchExpression* toDelete = pCur; - pCur = pCur->pNext; - delete toDelete; - } -} - -WatchCmd::WatchCmd() : -pExpressionListHead(NULL) -{ } -WatchCmd::~WatchCmd() -{ - Clear(); - PersistList* pCur = pPersistListHead; - while(pCur != NULL) - { - PersistList* toDelete = pCur; - pCur = pCur->pNext; - delete toDelete; - } -} - -// Deletes all current watch expressions from the watch list -// (does not delete persisted watch lists though) -HRESULT WatchCmd::Clear() -{ - WatchExpression* pCurrent = pExpressionListHead; - while(pCurrent != NULL) - { - WatchExpression* toDelete = pCurrent; - pCurrent = pCurrent->pNext; - delete toDelete; - } - pExpressionListHead = NULL; - return S_OK; -} - -// Adds a new expression to the active watch list -HRESULT WatchCmd::Add(__in_z WCHAR* pExpression) -{ - WatchExpression* pExpr = new WatchExpression; - if(pExpr == NULL) - return E_OUTOFMEMORY; - wcsncpy_s(pExpr->pExpression, MAX_EXPRESSION, pExpression, _TRUNCATE); - pExpr->pNext = NULL; - - WatchExpression** ppCurrent = &pExpressionListHead; - while(*ppCurrent != NULL) - ppCurrent = &((*ppCurrent)->pNext); - *ppCurrent = pExpr; - return S_OK; -} - -// removes an expression at the given index in the active watch list -HRESULT WatchCmd::Remove(int index) -{ - HRESULT Status = S_FALSE; - WatchExpression** ppCurrent = &pExpressionListHead; - for(int i=1; *ppCurrent != NULL; i++) - { - if(i == index) - { - WatchExpression* toDelete = *ppCurrent; - *ppCurrent = (*ppCurrent)->pNext; - delete toDelete; - Status = S_OK; - break; - } - ppCurrent = &((*ppCurrent)->pNext); - - } - return Status; -} - -// Evaluates and prints a tree version of the active watch list -// The tree will be expanded along the nodes in expansionPath -// Optionally the list is filtered to only show differences from pFilterName (the name of a persisted watch list) -HRESULT WatchCmd::Print(int expansionIndex, __in_z WCHAR* expansionPath, __in_z WCHAR* pFilterName) -{ - HRESULT Status = S_OK; - INIT_API_EE(); - INIT_API_DAC(); - EnableDMLHolder dmlHolder(TRUE); - IfFailRet(InitCorDebugInterface()); - - PersistList* pFilterList = NULL; - if(pFilterName != NULL) - { - pFilterList = pPersistListHead; - while(pFilterList != NULL) - { - if(_wcscmp(pFilterList->pName, pFilterName)==0) - break; - pFilterList = pFilterList->pNext; - } - } - - PersistWatchExpression* pHeadFilterExpr = (pFilterList != NULL) ? pFilterList->pHeadExpr : NULL; - - WatchExpression* pExpression = pExpressionListHead; - int index = 1; - while(pExpression != NULL) - { - ExpressionNode* pResult = NULL; - if(FAILED(Status = ExpressionNode::CreateExpressionNode(pExpression->pExpression, &pResult))) - { - ExtOut(" %d) Error: HRESULT 0x%x while evaluating expression \'%S\'", index, Status, pExpression->pExpression); - } - else - { - //check for matching absolute expression - PersistWatchExpression* pCurFilterExpr = pHeadFilterExpr; - while(pCurFilterExpr != NULL) - { - if(_wcscmp(pCurFilterExpr->pExpression, pResult->GetAbsoluteExpression())==0) - break; - pCurFilterExpr = pCurFilterExpr->pNext; - } - - // check for matching persist evaluation on the matching expression - BOOL print = TRUE; - if(pCurFilterExpr != NULL) - { - WCHAR pCurPersistResult[MAX_EXPRESSION]; - FormatPersistResult(pCurPersistResult, MAX_EXPRESSION, pResult); - if(_wcscmp(pCurPersistResult, pCurFilterExpr->pPersistResult)==0) - { - print = FALSE; - } - } - - //expand and print - if(print) - { - if(index == expansionIndex) - pResult->Expand(expansionPath); - PrintCallbackData data; - data.index = index; - WCHAR pCommand[MAX_EXPRESSION]; - swprintf_s(pCommand, MAX_EXPRESSION, L"!watch -expand %d", index); - data.pCommand = pCommand; - pResult->DFSVisit(EvalPrintCallback, (VOID*)&data); - } - delete pResult; - } - pExpression = pExpression->pNext; - index++; - } - return Status; -} - -// Deletes an persisted watch list by name -HRESULT WatchCmd::RemoveList(__in_z WCHAR* pListName) -{ - PersistList** ppList = &pPersistListHead; - while(*ppList != NULL) - { - if(_wcscmp((*ppList)->pName, pListName) == 0) - { - PersistList* toDelete = *ppList; - *ppList = (*ppList)->pNext; - delete toDelete; - return S_OK; - } - ppList = &((*ppList)->pNext); - } - return S_FALSE; -} - -// Renames a previously saved persisted watch list -HRESULT WatchCmd::RenameList(__in_z WCHAR* pOldName, __in_z WCHAR* pNewName) -{ - if(_wcscmp(pOldName, pNewName)==0) - return S_OK; - PersistList** ppList = &pPersistListHead; - while(*ppList != NULL) - { - if(_wcscmp((*ppList)->pName, pOldName) == 0) - { - PersistList* pListToChangeName = *ppList; - RemoveList(pNewName); - wcsncpy_s(pListToChangeName->pName, MAX_EXPRESSION, pNewName, _TRUNCATE); - return S_OK; - } - ppList = &((*ppList)->pNext); - } - return S_FALSE; -} - -// Saves the active watch list together with the current evaluations as -// a new persisted watch list -HRESULT WatchCmd::SaveList(__in_z WCHAR* pSaveName) -{ - HRESULT Status = S_OK; - INIT_API_EE(); - INIT_API_DAC(); - IfFailRet(InitCorDebugInterface()); - - RemoveList(pSaveName); - PersistList* pList = new PersistList(); - wcsncpy_s(pList->pName, MAX_EXPRESSION, pSaveName, _TRUNCATE); - pList->pHeadExpr = NULL; - PersistCallbackData data; - data.ppNext = &(pList->pHeadExpr); - WatchExpression* pExpression = pExpressionListHead; - while(pExpression != NULL) - { - ExpressionNode* pResult = NULL; - if(SUCCEEDED(Status = ExpressionNode::CreateExpressionNode(pExpression->pExpression, &pResult))) - { - pResult->DFSVisit(PersistCallback, (VOID*)&data); - delete pResult; - } - pExpression = pExpression->pNext; - } - - pList->pNext = pPersistListHead; - pPersistListHead = pList; - return Status; -} - -// Saves the current watch list to file as a sequence of commands that will -// recreate the list -HRESULT WatchCmd::SaveListToFile(FILE* pFile) -{ - WatchExpression* pExpression = pExpressionListHead; - while(pExpression != NULL) - { - fprintf_s(pFile, "!watch -a %S\n", pExpression->pExpression); - pExpression = pExpression->pNext; - } - return S_OK; -} - -// Escapes characters that would be interpretted as DML markup, namely angle brackets -// that often appear in generic type names -VOID WatchCmd::DmlEscape(__in_ecount(cchInput) WCHAR* pInput, int cchInput, __in_ecount(cchOutput) WCHAR* pEscapedOutput, int cchOutput) -{ - pEscapedOutput[0] = L'\0'; - for(int i = 0; i < cchInput; i++) - { - if(pInput[i] == L'<') - { - if(0 != wcscat_s(pEscapedOutput, cchOutput, L"<")) return; - pEscapedOutput += 4; - cchOutput -= 4; - } - else if(pInput[i] == L'>') - { - if(0 != wcscat_s(pEscapedOutput, cchOutput, L">")) return; - pEscapedOutput += 4; - cchOutput -= 4; - } - else if(cchOutput > 1) - { - pEscapedOutput[0] = pInput[i]; - pEscapedOutput[1] = '\0'; - pEscapedOutput++; - cchOutput--; - } - if(pInput[i] == L'\0' || cchOutput == 1) break; - } -} - -// A DFS traversal callback for the expression node tree that prints it -VOID WatchCmd::EvalPrintCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData) -{ - PrintCallbackData* pData = (PrintCallbackData*)pUserData; - for(int i = 0; i < depth; i++) ExtOut(" "); - if(depth == 0) - ExtOut(" %d) ", pData->index); - else - ExtOut(" |- "); - if(pExpressionNode->GetErrorMessage()[0] != 0) - { - ExtOut("%S (%S)\n", pExpressionNode->GetRelativeExpression(), pExpressionNode->GetErrorMessage()); - } - else - { - // names can have '<' and '>' in them, need to escape - WCHAR pEscapedTypeName[MAX_EXPRESSION]; - DmlEscape(pExpressionNode->GetTypeName(), (int)_wcslen(pExpressionNode->GetTypeName()), pEscapedTypeName, MAX_EXPRESSION); - WCHAR pRelativeExpression[MAX_EXPRESSION]; - DmlEscape(pExpressionNode->GetRelativeExpression(), (int)_wcslen(pExpressionNode->GetRelativeExpression()), pRelativeExpression, MAX_EXPRESSION); - DMLOut("%S %S %S\n", pEscapedTypeName, pData->pCommand, pEscapedTypeName, pExpressionNode->GetAbsoluteExpression(), pRelativeExpression, pExpressionNode->GetTextValue()); - } -} - -// A DFS traversal callback for the expression node tree that saves all the values into a new -// persisted watch list -VOID WatchCmd::PersistCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData) -{ - PersistCallbackData* pData = (PersistCallbackData*)pUserData; - if(depth != 0) - return; - - PersistWatchExpression* pPersistExpr = new PersistWatchExpression(); - wcsncpy_s(pPersistExpr->pExpression, MAX_EXPRESSION, pExpressionNode->GetAbsoluteExpression(), _TRUNCATE); - FormatPersistResult(pPersistExpr->pPersistResult, MAX_EXPRESSION, pExpressionNode); - pPersistExpr->pNext = NULL; - *(pData->ppNext) = pPersistExpr; - pData->ppNext = &(pPersistExpr->pNext); -} - -// Determines how the value of an expression node is saved as a persisted result. This effectively determines -// the definition of equality when determining if an expression has changed value -VOID WatchCmd::FormatPersistResult(__inout_ecount(cchPersistResult) WCHAR* pPersistResult, DWORD cchPersistResult, ExpressionNode* pExpressionNode) -{ - if(pExpressionNode->GetErrorMessage()[0] != 0) - { - _snwprintf_s(pPersistResult, MAX_EXPRESSION, _TRUNCATE, L"%s (%s)\n", pExpressionNode->GetRelativeExpression(), pExpressionNode->GetErrorMessage()); - } - else - { - _snwprintf_s(pPersistResult, MAX_EXPRESSION, _TRUNCATE, L"%s %s %s\n", pExpressionNode->GetTypeName(), pExpressionNode->GetRelativeExpression(), pExpressionNode->GetTextValue()); - } -} diff --git a/src/ToolBox/SOS/Strike/WatchCmd.h b/src/ToolBox/SOS/Strike/WatchCmd.h deleted file mode 100644 index a34e391..0000000 --- a/src/ToolBox/SOS/Strike/WatchCmd.h +++ /dev/null @@ -1,110 +0,0 @@ -// 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. - -#ifndef _WATCH_CMD_ -#define _WATCH_CMD_ - -#ifdef FEATURE_PAL -#error This file not designed for use with FEATURE_PAL -#endif - -#include "ExpressionNode.h" -#include "windows.h" - -// A linked list node for watch expressions -typedef struct _WatchExpression -{ - WCHAR pExpression[MAX_EXPRESSION]; - _WatchExpression* pNext; - -} WatchExpression; - -// A linked list node that stores both the watch expression and a persisted result -// of the evaluation at some point in the past -typedef struct _PersistWatchExpression -{ - WCHAR pExpression[MAX_EXPRESSION]; - WCHAR pPersistResult[MAX_EXPRESSION]; - _PersistWatchExpression* pNext; - -} PersistWatchExpression; - -// A named list of persisted watch expressions, each of which has an expression and -// a saved value -typedef struct _PersistList -{ - ~_PersistList(); - WCHAR pName[MAX_EXPRESSION]; - PersistWatchExpression* pHeadExpr; - _PersistList* pNext; -} PersistList; - -// An API for the functionality in the !watch command -class WatchCmd -{ -public: - WatchCmd(); - ~WatchCmd(); - - // Deletes all current watch expressions from the watch list - // (does not delete persisted watch lists though) - HRESULT Clear(); - - // Adds a new expression to the active watch list - HRESULT Add(__in_z WCHAR* pExpression); - - // removes an expression at the given index in the active watch list - HRESULT Remove(int index); - - // Evaluates and prints a tree version of the active watch list - // The tree will be expanded along the nodes in expansionPath - // Optionally the list is filtered to only show differences from pFilterName (the name of a persisted watch list) - HRESULT Print(int expansionIndex, __in_z WCHAR* expansionPath, __in_z WCHAR* pFilterName); - - // Deletes an persisted watch list by name - HRESULT RemoveList(__in_z WCHAR* pListName); - - // Renames a previously saved persisted watch list - HRESULT RenameList(__in_z WCHAR* pOldName, __in_z WCHAR* pNewName); - - // Saves the active watch list together with the current evaluations as - // a new persisted watch list - HRESULT SaveList(__in_z WCHAR* pSaveName); - - // Saves the current watch list to file as a sequence of commands that will - // recreate the list - HRESULT SaveListToFile(FILE* pFile); - -private: - WatchExpression* pExpressionListHead; - PersistList* pPersistListHead; - - // Escapes characters that would be interpretted as DML markup, namely angle brackets - // that often appear in generic type names - static VOID DmlEscape(__in_z WCHAR* pInput, int cchInput, __inout_ecount(cchOutput) WCHAR* pEscapedOutput, int cchOutput); - - typedef struct _PrintCallbackData - { - int index; - WCHAR* pCommand; - } PrintCallbackData; - - // A DFS traversal callback for the expression node tree that prints it - static VOID EvalPrintCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData); - - typedef struct _PersistCallbackData - { - PersistWatchExpression** ppNext; - } PersistCallbackData; - - // A DFS traversal callback for the expression node tree that saves all the values into a new - // persisted watch list - static VOID PersistCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData); - - // Determines how the value of an expression node is saved as a persisted result. This effectively determines - // the definition of equality when determining if an expression has changed value - static VOID FormatPersistResult(__inout_ecount(cchPersistResult) WCHAR* pPersistResult, DWORD cchPersistResult, ExpressionNode* pExpressionNode); -}; - -#endif diff --git a/src/ToolBox/SOS/Strike/apollososdocs.txt b/src/ToolBox/SOS/Strike/apollososdocs.txt deleted file mode 100644 index d2ac481..0000000 --- a/src/ToolBox/SOS/Strike/apollososdocs.txt +++ /dev/null @@ -1,2755 +0,0 @@ -------------------------------------------------------------------------------- -NOTE: THIS FILE CONTAINS SOS DOCUMENTATION. THE FORMAT OF THE FILE IS: - - -COMMAND: - -\\ - - - -The first command is "contents" which is the general help screen. The rest -correspond to SOS command names. This file is embedded as a resource in the SOS -binary. Be sure to list any new commands here. -------------------------------------------------------------------------------- - - - -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. - -Object Inspection Examining code and stacks ------------------------------ ----------------------------- -DumpObj (do) Threads -DumpArray (da) ThreadState -DumpAsync IP2MD -DumpDelegate U -DumpStackObjects (dso) DumpStack -DumpHeap EEStack -DumpVC CLRStack -GCRoot GCInfo -ObjSize EHInfo -FinalizeQueue BPMD -PrintException (pe) COMState -TraverseHeap StopOnCatch -Watch SuppressJitOptimization - - -Examining CLR data structures Diagnostic Utilities ------------------------------ ----------------------------- -DumpDomain VerifyHeap -EEHeap VerifyObj -Name2EE FindRoots -SyncBlk HeapStat -DumpMT GCWhere -DumpClass ListNearObj (lno) -DumpMD GCHandles -Token2EE GCHandleLeaks -EEVersion FinalizeQueue (fq) -DumpModule FindAppDomain -ThreadPool SaveModule -DumpAssembly ProcInfo -DumpSigElem StopOnException (soe) -DumpRuntimeTypes DumpLog -DumpSig VMMap -RCWCleanupList VMStat -DumpIL MinidumpMode -DumpRCW AnalyzeOOM (ao) -DumpCCW - -Examining the GC history Other ------------------------------ ----------------------------- -HistInit FAQ -HistRoot SaveState -HistObj -HistObjFind -HistClear -\\ - -COMMAND: faq. ->> Where can I get the right version of SOS for my build? - -If you are running version 1.1 or 2.0 of the CLR, SOS.DLL is installed in the -same directory as the main CLR dll (CLR.DLL). Newer versions of the -Windows Debugger provide a command to make it easy to load the right copy of -SOS.DLL: - - ".loadby sos clr" - -That will load the SOS extension DLL from the same place that CLR.DLL is -loaded in the process. You shouldn't attempt to use a version of SOS.DLL that -doesn't match the version of CLR.DLL. You can find the version of -CLR.DLL by running - - "lmvm clr" - -in the debugger. Note that if you are running CoreCLR (e.g. Silverlight) -then you should replace "clr" with "coreclr". - -If you are using a dump file created on another machine, it is a little bit -more complex. You need to make sure the mscordacwks.dll file that came with -that install is on your symbol path, and you need to load the corresponding -version of sos.dll (typing .load rather than using the -.loadby shortcut). Within the Microsoft corpnet, we keep tagged versions -of mscordacwks.dll, with names like mscordacwks__.dll -that the Windows Debugger can load. If you have the correct symbol path to the -binaries for that version of the Runtime, the Windows Debugger will load the -correct mscordacwks.dll file. - ->> I have a chicken and egg problem. I want to use SOS commands, but the CLR - isn't loaded yet. What can I do? - -In the debugger at startup you can type: - - "sxe clrn" - -Let the program run, and it will stop with the notice - - "CLR notification: module 'mscorlib' loaded" - -At this time you can use SOS commands. To turn off spurious notifications, -type: - - "sxd clrn" - ->> I got the following error message. Now what? - - 0:000> .loadby sos clr - 0:000> !DumpStackObjects - Failed to find runtime DLL (clr.dll), 0x80004005 - Extension commands need clr.dll in order to have something to do. - 0:000> - -This means that the CLR is not loaded yet, or has been unloaded. You need to -wait until your managed program is running in order to use these commands. If -you have just started the program a good way to do this is to type - - bp clr!EEStartup "g @$ra" - -in the debugger, and let it run. After the function EEStartup is finished, -there will be a minimal managed environment for executing SOS commands. - ->> I have a partial memory minidump, and !DumpObj doesn't work. Why? - -In order to run SOS commands, many CLR data structures need to be traversed. -When creating a minidump without full memory, special functions are called at -dump creation time to bring those structures into the minidump, and allow a -minimum set of SOS debugging commands to work. At this time, those commands -that can provide full or partial output are: - -CLRStack -Threads -Help -PrintException -EEVersion - -For a minidump created with this minimal set of functionality in mind, you -will get an error message when running any other commands. A full memory dump -(obtained with ".dump /ma " in the Windows Debugger) is often the -best way to debug a managed program at this level. - ->> What other tools can I use to find my bug? - -Turn on Managed Debugging Assistants. These enable additional runtime diagnostics, -particularly in the area of PInvoke/Interop. Adam Nathan has written some great -information about that: - -http://blogs.msdn.com/adam_nathan/ - ->> Does SOS support DML? - -Yes. SOS respects the .prefer_dml option in the debugger. If this setting is -turned on, then SOS will output DML by default. Alternatively, you may leave -it off and add /D to the beginning of a command to get DML based output for it. -Not all SOS commands support DML output. - -\\ - -COMMAND: stoponexception. -!StopOnException [-derived] - [-create | -create2] - - [] - -!StopOnException helps when you want the Windows Debugger to stop on a -particular managed exception, say a System.OutOfMemoryException, but continue -running if other exceptions are thrown. The command can be used in two ways: - -1) When you just want to stop on one particular CLR exception - - At the debugger prompt, anytime after loading SOS, type: - - !StopOnException -create System.OutOfMemoryException 1 - - The pseudo-register number (1) indicates that SOS can use register $t1 for - maintaining the breakpoint. The -create parameter allows SOS to go ahead - and set up the breakpoint as a first-chance exception. -create2 would set - it up as a 2nd-chance exception. - -2) When you need more complex logic for stopping on a CLR exception - - !StopOnException can be used purely as a predicate in a larger expression. - If you type: - - !StopOnException System.OutOfMemoryException 3 - - then register $t3 will be set to 1 if the last thrown exception on the - current thread is a System.OutOfMemoryException. Otherwise, $t3 will be set - to 0. Using the Windows Debugger scripting language, you could chain - such calls together to stop on various exception types. You'll have to - manually create such predicates, for example: - - sxe -c "!soe System.OutOfMemoryException 3; - !soe -derived System.IOException 4; - .if(@$t3==1 || @$t4==1) { .echo 'stop' } .else {g}" - -The -derived option will cause StopOnException to set the pseudo-register to -1 even if the thrown exception type doesn't exactly match the exception type -given, but merely derives from it. So, "-derived System.Exception" would catch -every exception in the System.Exception heirarchy. - -The pseudo-register number is optional. If you don't pass a number, SOS will -use pseudo-register $t1. - -Note that !PrintException with no parameters will print out the last thrown -exception on the current thread (if any). You can use !soe as a shortcut for -!StopOnException. -\\ - -COMMAND: minidumpmode. -!MinidumpMode <0 or 1> - -Minidumps created with ".dump /m" or ".dump" have a very small set of -CLR-specific data, just enough to run a subset of SOS commands correctly. You -are able to run other SOS commands, but they may fail with unexpected errors -because required areas of memory are not mapped in or only partially mapped -in. At this time, SOS cannot reliably detect if a dump file is of this type -(for one thing, custom dump commands can map in additional memory, but there -is no facility to read meta-information about this memory). You can turn this -option on to protect against running unsafe commands against small minidumps. - -By default, MinidumpMode is 0, so there is no restriction on commands that will -run against a minidump. -\\ - -COMMAND: dumpobj. -!DumpObj [-nofields] - -This command allows you to examine the fields of an object, as well as learn -important properties of the object such as the EEClass, the MethodTable, and -the size. - -You might find an object pointer by running !DumpStackObjects and choosing -from the resultant list. Here is a simple object: - - 0:000> !DumpObj a79d40 - Name: Customer - MethodTable: 009038ec - EEClass: 03ee1b84 - Size: 20(0x14) bytes - (C:\pub\unittest.exe) - Fields: - MT Field Offset Type VT Attr Value Name - 009038ec 4000008 4 Customer 0 instance 00a79ce4 name - 009038ec 4000009 8 Bank 0 instance 00a79d2c bank - -Note that fields of type Customer and Bank are themselves objects, and you can -run !DumpObj on them too. You could look at the field directly in memory using -the offset given. "dd a79d40+8 l1" would allow you to look at the bank field -directly. Be careful about using this to set memory breakpoints, since objects -can move around in the garbage collected heap. - -What else can you do with an object? You might run !GCRoot, to determine what -roots are keeping it alive. Or you can find all objects of that type with -"!DumpHeap -type Customer". - -The column VT contains the value 1 if the field is a valuetype structure, and -0 if the field contains a pointer to another object. For valuetypes, you can -take the MethodTable pointer in the MT column, and the Value and pass them to -the command !DumpVC. - -The abbreviation !do can be used for brevity. - -The arguments in detail: --nofields: do not print fields of the object, useful for objects like - String -\\ - -COMMAND: dumparray. -!DumpArray - [-start ] - [-length ] - [-details] - [-nofields] - - -This command allows you to examine elements of an array object. -The arguments in detail: - -start : optional, only supported for single dimension array. - Specify from which index the command shows the elements. - -length : optional, only supported for single dimension array. - Specify how many elements to show. - -details: optional. Ask the command to print out details - of the element using !DumpObj and !DumpVC format. - -nofields: optional, only takes effect when -details is used. Do - not print fields of the elements. Useful for arrays of - objects like String - - Example output: - - 0:000> !dumparray -start 2 -length 3 -details 00ad28d0 - Name: Value[] - MethodTable: 03e41044 - EEClass: 03e40fc0 - Size: 132(0x84) bytes - Array: Rank 1, Number of elements 10, Type VALUETYPE - Element Type: Value - [2] 00ad28f0 - Name: Value - MethodTable 03e40f4c - EEClass: 03ef1698 - Size: 20(0x14) bytes - (C:\bugs\225271\arraytest.exe) - Fields: - MT Field Offset Type Attr Value Name - 5b9a628c 4000001 0 System.Int32 instance 2 x - 5b9a628c 4000002 4 System.Int32 instance 4 y - 5b9a628c 4000003 8 System.Int32 instance 6 z - [3] 00ad28fc - Name: Value - MethodTable 03e40f4c - EEClass: 03ef1698 - Size: 20(0x14) bytes - (C:\bugs\225271\arraytest.exe) - Fields: - MT Field Offset Type Attr Value Name - 5b9a628c 4000001 0 System.Int32 instance 3 x - 5b9a628c 4000002 4 System.Int32 instance 6 y - 5b9a628c 4000003 8 System.Int32 instance 9 z - [4] 00ad2908 - Name: Value - MethodTable 03e40f4c - EEClass: 03ef1698 - Size: 20(0x14) bytes - (C:\bugs\225271\arraytest.exe) - Fields: - MT Field Offset Type Attr Value Name - 5b9a628c 4000001 0 System.Int32 instance 4 x - 5b9a628c 4000002 4 System.Int32 instance 8 y - 5b9a628c 4000003 8 System.Int32 instance 12 z - -\\ - -COMMAND: dumpasync. -!DumpAsync [-addr ] - [-mt ] - [-type ] - [-tasks] - [-completed] - [-fields] - [-stacks] - [-roots] - -!DumpAsync traverses the garbage collected heap, looking for objects representing -async state machines as created when an async method's state is transferred to the -heap. This command recognizes async state machines defined as "async void", "async Task", -"async Task", "async ValueTask", and "async ValueTask". It also optionally supports -any other tasks. -\\ - -COMMAND: dumpstackobjects. -!DumpStackObjects [-verify] [top stack [bottom stack]] - -This command will display any managed objects it finds within the bounds of -the current stack. Combined with the stack tracing commands like K and -!CLRStack, it is a good aid to determining the values of locals and -parameters. - -If you use the -verify option, each non-static CLASS field of an object -candidate is validated. This helps to eliminate false positives. It is not -on by default because very often in a debugging scenario, you are -interested in objects with invalid fields. - -The abbreviation !dso can be used for brevity. -\\ - -COMMAND: dumpdelegate. -!DumpDelegate - -!DumpDelegate finds and outputs the one or more method descriptors associated with a delegate object. - -For example: - - 0:000> !dumpdelegate - Target Method Name - 000001461bacb0d8 00007ffc5c894b80 ConsoleApp16.Program.InstanceMethod() - 000001461bacb098 00007ffc5c894b68 ConsoleApp16.Program.StaticMethod() -\\ - -COMMAND: dumpheap. -!DumpHeap [-stat] - [-strings] - [-short] - [-min ] - [-max ] - [-thinlock] - [-startAtLowerBound] - [-mt ] - [-type ] - [start [end]] - -!DumpHeap is a powerful command that traverses the garbage collected heap, -collection statistics about objects. With it's various options, it can look for -particular types, restrict to a range, or look for ThinLocks (see !SyncBlk -documentation). Finally, it will provide a warning if it detects excessive -fragmentation in the GC heap. - -When called without options, the output is first a list of objects in the heap, -followed by a report listing all the types found, their size and number: - - 0:000> !dumpheap - Address MT Size - 00a71000 0015cde8 12 Free - 00a7100c 0015cde8 12 Free - 00a71018 0015cde8 12 Free - 00a71024 5ba58328 68 - 00a71068 5ba58380 68 - 00a710ac 5ba58430 68 - 00a710f0 5ba5dba4 68 - ... - total 619 objects - Statistics: - MT Count TotalSize Class Name - 5ba7607c 1 12 System.Security.Permissions.HostProtectionResource - 5ba75d54 1 12 System.Security.Permissions.SecurityPermissionFlag - 5ba61f18 1 12 System.Collections.CaseInsensitiveComparer - ... - 0015cde8 6 10260 Free - 5ba57bf8 318 18136 System.String - ... - -"Free" objects are simply regions of space the garbage collector can use later. -If 30% or more of the heap contains "Free" objects, the process may suffer from -heap fragmentation. This is usually caused by pinning objects for a long time -combined with a high rate of allocation. Here is example output where !DumpHeap -provides a warning about fragmentation: - - - Fragmented blocks larger than 1MB: - Addr Size Followed by - 00a780c0 1.5MB 00bec800 System.Byte[] - 00da4e38 1.2MB 00ed2c00 System.Byte[] - 00f16df0 1.2MB 01044338 System.Byte[] - -The arguments in detail: - --stat Restrict the output to the statistical type summary --strings Restrict the output to a statistical string value summary --short Limits output to just the address of each object. This allows you - to easily pipe output from the command to another debugger - command for automation. --min Ignore objects less than the size given in bytes --max Ignore objects larger than the size given in bytes --thinlock Report on any ThinLocks (an efficient locking scheme, see !SyncBlk - documentation for more info) --startAtLowerBound - Force heap walk to begin at lower bound of a supplied address range. - (During plan phase, the heap is often not walkable because objects - are being moved. In this case, DumpHeap may report spurious errors, - in particular bad objects. It may be possible to traverse more of - the heap after the reported bad object. Even if you specify an - address range, !DumpHeap will start its walk from the beginning of - the heap by default. If it finds a bad object before the specified - range, it will stop before displaying the part of the heap in which - you are interested. This switch will force !DumpHeap to begin its - walk at the specified lower bound. You must supply the address of a - good object as the lower bound for this to work. Display memory at - the address of the bad object to manually find the next method - table (use !dumpmt to verify). If the GC is currently in a call to - memcopy, You may also be able to find the next object's address by - adding the size to the start address given as parameters.) --mt List only those objects with the MethodTable given --type List only those objects whose type name is a substring match of the - string provided. -start Begin listing from this address -end Stop listing at this address - -A special note about -type: Often, you'd like to find not only Strings, but -System.Object arrays that are constrained to contain Strings. ("new -String[100]" actually creates a System.Object array, but it can only hold -System.String object pointers). You can use -type in a special way to find -these arrays. Just pass "-type System.String[]" and those Object arrays will -be returned. More generally, "-type []". - -The start/end parameters can be obtained from the output of !EEHeap -gc. For -example, if you only want to list objects in the large heap segment: - - 0:000> !eeheap -gc - Number of GC Heaps: 1 - generation 0 starts at 0x00c32754 - generation 1 starts at 0x00c32748 - generation 2 starts at 0x00a71000 - segment begin allocated size - 00a70000 00a71000 010443a8 005d33a8(6108072) - Large object heap starts at 0x01a71000 - segment begin allocated size - 01a70000 01a71000 01a75000 0x00004000(16384) - Total Size 0x5d73a8(6124456) - ------------------------------ - GC Heap Size 0x5d73a8(6124456) - - 0:000> !dumpheap 1a71000 1a75000 - Address MT Size - 01a71000 5ba88bd8 2064 - 01a71810 0019fe48 2032 Free - 01a72000 5ba88bd8 4096 - 01a73000 0019fe48 4096 Free - 01a74000 5ba88bd8 4096 - total 5 objects - Statistics: - MT Count TotalSize Class Name - 0019fe48 2 6128 Free - 5ba88bd8 3 10256 System.Object[] - Total 5 objects - -Finally, if GC heap corruption is present, you may see an error like this: - - 0:000> !dumpheap -stat - object 00a73d24: does not have valid MT - curr_object : 00a73d24 - Last good object: 00a73d14 - ---------------- - -That indicates a serious problem. See the help for !VerifyHeap for more -information on diagnosing the cause. -\\ - -COMMAND: dumpvc. -!DumpVC
- -!DumpVC allows you to examine the fields of a value class. In C#, this is a -struct, and lives on the stack or within an Object on the GC heap. You need -to know the MethodTable address to tell SOS how to interpret the fields, as -a value class is not a first-class object with it's own MethodTable as the -first field. For example: - - 0:000> !DumpObj a79d98 - Name: Mainy - MethodTable: 009032d8 - EEClass: 03ee1424 - Size: 28(0x1c) bytes - (C:\pub\unittest.exe) - Fields: - MT Field Offset Type Attr Value Name - 0090320c 4000010 4 VALUETYPE instance 00a79d9c m_valuetype - 009032d8 400000f 4 CLASS static 00a79d54 m_sExcep - -m_valuetype is a value type. The value in the MT column (0090320c) is the -MethodTable for it, and the Value column provides the start address: - - 0:000> !DumpVC 0090320c 00a79d9c - Name: Funny - MethodTable 0090320c - EEClass: 03ee14b8 - Size: 28(0x1c) bytes - (C:\pub\unittest.exe) - Fields: - MT Field Offset Type Attr Value Name - 0090320c 4000001 0 CLASS instance 00a743d8 signature - 0090320c 4000002 8 System.Int32 instance 2345 m1 - 0090320c 4000003 10 System.Boolean instance 1 b1 - 0090320c 4000004 c System.Int32 instance 1234 m2 - 0090320c 4000005 4 CLASS instance 00a79d98 backpointer - -!DumpVC is quite a specialized function. Some managed programs make heavy use -of value classes, while others do not. -\\ - -COMMAND: gcroot. -!GCRoot [-nostacks] - -!GCRoot looks for references (or roots) to an object. These can exist in four -places: - - 1. On the stack - 2. Within a GC Handle - 3. In an object ready for finalization - 4. As a member of an object found in 1, 2 or 3 above. - -First, all stacks will be searched for roots, then handle tables, and finally -the freachable queue of the finalizer. Some caution about the stack roots: -!GCRoot doesn't attempt to determine if a stack root it encountered is valid -or is old (discarded) data. You would have to use !CLRStack and !U to -disassemble the frame that the local or argument value belongs to in order to -determine if it is still in use. - -Because people often want to restrict the search to gc handles and freachable -objects, there is a -nostacks option. -\\ - -COMMAND: objsize. -!ObjSize [] | [-aggregate] [-stat] - -With no parameters, !ObjSize lists the size of all objects found on managed -threads. It also enumerates all GCHandles in the process, and totals the size -of any objects pointed to by those handles. In calculating object size, -!ObjSize includes the size of all child objects in addition to the parent. - -For example, !DumpObj lists a size of 20 bytes for this Customer object: - - 0:000> !do a79d40 - Name: Customer - MethodTable: 009038ec - EEClass: 03ee1b84 - Size: 20(0x14) bytes - (C:\pub\unittest.exe) - Fields: - MT Field Offset Type Attr Value Name - 009038ec 4000008 4 CLASS instance 00a79ce4 name - 009038ec 4000009 8 CLASS instance 00a79d2c bank - 009038ec 400000a c System.Boolean instance 1 valid - -but !ObjSize lists 152 bytes: - - 0:000> !ObjSize a79d40 - sizeof(00a79d40) = 152 ( 0x98) bytes (Customer) - -This is because a Customer points to a Bank, has a name, and the Bank points to -an Address string. You can use !ObjSize to identify any particularly large -objects, such as a managed cache in a web server. - -While running ObjSize with no arguments may point to specific roots that hold -onto large amounts of memory it does not provide information regarding the -amount of managed memory that is still alive. This is due to the fact that a -number of roots can share a common subgraph, and that part will be reported in -the size of all the roots that reference the subgraph. The -aggregate argument -is meant to answer this question: - -The -aggregate option can be used in conjunction with the -stat argument to get -a detailed view of what are the types that are still rooted. Using !dumpheap --stat and !objsize -aggregate -stat one can determine what are the the objects -that are not rooted any more and diagnose various memory issues. - -Sample output when using the -aggregate and -stat: - - 0:003> !ObjSize -aggregate -stat - Scan Thread 0 OSTHread f70 - Scan Thread 2 OSTHread ef8 - Statistics: - MT Count TotalSize Class Name - 01e63768 1 12 Test+d - 01e63660 1 16 Test+c - 01e63548 1 16 Test+b - 01e632f8 1 16 Test+a - ... - 5b6c6d40 9 504 System.Collections.Hashtable - 5b6ebe28 3 552 System.Byte[] - 5b6ec0bc 9 1296 System.Collections.Hashtable+bucket[] - 5b6ec200 9 1436 System.Char[] - 5b6c447c 77 2468 System.String - 5b6ebd64 8 9020 System.Object[] - Total 203 objects - Total Memory Size 18332 (0x479c) bytes - -\\ - -COMMAND: finalizequeue. -!FinalizeQueue [-detail] | [-allReady] [-short] - -This command lists the objects registered for finalization. Here is output from -a simple program: - - 0:000> !finalizequeue - SyncBlocks to be cleaned up: 0 - MTA Interfaces to be released: 0 - STA Interfaces to be released: 1 - generation 0 has 4 finalizable objects (0015bc90->0015bca0) - generation 1 has 0 finalizable objects (0015bc90->0015bc90) - generation 2 has 0 finalizable objects (0015bc90->0015bc90) - Ready for finalization 0 objects (0015bca0->0015bca0) - Statistics: - MT Count TotalSize Class Name - 5ba6cf78 1 24 Microsoft.Win32.SafeHandles.SafeFileHandle - 5ba5db04 1 68 System.Threading.Thread - 5ba73e28 2 112 System.IO.StreamWriter - Total 4 objects - -The GC heap is divided into generations, and objects are listed accordingly. We -see that only generation 0 (the youngest generation) has any objects registered -for finalization. The notation "(0015bc90->0015bca0)" means that if you look at -memory in that range, you'll see the object pointers that are registered: - -0:000> dd 15bc90 15bca0-4 -0015bc90 00a743f4 00a79f00 00a7b3d8 00a7b47c - -You could run !DumpObj on any of those pointers to learn more. In this example, -there are no objects ready for finalization, presumably because they still have -roots (You can use !GCRoot to find out). The statistics section provides a -higher-level summary of the objects registered for finalization. Note that -objects ready for finalization are also included in the statistics (if any). - -Specifying -short will inhibit any display related to SyncBlocks or RCWs. - -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 - 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 - finalizer that are no longer rooted. If used independently it lists - all objects in the finalizable and "ready for finalization" queues. --detail 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. -\\ - -COMMAND: printexception. -!PrintException [-nested] [-lines] [] - -This will format fields of any object derived from System.Exception. One of the -more useful aspects is that it will format the _stackTrace field, which is a -binary array. If _stackTraceString field is not filled in, that can be helpful -for debugging. You can of course use !DumpObj on the same exception object to -explore more fields. - -If called with no parameters, PrintException will look for the last outstanding -exception on the current thread and print it. This will be the same exception -that shows up in a run of !Threads. - -!PrintException will notify you if there are any nested exceptions on the -current managed thread. (A nested exception occurs when you throw another -exception within a catch handler already being called for another exception). -If there are nested exceptions, you can re-run !PrintException with the -"-nested" option to get full details on the nested exception objects. The -!Threads command will also tell you which threads have nested exceptions. - -!PrintException can display source information if available, by specifying the --lines command line argument. - -The abbreviation !pe can be used for brevity. -\\ - -COMMAND: traverseheap. -!TraverseHeap [-xml] [-verify] - -!TraverseHeap writes out a file in a format understood by the CLR Profiler. -You can download the CLR Profiler from this link: - -http://www.microsoft.com/downloads/details.aspx?FamilyId=86CE6052-D7F4-4AEB- -9B7A-94635BEEBDDA&displaylang=en - -It creates a graphical display of the GC heap to help you analyze the state of -your application. - -If you pass the -verify option it will do more sanity checking of the heap -as it dumps it. Use this option if heap corruption is suspected. - -If you pass the "-xml" flag, the file is instead written out in an easy to -understand xml format: - - - - - ... - - - - - ... - - - - - - ... - - ... - - - -You can break into your process, load SOS, take a snapshot of your heap with -this function, then continue. -\\ -COMMAND: threadstate. -!ThreadState value - -The !Threads command outputs, among other things, the state of the thread. -This is a bit field which corresponds to various states the thread is in. -To check the state of the thread, simply pass that bit field from the -output of !Threads into !ThreadState. - -Example: - 0:003> !Threads - ThreadCount: 2 - UnstartedThread: 0 - BackgroundThread: 1 - PendingThread: 0 - DeadThread: 0 - Hosted Runtime: no - PreEmptive GC Alloc Lock - ID OSID ThreadOBJ State GC Context Domain Count APT Exception - 0 1 250 0019b068 a020 Disabled 02349668:02349fe8 0015def0 0 MTA - 2 2 944 001a6020 b220 Enabled 00000000:00000000 0015def0 0 MTA (Finalizer) - 0:003> !ThreadState b220 - Legal to Join - Background - CLR Owns - CoInitialized - In Multi Threaded Apartment - -Possible thread states: - Thread Abort Requested - GC Suspend Pending - User Suspend Pending - Debug Suspend Pending - GC On Transitions - Legal to Join - Yield Requested - Hijacked by the GC - Blocking GC for Stack Overflow - Background - Unstarted - Dead - CLR Owns - CoInitialized - In Single Threaded Apartment - In Multi Threaded Apartment - Reported Dead - Fully initialized - Task Reset - Sync Suspended - Debug Will Sync - Stack Crawl Needed - Suspend Unstarted - Aborted - Thread Pool Worker Thread - Interruptible - Interrupted - Completion Port Thread - Abort Initiated - Finalized - Failed to Start - Detached -\\ -COMMAND: threads. -!Threads [-live] [-special] - -!Threads lists all the mananaged threads in the process. - --live: optional. Only print threads associated with a live thread. --special: optional. With this switch, the command will display all the special - threads created by CLR. Those threads might not be managed threads - so they might not be shown in the first part of the command's - output. Example of special threads include: GC threads (in - concurrent GC and server GC), Debugger helper threads, Finalizer - threads, AppDomain Unload threads, and Threadpool timer threads. - -Each thread has many attributes, many of which can be ignored. The important -ones are discussed below: - -There are three ID columns: - -1) The debugger shorthand ID (When the runtime is hosted this column might - display the special string "<<<<" when this internal thread object is not - associated with any physical thread - this may happen when the host reuses - the runtime internal thread object) -2) The CLR Thread ID -3) The OS thread ID. - -If PreEmptiveGC is enabled for a thread, then a garbage collection -can occur while that thread is running. For example, if you break in while -a managed thread is making a PInvoke call to a Win32 function, that thread -will be in PreEmptive GC mode. - -The Domain column indicates what AppDomain the thread is currently executing -in. You can pass this value to !DumpDomain to find out more. - -The APT column gives the COM apartment mode. - -Exception will list the last thrown exception (if any) for the thread. More -details can be obtained by passing the pointer value to !PrintException. If -you get the notation "(nested exceptions)", you can get details on those -exceptions by switching to the thread in question, and running -"!PrintException -nested". -\\ - -COMMAND: clrstack. -!CLRStack [-a] [-l] [-p] [-n] -!CLRStack [-a] [-l] [-p] [-i] [variable name] [frame] - -CLRStack attempts to provide a true stack trace for managed code only. It is -handy for clean, simple traces when debugging straightforward managed -programs. The -p parameter will show arguments to the managed function. The --l parameter can be used to show information on local variables in a frame. -SOS can't retrieve local names at this time, so the output for locals is in -the format = . The -a (all) parameter is a short-cut -for -l and -p combined. - -If the debugger has the option SYMOPT_LOAD_LINES specified (either by the -.lines or .symopt commands), SOS will look up the symbols for every managed -frame and if successful will display the corresponding source file name and -line number. The -n (No line numbers) parameter can be specified to disable -this behavior. - -When you see methods with the name "[Frame:...", that indicates a transition -between managed and unmanaged code. You could run !IP2MD on the return -addresses in the call stack to get more information on each managed method. - -On x64 platforms, Transition Frames are not displayed at this time. To avoid -heavy optimization of parameters and locals one can request the JIT compiler -to not optimize functions in the managed app by creating a file myapp.ini -(if your program is myapp.exe) in the same directory. Put the following lines -in myapp.ini and re-run: - -[.NET Framework Debugging Control] -GenerateTrackingInfo=1 -AllowOptimize=0 - -The -i option is a new EXPERIMENTAL addition to CLRStack and will use the ICorDebug -interfaces to display the managed stack and variables. With this option you can also -view and expand arrays and fields for managed variables. If a stack frame number is -specified in the command line, CLRStack will show you the parameters and/or locals -only for that frame (provided you specify -l or -p or -a of course). If a variable -name and a stack frame number are specified in the command line, CLRStack will show -you the parameters and/or locals for that frame, and will also show you the fields -for that variable name you specified. Here are some examples: - !CLRStack -i -a : This will show you all parameters and locals for all frames - !CLRStack -i -a 3 : This will show you all parameters and locals, for frame 3 - !CLRStack -i var1 0 : This will show you the fields of 'var1' for frame 0 - !CLRStack -i var1.abc 2 : This will show you the fields of 'var1', and expand - 'var1.abc' to show you the fields of the 'abc' field, - for frame 2. - !CLRStack -i var1.[basetype] 0 : This will show you the fields of 'var1', and - expand the base type of 'var1' to show you its - fields. - !CLRStack -i var1.[6] 0 : If 'var1' is an array, this will show you the element - at index 6 in the array, along with its fields -The -i options uses DML output for a better debugging experience, so typically you -should only need to execute "!CLRStack -i", and from there, click on the DML -hyperlinks to inspect the different managed stack frames and managed variables. -\\ - -COMMAND: ip2md. -!IP2MD - -Given an address in managed JITTED code, IP2MD attempts to find the MethodDesc -associated with it. For example, this output from K: - - 0:000> K - ChildEBP RetAddr - 00a79c78 03ef02ab image00400000!Mainy.Top()+0xb - 00a79c78 03ef01a6 image00400000!Mainy.Level(Int32)+0xb - 00a79c78 5d3725a1 image00400000!Mainy.Main()+0xee - 0012ea04 5d512f59 clr!CallDescrWorkerInternal+0x30 - 0012ee34 5d7946aa clr!CallDescrWorker+0x109 - - 0:000> !IP2MD 03ef01a6 - MethodDesc: 00902f40 - Method Name: Mainy.Main() - Class: 03ee1424 - MethodTable: 009032d8 - mdToken: 0600000d - Module: 001caa38 - IsJitted: yes - CodeAddr: 03ef00b8 - Source file: c:\Code\prj.mini\exc.cs @ 39 - -We have taken a return address into Mainy.Main, and discovered information -about that method. You could run !U, !DumpMT, !DumpClass, !DumpMD, or -!DumpModule on the fields listed to learn more. - -The "Source line" output will only be present if the debugger can find the -symbols for the managed module containing the given , and if the -debugger is configured to load line number information. -\\ - -COMMAND: u. -!U [-gcinfo] [-ehinfo] [-n] | - -Presents an annotated disassembly of a managed method when given a MethodDesc -pointer for the method, or a code address within the method body. Unlike the -debugger "U" function, the entire method from start to finish is printed, -with annotations that convert metadata tokens to names. - - - ... - 03ef015d b901000000 mov ecx,0x1 - 03ef0162 ff156477a25b call dword ptr [mscorlib_dll+0x3c7764 (5ba27764)] (System.Console.InitializeStdOutError(Boolean), mdToken: 06000713) - 03ef0168 a17c20a701 mov eax,[01a7207c] (Object: SyncTextWriter) - 03ef016d 89442414 mov [esp+0x14],eax - -If you pass the -gcinfo flag, you'll get inline display of the GCInfo for -the method. You can also obtain this information with the !GCInfo command. - -If you pass the -ehinfo flag, you'll get inline display of exception info -for the method. (Beginning and end of try/finally/catch handlers, etc.). -You can also obtain this information with the !EHInfo command. - -If the debugger has the option SYMOPT_LOAD_LINES specified (either by the -.lines or .symopt commands), and if symbols are available for the managed -module containing the method being examined, the output of the command will -include the source file name and line number corresponding to the -disassembly. The -n (No line numbers) flag can be specified to disable this -behavior. - - - ... - c:\Code\prj.mini\exc.cs @ 38: - 001b00b0 8b0d3020ab03 mov ecx,dword ptr ds:[3AB2030h] ("Break in debugger. When done type to continue: ") - 001b00b6 e8d5355951 call mscorlib_ni+0x8b3690 (51743690) (System.Console.Write(System.String), mdToken: 0600091b) - 001b00bb 90 nop - - c:\Code\prj.mini\exc.cs @ 39: - 001b00bc e863cdc651 call mscorlib_ni+0xf8ce24 (51e1ce24) (System.Console.ReadLine(), mdToken: 060008f6) - >>> 001b00c1 90 nop - ... -\\ - -COMMAND: dumpstack. -!DumpStack [-EE] [-n] [top stack [bottom stack]] - -[x86 and x64 documentation] - -This command provides a verbose stack trace obtained by "scraping." Therefore -the output is very noisy and potentially confusing. The command is good for -viewing the complete call stack when "kb" gets confused. For best results, -make sure you have valid symbols. - --EE will only show managed functions. - -If the debugger has the option SYMOPT_LOAD_LINES specified (either by the -.lines or .symopt commands), SOS will look up the symbols for every managed -frame and if successful will display the corresponding source file name and -line number. The -n (No line numbers) parameter can be specified to disable -this behavior. - -You can also pass a stack range to limit the output. Use the debugger -extension !teb to get the top and bottom stack values. - -\\ - -COMMAND: eestack. -!EEStack [-short] [-EE] - -This command runs !DumpStack on all threads in the process. The -EE option is -passed directly to !DumpStack. The -short option tries to narrow down the -output to "interesting" threads only, which is defined by - -1) The thread has taken a lock. -2) The thread has been "hijacked" in order to allow a garbage collection. -3) The thread is currently in managed code. - -See the documentation for !DumpStack for more info. -\\ - -COMMAND: ehinfo. -!EHInfo ( | ) - -!EHInfo shows the exception handling blocks in a jitted method. For each -handler, it shows the type, including code addresses and offsets for the clause -block and the handler block. For a TYPED handler, this would be the "try" and -"catch" blocks respectively. - -Sample output: - - 0:000> !ehinfo 33bbd3a - MethodDesc: 03310f68 - Method Name: MainClass.Main() - Class: 03571358 - MethodTable: 0331121c - mdToken: 0600000b - Module: 001e2fd8 - IsJitted: yes - CodeAddr: 033bbca0 - - EHHandler 0: TYPED catch(System.IO.FileNotFoundException) - Clause: [033bbd2b, 033bbd3c] [8b, 9c] - Handler: [033bbd3c, 033bbd50] [9c, b0] - - EHHandler 1: FINALLY - Clause: [033bbd83, 033bbda3] [e3, 103] - Handler: [033bbda3, 033bbdc5] [103, 125] - - EHHandler 2: TYPED catch(System.Exception) - Clause: [033bbd7a, 033bbdc5] [da, 125] - Handler: [033bbdc5, 033bbdd6] [125, 136] - -\\ - -COMMAND: gcinfo. -!GCInfo ( | ) - -!GCInfo is especially useful for CLR Devs who are trying to determine if there -is a bug in the JIT Compiler. It parses the GCEncoding for a method, which is a -compressed stream of data indicating when registers or stack locations contain -managed objects. It is important to keep track of this information, because if -a garbage collection occurs, the collector needs to know where roots are so it -can update them with new object pointer values. - -Here is sample output where you can see the change in register state. Normally -you would print this output out and read it alongside a disassembly of the -method. For example, the notation "reg EDI becoming live" at offset 0x11 of the -method might correspond to a "mov edi,ecx" statement. - - 0:000> !gcinfo 5b68dbb8 (5b68dbb8 is the start of a JITTED method) - entry point 5b68dbb8 - preJIT generated code - GC info 5b9f2f09 - Method info block: - method size = 0036 - prolog size = 19 - epilog size = 8 - epilog count = 1 - epilog end = yes - saved reg. mask = 000B - ebp frame = yes - fully interruptible=yes - double align = no - security check = no - exception handlers = no - local alloc = no - edit & continue = no - varargs = no - argument count = 4 - stack frame size = 1 - untracked count = 5 - var ptr tab count = 0 - epilog at 002E - 36 D4 8C C7 AA | - 93 F3 40 05 | - - Pointer table: - 14 | [EBP+14H] an untracked local - 10 | [EBP+10H] an untracked local - 0C | [EBP+0CH] an untracked local - 08 | [EBP+08H] an untracked local - 44 | [EBP-04H] an untracked local - F1 79 | 0011 reg EDI becoming live - 72 | 0013 reg ESI becoming live - 83 | 0016 push ptr 0 - 8B | 0019 push ptr 1 - 93 | 001C push ptr 2 - 9B | 001F push ptr 3 - 56 | 0025 reg EDX becoming live - 4A | 0027 reg ECX becoming live - 0E | 002D reg ECX becoming dead - 10 | 002D reg EDX becoming dead - E0 | 002D pop 4 ptrs - F0 31 | 0036 reg ESI becoming dead - 38 | 0036 reg EDI becoming dead - FF | - -This function is important for CLR Devs, but very difficult for anyone else to -make sense of it. You would usually come to use it if you suspect a gc heap -corruption bug caused by invalid GCEncoding for a particular method. -\\ - -COMMAND: comstate. -!COMState - -!COMState lists the com apartment model for each thread, as well as a Context -pointer if provided. -\\ - -COMMAND: bpmd. -!BPMD [-nofuturemodule] [] -!BPMD : -!BPMD -md -!BPMD -list -!BPMD -clear -!BPMD -clearall - -!BPMD provides managed breakpoint support. If it can resolve the method name -to a loaded, jitted or ngen'd function it will create a breakpoint with "bp". -If not then either the module that contains the method hasn't been loaded yet -or the module is loaded, but the function is not jitted yet. In these cases, -!bpmd asks the Windows Debugger to receive CLR Notifications, and waits to -receive news of module loads and JITs, at which time it will try to resolve -the function to a breakpoint. -nofuturemodule can be used to suppress -creating a breakpoint against a module that has not yet been loaded. - -Management of the list of pending breakpoints can be done via !BPMD -list, -!BPMD -clear, and !BPMD -clearall commands. !BPMD -list generates a list of -all of the pending breakpoints. If the pending breakpoint has a non-zero -module id, then that pending breakpoint is specific to function in that -particular loaded module. If the pending breakpoint has a zero module id, then -the breakpoint applies to modules that have not yet been loaded. Use -!BPMD -clear or !BPMD -clearall to remove pending breakpoints from the list. - -This brings up a good question: "I want to set a breakpoint on the main -method of my application. How can I do this?" - - 1) If you know the full path to SOS, use this command and skip to step 6 - .load - - 2) If you don't know the full path to sos, its usually next to clr.dll - You can wait for clr to load and then find it. - Start the debugger and type: - sxe -c "" clrn - 3) g - 4) You'll get the following notification from the debugger: - "CLR notification: module 'mscorlib' loaded" - 5) Now you can load SOS. Type - .loadby sos clr - - 6) Add the breakpoint with command such as: - !bpmd myapp.exe MyApp.Main - 7) g - 8) You will stop at the start of MyApp.Main. If you type "bl" you will - see the breakpoint listed. - -You can specify breakpoints by file and line number if: - a) You have some version of .NET Framework installed on your machine. Any OS from - Vista onwards should have .NET Framework installed by default. - b) You have PDBs for the managed modules that need breakpoints, and your symbol - path points to those PDBs. -This is often easier than module and method name syntax. For example: - !bpmd Demo.cs:15 - - -To correctly specify explicitly implemented methods make sure to retrieve the -method name from the metadata, or from the output of the "!dumpmt -md" command. -For example: - - public interface I1 - { - void M1(); - } - public class ExplicitItfImpl : I1 - { - ... - void I1.M1() // this method's name is 'I1.M1' - { ... } - } - - !bpmd myapp.exe ExplicitItfImpl.I1.M1 - - -!BPMD works equally well with generic types. Adding a breakpoint on a generic -type sets breakpoints on all already JIT-ted generic methods and sets a pending -breakpoint for any instantiation that will be JIT-ted in the future. - -Example for generics: - Given the following two classes: - - class G3 - { - ... - public void F(T1 p1, T2 p2, T3 p3) - { ... } - } - - public class G1 { - // static method - static public void G(W w) - { ... } - } - - One would issue the following commands to set breapoints on G3.F() and - G1.G(): - - !bpmd myapp.exe G3`3.F - !bpmd myapp.exe G1`1.G - -And for explicitly implemented methods on generic interfaces: - public interface IT1 - { - void M1(T t); - } - - public class ExplicitItfImpl : IT1 - { - ... - void IT1.M1(U u) // this method's name is 'IT1.M1' - { ... } - } - - !bpmd bpmd.exe ExplicitItfImpl`1.IT1.M1 - -Additional examples: - If IT1 and ExplicitItfImpl are types declared inside another class, - Outer, the bpmd command would become: - - !bpmd bpmd.exe Outer+ExplicitItfImpl`1.Outer.IT1.M1 - - (note that the fully qualified type name for ExplicitItfImpl became - Outer+ExplicitItfImpl, using the '+' separator, while the method name - is Outer.IT1.M1, using a '.' as the separator) - - Furthermore, if the Outer class resides in a namespace, NS, the bpmd - command to use becomes: - - !bpmd bpmd.exe NS.Outer+ExplicitItfImpl`1.NS.Outer.IT1.M1 - -!BPMD does not accept offsets nor parameters in the method name. You can add -an IL offset as an optional parameter seperate from the name. If there are overloaded -methods, !bpmd will set a breakpoint for all of them. - -In the case of hosted environments such as SQL, the module name may be -complex, like 'price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. -For this case, just be sure to surround the module name with single quotes, -like: - -!bpmd 'price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' Price.M2 - -\\ - -COMMAND: dumpdomain. -!DumpDomain [] - -When called with no parameters, !DumpDomain will list all the AppDomains in the -process. It enumerates each Assembly loaded into those AppDomains as well. -In addition to your application domain, and any domains it might create, there -are two special domains: the Shared Domain and the System Domain. - -Any Assembly pointer in the output can be passed to !DumpAssembly. Any Module -pointer in the output can be passed to !DumpModule. Any AppDomain pointer can -be passed to !DumpDomain to limit output only to that AppDomain. Other -functions provide an AppDomain pointer as well, such as !Threads where it lists -the current AppDomain for each thread. -\\ - -COMMAND: eeheap. -!EEHeap [-gc] [-loader] - -!EEHeap enumerates process memory consumed by internal CLR data structures. You -can limit the output by passing "-gc" or "-loader". All information will be -displayed otherwise. - -The information for the Garbage Collector lists the ranges of each Segment in -the managed heap. This can be useful if you believe you have an object pointer. -If the pointer falls within a segment range given by "!EEHeap -gc", then you do -have an object pointer, and can attempt to run "!DumpObj" on it. - -Here is output for a simple program: - - 0:000> !eeheap -gc - Number of GC Heaps: 1 - generation 0 starts at 0x00a71018 - generation 1 starts at 0x00a7100c - generation 2 starts at 0x00a71000 - segment begin allocated size - 00a70000 00a71000 00a7e01c 0000d01c(53276) - Large object heap starts at 0x01a71000 - segment begin allocated size - 01a70000 01a71000 01a76000 0x00005000(20480) - Total Size 0x1201c(73756) - ------------------------------ - GC Heap Size 0x1201c(73756) - -So the total size of the GC Heap is only 72K. On a large web server, with -multiple processors, you can expect to see a GC Heap of 400MB or more. The -Garbage Collector attempts to collect and reclaim memory only when required to -by memory pressure for better performance. You can also see the notion of -"generations," wherein the youngest objects live in generation 0, and -long-lived objects eventually get "promoted" to generation 2. - -The loader output lists various private heaps associated with AppDomains. It -also lists heaps associated with the JIT compiler, and heaps associated with -Modules. For example: - - 0:000> !EEHeap -loader - Loader Heap: - -------------------------------------- - System Domain: 5e0662a0 - LowFrequencyHeap:008f0000(00002000:00001000) Size: 0x00001000 bytes. - HighFrequencyHeap:008f2000(00008000:00001000) Size: 0x00001000 bytes. - StubHeap:008fa000(00002000:00001000) Size: 0x00001000 bytes. - Total size: 0x3000(12288)bytes - -------------------------------------- - Shared Domain: 5e066970 - LowFrequencyHeap:00920000(00002000:00001000) 03e30000(00010000:00003000) Size: 0x00004000 bytes. - Wasted: 0x00001000 bytes. - HighFrequencyHeap:00922000(00008000:00001000) Size: 0x00001000 bytes. - StubHeap:0092a000(00002000:00001000) Size: 0x00001000 bytes. - Total size: 0x6000(24576)bytes - -------------------------------------- - Domain 1: 14f000 - LowFrequencyHeap:00900000(00002000:00001000) 03ee0000(00010000:00003000) Size: 0x00004000 bytes. - Wasted: 0x00001000 bytes. - HighFrequencyHeap:00902000(00008000:00003000) Size: 0x00003000 bytes. - StubHeap:0090a000(00002000:00001000) Size: 0x00001000 bytes. - Total size: 0x8000(32768)bytes - -------------------------------------- - Jit code heap: - Normal JIT:03ef0000(00010000:00002000) Size: 0x00002000 bytes. - Total size: 0x2000(8192)bytes - -------------------------------------- - Module Thunk heaps: - Module 5ba22410: Size: 0x00000000 bytes. - Module 001c1320: Size: 0x00000000 bytes. - Module 001c03f0: Size: 0x00000000 bytes. - Module 001caa38: Size: 0x00000000 bytes. - Total size: 0x0(0)bytes - -------------------------------------- - Module Lookup Table heaps: - Module 5ba22410:Size: 0x00000000 bytes. - Module 001c1320:Size: 0x00000000 bytes. - Module 001c03f0:Size: 0x00000000 bytes. - Module 001caa38:03ec0000(00010000:00002000) Size: 0x00002000 bytes. - Total size: 0x2000(8192)bytes - -------------------------------------- - Total LoaderHeap size: 0x15000(86016)bytes - ======================================= - -By using !EEHeap to keep track of the growth of these private heaps, we are -able to rule out or include them as a source of a memory leak. -\\ - -COMMAND: name2ee. -!Name2EE -!Name2EE ! - -This function allows you to turn a class name into a MethodTable and EEClass. -It turns a method name into a MethodDesc. Here is an example for a method: - - 0:000> !name2ee unittest.exe MainClass.Main - Module: 001caa38 - Token: 0x0600000d - MethodDesc: 00902f40 - Name: MainClass.Main() - JITTED Code Address: 03ef00b8 - -and for a class: - - 0:000> !name2ee unittest!MainClass - Module: 001caa38 - Token: 0x02000005 - MethodTable: 009032d8 - EEClass: 03ee1424 - Name: MainClass - -The module you are "browsing" with Name2EE needs to be loaded in the process. -To get a type name exactly right, first browse the module with ILDASM. You -can also pass * as the to search all loaded managed modules. - can also be the debugger's name for a module, such as -mscorlib or image00400000. - -The Windows Debugger syntax of ! is also supported. You can -use an asterisk on the left of the !, but the type on the right side needs -to be fully qualified. - -If you are looking for a way to display a static field of a class (and you -don't have an instance of the class, so !dumpobj won't help you), note that -once you have the EEClass, you can run !DumpClass, which will display the -value of all static fields. - -There is yet one more way to specify a module name. In the case of modules -loaded from an assembly store (such as a SQL db) rather than disk, the -module name will look like this: - -price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null - -For this kind of module, simply use price as the module name: - - 0:044> !name2ee price Price - Module: 10f028b0 (price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null) - Token: 0x02000002 - MethodTable: 11a47ae0 - EEClass: 11a538c8 - Name: Price - -Where are we getting these module names from? Run !DumpDomain to see a list of -all loaded modules in all domains. And remember that you can browse all the -types in a module with !DumpModule -mt . -\\ - -COMMAND: syncblk. -!SyncBlk [-all | ] - -A SyncBlock is a holder for extra information that doesn't need to be created -for every object. It can hold COM Interop data, HashCodes, and locking -information for thread-safe operations. - -When called without arguments, !SyncBlk will print the list of SyncBlocks -corresponding to objects that are owned by a thread. For example, a - - lock(MyObject) - { - .... - } - -statement will set MyObject to be owned by the current thread. A SyncBlock will -be created for MyObject, and the thread ownership information stored there -(this is an oversimplification, see NOTE below). If another thread tries to -execute the same code, they won't be able to enter the block until the first -thread exits. - -This makes !SyncBlk useful for detecting managed deadlocks. Consider that the -following code is executed by Threads A & B: - - Resource r1 = new Resource(); - Resource r2 = new Resource(); - - ... - - lock(r1) lock(r2) - { { - lock(r2) lock(r1) - { { - ... ... - } } - } } - -This is a deadlock situation, as Thread A could take r1, and Thread B r2, -leaving both threads with no option but to wait forever in the second lock -statement. !SyncBlk will detect this with the following output: - - 0:003> !syncblk - Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner - 238 001e40ec 3 1 001e4e60 e04 3 00a7a194 Resource - 239 001e4124 3 1 001e5980 ab8 4 00a7a1a4 Resource - -It means that Thread e04 owns object 00a7a194, and Thread ab8 owns object -00a7a1a4. Combine that information with the call stacks of the deadlock: - -(threads 3 and 4 have similar output) - 0:003> k - ChildEBP RetAddr - 0404ea04 77f5c524 SharedUserData!SystemCallStub+0x4 - 0404ea08 77e75ee0 ntdll!NtWaitForMultipleObjects+0xc - 0404eaa4 5d9de9d6 KERNEL32!WaitForMultipleObjectsEx+0x12c - 0404eb38 5d9def80 clr!Thread::DoAppropriateAptStateWait+0x156 - 0404ecc4 5d9dd8bb clr!Thread::DoAppropriateWaitWorker+0x360 - 0404ed20 5da628dd clr!Thread::DoAppropriateWait+0xbb - 0404ede4 5da4e2e2 clr!CLREvent::Wait+0x29d - 0404ee70 5da4dd41 clr!AwareLock::EnterEpilog+0x132 - 0404ef34 5da4efa3 clr!AwareLock::Enter+0x2c1 - 0404f09c 5d767880 clr!AwareLock::Contention+0x483 - 0404f1c4 03f00229 clr!JITutil_MonContention+0x2c0 - 0404f1f4 5b6ef077 image00400000!Worker.Work()+0x79 - ... - -By looking at the code corresponding to Worker.Work()+0x79 (run "!u 03f00229"), -you can see that thread 3 is attempting to acquire the Resource 00a7a1a4, which -is owned by thread 4. - -NOTE: -It is not always the case that a SyncBlock will be created for every object -that is locked by a thread. In version 2.0 of the CLR and above, a mechanism -called a ThinLock will be used if there is not already a SyncBlock for the -object in question. ThinLocks will not be reported by the !SyncBlk command. -You can use "!DumpHeap -thinlock" to list objects locked in this way. -\\ - -COMMAND: dumpmt. -!DumpMT [-MD] - -Examine a MethodTable. Each managed object has a MethodTable pointer at the -start. If you pass the "-MD" flag, you'll also see a list of all the methods -defined on the object. -\\ - -COMMAND: dumpclass. -!DumpClass - -The EEClass is a data structure associated with an object type. !DumpClass -will show attributes, as well as list the fields of the type. The output is -similar to !DumpObj. Although static field values will be displayed, -non-static values won't because you need an instance of an object for that. - -You can get an EEClass to look at from !DumpMT, !DumpObj, !Name2EE, and -!Token2EE among others. -\\ - -COMMAND: dumpmd. -!DumpMD - -This command lists information about a MethodDesc. You can use !IP2MD to turn -a code address in a managed function into a MethodDesc: - - 0:000> !dumpmd 902f40 - Method Name: Mainy.Main() - Class: 03ee1424 - MethodTable: 009032d8 - mdToken: 0600000d - Module: 001caa78 - IsJitted: yes - CodeAddr: 03ef00b8 - -If IsJitted is "yes," you can run !U on the CodeAddr pointer to see a -disassembly of the JITTED code. You can also call !DumpClass, !DumpMT, -!DumpModule on the Class, MethodTable and Module fields above. -\\ - -COMMAND: token2ee. -!Token2EE - -This function allows you to turn a metadata token into a MethodTable or -MethodDesc. Here is an example showing class tokens being resolved: - - 0:000> !token2ee unittest.exe 02000003 - Module: 001caa38 - Token: 0x02000003 - MethodTable: 0090375c - EEClass: 03ee1ae0 - Name: Bank - 0:000> !token2ee image00400000 02000004 - Module: 001caa38 - Token: 0x02000004 - MethodTable: 009038ec - EEClass: 03ee1b84 - Name: Customer - -The module you are "browsing" with Token2EE needs to be loaded in the process. -This function doesn't see much use, especially since a tool like ILDASM can -show the mapping between metadata tokens and types/methods in a friendlier way. -But it could be handy sometimes. - -You can pass "*" for to find what that token maps to in every -loaded managed module. can also be the debugger's name for a -module, such as mscorlib or image00400000. -\\ - -COMMAND: eeversion. -!EEVersion - -This prints the Common Language Runtime version. It also tells you if the code -is running in "Workstation" or "Server" mode, a distinction which affects the -garbage collector. The most apparent difference in the debugger is that in -"Server" mode there is one dedicated garbage collector thread per CPU. - -A handy supplement to this function is to also run "lm v m clr". That -will provide more details about the CLR, including where clr.dll is -loaded from. -\\ - -COMMAND: dumpmodule. -!DumpModule [-mt] - -You can get a Module address from !DumpDomain, !DumpAssembly and other -functions. Here is sample output: - - 0:000> !DumpModule 1caa50 - Name: C:\pub\unittest.exe - Attributes: PEFile - Assembly: 001ca248 - LoaderHeap: 001cab3c - TypeDefToMethodTableMap: 03ec0010 - TypeRefToMethodTableMap: 03ec0024 - MethodDefToDescMap: 03ec0064 - FieldDefToDescMap: 03ec00a4 - MemberRefToDescMap: 03ec00e8 - FileReferencesMap: 03ec0128 - AssemblyReferencesMap: 03ec012c - MetaData start address: 00402230 (1888 bytes) - -The Maps listed map metadata tokens to CLR data structures. Without going into -too much detail, you can examine memory at those addresses to find the -appropriate structures. For example, the TypeDefToMethodTableMap above can be -examined: - - 0:000> dd 3ec0010 - 03ec0010 00000000 00000000 0090320c 0090375c - 03ec0020 009038ec ... - -This means TypeDef token 2 maps to a MethodTable with the value 0090320c. You -can run !DumpMT to verify that. The MethodDefToDescMap takes a MethodDef token -and maps it to a MethodDesc, which can be passed to !DumpMD. - -There is a new option "-mt", which will display the types defined in a module, -and the types referenced by the module. For example: - - 0:000> !dumpmodule -mt 1aa580 - Name: C:\pub\unittest.exe - ...... - MetaData start address: 0040220c (1696 bytes) - - Types defined in this module - - MT TypeDef Name - -------------------------------------------------------------------------- - 030d115c 0x02000002 Funny - 030d1228 0x02000003 Mainy - - Types referenced in this module - - MT TypeRef Name - -------------------------------------------------------------------------- - 030b6420 0x01000001 System.ValueType - 030b5cb0 0x01000002 System.Object - 030fceb4 0x01000003 System.Exception - 0334e374 0x0100000c System.Console - 03167a50 0x0100000e System.Runtime.InteropServices.GCHandle - 0336a048 0x0100000f System.GC - -\\ - -COMMAND: threadpool. -!ThreadPool - -This command lists basic information about the ThreadPool, including the number -of work requests in the queue, number of completion port threads, and number of -timers. -\\ - -COMMAND: dumpassembly. -!DumpAssembly - -Example output: - - 0:000> !dumpassembly 1ca248 - Parent Domain: 0014f000 - Name: C:\pub\unittest.exe - ClassLoader: 001ca060 - Module Name - 001caa50 C:\pub\unittest.exe - -An assembly can consist of multiple modules, and those will be listed. You can -get an Assembly address from the output of !DumpDomain. -\\ - -COMMAND: dumpruntimetypes. -!DumpRuntimeTypes - -!DumpRuntimeTypes finds all System.RuntimeType objects in the gc heap and -prints the type name and MethodTable they refer too. Sample output: - - Address Domain MT Type Name - ------------------------------------------------------------------------------ - a515f4 14a740 5baf8d28 System.TypedReference - a51608 14a740 5bb05764 System.Globalization.BaseInfoTable - a51958 14a740 5bb05b24 System.Globalization.CultureInfo - a51a44 14a740 5bb06298 System.Globalization.GlobalizationAssembly - a51de0 14a740 5bb069c8 System.Globalization.TextInfo - a56b98 14a740 5bb12d28 System.Security.Permissions.HostProtectionResource - a56bbc 14a740 5baf7248 System.Int32 - a56bd0 14a740 5baf3fdc System.String - a56cfc 14a740 5baf36a4 System.ValueType - ... - -This command will print a "?" in the domain column if the type is loaded into multiple -AppDomains. For example: - - 0:000> !DumpRuntimeTypes - Address Domain MT Type Name - ------------------------------------------------------------------------------ - 28435a0 ? 3f6a8c System.TypedReference - 28435b4 ? 214d6c System.ValueType - 28435c8 ? 216314 System.Enum - 28435dc ? 2147cc System.Object - 284365c ? 3cd57c System.IntPtr - 2843670 ? 3feaac System.Byte - 2843684 ? 23a544c System.IEquatable`1[[System.IntPtr, mscorlib]] - 2843784 ? 3c999c System.Int32 - 2843798 ? 3caa04 System.IEquatable`1[[System.Int32, mscorlib]] - -\\ - -COMMAND: dumpsig. -!DumpSig - -This command dumps the signature of a method or field given by . This is -useful when you are debugging parts of the runtime which returns a raw PCCOR_SIGNATURE -structure and need to know what its contents are. - -Sample output for a method: - 0:000> !dumpsig 0x000007fe`ec20879d 0x000007fe`eabd1000 - [DEFAULT] [hasThis] Void (Boolean,String,String) - -The first section of the output is the calling convention. This includes, but is not -limited to, "[DEFAULT]", "[C]", "[STDCALL]", "[THISCALL]", and so on. The second -portion of the output is either "[hasThis]" or "[explicit]" for whether the method -is an instance method or a static method respectively. The third portion of the -output is the return value (in this case a "void"). Finally, the method's arguments -are printed as the final portion of the output. - -Sample output for a field: - 0:000> !dumpsig 0x000007fe`eb7fd8cd 0x000007fe`eabd1000 - [FIELD] ValueClass System.RuntimeTypeHandle - -!DumpSig will also work with generics. Here is the output for the following -function: - public A Test(IEnumerable n) - - 0:000> !dumpsig 00000000`00bc2437 000007ff00043178 - [DEFAULT] [hasThis] __Canon (Class System.Collections.Generic.IEnumerable`1<__Canon>) - -\\ - -COMMAND: dumpsigelem. -!DumpSigElem - -This command dumps a single element of a signature object. For most circumstances, -you should use !DumpSig to look at individual signature objects, but if you find a -signature that has been corrupted in some manner you can use !DumpSigElem to read out -the valid portions of it. - -If we look at a valid signature object for a method we see the following: - 0:000> !dumpsig 0x000007fe`ec20879d 0x000007fe`eabd1000 - [DEFAULT] [hasThis] Void (Boolean,String,String) - -We can look at the individual elements of this object by adding the offsets into the -object which correspond to the return value and parameters: - 0:000> !dumpsigelem 0x000007fe`ec20879d+2 0x000007fe`eabd1000 - Void - 0:000> !dumpsigelem 0x000007fe`ec20879d+3 0x000007fe`eabd1000 - Boolean - 0:000> !dumpsigelem 0x000007fe`ec20879d+4 0x000007fe`eabd1000 - String - 0:000> !dumpsigelem 0x000007fe`ec20879d+5 0x000007fe`eabd1000 - String - -We can do something similar for fields. Here is the full signature of a field: - 0:000> !dumpsig 0x000007fe`eb7fd8cd 0x000007fe`eabd1000 - [FIELD] ValueClass System.RuntimeTypeHandle - -Using !DumpSigElem we can find the type of the field by adding the offset of it (1) to -the address of the signature: - 0:000> !dumpsigelem 0x000007fe`eb7fd8cd+1 0x000007fe`eabd1000 - ValueClass System.RuntimeTypeHandle - -!DumpSigElem will also work with generics. Let a function be defined as follows: - public A Test(IEnumerable n) - -The elements of this signature can be obtained by adding offsets into the signature -when calling !DumpSigElem: - - 0:000> !dumpsigelem 00000000`00bc2437+2 000007ff00043178 - __Canon - 0:000> !dumpsigelem 00000000`00bc2437+4 000007ff00043178 - Class System.Collections.Generic.IEnumerable`1<__Canon> - -The actual offsets that you should add are determined by the contents of the -signature itself. By trial and error you should be able to find various elements -of the signature. - -\\ - -COMMAND: rcwcleanuplist. -!RCWCleanupList [address] - -A RuntimeCallableWrapper is an internal CLR structure used to host COM objects -which are exposed to managed code. This is exposed to managed code through the -System.__ComObject class, and when objects of this type are collected, and a -reference to the underlying COM object is no longer needed, the corresponding -RCW is cleaned up. If you are trying to debug an issue related to one of these -RCWs, then you can use the !RCWCleanupList function to display which COM objects -will be released the next time a cleanup occurs. - -If given an address, this function will display the RCWCleanupList at that address. -If no address is specified, it displays the default cleanup list, printing the -wrapper, the context, and the thread of the object. - -Example: - 0:002> !rcwcleanuplist 001c04d0 - RuntimeCallableWrappers (RCW) to be cleaned: - RCW CONTEXT THREAD Apartment - 1d54e0 192008 181180 STA - 1d4140 192178 0 MTA - 1dff50 192178 0 MTA - MTA Interfaces to be released: 2 - STA Interfaces to be released: 1 - -Note that CLR keeps track of which RCWs are bound to which managed objects through -the SyncBlock of the object. As such, you can see more information about RCW -objects through the !SyncBlk command. You can find more information about RCW -cleanup through the !FinalizeQueue command. - -\\ - -COMMAND: dumpil. -!DumpIL | - | - | - /i - -!DumpIL prints the IL code associated with a managed method. We added this -function specifically to debug DynamicMethod code which was constructed on -the fly. Happily it works for non-dynamic code as well. - -You can use it in four ways: - - 1) If you have a System.Reflection.Emit.DynamicMethod object, just pass - the pointer as the first argument. - 2) If you have a DynamicMethodDesc pointer you can use that to print the - IL associated with the dynamic method. - 3) If you have an ordinary MethodDesc, you can see the IL for that as well, - just pass it as the first argument. - 4) If you have a pointer directly to the IL, specify /i followed by the - the IL address. This is useful for writers of profilers that instrument - IL. - - -Note that dynamic IL is constructed a bit differently. Rather than referring -to metadata tokens, the IL points to objects in a managed object array. Here -is a simple example of the output for a dynamic method: - - 0:000> !dumpil b741dc - This is dynamic IL. Exception info is not reported at this time. - If a token is unresolved, run "!do " on the addr given - in parenthesis. You can also look at the token table yourself, by - running "!DumpArray 00b77388". - - IL_0000: ldstr 70000002 "Inside invoked method " - IL_0005: call 6000003 System.Console.WriteLine(System.String) - IL_000a: ldc.i4.1 - IL_000b: newarr 2000004 "System.Int32" - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: ret - -\\ - -COMMAND: verifyheap. -!VerifyHeap - -!VerifyHeap is a diagnostic tool that checks the garbage collected heap for -signs of corruption. It walks objects one by one in a pattern like this: - - o = firstobject; - while(o != endobject) - { - o.ValidateAllFields(); - o = (Object *) o + o.Size(); - } - -If an error is found, !VerifyHeap will report it. I'll take a perfectly good -object and corrupt it: - - 0:000> !DumpObj a79d40 - Name: Customer - MethodTable: 009038ec - EEClass: 03ee1b84 - Size: 20(0x14) bytes - (C:\pub\unittest.exe) - Fields: - MT Field Offset Type Attr Value Name - 009038ec 4000008 4 CLASS instance 00a79ce4 name - 009038ec 4000009 8 CLASS instance 00a79d2c bank - 009038ec 400000a c System.Boolean instance 1 valid - - 0:000> ed a79d40+4 01 (change the name field to the bogus pointer value 1) - 0:000> !VerifyHeap - object 01ee60dc: bad member 00000003 at 01EE6168 - Last good object: 01EE60C4. - -If this gc heap corruption exists, there is a serious bug in your own code or -in the CLR. In user code, an error in constructing PInvoke calls can cause -this problem, and running with Managed Debugging Assistants is advised. If that -possibility is eliminated, consider contacting Microsoft Product Support for -help. - -\\ - -COMMAND: verifyobj. -!VerifyObj - -!VerifyObj is a diagnostic tool that checks the object that is passed as an -argument for signs of corruption. - - 0:002> !verifyobj 028000ec - object 0x28000ec does not have valid method table - - 0:002> !verifyobj 0680017c - object 0x680017c: bad member 00000001 at 06800184 - -\\ - -COMMAND: findroots. -!FindRoots -gen | -gen any | - -The "-gen" form causes the debugger to break in the debuggee on the next -collection of the specified generation. The effect is reset as soon as the -break occurs, in other words, if you need to break on the next collection you -would need to reissue the command. - -The last form of this command is meant to be used after the break caused by the -other forms has occurred. Now the debuggee is in the right state for -!FindRoots to be able to identify roots for objects from the current condemned -generations. - -!FindRoots is a diagnostic command that is meant to answer the following -question: - -"I see that GCs are happening, however my objects have still not been -collected. Why? Who is holding onto them?" - -The process of answering the question would go something like this: - -1. Find out the generation of the object of interest using the !GCWhere -command, say it is gen 1: - !GCWhere - -2. Instruct the runtime to stop the next time it collects that generation using -the !FindRoots command: - !FindRoots -gen 1 - g - -3. When the next GC starts, and has proceeded past the mark phase a CLR -notification will cause a break in the debugger: - (fd0.ec4): CLR notification exception - code e0444143 (first chance) - CLR notification: GC - end of mark phase. - Condemned generation: 1. - -4. Now we can use the !FindRoots to find out the cross -generational references to the object of interest. In other words, even if the -object is not referenced by any "proper" root it may still be referenced by an -older object (from an older generation), from a generation that has not yet been -scheduled for collection. At this point !FindRoots will search those older -generations too, and report those roots. - 0:002> !findroots 06808094 - older generations::Root: 068012f8(AAA.Test+a)-> - 06808094(AAA.Test+b) - - -\\ - -COMMAND: heapstat. -!HeapStat [-inclUnrooted | -iu] - -This command shows the generation sizes for each heap and the total, how much free -space there is in each generation on each heap. If the -inclUnrooted option is -specified the report will include information about the managed objects from the -GC heap that are not rooted anymore. - -Sample output: - - 0:002> !heapstat - Heap Gen0 Gen1 Gen2 LOH - Heap0 177904 12 306956 8784 - Heap1 159652 12 12 16 - Total 337556 24 306968 8800 - - Free space: Percentage - Heap0 28 12 12 64 SOH: 0% LOH: 0% - Heap1 104 12 12 16 SOH: 0% LOH:100% - Total 132 24 24 80 - - 0:002> !heapstat -inclUnrooted - Heap Gen0 Gen1 Gen2 LOH - Heap0 177904 12 306956 8784 - Heap1 159652 12 12 16 - Total 337556 24 306968 8800 - - Free space: Percentage - Heap0 28 12 12 64 SOH: 0% LOH: 0% - Heap1 104 12 12 16 SOH: 0% LOH:100% - Total 132 24 24 80 - - Unrooted objects: Percentage - Heap0 152212 0 306196 0 SOH: 94% LOH: 0% - Heap1 155704 0 0 0 SOH: 97% LOH: 0% - Total 307916 0 306196 0 - -The percentage column contains a breakout of free or unrooted bytes to total bytes. - -\\ - -COMMAND: analyzeoom. -!AnalyzeOOM - -!AnalyzeOOM displays the info of the last OOM occurred on an allocation request to -the GC heap (in Server GC it displays OOM, if any, on each GC heap). - -To see the managed exception(s) use the !Threads command which will show you -managed exception(s), if any, on each managed thread. If you do see an -OutOfMemoryException exception you can use the !PrintException command on it. -To get the full callstack use the "kb" command in the debugger for that thread. -For example, to display thread 3's stack use ~3kb. - -OOM exceptions could be because of the following reasons: - -1) allocation request to GC heap - in which case you will see JIT_New* on the call stack because managed code called new. -2) other runtime allocation failure - for example, failure to expand the finalize queue when GC.ReRegisterForFinalize is - called. -3) some other code you use throws a managed OOM exception - for example, some .NET framework code converts a native OOM exception to managed - and throws it. - -The !AnalyzeOOM command aims to help you with investigating 1) which is the most -difficult because it requires some internal info from GC. The only exception is -we don't support allocating objects larger than 2GB on CLR v2.0 or prior. And this -command will not display any managed OOM because we will throw OOM right away -instead of even trying to allocate it on the GC heap. - -There are 2 legitimate scenarios where GC would return OOM to allocation requests - -one is if the process is running out of VM space to reserve a segment; the other -is if the system is running out physical memory (+ page file if you have one) so -GC can not commit memory it needs. You can look at these scenarios by using performance -counters or debugger commands. For example for the former scenario the "!address --summary" debugger command will show you the largest free region in the VM. For -the latter scenario you can look at the "Memory\% Committed Bytes In Use" see -if you are running low on commit space. One important thing to keep in mind is -when you do this kind of memory analysis it could an aftereffect and doesn't -completely agree with what this command tells you, in which case the command should -be respected because it truly reflects what happened during GC. - -The other cases should be fairly obvious from the callstack. - -Sample output: - -0:011> !ao ----------Heap 2 --------- -Managed OOM occurred after GC #28 (Requested to allocate 1234 bytes) -Reason: Didn't have enough memory to commit -Detail: SOH: Didn't have enough memory to grow the internal GC datastructures (800000 bytes) - - on GC entry available commit space was 500 MB ----------Heap 4 --------- -Managed OOM occurred after GC #12 (Requested to allocate 100000 bytes) -Reason: Didn't have enough memory to allocate an LOH segment -Detail: LOH: Failed to reserve memory (16777216 bytes) - -\\ - -COMMAND: gcwhere. -!GCWhere - -!GCWhere displays the location in the GC heap of the argument passed in. - - 0:002> !GCWhere 02800038 - Address Gen Heap segment begin allocated size - 02800038 2 0 02800000 02800038 0282b740 12 - -When the argument lies in the managed heap, but is not a valid *object* address -the "size" is displayed as 0: - - 0:002> !GCWhere 0280003c - Address Gen Heap segment begin allocated size - 0280003c 2 0 02800000 02800038 0282b740 0 - -\\ - -COMMAND: listnearobj. -!ListNearObj - -!ListNearObj is a diagnostic tool that displays the object preceeding and -succeeding the address passed in: - -The command looks for the address in the GC heap that looks like a valid -beginning of a managed object (based on a valid method table) and the object -following the argument address. - - 0:002> !ListNearObj 028000ec - Before: 0x28000a4 72 (0x48 ) System.StackOverflowException - After: 0x2800134 72 (0x48 ) System.Threading.ThreadAbortException - Heap local consistency confirmed. - - 0:002> !ListNearObj 028000f0 - Before: 0x28000ec 72 (0x48 ) System.ExecutionEngineException - After: 0x2800134 72 (0x48 ) System.Threading.ThreadAbortException - Heap local consistency confirmed. - -The command considers the heap as "locally consistent" if: - prev_obj_addr + prev_obj_size = arg_addr && arg_obj + arg_size = next_obj_addr -OR - prev_obj_addr + prev_obj_size = next_obj_addr - -When the condition is not satisfied: - - 0:002> !lno 028000ec - Before: 0x28000a4 72 (0x48 ) System.StackOverflowException - After: 0x2800134 72 (0x48 ) System.Threading.ThreadAbortException - Heap local consistency not confirmed. - -\\ - -COMMAND: dumplog. -!DumpLog [-addr ] [] - -To aid in diagnosing hard-to-reproduce stress failures, the CLR team added an -in-memory log capability. The idea was to avoid using locks or I/O which could -disturb a fragile repro environment. The !DumpLog function allows you to write -that log out to a file. If no Filename is specified, the file "Stresslog.txt" -in the current directory is created. - -The optional argument addr allows one to specify a stress log other then the -default one. - - 0:000> !DumpLog - Attempting to dump Stress log to file 'StressLog.txt' - ................. - SUCCESS: Stress log dumped - -To turn on the stress log, set the following registry keys under -HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework: - - -(DWORD) StressLog = 1 -(DWORD) LogFacility = 0xffffffbf (this is a bit mask, almost all logging is on. - This is also the default value if the key - isn't specified) -(DWORD) StressLogSize = 65536 (this is the default value if the key isn't - specified) -(DWORD) LogLevel = 6 (this is the default value if the key isn't - specified. The higher the number the more - detailed logs are generated. The maximum - value is decimal 10) - -StressLogSize is the size in bytes of the in-memory log allocated for each -thread in the process. In the case above, each thread gets a 64K log. You -could increase this to get more logging, but more memory will be required for -this log in the process. For example, 20 threads with 524288 bytes per thread -has a memory demand of 10 Megabytes. The stress log is circular so new entries -will replace older ones on threads which have reached their buffer limit. - -The log facilities are defined as follows: - GC 0x00000001 - GCINFO 0x00000002 - STUBS 0x00000004 - JIT 0x00000008 - LOADER 0x00000010 - METADATA 0x00000020 - SYNC 0x00000040 - EEMEM 0x00000080 - GCALLOC 0x00000100 - CORDB 0x00000200 - CLASSLOADER 0x00000400 - CORPROF 0x00000800 - REMOTING 0x00001000 - DBGALLOC 0x00002000 - EH 0x00004000 - ENC 0x00008000 - ASSERT 0x00010000 - VERIFIER 0x00020000 - THREADPOOL 0x00040000 - GCROOTS 0x00080000 - INTEROP 0x00100000 - MARSHALER 0x00200000 - IJW 0x00400000 - ZAP 0x00800000 - STARTUP 0x01000000 - APPDOMAIN 0x02000000 - CODESHARING 0x04000000 - STORE 0x08000000 - SECURITY 0x10000000 - LOCKS 0x20000000 - BCL 0x40000000 - -Here is some sample output: - - 3560 9.981137099 : `SYNC` RareEnablePremptiveGC: entering. - Thread state = a030 - - 3560 9.981135033 : `GC`GCALLOC`GCROOTS` ========== ENDGC 4194 (gen = 2, - collect_classes = 0) ==========={ - - 3560 9.981125826 : `GC` Segment mem 00C61000 alloc - = 00D071F0 used 00D09254 committed 00D17000 - - 3560 9.981125726 : `GC` Generation 0 [00CED07C, 00000000 - ] cur = 00000000 - - 3560 9.981125529 : `GC` Generation 1 [00CED070, 00000000 - ] cur = 00000000 - - 3560 9.981125103 : `GC` Generation 2 [00C61000, 00000000 - ] cur = 00000000 - - 3560 9.981124963 : `GC` GC Heap 00000000 - - 3560 9.980618994 : `GC`GCROOTS` GcScanHandles (Promotion Phase = 0) - -The first column is the OS thread ID for the thread appending to the log, -the second column is the timestamp, the third is the facility category for the -log entry, and the fourth contains the log message. The facility field is -expressed as `facility1`facility2`facility3`. This facilitates the creation of -filters for displaying only specific message categories. To make sense of this -log, you would probably want the Shared Source CLI to find out exactly where -the log comes from. -\\ - -COMMAND: findappdomain. -!FindAppDomain - -!FindAppDomain will attempt to resolve the AppDomain of an object. For example, -using an Object Pointer from the output of !DumpStackObjects: - - 0:000> !findappdomain 00a79d98 - AppDomain: 0014f000 - Name: unittest.exe - ID: 1 - -You can find out more about the AppDomain with the !DumpDomain command. Not -every object has enough clues about it's origin to determine the AppDomain. -Objects with Finalizers are the easiest case, as the CLR needs to be able to -call those when an AppDomain shuts down. -\\ - -COMMAND: savemodule. -!SaveModule - -This command allows you to take a image loaded in memory and write it to a -file. This is especially useful if you are debugging a full memory dump, and -don't have the original DLLs or EXEs. This is most often used to save a managed -binary to a file, so you can disassemble the code and browse types with ILDASM. - -The base address of an image can be found with the "LM" debugger command: - - 0:000> lm - start end module name - 00400000 00408000 image00400000 (deferred) - 10200000 102ac000 MSVCR80D (deferred) - 5a000000 5a0b1000 mscoree (deferred) - 5a140000 5a29e000 clrjit (deferred) - 5b660000 5c440000 mscorlib_dll (deferred) - 5d1d0000 5e13c000 clr (deferred) - ... - -If I wanted to save a copy of clr.dll, I could run: - - 0:000> !SaveModule 5d1d0000 c:\pub\out.tmp - 4 sections in file - section 0 - VA=1000, VASize=e82da9, FileAddr=400, FileSize=e82e00 - section 1 - VA=e84000, VASize=24d24, FileAddr=e83200, FileSize=ec00 - section 2 - VA=ea9000, VASize=5a8, FileAddr=e91e00, FileSize=600 - section 3 - VA=eaa000, VASize=c183c, FileAddr=e92400, FileSize=c1a00 - -The diagnostic output indicates that the operation was successful. If -c:\pub\out.tmp already exists, it will be overwritten. -\\ - -COMMAND: gchandles. -!GCHandles [-type handletype] [-stat] [-perdomain] - -!GCHandles provides statistics about GCHandles in the process. - -Paremeters: - stat - Only display the statistics and not the list of handles and - what they point to. - perdomain - Break down the statistics by the app domain in which - the handles reside. - type - A type of handle to filter it by. The handle types are: - Pinned - RefCounted - WeakShort - WeakLong - Strong - Variable - AsyncPinned - SizedRef - -Sometimes the source of a memory leak is a GCHandle leak. For example, code -might keep a 50 Megabyte array alive because a strong GCHandle points to it, -and the handle was discarded without freeing it. - -The most common handles are "Strong Handles," which keep the object they point -to alive until the handle is explicitly freed. "Pinned Handles" are used to -prevent the garbage collector from moving an object during collection. These -should be used sparingly, and for short periods of time. If you don't follow -that precept, the gc heap can become very fragmented. - -Here is sample output from a very simple program. Note that the "RefCount" -field only applies to RefCount Handles, and this field will contain the -reference count: - - 0:000> !GCHandles - Handle Type Object Size RefCount Type - 001611c0 Strong 01d00b58 84 System.IndexOutOfRangeException - 001611c4 Strong 01d00b58 84 System.IndexOutOfRangeException - 001611c8 Strong 01d1b48c 40 System.Diagnostics.LogSwitch - 001611d0 Strong 01cfd2c0 36 System.Security.PermissionSet - 001611d4 Strong 01cf7484 56 System.Object[] - 001611d8 Strong 01cf1238 32 System.SharedStatics - 001611dc Strong 01cf11c8 84 System.Threading.ThreadAbortException - 001611e0 Strong 01cf1174 84 System.Threading.ThreadAbortException - 001611e4 Strong 01cf1120 84 System.ExecutionEngineException - 001611e8 Strong 01cf10cc 84 System.StackOverflowException - 001611ec Strong 01cf1078 84 System.OutOfMemoryException - 001611f0 Strong 01cf1024 84 System.Exception - 001611f8 Strong 01cf2068 48 System.Threading.Thread - 001611fc Strong 01cf1328 112 System.AppDomain - 001613ec Pinned 02cf3268 8176 System.Object[] - 001613f0 Pinned 02cf2258 4096 System.Object[] - 001613f4 Pinned 02cf2038 528 System.Object[] - 001613f8 Pinned 01cf121c 12 System.Object - 001613fc Pinned 02cf1010 4116 System.Object[] - - Statistics: - MT Count TotalSize Class Name - 563266dc 1 12 System.Object - 56329708 1 32 System.SharedStatics - 5632bc38 1 36 System.Security.PermissionSet - 5635f934 1 40 System.Diagnostics.LogSwitch - 5632759c 1 48 System.Threading.Thread - 5632735c 1 84 System.ExecutionEngineException - 56327304 1 84 System.StackOverflowException - 563272ac 1 84 System.OutOfMemoryException - 563270c4 1 84 System.Exception - 56328914 1 112 System.AppDomain - 56335f78 2 168 System.IndexOutOfRangeException - 563273b4 2 168 System.Threading.ThreadAbortException - 563208d0 5 16972 System.Object[] - Total 19 objects - - Handles: - Strong Handles: 14 - Pinned Handles: 5 -\\ - -COMMAND: gchandleleaks. -!GCHandleLeaks - -This command is an aid in tracking down GCHandle leaks. It searches all of -memory for any references to the Strong and Pinned GCHandles in the process, -and reports what it found. If a handle is found, you'll see the address of the -reference. This might be a stack address or a field within an object, for -example. If a handle is not found in memory, you'll get notification of that -too. - -The command has diagnostic output which doesn't need to be repeated here. One -thing to keep in mind is that anytime you search all of memory for a value, you -can get false positives because even though the value was found, it might be -garbage in that no code knows about the address. You can also get false -negatives because a user is free to pass that GCHandle to unmanaged code that -might store the handle in a strange way (shifting bits, for example). - -For example, a GCHandle valuetype is stored on the stack with the low bit set -if it points to a Pinned handle. So !GCHandleLeaks ignores the low bit in it's -searches. - -That said, if a serious leak is going on, you'll get a ever-growing set of -handle addresses that couldn't be found. -\\ - -COMMAND: vmmap. -!VMMap - -!VMMap traverses the virtual address space and lists the type of protection -applied to each region. Sample output: - - 0:000> !VMMap - Start Stop Length AllocProtect Protect State Type - 00000000-0000ffff 00010000 NA Free - 00010000-00011fff 00002000 RdWr RdWr Commit Private - 00012000-0001ffff 0000e000 NA Free - 00020000-00020fff 00001000 RdWr RdWr Commit Private - 00021000-0002ffff 0000f000 NA Free - 00030000-00030fff 00001000 RdWr Reserve Private - ... -\\ - -COMMAND: vmstat. -!VMStat - -Provides a summary view of the virtual address space, ordered by each type of -protection applied to that memory (free, reserved, committed, private, mapped, -image). The TOTAL column is (AVERAGE * BLK COUNT). Sample output below: - - 0:000> !VMStat - ~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~~~ ~~~~~ - TYPE MINIMUM MAXIMUM AVERAGE BLK COUNT TOTAL - Free: - Small 4,096 65,536 48,393 27 1,306,611 - Medium 139,264 528,384 337,920 4 1,351,680 - Large 6,303,744 974,778,368 169,089,706 12 2,029,076,472 - Summary 4,096 974,778,368 47,249,646 43 2,031,734,778 - - Reserve: - Small 4,096 65,536 43,957 41 1,802,237 - Medium 249,856 1,019,904 521,557 6 3,129,342 - Large 2,461,696 16,703,488 11,956,224 3 35,868,672 - Summary 4,096 16,703,488 816,005 50 40,800,250 - -\\ - -COMMAND: procinfo. -!ProcInfo [-env] [-time] [-mem] - -!ProcInfo lists the environment variables for the process, kernel CPU time, as -well as memory usage statistics. -\\ - -COMMAND: histinit. -!HistInit - -Before running any of the Hist - family commands you need to initialize the SOS -structures from the stress log saved in the debuggee. This is achieved by the -HistInit command. - -Sample output: - - 0:001> !HistInit - Attempting to read Stress log - STRESS LOG: - facilitiesToLog = 0xffffffff - levelToLog = 6 - MaxLogSizePerThread = 0x10000 (65536) - MaxTotalLogSize = 0x1000000 (16777216) - CurrentTotalLogChunk = 9 - ThreadsWithLogs = 3 - Clock frequency = 3.392 GHz - Start time 15:26:31 - Last message time 15:26:56 - Total elapsed time 25.077 sec - ..................................... - ---------------------------- 2407 total entries ----------------------------- - - - SUCCESS: GCHist structures initialized - -\\ - -COMMAND: histobjfind. -!HistObjFind - -To examine log entries related to an object whose present address is known one -would use this command. The output of this command contains all entries that -reference the object: - - 0:003> !HistObjFind 028970d4 - GCCount Object Message - --------------------------------------------------------- - 2296 028970d4 Promotion for root 01e411b8 (MT = 5b6c5cd8) - 2296 028970d4 Relocation NEWVALUE for root 00223fc4 - 2296 028970d4 Relocation NEWVALUE for root 01e411b8 - ... - 2295 028970d4 Promotion for root 01e411b8 (MT = 5b6c5cd8) - 2295 028970d4 Relocation NEWVALUE for root 00223fc4 - 2295 028970d4 Relocation NEWVALUE for root 01e411b8 - ... - -\\ - -COMMAND: histroot. -!HistRoot - -The root value obtained from !HistObjFind can be used to track the movement of -an object through the GCs. - -HistRoot provides information related to both promotions and relocations of the -root specified as the argument. - - 0:003> !HistRoot 01e411b8 - GCCount Value MT Promoted? Notes - --------------------------------------------------------- - 2296 028970d4 5b6c5cd8 yes - 2295 028970d4 5b6c5cd8 yes - 2294 028970d4 5b6c5cd8 yes - 2293 028970d4 5b6c5cd8 yes - 2292 028970d4 5b6c5cd8 yes - 2291 028970d4 5b6c5cd8 yes - 2290 028970d4 5b6c5cd8 yes - 2289 028970d4 5b6c5cd8 yes - 2288 028970d4 5b6c5cd8 yes - 2287 028970d4 5b6c5cd8 yes - 2286 028970d4 5b6c5cd8 yes - 2285 028970d4 5b6c5cd8 yes - 322 028970e8 5b6c5cd8 yes Duplicate promote/relocs - ... - -\\ - -COMMAND: histobj. -!HistObj - -This command examines all stress log relocation records and displays the chain -of GC relocations that may have led to the address passed in as an argument. -Conceptually the output is: - - GenN obj_address root1, root2, root3, - GenN-1 prev_obj_addr root1, root2, - GenN-2 prev_prev_oa root1, root4, - ... - -Sample output: - 0:003> !HistObj 028970d4 - GCCount Object Roots - --------------------------------------------------------- - 2296 028970d4 00223fc4, 01e411b8, - 2295 028970d4 00223fc4, 01e411b8, - 2294 028970d4 00223fc4, 01e411b8, - 2293 028970d4 00223fc4, 01e411b8, - 2292 028970d4 00223fc4, 01e411b8, - 2291 028970d4 00223fc4, 01e411b8, - 2290 028970d4 00223fc4, 01e411b8, - 2289 028970d4 00223fc4, 01e411b8, - 2288 028970d4 00223fc4, 01e411b8, - 2287 028970d4 00223fc4, 01e411b8, - 2286 028970d4 00223fc4, 01e411b8, - 2285 028970d4 00223fc4, 01e411b8, - 322 028970d4 01e411b8, - 0 028970d4 - -\\ - -COMMAND: histclear. -!HistClear - -This command releases any resources used by the Hist-family of commands. -Generally there's no need to call this explicitly, as each HistInit will first -cleanup the previous resources. - - 0:003> !HistClear - Completed successfully. - -\\ - -COMMAND: dumprcw. -!DumpRCW - -This command lists information about a Runtime Callable Wrapper. You can use -!DumpObj to obtain the RCW address corresponding to a managed object. - -The output contains all COM interface pointers that the RCW holds on to, which -is useful for investigating lifetime issues of interop-heavy applications. -\\ - -COMMAND: dumpccw. -!DumpCCW - -This command lists information about a COM Callable Wrapper. You can use -!DumpObj to obtain the CCW address corresponding to a managed object or pass -a COM interface pointer to which the object has been marshaled. - -The output contains the COM reference count of the CCW, which is useful for -investigating lifetime issues of interop-heavy applications. -\\ - -COMMAND: suppressjitoptimization. -!SuppressJitOptimization [on|off] - -!SuppressJitOptimization helps when you want the CLR to generate more debuggable -jitted code. This will turn off inlining, enregistering of local variables, -optimizing across sequence point boundaries, and precise variable lifetimes. -The resulting code should step more cleanly and make local variables more -accesible to inspection. The cost is that code quality is lower so your -application may execute a little slower. - -Once you execute !SuppressJitOptimization on, all managed modules that load -from that point onwards will not be optimized. The setting is only checked once -per module, at the time that module loads. Changing the setting later will only -affect modules that are loaded later. The recommendation is to set this once at -app startup and let the same setting apply for all managed modules. -\\ - -COMMAND: stoponcatch. -!StopOnCatch - -This commands sets a one-time breakpoint on the first managed catch clause -entered by managed code. This will cause the debugger to stop on the first line -of the catch handler. This is useful step from from the point of an exception -throw to the catch handler. -\\ - - -COMMAND: savestate. -!SaveState - -!SaveState serializes SOS managed breakpoints and watch values into a script file -that can be executed on a future windbg session to restore them. Use the windbg -command <$ to execute the saved script. -\\ - -COMMAND: watch. -!Watch -!Watch -add -!Watch -remove -!Watch -save -!Watch -rename -!Watch [-filter ] [-expand [type_cast]] - -!Watch displays the value of managed expressions, and manages the list of -expressions. A quick example... - -DEBUGGEE CODE: - static int Main(string[] args) - { - int p14 = -123; - MyStruct ms = new MyStruct(); - ms.a = p14; - ms.b = true; - MyStruct.c = "Foo"; - ... - -COMMAND WINDOW: -0:000> !Watch -add ms -0:000> !Watch -add p14 -0:000> !Watch - 1) MyStruct ms @ 0x0093D1CC - 2) int p14 = -123 - -ms in the command window will be a DML link, if you click on it then you see: - -COMMAND WINDOW: -0:000> !watch -expand 1 (MyStruct)ms - 1) MyStruct ms @ 0x0093D1CC - |- int a = -123 - |- bool b = true - |- string c = null - 2) int p14 = -123 - - - -Watch is capable of parsing a variety of different expressions. Expressions -can start with the name of a local variable or parameter in any stack frame. -To access fields of classes and structures simply add a '.' and then the field -name. To access array elements use standard [] notation. In the above -code example these would be valid expressions: -args[19] -p14 -ms.a - -Additionally 'this' is available in the leafmost frame to access fields. -You can not use unqualified field names. For example if there is a class: -class C -{ - int m_foo; - public void Hello() { Console.WriteLine(m_foo); } -} - -this.m_foo is a legal expression when executing inside C.Hello(), -m_foo would not work. - -Expressions also allow fully qualified type names and dereferencing static -fields from those types using the same '.' syntax. For example: -System.Int32 -System.Type.Missing - -Generic types can be used, though don't forget to use the CLI type -name which adds ` to the type name you would see -in VB or C#. For example: -System.Collections.Generic.Dictionary`2 - -Dereferencing fields or array indices can recurse as you might expect. A -made up example: -foo.bar.baz[19][12].m_field[4] - -Property values and method invocations can not be used in expressions as -the !Watch command will never execute any managed code to evaluate the -expressions. - -To remove entries in the list use !Watch -remove where index is -the number printed next to the expression in the viewing list. In our -initial example !Watch -remove 2 would remove the 'p14' expression from -the list. - -Saving/Renaming/Filtering -Sometimes it can be helpful to see only those values which have changed -since some point in the past. As an example assume that MyStruct.c changes -values during the go: - -0:000> !Watch -save myOldValues -0:000> g -... -0:000> !Watch -filter myOldValues - 3) string MyStruct.c = "Foo" - -When saving a list of expressions, the string that would be displayed in a -!Watch command is also saved. It is this string that is compared to determine -if the value has changed. Some changes might not alter the console string -representation. In the first example changing the value of ms.a would not -cause the 'ms' expression to be different. - -Renaming can be useful in scripts. It allows the name of a saved watch list to -change after saving it. For example: -0:000> !Watch -rename myCurrentValues myOldValues -0:000> !Watch -save myCurrentValues -0:000> !Watch -filter myOldValues - 3) string MyStruct.c = "Foo" - -Placing that pattern inside a script shows the changed watch values -since the last time the script was run. -\\ - diff --git a/src/ToolBox/SOS/Strike/data.h b/src/ToolBox/SOS/Strike/data.h deleted file mode 100644 index 9989038..0000000 --- a/src/ToolBox/SOS/Strike/data.h +++ /dev/null @@ -1,51 +0,0 @@ -// 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. - -// ==++== -// - -// -// ==--== -#ifndef __data_h__ -#define __data_h__ - -#include "cor.h" -#include "corhdr.h" -#include "cor.h" -#include "dacprivate.h" - -BOOL FileExist (const char *filename); -BOOL FileExist (const WCHAR *filename); - -// We use global variables -// because move returns void if it fails -//typedef DWORD DWORD_PTR; -//typedef ULONG ULONG_PTR; - -// Max length in WCHAR for a buffer to store metadata name -const int mdNameLen = 2048; -extern WCHAR g_mdName[mdNameLen]; - -const int nMDIMPORT = 128; -struct MDIMPORT -{ - enum MDType {InMemory, InFile, Dynamic}; - WCHAR *name; - size_t base; // base of the PE module - size_t mdBase; // base of the metadata - char *metaData; - ULONG metaDataSize; - MDType type; - IMetaDataImport *pImport; - - MDIMPORT *left; - MDIMPORT *right; -}; - -class Module; - -extern "C" BOOL ControlC; -extern IMetaDataDispenserEx *pDisp; - -#endif // __data_h__ diff --git a/src/ToolBox/SOS/Strike/datatarget.cpp b/src/ToolBox/SOS/Strike/datatarget.cpp deleted file mode 100644 index fe90f0e..0000000 --- a/src/ToolBox/SOS/Strike/datatarget.cpp +++ /dev/null @@ -1,215 +0,0 @@ -// 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. - -#include "sos.h" -#include "datatarget.h" -#include "corhdr.h" -#include "cor.h" -#include "dacprivate.h" -#include "sospriv.h" -#include "corerror.h" - -#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) - -DataTarget::DataTarget(void) : - m_ref(0) -{ -} - -STDMETHODIMP -DataTarget::QueryInterface( - THIS_ - ___in REFIID InterfaceId, - ___out PVOID* Interface - ) -{ - if (InterfaceId == IID_IUnknown || - InterfaceId == IID_ICLRDataTarget) - { - *Interface = (ICLRDataTarget*)this; - AddRef(); - return S_OK; - } - else if (InterfaceId == IID_ICorDebugDataTarget4) - { - *Interface = (ICorDebugDataTarget4*)this; - AddRef(); - return S_OK; - } - else - { - *Interface = NULL; - return E_NOINTERFACE; - } -} - -STDMETHODIMP_(ULONG) -DataTarget::AddRef( - THIS - ) -{ - LONG ref = InterlockedIncrement(&m_ref); - return ref; -} - -STDMETHODIMP_(ULONG) -DataTarget::Release( - THIS - ) -{ - LONG ref = InterlockedDecrement(&m_ref); - if (ref == 0) - { - delete this; - } - return ref; -} - -HRESULT STDMETHODCALLTYPE -DataTarget::GetMachineType( - /* [out] */ ULONG32 *machine) -{ - if (g_ExtControl == NULL) - { - return E_UNEXPECTED; - } - return g_ExtControl->GetExecutingProcessorType(machine); -} - -HRESULT STDMETHODCALLTYPE -DataTarget::GetPointerSize( - /* [out] */ ULONG32 *size) -{ -#if defined(SOS_TARGET_AMD64) || defined(SOS_TARGET_ARM64) - *size = 8; -#elif defined(SOS_TARGET_ARM) || defined(SOS_TARGET_X86) - *size = 4; -#else - #error Unsupported architecture -#endif - - return S_OK; -} - -HRESULT STDMETHODCALLTYPE -DataTarget::GetImageBase( - /* [string][in] */ LPCWSTR name, - /* [out] */ CLRDATA_ADDRESS *base) -{ - if (g_ExtSymbols == NULL) - { - return E_UNEXPECTED; - } - CHAR lpstr[MAX_LONGPATH]; - int name_length = WideCharToMultiByte(CP_ACP, 0, name, -1, lpstr, MAX_LONGPATH, NULL, NULL); - if (name_length == 0) - { - return E_FAIL; - } - return g_ExtSymbols->GetModuleByModuleName(lpstr, 0, NULL, base); -} - -HRESULT STDMETHODCALLTYPE -DataTarget::ReadVirtual( - /* [in] */ CLRDATA_ADDRESS address, - /* [length_is][size_is][out] */ PBYTE buffer, - /* [in] */ ULONG32 request, - /* [optional][out] */ ULONG32 *done) -{ - if (g_ExtData == NULL) - { - return E_UNEXPECTED; - } - return g_ExtData->ReadVirtual(address, (PVOID)buffer, request, done); -} - -HRESULT STDMETHODCALLTYPE -DataTarget::WriteVirtual( - /* [in] */ CLRDATA_ADDRESS address, - /* [size_is][in] */ PBYTE buffer, - /* [in] */ ULONG32 request, - /* [optional][out] */ ULONG32 *done) -{ - if (g_ExtData == NULL) - { - return E_UNEXPECTED; - } - return g_ExtData->WriteVirtual(address, (PVOID)buffer, request, done); -} - -HRESULT STDMETHODCALLTYPE -DataTarget::GetTLSValue( - /* [in] */ ULONG32 threadID, - /* [in] */ ULONG32 index, - /* [out] */ CLRDATA_ADDRESS* value) -{ - return E_NOTIMPL; -} - -HRESULT STDMETHODCALLTYPE -DataTarget::SetTLSValue( - /* [in] */ ULONG32 threadID, - /* [in] */ ULONG32 index, - /* [in] */ CLRDATA_ADDRESS value) -{ - return E_NOTIMPL; -} - -HRESULT STDMETHODCALLTYPE -DataTarget::GetCurrentThreadID( - /* [out] */ ULONG32* threadID) -{ - if (g_ExtSystem == NULL) - { - return E_UNEXPECTED; - } - return g_ExtSystem->GetCurrentThreadSystemId(threadID); -} - -HRESULT STDMETHODCALLTYPE -DataTarget::GetThreadContext( - /* [in] */ ULONG32 threadID, - /* [in] */ ULONG32 contextFlags, - /* [in] */ ULONG32 contextSize, - /* [out, size_is(contextSize)] */ PBYTE context) -{ - if (g_ExtSystem == NULL) - { - return E_UNEXPECTED; - } - return g_ExtSystem->GetThreadContextById(threadID, contextFlags, contextSize, context); -} - -HRESULT STDMETHODCALLTYPE -DataTarget::SetThreadContext( - /* [in] */ ULONG32 threadID, - /* [in] */ ULONG32 contextSize, - /* [out, size_is(contextSize)] */ PBYTE context) -{ - return E_NOTIMPL; -} - -HRESULT STDMETHODCALLTYPE -DataTarget::Request( - /* [in] */ ULONG32 reqCode, - /* [in] */ ULONG32 inBufferSize, - /* [size_is][in] */ BYTE *inBuffer, - /* [in] */ ULONG32 outBufferSize, - /* [size_is][out] */ BYTE *outBuffer) -{ - return E_NOTIMPL; -} - -HRESULT STDMETHODCALLTYPE -DataTarget::VirtualUnwind( - /* [in] */ DWORD threadId, - /* [in] */ ULONG32 contextSize, - /* [in, out, size_is(contextSize)] */ PBYTE context) -{ - if (g_ExtServices == NULL) - { - return E_UNEXPECTED; - } - return g_ExtServices->VirtualUnwind(threadId, contextSize, context); -} diff --git a/src/ToolBox/SOS/Strike/datatarget.h b/src/ToolBox/SOS/Strike/datatarget.h deleted file mode 100644 index 0293bc6..0000000 --- a/src/ToolBox/SOS/Strike/datatarget.h +++ /dev/null @@ -1,90 +0,0 @@ -// 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. - -class DataTarget : public ICLRDataTarget, ICorDebugDataTarget4 -{ -private: - LONG m_ref; // Reference count. - -public: - DataTarget(void); - virtual ~DataTarget() {} - - // IUnknown. - STDMETHOD(QueryInterface)( - THIS_ - ___in REFIID InterfaceId, - ___out PVOID* Interface - ); - STDMETHOD_(ULONG, AddRef)( - THIS - ); - STDMETHOD_(ULONG, Release)( - THIS - ); - - // - // ICLRDataTarget. - // - - virtual HRESULT STDMETHODCALLTYPE GetMachineType( - /* [out] */ ULONG32 *machine); - - virtual HRESULT STDMETHODCALLTYPE GetPointerSize( - /* [out] */ ULONG32 *size); - - virtual HRESULT STDMETHODCALLTYPE GetImageBase( - /* [string][in] */ LPCWSTR name, - /* [out] */ CLRDATA_ADDRESS *base); - - virtual HRESULT STDMETHODCALLTYPE ReadVirtual( - /* [in] */ CLRDATA_ADDRESS address, - /* [length_is][size_is][out] */ PBYTE buffer, - /* [in] */ ULONG32 request, - /* [optional][out] */ ULONG32 *done); - - virtual HRESULT STDMETHODCALLTYPE WriteVirtual( - /* [in] */ CLRDATA_ADDRESS address, - /* [size_is][in] */ PBYTE buffer, - /* [in] */ ULONG32 request, - /* [optional][out] */ ULONG32 *done); - - virtual HRESULT STDMETHODCALLTYPE GetTLSValue( - /* [in] */ ULONG32 threadID, - /* [in] */ ULONG32 index, - /* [out] */ CLRDATA_ADDRESS* value); - - virtual HRESULT STDMETHODCALLTYPE SetTLSValue( - /* [in] */ ULONG32 threadID, - /* [in] */ ULONG32 index, - /* [in] */ CLRDATA_ADDRESS value); - - virtual HRESULT STDMETHODCALLTYPE GetCurrentThreadID( - /* [out] */ ULONG32* threadID); - - virtual HRESULT STDMETHODCALLTYPE GetThreadContext( - /* [in] */ ULONG32 threadID, - /* [in] */ ULONG32 contextFlags, - /* [in] */ ULONG32 contextSize, - /* [out, size_is(contextSize)] */ PBYTE context); - - virtual HRESULT STDMETHODCALLTYPE SetThreadContext( - /* [in] */ ULONG32 threadID, - /* [in] */ ULONG32 contextSize, - /* [in, size_is(contextSize)] */ PBYTE context); - - virtual HRESULT STDMETHODCALLTYPE Request( - /* [in] */ ULONG32 reqCode, - /* [in] */ ULONG32 inBufferSize, - /* [size_is][in] */ BYTE *inBuffer, - /* [in] */ ULONG32 outBufferSize, - /* [size_is][out] */ BYTE *outBuffer); - - // ICorDebugDataTarget4 - - virtual HRESULT STDMETHODCALLTYPE VirtualUnwind( - /* [in] */ DWORD threadId, - /* [in] */ ULONG32 contextSize, - /* [in, out, size_is(contextSize)] */ PBYTE context); -}; \ No newline at end of file diff --git a/src/ToolBox/SOS/Strike/disasm.cpp b/src/ToolBox/SOS/Strike/disasm.cpp deleted file mode 100644 index ceb0fed..0000000 --- a/src/ToolBox/SOS/Strike/disasm.cpp +++ /dev/null @@ -1,1140 +0,0 @@ -// 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. - -// ==++== -// - -// -// ==--== - -#include "strike.h" -#include "gcinfo.h" -#include "util.h" -#include -#include - -#include "sos_md.h" - -#ifdef SOS_TARGET_X86 -namespace X86GCDump -{ -#include "gcdump.h" -#undef assert -#define assert(a) -#define CONTRACTL -#define DAC_ARG(x) -#define CONTRACTL_END -#define LIMITED_METHOD_CONTRACT -#define NOTHROW -#define GC_NOTRIGGER -#define SUPPORTS_DAC -#define LIMITED_METHOD_DAC_CONTRACT -#include "gcdecoder.cpp" -#undef CONTRACTL -#undef CONTRACTL_END -#undef LIMITED_METHOD_CONTRACT -#undef NOTHROW -#undef GC_NOTRIGGER -#undef _ASSERTE -#define _ASSERTE(a) do {} while (0) - -#include "gcdump.cpp" -#include "i386/gcdumpx86.cpp" -} -#endif // SOS_TARGET_X86 - -#ifdef SOS_TARGET_AMD64 -#include "gcdump.h" -#define DAC_ARG(x) -#define SUPPORTS_DAC -#define LIMITED_METHOD_DAC_CONTRACT -#undef LIMITED_METHOD_CONTRACT -#undef PREGDISPLAY - #ifdef LOG - #undef LOG - #endif - #define LOG(x) ((void)0) - #ifdef LOG_PIPTR - #undef LOG_PIPTR - #endif - #define LOG_PIPTR(pObjRef, gcFlags, hCallBack) ((void)0) -#include "gcdumpnonx86.cpp" -#endif // SOS_TARGET_AMD64 - -#include "disasm.h" - -#ifndef ERANGE -#define ERANGE 34 -#endif - -PVOID -GenOpenMapping( - PCSTR FilePath, - PULONG Size - ) -{ -#ifndef FEATURE_PAL - HANDLE hFile; - HANDLE hMappedFile; - PVOID MappedFile; - - hFile = CreateFileA( - FilePath, - GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, - OPEN_EXISTING, - 0, - NULL - ); -#if 0 - if ( hFile == NULL || hFile == INVALID_HANDLE_VALUE ) { - - if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) { - - // We're on an OS that doesn't support Unicode - // file operations. Convert to ANSI and see if - // that helps. - - CHAR FilePathA [ MAX_LONGPATH + 10 ]; - - if (WideCharToMultiByte (CP_ACP, - 0, - FilePath, - -1, - FilePathA, - sizeof (FilePathA), - 0, - 0 - ) > 0) { - - hFile = CreateFileA(FilePathA, - GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, - OPEN_EXISTING, - 0, - NULL - ); - } - } - - if ( hFile == NULL || hFile == INVALID_HANDLE_VALUE ) { - return NULL; - } - } -#endif - - *Size = GetFileSize(hFile, NULL); - if (*Size == ULONG_MAX) { - CloseHandle( hFile ); - return NULL; - } - - hMappedFile = CreateFileMapping ( - hFile, - NULL, - PAGE_READONLY, - 0, - 0, - NULL - ); - - if ( !hMappedFile ) { - CloseHandle ( hFile ); - return NULL; - } - - MappedFile = MapViewOfFile ( - hMappedFile, - FILE_MAP_READ, - 0, - 0, - 0 - ); - - CloseHandle (hMappedFile); - CloseHandle (hFile); - - return MappedFile; -#else // FEATURE_PAL - return NULL; -#endif // FEATURE_PAL -} - -char* PrintOneLine (__in_z char *begin, __in_z char *limit) -{ - if (begin == NULL || begin >= limit) { - return NULL; - } - char line[128]; - size_t length; - char *end; - while (1) { - if (IsInterrupt()) - return NULL; - length = strlen (begin); - end = strstr (begin, "\r\xa"); - if (end == NULL) { - ExtOut ("%s", begin); - end = begin+length+1; - if (end >= limit) { - return NULL; - } - } - else { - end += 2; - length = end-begin; - while (length) { - if (IsInterrupt()) - return NULL; - size_t n = length; - if (n > 127) { - n = 127; - } - strncpy_s (line,_countof(line), begin, n); - line[n] = '\0'; - ExtOut ("%s", line); - begin += n; - length -= n; - } - return end; - } - } -} - -void UnassemblyUnmanaged(DWORD_PTR IP, BOOL bSuppressLines) -{ - char filename[MAX_PATH_FNAME+1]; - char line[256]; - int lcount = 10; - - ULONG linenum = 0; - ULONG64 Displacement = 0; - BOOL fLineAvailable = FALSE; - ULONG64 vIP = 0; - - if (!bSuppressLines) - { - ReloadSymbolWithLineInfo(); - fLineAvailable = SUCCEEDED (g_ExtSymbols->GetLineByOffset(TO_CDADDR(IP), - &linenum, - filename, - MAX_PATH_FNAME+1, - NULL, - &Displacement)); - } - ULONG FileLines = 0; - ArrayHolder Buffer = NULL; - - if (fLineAvailable) - { - g_ExtSymbols->GetSourceFileLineOffsets(filename, NULL, 0, &FileLines); - if (FileLines == 0xFFFFFFFF || FileLines == 0) - fLineAvailable = FALSE; - } - - if (fLineAvailable) - { - Buffer = new ULONG64[FileLines]; - if (Buffer == NULL) - fLineAvailable = FALSE; - } - - if (!fLineAvailable) - { - vIP = TO_CDADDR(IP); - // There is no line info. Just disasm the code. - while (lcount-- > 0) - { - if (IsInterrupt()) - return; - g_ExtControl->Disassemble (vIP, 0, line, 256, NULL, &vIP); - ExtOut (line); - } - return; - } - - g_ExtSymbols->GetSourceFileLineOffsets(filename, Buffer, FileLines, NULL); - - int beginLine = 0; - int endLine = 0; - int lastLine; - linenum --; - for (lastLine = linenum; lastLine >= 0; lastLine --) { - if (IsInterrupt()) - return; - if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) { - g_ExtSymbols->GetNameByOffset(Buffer[lastLine], NULL, 0, NULL, &Displacement); - if (Displacement == 0) { - beginLine = lastLine; - break; - } - } - } - if (lastLine < 0) { - int n = lcount / 2; - lastLine = linenum-1; - beginLine = lastLine; - while (lastLine >= 0) { - if (IsInterrupt()) - return; - if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) { - beginLine = lastLine; - n --; - if (n == 0) { - break; - } - } - lastLine --; - } - } - while (beginLine > 0 && Buffer[beginLine-1] == DEBUG_INVALID_OFFSET) { - if (IsInterrupt()) - return; - beginLine --; - } - int endOfFunc = 0; - for (lastLine = linenum+1; (ULONG)lastLine < FileLines; lastLine ++) { - if (IsInterrupt()) - return; - if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) { - g_ExtSymbols->GetNameByOffset(Buffer[lastLine], NULL, 0, NULL, &Displacement); - if (Displacement == 0) { - endLine = lastLine; - break; - } - endOfFunc = lastLine; - } - } - if ((ULONG)lastLine == FileLines) { - int n = lcount / 2; - lastLine = linenum+1; - endLine = lastLine; - while ((ULONG)lastLine < FileLines) { - if (IsInterrupt()) - return; - if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) { - endLine = lastLine; - n --; - if (n == 0) { - break; - } - } - lastLine ++; - } - } - - PVOID MappedBase = NULL; - ULONG MappedSize = 0; - - class ToUnmap - { - PVOID *m_Base; - public: - ToUnmap (PVOID *base) - :m_Base(base) - {} - ~ToUnmap () - { - if (*m_Base) { - UnmapViewOfFile (*m_Base); - *m_Base = NULL; - } - } - }; - ToUnmap toUnmap(&MappedBase); - -#define MAX_SOURCE_PATH 1024 - char Found[MAX_SOURCE_PATH]; - char *pFile; - if (g_ExtSymbols->FindSourceFile(0, - filename, - DEBUG_FIND_SOURCE_BEST_MATCH | DEBUG_FIND_SOURCE_FULL_PATH, - NULL, - Found, - sizeof(Found), - NULL) != S_OK) - { - pFile = filename; - } - else - { - MappedBase = GenOpenMapping(Found, &MappedSize); - pFile = Found; - } - - lastLine = beginLine; - char *pFileCh = (char*)MappedBase; - if (MappedBase) { - ExtOut ("%s\n", pFile); - int n = beginLine; - while (n > 0) { - while (!(pFileCh[0] == '\r' && pFileCh[1] == 0xa)) { - if (IsInterrupt()) - return; - pFileCh ++; - } - pFileCh += 2; - n --; - } - } - - char filename1[MAX_PATH_FNAME+1]; - for (lastLine = beginLine; lastLine < endLine; lastLine ++) { - if (IsInterrupt()) - return; - if (MappedBase) { - ExtOut("%4d ", lastLine+1); - pFileCh = PrintOneLine(pFileCh, (char*)MappedBase+MappedSize); - } - if (Buffer[lastLine] != DEBUG_INVALID_OFFSET) { - if (MappedBase == 0) { - ExtOut (">>> %s:%d\n", pFile, lastLine+1); - } - vIP = Buffer[lastLine]; - ULONG64 vNextLineIP; - int i; - for (i = lastLine + 1; (ULONG)i < FileLines && Buffer[i] == DEBUG_INVALID_OFFSET; i ++) { - if (IsInterrupt()) - return; - } - if ((ULONG)i == FileLines) { - vNextLineIP = 0; - } - else - vNextLineIP = Buffer[i]; - while (1) { - if (IsInterrupt()) - return; - g_ExtControl->Disassemble(vIP, 0, line, 256, NULL, &vIP); - ExtOut (line); - if (vIP > vNextLineIP || vNextLineIP - vIP > 40) { - if (FAILED (g_ExtSymbols->GetLineByOffset(vIP, &linenum, - filename1, - MAX_PATH_FNAME+1, - NULL, - &Displacement))) { - if (lastLine != endOfFunc) { - break; - } - if (strstr (line, "ret") || strstr (line, "jmp")) { - break; - } - } - - if (linenum != (ULONG)lastLine+1 || strcmp (filename, filename1)) { - break; - } - } - else if (vIP == vNextLineIP) { - break; - } - } - } - } -} - -void DisasmAndClean (DWORD_PTR &IP, __out_ecount_opt(length) char *line, ULONG length) -{ - ULONG64 vIP = TO_CDADDR(IP); - g_ExtControl->Disassemble (vIP, 0, line, length, NULL, &vIP); - IP = (DWORD_PTR)vIP; - // remove the ending '\n' - char *ptr = strrchr (line, '\n'); - if (ptr != NULL) - ptr[0] = '\0'; -} - -// If byref, move to pass the byref prefix -BOOL IsByRef (__deref_inout_z char *& ptr) -{ - BOOL bByRef = FALSE; - const char* qindirCh = "qword ptr ["; - const char* dindirCh = "dword ptr ["; - const char* qindirDsCh = "qword ptr ds:["; - const char* dindirDsCh = "dword ptr ds:["; - if (ptr[0] == '[') - { - bByRef = TRUE; - ptr ++; - } - else if (!IsDbgTargetArm() && !strncmp (ptr, IsDbgTargetWin64() ? qindirCh : dindirCh, 11)) - { - bByRef = TRUE; - ptr += 11; - } - // The new disassembly engine for windbg formats indirect calls - // slightly differently: - else if (!IsDbgTargetArm() && !strncmp (ptr, IsDbgTargetWin64() ? qindirDsCh : dindirDsCh, 14)) - { - bByRef = TRUE; - ptr += 14; - } - return bByRef; -} - -BOOL IsTermSep (char ch) -{ - return (ch == '\0' || isspace (ch) || ch == ',' || ch == '\n'); -} - -// Find next term. A term is seperated by space or , -void NextTerm (__deref_inout_z char *& ptr) -{ - // If we have a byref, skip to ']' - if (IsByRef (ptr)) - { - while (ptr[0] != ']' && ptr[0] != '\0') - { - if (IsInterrupt()) - return; - ptr ++; - } - if (ptr[0] == ']') - ptr ++; - } - - while (!IsTermSep (ptr[0])) - { - if (IsInterrupt()) - return; - ptr ++; - } - - while (IsTermSep(ptr[0]) && (*ptr != '\0')) - { - if (IsInterrupt()) - return; - ptr ++; - } -} - - -// Parses something like 6e24d310, 0x6e24d310, or 6e24d310h. -// On 64-bit, also parses things like 000006fb`f9b70f50 and -// 000006fbf9b70f50 (as well as their 0x-prefix, -h suffix variations). -INT_PTR ParseHexNumber (__in_z char *ptr, ___out char **endptr) -{ - char *endptr1; - INT_PTR value1 = strtoul(ptr, &endptr1, 16); - -#ifdef _TARGET_WIN64_ - if ('`' == endptr1[0] && isxdigit(endptr1[1])) - { - char *endptr2; - INT_PTR value2 = strtoul(endptr1+1, &endptr2, 16); - - value1 = (value1 << 32) | value2; - endptr1 = endptr2; - } - // if the hex number was specified as 000006fbf9b70f50, an overflow occurred - else if ((INT_PTR)ULONG_MAX == value1 && errno == ERANGE) - { - if (!strncmp(ptr, "0x", 2)) - ptr += 2; - - char savedigit = ptr[8]; - ptr[8] = '\0'; - - value1 = strtoul(ptr, &endptr1, 16); - - ptr[8] = savedigit; - - char *endptr2; - INT_PTR value2 = strtoul(ptr+8, &endptr2, 16); - - size_t ndigits2 = endptr2 - (ptr+8); - - value1 = (value1 << (ndigits2*4)) | value2; - endptr1 = endptr2; - } -#endif // _TARGET_WIN64_ - - // account for the possible 'h' suffix - if ((*endptr1 == 'h') || (*endptr1 == 'H')) - { - ++endptr1; - } - - *endptr = endptr1; - return value1; -} - - -// only handle pure value, or memory address -INT_PTR GetValueFromExpr(__in_z char *ptr, INT_PTR &value) -{ - BOOL bNegative = FALSE; - value = 0; - char *myPtr = ptr; - BOOL bByRef = IsByRef (myPtr); - - // ARM disassembly contains '#' prefixes for hex constants - if (*myPtr == '#') - ++myPtr; - - if (myPtr[0] == '-') - { - myPtr ++; - bNegative = TRUE; - } - if (!strncmp (myPtr, "0x", 2) || isxdigit (myPtr[0])) - { - char *endptr; - value = ParseHexNumber(myPtr, &endptr); - if ((!bByRef && IsTermSep(endptr[0])) || (bByRef && endptr[0] == ']')) - { - if (bNegative) - value = -value; - ptr = endptr; - if (bByRef) - { - ptr += 1; - SafeReadMemory (TO_TADDR(value), &value, 4, NULL); - } - return ptr - myPtr; - } - } - - // handle mscorlib+0xed310 (6e24d310) - if (!bByRef) - { - ptr = myPtr; - // handle 'offset ' before the expression: - if (strncmp(ptr, "offset ", 7) == 0) - { - ptr += 7; - } - while (ptr[0] != ' ' && ptr[0] != '+' && ptr[0] != '\0') - { - if (IsInterrupt()) - return 0; - ptr ++; - } - if (ptr[0] == '+') - { - NextTerm (ptr); - if (ptr[0] == '(') - { - ptr ++; - char *endptr; - value = ParseHexNumber(ptr, &endptr); - if (endptr[0] == ')') - { - ptr ++; - return ptr - myPtr; - } - } - } - } - if (bByRef) - { - // handle dword [mscorlib+0x2bd788 (02ead788)] - ptr = myPtr; - // handle 'offset ' before the expression: - if (strncmp(ptr, "offset ", 7) == 0) - { - ptr += 7; - } - while (ptr[0] != '(' && ptr[0] != '\0') - { - if (IsInterrupt()) - return 0; - ptr ++; - } - if (ptr[0] == '(') - { - ptr ++; - char *endptr; - value = ParseHexNumber(ptr, &endptr); - if (endptr[0] == ')' && endptr[1] == ']') - { - ptr = endptr + 2; - SafeReadMemory (TO_TADDR(value), &value, 4, NULL); - return ptr - myPtr; - } - } - } - -#ifdef _TARGET_WIN64_ - // handle CLRStub@7fffc8601cc (000007fffc8601cc) - if (!bByRef && !strncmp(myPtr, "CLRStub[", 8)) - { - ptr = myPtr; - while (ptr[0] != '(' && ptr[0] != '\0') - { - if (IsInterrupt()) - return 0; - ptr ++; - } - if (ptr[0] == '(') - { - ptr ++; - char *endptr; - value = ParseHexNumber(ptr, &endptr); - if (endptr[0] == ')') - { - ptr ++; - return ptr - myPtr; - } - } - } -#endif // _TARGET_WIN64_ - - return 0; -} - - -const char * HelperFuncName (size_t IP) -{ - static char s_szHelperName[100]; - if (S_OK == g_sos->GetJitHelperFunctionName(IP, sizeof(s_szHelperName), &s_szHelperName[0], NULL)) - return &s_szHelperName[0]; - else - return NULL; -} - - -// Returns: -// NULL if the EHInfo passed in does not refer to a Typed clause -// "..." if pEHInfo->isCatchAllHandler is TRUE -// "TypeName" if pEHInfo is a DACEHInfo*. -// Note: -// The return is a pointer to a global buffer, therefore this value must -// be consumed as soon as possible after a call to this function. -LPCWSTR EHTypedClauseTypeName(___in const DACEHInfo* pEHInfo) -{ - _ASSERTE(pEHInfo != NULL); - if ((pEHInfo->clauseType == EHTyped) && pEHInfo->isCatchAllHandler) - { - return W("..."); - } - - // is there a method table or a token to look at? - if (pEHInfo->clauseType == EHTyped) - { - TADDR mt; - if (pEHInfo->moduleAddr == 0) - { - mt = TO_TADDR(pEHInfo->mtCatch); - NameForMT_s(mt, g_mdName, mdNameLen); - } else { - PrettyPrintClassFromToken(TO_TADDR(pEHInfo->moduleAddr), pEHInfo->tokCatch, g_mdName, mdNameLen, FormatCSharp); - } - return g_mdName; - } - - return NULL; -} - -BOOL IsClonedFinally(DACEHInfo *pEHInfo) -{ - // This maybe should be determined in the VM and passed in the DACEHInfo struct. -#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_) - return ((pEHInfo->tryStartOffset == pEHInfo->tryEndOffset) && - (pEHInfo->tryStartOffset == pEHInfo->handlerStartOffset) && - (pEHInfo->clauseType == EHFinally) && - pEHInfo->isDuplicateClause); -#else - return FALSE; -#endif -} - - -void SOSEHInfo::FormatForDisassembly(CLRDATA_ADDRESS offSet) -{ - LPCWSTR typeName = NULL; - // the order of printing and iterating will matter on the boundaries - - // Print END tags in forward order (most nested to least nested). However, cloned - // finally clauses are always at the end, but they should be considered most nested, - // so have a separate loop to output them first. - for (UINT i=0; i < EHCount; i++) - { - DACEHInfo *pCur = &m_pInfos[i]; - - if (IsClonedFinally(pCur) && - (offSet == pCur->handlerEndOffset)) - { - ExtOut ("EHHandler %d: CLONED FINALLY END\n", i); - } - } - - for (UINT i=0; i < EHCount; i++) - { - DACEHInfo *pCur = &m_pInfos[i]; - - if (pCur->isDuplicateClause) - { - // Don't print anything for duplicate clauses - continue; - } - - if (offSet == pCur->tryEndOffset) - { - ExtOut ("EHHandler %d: %s CLAUSE END\n", i, EHTypeName(pCur->clauseType)); - } - - if (offSet == pCur->handlerEndOffset) - { - ExtOut ("EHHandler %d: %s HANDLER END\n", i, EHTypeName(pCur->clauseType)); - } - } - - // Print BEGIN tags in reverse order (least nested to most nested). - for (UINT i=EHCount-1; i != (UINT)-1; --i) - { - DACEHInfo *pCur = &m_pInfos[i]; - - // Must do this before the isDuplicatedClause check, since these are marked as duplicated clauses. - if (IsClonedFinally(pCur) && - (offSet == pCur->handlerStartOffset)) - { - ExtOut ("EHHandler %d: CLONED FINALLY BEGIN\n", i); - } - - if (pCur->isDuplicateClause) - { - // Don't print anything for duplicate clauses - continue; - } - - if (offSet == pCur->tryStartOffset) - { - ExtOut ("EHHandler %d: %s CLAUSE BEGIN", i, EHTypeName(pCur->clauseType)); - typeName = EHTypedClauseTypeName(pCur); - if (typeName != NULL) - { - ExtOut(" catch(%S) ", typeName); - } - ExtOut ("\n"); - } - - if (offSet == pCur->handlerStartOffset) - { - ExtOut ("EHHandler %d: %s HANDLER BEGIN", i, EHTypeName(pCur->clauseType)); - typeName = EHTypedClauseTypeName(pCur); - if (typeName != NULL) - { - ExtOut(" catch(%S) ", typeName); - } - ExtOut ("\n"); - } - - if ((pCur->clauseType == EHFilter) && - (offSet == pCur->filterOffset)) - { - ExtOut ("EHHandler %d: %s FILTER BEGIN\n",i, EHTypeName(pCur->clauseType)); - } - } -} - - -// -// Implementation shared by X86, ARM, and X64 -// Any cross platform code should resolve through g_targetMachine or should -// use the IS_DBG_TARGET_XYZ macro. -// - -void PrintNativeStack(DWORD_PTR ip, BOOL bSuppressLines) -{ - char filename[MAX_PATH_FNAME + 1]; - char symbol[1024]; - ULONG64 displacement; - - HRESULT hr = g_ExtSymbols->GetNameByOffset(TO_CDADDR(ip), symbol, _countof(symbol), NULL, &displacement); - if (SUCCEEDED(hr) && symbol[0] != '\0') - { - ExtOut("%s", symbol); - - if (displacement) - { - ExtOut(" + %#x", displacement); - } - - if (!bSuppressLines) - { - ULONG line; - hr = g_ExtSymbols->GetLineByOffset(TO_CDADDR(ip), &line, filename, _countof(filename), NULL, NULL); - if (SUCCEEDED(hr)) - { - ExtOut(" [%s:%d]", filename, line); - } - } - } - else - { - DMLOut(DMLIP(ip)); - } -} - -// Return TRUE if we have printed something. -BOOL PrintCallInfo(DWORD_PTR vEBP, DWORD_PTR IP, DumpStackFlag& DSFlag, BOOL bSymbolOnly) -{ - ULONG64 Displacement; - BOOL bOutput = FALSE; - - // degrade gracefully for debuggees that don't have a runtime loaded, or a DAC available - DWORD_PTR methodDesc = 0; - if (!g_bDacBroken) - { - methodDesc = FunctionType (IP); - } - - if (methodDesc > 1) - { - bOutput = TRUE; - if (!bSymbolOnly) - DMLOut("%p %s ", SOS_PTR(vEBP), DMLIP(IP)); - DMLOut("(MethodDesc %s ", DMLMethodDesc(methodDesc)); - - // TODO: Microsoft, more checks to make sure method is not eeimpl, etc. Add this field to MethodDesc - - DacpCodeHeaderData codeHeaderData; - if (codeHeaderData.Request(g_sos, TO_CDADDR(IP)) == S_OK) - { - DWORD_PTR IPBegin = (DWORD_PTR) codeHeaderData.MethodStart; - methodDesc = (DWORD_PTR) codeHeaderData.MethodDescPtr; - Displacement = IP - IPBegin; - if (IP >= IPBegin && Displacement <= codeHeaderData.MethodSize) - ExtOut ("+ %#x ", Displacement); - } - if (NameForMD_s(methodDesc, g_mdName, mdNameLen)) - { - ExtOut("%S)", g_mdName); - } - else - { - ExtOut("%s)", DMLIP(IP)); - } - } - else - { - if (!DSFlag.fEEonly) - { - bOutput = TRUE; - const char *name; - if (!bSymbolOnly) - DMLOut("%p %s ", SOS_PTR(vEBP), DMLIP(IP)); - - // if AMD64 ever becomes a cross platform target this must be resolved through - // virtual dispatch rather than conditional compilation -#if defined(_TARGET_AMD64_) || defined(_TARGET_X86_) - // degrade gracefully for debuggees that don't have a runtime loaded, or a DAC available - eTargetType ett = ettUnk; - if (!g_bDacBroken) - { - DWORD_PTR finalMDorIP = 0; - ett = GetFinalTarget(IP, &finalMDorIP); - if (ett == ettNative || ett==ettJitHelp) - { - methodDesc = 0; - IP = finalMDorIP; - } - else - { - methodDesc = finalMDorIP; - } - } -#endif // _TARGET_AMD64_ || _TARGET_X86_ - if (methodDesc == 0) - { - PrintNativeStack(IP, DSFlag.fSuppressSrcInfo); - } - else if (g_bDacBroken) - { - // degrade gracefully for debuggees that don't have a runtime loaded, or a DAC available - DMLOut(DMLIP(IP)); - } - else if (IsMethodDesc (IP)) - { - NameForMD_s(IP, g_mdName, mdNameLen); - ExtOut(" (stub for %S)", g_mdName); - } - else if (IsMethodDesc(IP+5)) { - NameForMD_s((DWORD_PTR)(IP+5), g_mdName, mdNameLen); - DMLOut("%s (MethodDesc %s %S)", DMLIP(IP), DMLMethodDesc(IP+5), g_mdName); - } - else if ((name = HelperFuncName(IP)) != NULL) { - ExtOut(" (JitHelp: %s)", name); - } -#if defined(_TARGET_AMD64_) || defined(_TARGET_X86_) - else if (ett == ettMD || ett == ettStub) - { - NameForMD_s(methodDesc, g_mdName,mdNameLen); - DMLOut("%s (stub for %S)", DMLIP(IP), g_mdName); - // fallthrough to return - } -#endif // _TARGET_AMD64_ || _TARGET_X86_ - else - { - DMLOut(DMLIP(IP)); - } - } - } - return bOutput; -} - -void DumpStackWorker (DumpStackFlag &DSFlag) -{ - DWORD_PTR eip; - ULONG64 Offset; - g_ExtRegisters->GetInstructionOffset(&Offset); - eip = (DWORD_PTR)Offset; - - ExtOut("Current frame: "); - PrintCallInfo (0, eip, DSFlag, TRUE); - ExtOut ("\n"); - - // make certain dword/qword aligned - DWORD_PTR ptr = DSFlag.top & (~ALIGNCONST); - - ExtOut (g_targetMachine->GetDumpStackHeading()); - while (ptr < DSFlag.end) - { - if (IsInterrupt()) - return; - DWORD_PTR retAddr; - DWORD_PTR whereCalled; - move_xp(retAddr, ptr); - g_targetMachine->IsReturnAddress(retAddr, &whereCalled); - if (whereCalled) - { - BOOL bOutput = PrintCallInfo(ptr-sizeof(TADDR), retAddr, DSFlag, FALSE); - if (!DSFlag.fEEonly) - { - if (whereCalled != 0xFFFFFFFF) - { - ExtOut (", calling "); - PrintCallInfo (0, whereCalled, DSFlag, TRUE); - } - } - if (bOutput) - ExtOut ("\n"); - - DWORD_PTR cxrAddr; - CROSS_PLATFORM_CONTEXT cxr; - DWORD_PTR exrAddr; - EXCEPTION_RECORD exr; - - if (g_targetMachine->GetExceptionContext(ptr,retAddr,&cxrAddr,&cxr,&exrAddr,&exr)) - { - TADDR sp = g_targetMachine->GetSP(cxr); - TADDR ip = g_targetMachine->GetIP(cxr); - bOutput = PrintCallInfo(sp, ip, DSFlag, FALSE); - if (bOutput) - { - ExtOut(" ====> Exception "); - if (exrAddr) - ExtOut("Code %x ", exr.ExceptionCode); - ExtOut ("cxr@%p", SOS_PTR(cxrAddr)); - if (exrAddr) - ExtOut(" exr@%p", SOS_PTR(exrAddr)); - ExtOut("\n"); - } - } - } - ptr += sizeof (DWORD_PTR); - } -} - -#ifdef SOS_TARGET_X86 -/// -/// X86Machine implementation -/// -LPCSTR X86Machine::s_DumpStackHeading = "ChildEBP RetAddr Caller, Callee\n"; -LPCSTR X86Machine::s_DSOHeading = "ESP/REG Object Name\n"; -LPCSTR X86Machine::s_GCRegs[7] = {"eax", "ebx", "ecx", "edx", "esi", "edi", "ebp"}; -LPCSTR X86Machine::s_SPName = "ESP"; - -void PrintNothing (const char *fmt, ...) -{ - // Do nothing. -} - -/// -/// Dump X86 GCInfo header and table -/// -void X86Machine::DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const -{ - X86GCDump::InfoHdr header; - X86GCDump::GCDump gcDump(gcInfoToken.Version, encBytes, 5, true); - BYTE* pTable = dac_cast(gcInfoToken.Info); - if (bPrintHeader) - { - gcDump.gcPrintf = gcPrintf; - gcPrintf("Method info block:\n"); - } - else - { - gcDump.gcPrintf = PrintNothing; - } - pTable += gcDump.DumpInfoHdr(pTable, &header, &methodSize, 0); - if (bPrintHeader) - { - gcPrintf("\n"); - gcPrintf("Pointer table:\n"); - } - gcDump.gcPrintf = gcPrintf; - gcDump.DumpGCTable(pTable, header, methodSize, 0); -} -#endif // SOS_TARGET_X86 - -#ifdef SOS_TARGET_ARM -/// -/// ARMMachine implementation -/// -LPCSTR ARMMachine::s_DumpStackHeading = "ChildFP RetAddr Caller, Callee\n"; -LPCSTR ARMMachine::s_DSOHeading = "SP/REG Object Name\n"; -LPCSTR ARMMachine::s_GCRegs[14] = {"r0", "r1", "r2", "r3", "r4", "r5", "r6", - "r7", "r8", "r9", "r10", "r11", "r12", "lr"}; -LPCSTR ARMMachine::s_SPName = "sp"; - -#endif // SOS_TARGET_ARM - -#ifdef SOS_TARGET_AMD64 -/// -/// AMD64Machine implementation -/// -LPCSTR AMD64Machine::s_DumpStackHeading = "Child-SP RetAddr Caller, Callee\n"; -LPCSTR AMD64Machine::s_DSOHeading = "RSP/REG Object Name\n"; -LPCSTR AMD64Machine::s_GCRegs[15] = {"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", - "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"}; -LPCSTR AMD64Machine::s_SPName = "RSP"; - -/// -/// Dump AMD64 GCInfo table -/// -void AMD64Machine::DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const -{ - if (bPrintHeader) - { - ExtOut("Pointer table:\n"); - } - - GCDump gcDump(gcInfoToken.Version, encBytes, 5, true); - gcDump.gcPrintf = gcPrintf; - - gcDump.DumpGCTable(dac_cast(gcInfoToken.Info), methodSize, 0); -} - -#endif // SOS_TARGET_AMD64 - -#ifdef SOS_TARGET_ARM64 -/// -/// ARM64Machine implementation -/// -LPCSTR ARM64Machine::s_DumpStackHeading = "ChildFP RetAddr Caller, Callee\n"; -LPCSTR ARM64Machine::s_DSOHeading = "SP/REG Object Name\n"; -// excluding x18, fp & lr as these will not contain object references -LPCSTR ARM64Machine::s_GCRegs[28] = {"x0", "x1", "x2", "x3", "x4", "x5", "x6", - "x7", "x8", "x9", "x10", "x11", "x12", "x13", - "x14", "x15", "x16", "x17", "x19", "x20","x21", - "x22", "x23", "x24", "x25", "x26", "x27", "x28"}; -LPCSTR ARM64Machine::s_SPName = "sp"; - -#endif // SOS_TARGET_ARM64 - - diff --git a/src/ToolBox/SOS/Strike/disasm.h b/src/ToolBox/SOS/Strike/disasm.h deleted file mode 100644 index 2a2d9f7..0000000 --- a/src/ToolBox/SOS/Strike/disasm.h +++ /dev/null @@ -1,450 +0,0 @@ -// 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. - -// ==++== -// - -// -// ==--== -#ifndef __disasm_h__ -#define __disasm_h__ - -#include "sos_stacktrace.h" - -struct InfoHdr; -class GCDump; - - -struct DumpStackFlag -{ - BOOL fEEonly; - BOOL fSuppressSrcInfo; - DWORD_PTR top; - DWORD_PTR end; -}; - -struct GCEncodingInfo -{ - LPVOID pvMainFiber; - LPVOID pvGCTableFiber; - - BYTE *table; - unsigned int methodSize; - - char buf[1000]; - int cch; - - SIZE_T ofs; - - // When decoding a cold region, set this to the size of the hot region to keep offset - // calculations working. - SIZE_T hotSizeToAdd; - bool fDoneDecoding; -}; - -// Returns: -// NULL if the EHInfo passed in does not refer to a Typed clause -// "..." if pEHInfo->isCatchAllHandler is TRUE -// "TypeName" if pEHInfo is a DACEHInfo* that references type "TypeName". -// Note: -// The return is a pointer to a global buffer, therefore this value must -// be consumed as soon as possible after a call to this function. -LPCWSTR EHTypedClauseTypeName(const DACEHInfo* pEHInfo); - -struct SOSEHInfo -{ - DACEHInfo *m_pInfos; - UINT EHCount; - CLRDATA_ADDRESS methodStart; - - SOSEHInfo() { ZeroMemory(this, sizeof(SOSEHInfo)); } - ~SOSEHInfo() { if (m_pInfos) { delete [] m_pInfos; } } - - void FormatForDisassembly(CLRDATA_ADDRESS offSet); -}; - -BOOL IsClonedFinally(DACEHInfo *pEHInfo); - -void DumpStackWorker (DumpStackFlag &DSFlag); - -void UnassemblyUnmanaged (DWORD_PTR IP, BOOL bSuppressLines); - -HRESULT CheckEEDll (); - -BOOL GetCalleeSite (DWORD_PTR IP, DWORD_PTR &IPCallee); - -void DisasmAndClean (DWORD_PTR &IP, __out_ecount_opt(length) char *line, ULONG length); - -INT_PTR GetValueFromExpr(___in __in_z char *ptr, INT_PTR &value); - -void NextTerm (__deref_inout_z char *& ptr); - -BOOL IsByRef (__deref_inout_z char *& ptr); - -BOOL IsTermSep (char ch); - -const char * HelperFuncName (size_t IP); - -enum eTargetType { ettUnk = 0, ettNative = 1, ettJitHelp = 2, ettStub = 3, ettMD = 4 }; - -// GetFinalTarget is based on HandleCall, but avoids printing anything to the output. -// This is currently only called on x64 -eTargetType GetFinalTarget(DWORD_PTR callee, DWORD_PTR* finalMDorIP); - -#ifdef _MSC_VER -// SOS is essentially single-threaded. ignore "construction of local static object is not thread-safe" -#pragma warning(push) -#pragma warning(disable:4640) -#endif // _MSC_VER - -//----------------------------------------------------------------------------------------- -// -// Implementations for the supported target platforms -// -//----------------------------------------------------------------------------------------- - -#ifndef THUMB_CODE -#define THUMB_CODE 1 -#endif -#define STACKWALK_CONTROLPC_ADJUST_OFFSET 2 - -#ifdef SOS_TARGET_X86 - -/// X86 Machine specific code -class X86Machine : public IMachine -{ -public: - typedef X86_CONTEXT TGT_CTXT; - - static IMachine* GetInstance() - { static X86Machine s_X86MachineInstance; return &s_X86MachineInstance; } - - ULONG GetPlatform() const { return IMAGE_FILE_MACHINE_I386; } - ULONG GetContextSize() const { return sizeof(X86_CONTEXT); } - virtual void Unassembly( - TADDR IPBegin, - TADDR IPEnd, - TADDR IPAskedFor, - TADDR GCStressCodeCopy, - GCEncodingInfo * pGCEncodingInfo, - SOSEHInfo *pEHInfo, - BOOL bSuppressLines, - BOOL bDisplayOffsets) const; - virtual void IsReturnAddress( - TADDR retAddr, - TADDR* whereCalled) const; - virtual BOOL GetExceptionContext ( - TADDR stack, - TADDR PC, - TADDR *cxrAddr, - CROSS_PLATFORM_CONTEXT * cxr, - TADDR * exrAddr, - PEXCEPTION_RECORD exr) const; - - // retrieve stack pointer, frame pointer, and instruction pointer from the target context - virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.X86Context.Esp; } - virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.X86Context.Ebp; } - virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.X86Context.Eip; } - - virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const; - virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const; - - virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; } - virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; } - virtual LPCSTR GetSPName() const { return s_SPName; } - virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const - { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = _countof(s_GCRegs); } - - virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const; - -private: - X86Machine() {} - ~X86Machine() {} - X86Machine(const X86Machine& machine); // undefined - X86Machine & operator=(const X86Machine&); // undefined - -private: - static LPCSTR s_DumpStackHeading; - static LPCSTR s_DSOHeading; - static LPCSTR s_GCRegs[7]; - static LPCSTR s_SPName; -}; // class X86Machine - -#endif // SOS_TARGET_X86 - - -#ifdef SOS_TARGET_ARM - -/// ARM Machine specific code -class ARMMachine : public IMachine -{ -public: - typedef ARM_CONTEXT TGT_CTXT; - - static IMachine* GetInstance() - { return &s_ARMMachineInstance; } - - ULONG GetPlatform() const { return IMAGE_FILE_MACHINE_ARMNT; } - ULONG GetContextSize() const { return sizeof(ARM_CONTEXT); } - virtual void Unassembly( - TADDR IPBegin, - TADDR IPEnd, - TADDR IPAskedFor, - TADDR GCStressCodeCopy, - GCEncodingInfo *pGCEncodingInfo, - SOSEHInfo *pEHInfo, - BOOL bSuppressLines, - BOOL bDisplayOffsets) const; - virtual void IsReturnAddress( - TADDR retAddr, - TADDR* whereCalled) const; - virtual BOOL GetExceptionContext ( - TADDR stack, - TADDR PC, - TADDR *cxrAddr, - CROSS_PLATFORM_CONTEXT * cxr, - TADDR *exrAddr, - PEXCEPTION_RECORD exr) const; - - // retrieve stack pointer, frame pointer, and instruction pointer from the target context - virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.ArmContext.Sp; } - // @ARMTODO: frame pointer - virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const { return 0; } - virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.ArmContext.Pc; } - - virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const; - virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const; - - virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; } - virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; } - virtual LPCSTR GetSPName() const { return s_SPName; } - virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const - { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = _countof(s_GCRegs); } - - virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const; - -private: - ARMMachine() {} - ~ARMMachine() {} - ARMMachine(const ARMMachine& machine); // undefined - ARMMachine & operator=(const ARMMachine&); // undefined - -private: - static LPCSTR s_DumpStackHeading; - static LPCSTR s_DSOHeading; - static LPCSTR s_GCRegs[14]; - static LPCSTR s_SPName; - static ARMMachine s_ARMMachineInstance; -}; // class ARMMachine - -#endif // SOS_TARGET_ARM - -#ifdef SOS_TARGET_AMD64 - -/// AMD64 Machine specific code -class AMD64Machine : public IMachine -{ -public: - typedef AMD64_CONTEXT TGT_CTXT; - - static IMachine* GetInstance() - { static AMD64Machine s_AMD64MachineInstance; return &s_AMD64MachineInstance; } - - ULONG GetPlatform() const { return IMAGE_FILE_MACHINE_AMD64; } - ULONG GetContextSize() const { return sizeof(AMD64_CONTEXT); } - - virtual void Unassembly( - TADDR IPBegin, - TADDR IPEnd, - TADDR IPAskedFor, - TADDR GCStressCodeCopy, - GCEncodingInfo *pGCEncodingInfo, - SOSEHInfo *pEHInfo, - BOOL bSuppressLines, - BOOL bDisplayOffsets) const; - - virtual void IsReturnAddress( - TADDR retAddr, - TADDR* whereCalled) const; - - virtual BOOL GetExceptionContext ( - TADDR stack, - TADDR PC, - TADDR *cxrAddr, - CROSS_PLATFORM_CONTEXT * cxr, - TADDR *exrAddr, - PEXCEPTION_RECORD exr) const; - - // retrieve stack pointer, frame pointer, and instruction pointer from the target context - virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Amd64Context.Rsp; } - virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Amd64Context.Rbp; } - virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Amd64Context.Rip; } - - virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const; - virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const; - - virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; } - virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; } - virtual LPCSTR GetSPName() const { return s_SPName; } - virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const - { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = _countof(s_GCRegs); } - - virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const; - -private: - AMD64Machine() {} - ~AMD64Machine() {} - AMD64Machine(const AMD64Machine& machine); // undefined - AMD64Machine & operator=(const AMD64Machine&); // undefined - -private: - static LPCSTR s_DumpStackHeading; - static LPCSTR s_DSOHeading; - static LPCSTR s_GCRegs[15]; - static LPCSTR s_SPName; -}; // class AMD64Machine - -#endif // SOS_TARGET_AMD64 - -#ifdef SOS_TARGET_ARM64 - -/// ARM64 Machine specific code -class ARM64Machine : public IMachine -{ -public: - typedef ARM64_CONTEXT TGT_CTXT; - - static IMachine* GetInstance() - { static ARM64Machine s_ARM64MachineInstance; return &s_ARM64MachineInstance; } - - ULONG GetPlatform() const { return IMAGE_FILE_MACHINE_ARM64; } - ULONG GetContextSize() const { return sizeof(ARM64_CONTEXT); } - virtual void Unassembly( - TADDR IPBegin, - TADDR IPEnd, - TADDR IPAskedFor, - TADDR GCStressCodeCopy, - GCEncodingInfo *pGCEncodingInfo, - SOSEHInfo *pEHInfo, - BOOL bSuppressLines, - BOOL bDisplayOffsets) const; - virtual void IsReturnAddress( - TADDR retAddr, - TADDR* whereCalled) const; - virtual BOOL GetExceptionContext ( - TADDR stack, - TADDR PC, - TADDR *cxrAddr, - CROSS_PLATFORM_CONTEXT * cxr, - TADDR *exrAddr, - PEXCEPTION_RECORD exr) const; - - // retrieve stack pointer, frame pointer, and instruction pointer from the target context - virtual TADDR GetSP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Arm64Context.Sp; } - virtual TADDR GetBP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Arm64Context.Fp; } - virtual TADDR GetIP(const CROSS_PLATFORM_CONTEXT & ctx) const { return ctx.Arm64Context.Pc; } - - virtual void FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const; - virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const; - - virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; } - virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; } - virtual LPCSTR GetSPName() const { return s_SPName; } - virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const - { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = _countof(s_GCRegs);} - - virtual void DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printfFtn gcPrintf, bool encBytes, bool bPrintHeader) const; - -private: - ARM64Machine() {} - ~ARM64Machine() {} - ARM64Machine(const ARM64Machine& machine); // undefined - ARM64Machine & operator=(const ARM64Machine&); // undefined - - static LPCSTR s_DumpStackHeading; - static LPCSTR s_DSOHeading; - static LPCSTR s_GCRegs[28]; - static LPCSTR s_SPName; - -}; // class ARM64Machine - -#endif // SOS_TARGET_ARM64 -#ifdef _MSC_VER -#pragma warning(pop) -#endif // _MSC_VER - - -// -// Inline methods -// - - -#ifdef SOS_TARGET_X86 -inline void X86Machine::FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const -{ - TGT_CTXT& src = *(TGT_CTXT*) srcCtx; - dest->StackOffset = src.Esp; - dest->FrameOffset = src.Ebp; - dest->InstructionOffset = src.Eip; -} - -inline void X86Machine::FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx /*= 0*/) const -{ - TGT_CTXT* dest = (TGT_CTXT*)destCtx + idx; - *dest = *(TGT_CTXT*)srcCtx; -} -#endif // SOS_TARGET_X86 - - -#ifdef SOS_TARGET_ARM -inline void ARMMachine::FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const -{ - TGT_CTXT& src = *(TGT_CTXT*) srcCtx; - dest->StackOffset = src.Sp; - // @ARMTODO: frame pointer - keep in sync with ARMMachine::GetBP - dest->FrameOffset = 0; - dest->InstructionOffset = src.Pc; -} - -inline void ARMMachine::FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx /*= 0*/) const -{ - TGT_CTXT* dest = (TGT_CTXT*)destCtx + idx; - *dest = *(TGT_CTXT*)srcCtx; -} -#endif // SOS_TARGET_ARM - - -#ifdef SOS_TARGET_AMD64 -inline void AMD64Machine::FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const -{ - TGT_CTXT& src = *(TGT_CTXT*) srcCtx; - dest->StackOffset = src.Rsp; - dest->FrameOffset = src.Rbp; - dest->InstructionOffset = src.Rip; -} - -inline void AMD64Machine::FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx /*= 0*/) const -{ - TGT_CTXT* dest = (TGT_CTXT*)destCtx + idx; - *dest = *(TGT_CTXT*)srcCtx; -} -#endif // SOS_TARGET_AMD64 - -#ifdef SOS_TARGET_ARM64 -inline void ARM64Machine::FillSimpleContext(StackTrace_SimpleContext * dest, LPVOID srcCtx) const -{ - TGT_CTXT& src = *(TGT_CTXT*) srcCtx; - dest->StackOffset = src.Sp; - dest->FrameOffset = src.Fp; - dest->InstructionOffset = src.Pc; -} - -inline void ARM64Machine::FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx /*= 0*/) const -{ - TGT_CTXT* dest = (TGT_CTXT*)destCtx + idx; - *dest = *(TGT_CTXT*)srcCtx; -} -#endif // SOS_TARGET_ARM64 - -#endif // __disasm_h__ diff --git a/src/ToolBox/SOS/Strike/disasmARM.cpp b/src/ToolBox/SOS/Strike/disasmARM.cpp deleted file mode 100644 index e9a7a69..0000000 --- a/src/ToolBox/SOS/Strike/disasmARM.cpp +++ /dev/null @@ -1,627 +0,0 @@ -// 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. - -// ==++== -// - -// -// ==--== - -#ifndef _TARGET_ARM_ -#define _TARGET_ARM_ -#endif - - -#include "strike.h" -#include "util.h" -#include - - -#include "disasm.h" - -#include "../../../inc/corhdr.h" -#include "../../../inc/cor.h" -#include "../../../inc/dacprivate.h" - -#ifndef FEATURE_PAL -namespace ARMGCDump -{ -#undef _TARGET_X86_ -#define WIN64EXCEPTIONS -#undef LIMITED_METHOD_CONTRACT -#define LIMITED_METHOD_DAC_CONTRACT -#define SUPPORTS_DAC -#define LF_GCROOTS -#define LL_INFO1000 -#define LOG(x) -#define LOG_PIPTR(pObjRef, gcFlags, hCallBack) -#define DAC_ARG(x) -#include "gcdumpnonx86.cpp" -} -#endif // !FEATURE_PAL - -#if defined(_TARGET_WIN64_) -#error This file does not support SOS targeting ARM from a 64-bit debugger -#endif - -#if !defined(SOS_TARGET_ARM) -#error This file should be used to support SOS targeting ARM debuggees -#endif - -#ifdef SOS_TARGET_ARM -ARMMachine ARMMachine::s_ARMMachineInstance; - -// Decodes the target label of the immediate form of bl and blx instructions. The PC given is that of the -// start of the instruction. -static TADDR DecodeCallTarget(TADDR PC, WORD rgInstr[2]) -{ - // Displacement is spread across several bitfields in the two words of the instruction. Using the same - // bitfield names as the ARM Architecture Reference Manual. - DWORD S = (rgInstr[0] & 0x0400) >> 10; - DWORD imm10 = rgInstr[0] & 0x03ff; - DWORD J1 = (rgInstr[1] & 0x2000) >> 13; - DWORD J2 = (rgInstr[1] & 0x0800) >> 11; - DWORD imm11 = rgInstr[1] & 0x07ff; - - // For reasons that escape me the I1 and I2 fields are computed by XOR'ing J1 and J2 with S. - DWORD I1 = (~J1 ^ S) & 0x1; - DWORD I2 = (~J2 ^ S) & 0x1; - - // The final displacement is put together as: SignExtend(S:I1:I2:imm10:imm11:0) - DWORD highByte = S ? 0xff000000 : 0x00000000; - DWORD disp = highByte | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1); - - // The displacement is relative to the PC but the PC for a given instruction reads as the PC for the - // beginning of the instruction plus 4. - return PC + 4 + disp; -} - -// Validate that a potential call target points to readable memory. If so, and the code appears to be one of -// our standard jump thunks we'll deference through that and return the real target. Returns 0 if any checks -// fail. -static TADDR GetRealCallTarget(TADDR PC) -{ - WORD instr[2]; - - // Read the minimum (a WORD) first in case we're calling to a single WORD method at the end of a page - // (e.g. BLX ). - if (g_ExtData->ReadVirtual(TO_CDADDR(PC), &instr[0], sizeof(WORD), NULL) != S_OK) - return 0; - - // All the jump thunks we handle start with the literal form of LDR (i.e. LDR , [PC +/- ]). It's - // always the two word form since we're either loading R12 or PC. We never use the decrement version of - // the instruction (U == 0). - // If it's not an instruction of that form we can return immediately. - if (instr[0] != 0xf8df) - return PC; - - // The first instruction is definitely a LDR of the form we expect so it's OK to read the second half of - // the encoding. - if (g_ExtData->ReadVirtual(TO_CDADDR(PC + 2), &instr[1], sizeof(WORD), NULL) != S_OK) - return 0; - - // Determine which register we're loading. There are three cases: - // 1) PC: we're jumping, perform final calculation of the jump target - // 2) R12: we're possibly setting up a special argument to the jump target. Ignore this instruction and - // check for a LDR PC in the next instruction - // 3) Any other register: we don't recognize this instruction sequence, just return the PC we have - WORD reg = (instr[1] & 0xf000) >> 12; - if (reg == 12) - { - // Possibly a LDR R12, [...]; LDR PC, [...] thunk. Overwrite the current instruction with the next and - // then fall through into the common LDR PC, [...] handling below. If we fail to read the next word - // we're not really looking at valid code. But we need to be more careful reading the second word of - // the potential instruction since there are valid sequences that would terminate with a single word - // at the end of page. - if (g_ExtData->ReadVirtual(TO_CDADDR(PC + 4), &instr[0], sizeof(WORD), NULL) != S_OK) - return 0; - - // Following instruction is not a LDR . Return this PC as the real target. - if (instr[0] != 0xf8df) - return PC; - - // Read second half of the LDR instruction. - if (g_ExtData->ReadVirtual(TO_CDADDR(PC + 6), &instr[1], sizeof(WORD), NULL) != S_OK) - return 0; - - // Determine the target register. If it's not the PC then return this PC as the real target. - reg = (instr[1] & 0xf000) >> 12; - if (reg != 12) - return PC; - - // Fall through to process this LDR PC, [...] instruction. Update the input PC because it figures into - // the calculation below. - PC += 4; - } - else if (reg == 15) - { - // First instruction was a LDR PC, [...] Just fall through to common handling below. - } - else - { - // Any other target register is unrecognized. Just return what we have as the final target. - return PC; - } - - // Decode the LDR PC, [PC + ] to find the jump target. - // The displacement is in the low order 12 bits of the second instruction word. - DWORD disp = instr[1] & 0x0fff; - - // The PC used for the effective address calculation is the PC from the start of the instruction rounded - // down to 4-byte alignment then incremented by 4. - TADDR targetAddress = (PC & ~3) + 4 + disp; - - // Read the target address from this routine. - TADDR target; - if (g_ExtData->ReadVirtual(TO_CDADDR(targetAddress), &target, sizeof(target), NULL) != S_OK) - return 0; - - // Clear the low-bit in the target used to indicate a Thumb mode destination. If this is not set we can't - // be looking at one of our jump thunks (in fact ARM mode code is illegal under CoreARM so this would - // indicate an issue). - _ASSERTE((target & 1) == 1); - target &= ~1; - - // Recursively call ourselves on this target in case we have any double jump thunks. - return GetRealCallTarget(target); -} - -// Determine (heuristically, basically a best effort guess) whether an address on the stack represents a -// return address. This is achieved by looking at the memory prior to the potential return address and -// disassembling it to see whether it looks like a potential call. If possible the target of the callsite is -// also returned. -// -// Result is returned in whereCalled: -// 0 : retAddr doesn't look like a return address -// 0xffffffff : retAddr looks like a return address but we couldn't tell where the call site was targeted -// : retAddr looks like a return address, *whereCalled set to target address -void ARMMachine::IsReturnAddress(TADDR retAddr, TADDR* whereCalled) const -{ - *whereCalled = 0; - - // If retAddr doesn't have the low-order bit set (indicating a return to Thumb code) then it can't be a - // legal return address. - if ((retAddr & 1) == 0) - return; - retAddr &= ~1; - - // Potential calling instructions may have been one or two WORDs in length. - WORD rgPrevious[2]; - move_xp(rgPrevious, retAddr - sizeof(rgPrevious)); - - // Check two-word variants first. - if (((rgPrevious[0] & 0xf800) == 0xf000) && - ((rgPrevious[1] & 0xd000) == 0xd000)) - { - // BL