From: Mike McLaughlin Date: Sun, 20 May 2018 01:11:08 +0000 (-0700) Subject: Add the PAL and SOS itself. X-Git-Tag: submit/tizen/20190813.035844~98^2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c2905b26bdbacef24752aae848eb40f3d46fe4a3;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Add the PAL and SOS itself. Enable native SOS build on windows. Remove libunwind from the PAL Cleanup and include file trimming. Change the SOS.NETCore.dll load directory to sos's location. Load sos from current working directory that the plugin is in. Update help for stress logs. Add "sethostruntime" SOS command. --- diff --git a/Build.cmd b/Build.cmd index 7e7754462..075bdce94 100644 --- a/Build.cmd +++ b/Build.cmd @@ -1,3 +1,3 @@ @echo off -powershell -ExecutionPolicy ByPass -command "& """%~dp0eng\common\Build.ps1""" -restore -build %*" +call %~dp0eng\Build.cmd -restore -build %* exit /b %ErrorLevel% diff --git a/CMakeLists.txt b/CMakeLists.txt index ff558391a..2475b2e3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 2.8.12) if(CMAKE_VERSION VERSION_EQUAL 3.0 OR CMAKE_VERSION VERSION_GREATER 3.0) - cmake_policy(SET CMP0042 NEW) + cmake_policy(SET CMP0042 NEW) endif() # Set the project name @@ -19,10 +19,13 @@ if (WIN32) message(STATUS "VS_PLATFORM_NAME is ${CMAKE_VS_PLATFORM_NAME}") endif (WIN32) +set(ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +#set(VERSION_FILE_PATH "${CMAKE_BINARY_DIR}/version.cpp") + set(CORECLR_SET_RPATH ON) if(CORECLR_SET_RPATH) - # Enable @rpath support for shared libraries. - set(MACOSX_RPATH ON) + # Enable @rpath support for shared libraries. + set(MACOSX_RPATH ON) endif(CORECLR_SET_RPATH) OPTION(CLR_CMAKE_ENABLE_CODE_COVERAGE "Enable code coverage" OFF) @@ -31,7 +34,7 @@ OPTION(CLR_CMAKE_WARNINGS_ARE_ERRORS "Warnings are errors" ON) # Ensure that python is present find_program(PYTHON NAMES python2.7 python2 python) if (PYTHON STREQUAL "PYTHON-NOTFOUND") - message(FATAL_ERROR "PYTHON not found: Please install Python 2.7.9 or later from https://www.python.org/downloads/") + message(FATAL_ERROR "PYTHON not found: Please install Python 2.7.9 or later from https://www.python.org/downloads/") endif() # Ensure other tools are present @@ -450,9 +453,9 @@ endif(WIN32) # ./build-native.sh cmakeargs "-DCLR_ADDITIONAL_COMPILER_OPTIONS=<...>" cmakeargs "-DCLR_ADDITIONAL_LINKER_FLAGS=<...>" # if(CLR_CMAKE_PLATFORM_UNIX) - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${CLR_ADDITIONAL_LINKER_FLAGS}") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CLR_ADDITIONAL_LINKER_FLAGS}" ) - add_compile_options(${CLR_ADDITIONAL_COMPILER_OPTIONS}) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${CLR_ADDITIONAL_LINKER_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CLR_ADDITIONAL_LINKER_FLAGS}" ) + add_compile_options(${CLR_ADDITIONAL_COMPILER_OPTIONS}) endif(CLR_CMAKE_PLATFORM_UNIX) if(CLR_CMAKE_PLATFORM_LINUX) @@ -464,6 +467,7 @@ endif(CLR_CMAKE_PLATFORM_LINUX) #------------------------------------ # Definitions (for platform) #----------------------------------- + if (CLR_CMAKE_PLATFORM_ARCH_AMD64) add_definitions(-D_AMD64_) add_definitions(-D_WIN64) @@ -539,6 +543,86 @@ else () clr_unknown_arch() endif () +if (CLR_CMAKE_TARGET_ARCH_AMD64) + if (CLR_CMAKE_PLATFORM_UNIX) + add_definitions(-DDBG_TARGET_AMD64_UNIX) + endif() + add_definitions(-D_TARGET_64BIT_=1) + add_definitions(-D_TARGET_AMD64_=1) + add_definitions(-DDBG_TARGET_64BIT=1) + add_definitions(-DDBG_TARGET_AMD64=1) + add_definitions(-DDBG_TARGET_WIN64=1) +elseif (CLR_CMAKE_TARGET_ARCH_ARM64) + if (CLR_CMAKE_PLATFORM_UNIX) + add_definitions(-DDBG_TARGET_ARM64_UNIX) + endif() + add_definitions(-D_TARGET_ARM64_=1) + add_definitions(-D_TARGET_64BIT_=1) + add_definitions(-DDBG_TARGET_64BIT=1) + add_definitions(-DDBG_TARGET_ARM64=1) + add_definitions(-DDBG_TARGET_WIN64=1) + add_definitions(-DFEATURE_MULTIREG_RETURN) +elseif (CLR_CMAKE_TARGET_ARCH_ARM) + if (CLR_CMAKE_PLATFORM_UNIX) + add_definitions(-DDBG_TARGET_ARM_UNIX) + elseif (WIN32 AND NOT DEFINED CLR_CROSS_COMPONENTS_BUILD) + # Set this to ensure we can use Arm SDK for Desktop binary linkage when doing native (Arm32) build + add_definitions(-D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1) + add_definitions(-D_ARM_WORKAROUND_) + endif (CLR_CMAKE_PLATFORM_UNIX) + add_definitions(-D_TARGET_ARM_=1) + add_definitions(-D_TARGET_WIN32_=1) + add_definitions(-DDBG_TARGET_32BIT=1) + add_definitions(-DDBG_TARGET_ARM=1) +elseif (CLR_CMAKE_TARGET_ARCH_I386) + add_definitions(-D_TARGET_X86_=1) + add_definitions(-DDBG_TARGET_32BIT=1) + add_definitions(-DDBG_TARGET_X86=1) +else () + clr_unknown_arch() +endif (CLR_CMAKE_TARGET_ARCH_AMD64) + +if(WIN32) + add_definitions(-DWIN32) + add_definitions(-D_WIN32) + add_definitions(-DWINVER=0x0602) + add_definitions(-D_WIN32_WINNT=0x0602) + add_definitions(-DWIN32_LEAN_AND_MEAN=1) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) +endif(WIN32) + +#-------------------------------------- +# FEATURE Defines +#-------------------------------------- + +add_definitions(-DFEATURE_CORESYSTEM) + +if(CLR_CMAKE_PLATFORM_UNIX) + add_definitions(-DFEATURE_PAL) + add_definitions(-DFEATURE_PAL_ANSI) +endif(CLR_CMAKE_PLATFORM_UNIX) + +if(WIN32) + add_definitions(-DFEATURE_COMINTEROP) +endif(WIN32) + +if(NOT CMAKE_SYSTEM_NAME STREQUAL NetBSD) + add_definitions(-DFEATURE_HIJACK) +endif(NOT CMAKE_SYSTEM_NAME STREQUAL NetBSD) + +if(FEATURE_EVENT_TRACE) + add_definitions(-DFEATURE_EVENT_TRACE=1) + add_definitions(-DFEATURE_PERFTRACING=1) +endif(FEATURE_EVENT_TRACE) + +if(CLR_CMAKE_PLATFORM_UNIX_AMD64) + add_definitions(-DFEATURE_MULTIREG_RETURN) +endif (CLR_CMAKE_PLATFORM_UNIX_AMD64) + +if(CLR_CMAKE_PLATFORM_UNIX AND CLR_CMAKE_TARGET_ARCH_AMD64) + add_definitions(-DUNIX_AMD64_ABI) +endif(CLR_CMAKE_PLATFORM_UNIX AND CLR_CMAKE_TARGET_ARCH_AMD64) + #-------------------------------------- # Compile Options #-------------------------------------- diff --git a/README.md b/README.md index a7b0679e3..eed358df5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ .NET Core Diagnostics Repo ========================== -This repository contains the source code for various .NET Core runtime diagnostic tools. It currently contains the managed portion of SOS and the lldb SOS plugin. One of goals of this repo is to build the lldb SOS plugin for the portable Linux platform (Centos 7) and the platforms not supported by the portable build (Centos 6, Alpine, eventually macOS) and to test across various indexes in a very large matrix: OSs/distros (Centos 6/7, Ubuntu, Alpine, Fedora, Debian, RHEL 7.2), architectures (x64, x86, arm, arm64), lldb versions (3.9, 4.0, 5.0, 6.0) and even .NET Core versions (1.1, 2.0.x, 2.1). +**Currently under construction** + +This repository contains the source code for various .NET Core runtime diagnostic tools. It currently contains SOS, the managed portion of SOS and the lldb SOS plugin. One of goals of this repo is to build SOS and the lldb SOS plugin for the portable Linux platform (Centos 7) and the platforms not supported by the portable build (Centos 6, Alpine, eventually macOS) and to test across various indexes in a very large matrix: OSs/distros (Centos 6/7, Ubuntu, Alpine, Fedora, Debian, RHEL 7.2), architectures (x64, x86, arm, arm64), lldb versions (3.9, 4.0, 5.0, 6.0) and even .NET Core versions (1.1, 2.0.x, 2.1). Another goal to make it easier to obtain a version of lldb (currently 3.9) with scripts and documentation for platforms/distros like Centos, Alpine, Fedora, etc. that by default provide really old versions. diff --git a/Restore.cmd b/Restore.cmd index 3fc17e8f7..cb0cb90b1 100644 --- a/Restore.cmd +++ b/Restore.cmd @@ -1,3 +1,3 @@ @echo off -powershell -ExecutionPolicy ByPass -command "& """%~dp0eng\common\Build.ps1""" -restore %*" +call %~dp0eng\Build.cmd -restore %* exit /b %ErrorLevel% diff --git a/Test.cmd b/Test.cmd index 8a841f681..ef857c81d 100644 --- a/Test.cmd +++ b/Test.cmd @@ -1,3 +1,3 @@ @echo off -powershell -ExecutionPolicy ByPass -command "& """%~dp0eng\common\Build.ps1""" -test %*" -exit /b %ErrorLevel% \ No newline at end of file +call %~dp0eng\Build.cmd -test %* +exit /b %ErrorLevel% diff --git a/crossgen.cmake b/crossgen.cmake new file mode 100644 index 000000000..75739dfc7 --- /dev/null +++ b/crossgen.cmake @@ -0,0 +1,27 @@ +# Contains the crossgen build specific definitions. Included by the leaf crossgen cmake files. + +add_definitions( + -DCROSSGEN_COMPILE + -DCROSS_COMPILE + -DFEATURE_NATIVE_IMAGE_GENERATION + -DSELF_NO_HOST) + +remove_definitions( + -DFEATURE_CODE_VERSIONING + -DEnC_SUPPORTED + -DFEATURE_EVENT_TRACE=1 + -DFEATURE_LOADER_OPTIMIZATION + -DFEATURE_MULTICOREJIT + -DFEATURE_PERFMAP + -DFEATURE_REJIT + -DFEATURE_TIERED_COMPILATION + -DFEATURE_VERSIONING_LOG +) + +if(FEATURE_READYTORUN) + add_definitions(-DFEATURE_READYTORUN_COMPILER) +endif(FEATURE_READYTORUN) + +if(CLR_CMAKE_PLATFORM_LINUX) + add_definitions(-DFEATURE_PERFMAP) +endif(CLR_CMAKE_PLATFORM_LINUX) diff --git a/eng/Build-Native.cmd b/eng/Build-Native.cmd new file mode 100644 index 000000000..960024869 --- /dev/null +++ b/eng/Build-Native.cmd @@ -0,0 +1,371 @@ +@if not defined _echo @echo off +setlocal EnableDelayedExpansion EnableExtensions + +:: Define a prefix for most output progress messages that come from this script. That makes +:: it easier to see where these are coming from. Note that there is a trailing space here. +set "__MsgPrefix=BUILD: " + +echo %__MsgPrefix%Starting Build at %TIME% +set __ThisScriptFull="%~f0" +set __ThisScriptDir="%~dp0" + +call "%__ThisScriptDir%"\setup_vs_tools.cmd +if NOT '%ERRORLEVEL%' == '0' exit /b 1 + +if defined VS150COMNTOOLS ( + set "__VSToolsRoot=%VS150COMNTOOLS%" + set "__VCToolsRoot=%VS150COMNTOOLS%\..\..\VC\Auxiliary\Build" + set __VSVersion=vs2017 +) else ( + set "__VSToolsRoot=%VS140COMNTOOLS%" + set "__VCToolsRoot=%VS140COMNTOOLS%\..\..\VC" + set __VSVersion=vs2015 +) + +:: Work around Jenkins CI + msbuild problem: Jenkins sometimes creates very large environment +:: variables, and msbuild can't handle environment blocks with such large variables. So clear +:: out the variables that might be too large. +set ghprbCommentBody= + +:: Note that the msbuild project files (specifically, dir.proj) will use the following variables, if set: +:: __BuildArch -- default: x64 +:: __BuildType -- default: Debug +:: __BuildOS -- default: Windows_NT +:: __ProjectDir -- default: directory of the dir.props file +:: __SourceDir -- default: %__ProjectDir%\src\ +:: __RootBinDir -- default: %__ProjectDir%\artifacts\ +:: __BinDir -- default: %__RootBinDir%\bin\%__BuildOS%.%__BuildArch.%__BuildType%\ +:: __IntermediatesDir -- default: %__RootBinDir%\obj\%__BuildOS%.%__BuildArch.%__BuildType%\ +:: __LogDir -- default: %__RootBinDir%\log\%__BuildOS%.%__BuildArch.%__BuildType%\ +:: +:: Thus, these variables are not simply internal to this script! + +:: Set the default arguments for build +set __BuildArch=x64 +set __BuildType=Debug +set __BuildOS=Windows_NT +set __Build=0 +set __Test=0 + +:: Set the various build properties here so that CMake and MSBuild can pick them up +set "__ProjectDir=%~dp0" +:: remove trailing slash +if %__ProjectDir:~-1%==\ set "__ProjectDir=%__ProjectDir:~0,-1%" +set "__ProjectDir=%__ProjectDir%\.." +set "__SourceDir=%__ProjectDir%\src" +set "__PackagesDir=%DotNetRestorePackagesPath%" +if [%__PackagesDir%]==[] set "__PackagesDir=%__ProjectDir%\packages" +set "__RootBinDir=%__ProjectDir%\artifacts" + +REM __UnprocessedBuildArgs are args that we pass to msbuild (e.g. /p:__BuildArch=x64) +set "__args= %*" +set processedArgs= +set __UnprocessedBuildArgs= + +:Arg_Loop +if "%1" == "" goto ArgsDone + +if /i "%1" == "-?" goto Usage +if /i "%1" == "-h" goto Usage +if /i "%1" == "-help" goto Usage +if /i "%1" == "--help" goto Usage + +if /i "%1" == "-build" (set __Build=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "-test" (set __Test=1&set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "-configuration" (set __BuildType=%2&set processedArgs=!processedArgs! %1 %2&shift&shift&goto Arg_Loop) +if /i "%1" == "-architecture" (set __BuildArch=%2&set processedArgs=!processedArgs! %1 %2&shift&shift&goto Arg_Loop) +rem these options are ignored for a native build +if /i "%1" == "-restore" (set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "-sign" (set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "-pack" (set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "-ci" (set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) +if /i "%1" == "-prepareMachine" (set processedArgs=!processedArgs! %1&shift&goto Arg_Loop) + +if [!processedArgs!]==[] ( + set __UnprocessedBuildArgs=%__args% +) else ( + set __UnprocessedBuildArgs=%__args% + for %%t in (!processedArgs!) do ( + set __UnprocessedBuildArgs=!__UnprocessedBuildArgs:*%%t=! + ) +) + +:ArgsDone + +REM Determine if this is a cross-arch build + +if /i "%__BuildArch%"=="arm64" ( + set __DoCrossArchBuild=1 + set __CrossArch=x86 + ) + +if /i "%__BuildArch%"=="arm" ( + set __DoCrossArchBuild=1 + set __CrossArch=x64 + ) + +:: Set the remaining variables based upon the determined build configuration +set "__BinDir=%__RootBinDir%\bin\%__BuildOS%.%__BuildArch%.%__BuildType%" +set "__IntermediatesDir=%__RootBinDir%\obj\%__BuildOS%.%__BuildArch%.%__BuildType%" +set "__LogDir=%__RootBinDir%\log\%__BuildOS%.%__BuildArch%.%__BuildType%" +if "%__NMakeMakefiles%"=="1" (set "__IntermediatesDir=%__RootBinDir%\nmakeobj\%__BuildOS%.%__BuildArch%.%__BuildType%") + +set "__PackagesBinDir=%__BinDir%\.nuget" +set "__CrossComponentBinDir=%__BinDir%" +set "__CrossCompIntermediatesDir=%__IntermediatesDir%\crossgen" + +if NOT "%__CrossArch%" == "" set __CrossComponentBinDir=%__CrossComponentBinDir%\%__CrossArch% +set "__CrossGenCoreLibLog=%__LogDir%\CrossgenCoreLib_%__BuildOS%__%__BuildArch%__%__BuildType%.log" +set "__CrossgenExe=%__CrossComponentBinDir%\crossgen.exe" + +:: Generate path to be set for CMAKE_INSTALL_PREFIX to contain forward slash +set "__CMakeBinDir=%__BinDir%" +set "__CMakeBinDir=%__CMakeBinDir:\=/%" + +if not exist "%__BinDir%" md "%__BinDir%" +if not exist "%__IntermediatesDir%" md "%__IntermediatesDir%" +if not exist "%__LogDir%" md "%__LogDir%" + +echo %__MsgPrefix%Commencing diagnostics repo build + +:: Set the remaining variables based upon the determined build configuration + +echo %__MsgPrefix%Checking prerequisites +:: Eval the output from probe-win1.ps1 +for /f "delims=" %%a in ('powershell -NoProfile -ExecutionPolicy ByPass "& ""%__ProjectDir%\eng\probe-win.ps1"""') do %%a + +REM ========================================================================================= +REM === +REM === Start the build steps +REM === +REM ========================================================================================= + +@if defined _echo @echo on + +REM Parse the optdata package versions out of msbuild so that we can pass them on to CMake +set DotNetCli=%__ProjectDir%\.dotnet\dotnet.exe +if not exist "%DotNetCli%" ( + echo %__MsgPrefix%Assertion failed: dotnet.exe not found at path "%DotNetCli%" + exit /b 1 +) +set MSBuildPath=%__ProjectDir%\.dotnet\sdk\2.1.300-rc1-008673\msbuild.dll + +REM ========================================================================================= +REM === +REM === Build the native code +REM === +REM ========================================================================================= + +if %__Build% EQU 1 ( + REM Scope environment changes start { + setlocal + + echo %__MsgPrefix%Commencing build of native components for %__BuildOS%.%__BuildArch%.%__BuildType% + + set __NativePlatformArgs=-platform=%__BuildArch% + if not "%__ToolsetDir%" == "" ( set __NativePlatformArgs=-useEnv ) + + if not "%__ToolsetDir%" == "" ( + rem arm64 builds currently use private toolset which has not been released yet + REM TODO, remove once the toolset is open. + call :PrivateToolSet + goto GenVSSolution + ) + + :: Set the environment for the native build + set __VCBuildArch=x86_amd64 + if /i "%__BuildArch%" == "x86" ( set __VCBuildArch=x86 ) + if /i "%__BuildArch%" == "arm" ( + set __VCBuildArch=x86_arm + + REM Make CMake pick the highest installed version in the 10.0.* range + set ___SDKVersion="-DCMAKE_SYSTEM_VERSION=10.0" + ) + if /i "%__BuildArch%" == "arm64" ( + set __VCBuildArch=x86_arm64 + + REM Make CMake pick the highest installed version in the 10.0.* range + set ___SDKVersion="-DCMAKE_SYSTEM_VERSION=10.0" + ) + + echo %__MsgPrefix%Using environment: "%__VCToolsRoot%\vcvarsall.bat" !__VCBuildArch! + call "%__VCToolsRoot%\vcvarsall.bat" !__VCBuildArch! + @if defined _echo @echo on + + if not defined VSINSTALLDIR ( + echo %__MsgPrefix%Error: VSINSTALLDIR variable not defined. + exit /b 1 + ) + if not exist "!VSINSTALLDIR!DIA SDK" goto NoDIA + +:GenVSSolution + if defined __SkipConfigure goto SkipConfigure + + echo %__MsgPrefix%Regenerating the Visual Studio solution + + set "__ManagedBinaryDir=%__RootBinDir%\%__BuildType%\bin" + set "__ManagedBinaryDir=!__ManagedBinaryDir:\=/!" + set __ExtraCmakeArgs=!___SDKVersion! "-DCLR_MANAGED_BINARY_DIR=!__ManagedBinaryDir!" + + pushd "%__IntermediatesDir%" + call "%__ProjectDir%\eng\gen-buildsys-win.bat" "%__ProjectDir%" %__VSVersion% %__BuildArch% !__ExtraCmakeArgs! + @if defined _echo @echo on + popd + +:SkipConfigure + if defined __ConfigureOnly goto SkipNativeBuild + + if not exist "%__IntermediatesDir%\install.vcxproj" ( + echo %__MsgPrefix%Error: failed to generate native component build project! + exit /b 1 + ) + + set __BuildLogRootName=Native + set __BuildLog="%__LogDir%\!__BuildLogRootName!.log" + set __BuildWrn="%__LogDir%\!__BuildLogRootName!.wrn" + set __BuildErr="%__LogDir%\!__BuildLogRootName!.err" + set __MsbuildLog=/flp:Verbosity=normal;LogFile=!__BuildLog! + set __MsbuildWrn=/flp1:WarningsOnly;LogFile=!__BuildWrn! + set __MsbuildErr=/flp2:ErrorsOnly;LogFile=!__BuildErr! + + msbuild.exe %__IntermediatesDir%\install.vcxproj /v:m !__MsbuildLog! !__MsbuildWrn! !__MsbuildErr! /p:Configuration=%__BuildType% /p:Platform=%__BuildArch% %__UnprocessedBuildArgs% + + if not !errorlevel! == 0 ( + echo %__MsgPrefix%Error: native component build failed. Refer to the build log files for details: + echo !__BuildLog! + echo !__BuildWrn! + echo !__BuildErr! + exit /b 1 + ) + +:SkipNativeBuild + REM } Scope environment changes end + endlocal +) + +REM ========================================================================================= +REM === +REM === Build Cross-Architecture Native Components (if applicable) +REM === +REM ========================================================================================= + +if /i "%__DoCrossArchBuild%"=="1" ( + REM Scope environment changes start { + setlocal + + echo %__MsgPrefix%Commencing build of cross architecture native components for %__BuildOS%.%__BuildArch%.%__BuildType% + + :: Set the environment for the native build + set __VCBuildArch=x86_amd64 + if /i "%__CrossArch%" == "x86" ( set __VCBuildArch=x86 ) + + echo %__MsgPrefix%Using environment: "%__VCToolsRoot%\vcvarsall.bat" !__VCBuildArch! + call "%__VCToolsRoot%\vcvarsall.bat" !__VCBuildArch! + @if defined _echo @echo on + + if not exist "%__CrossCompIntermediatesDir%" md "%__CrossCompIntermediatesDir%" + if defined __SkipConfigure goto SkipConfigureCrossBuild + + set __CMakeBinDir=%__CrossComponentBinDir% + set "__CMakeBinDir=!__CMakeBinDir:\=/!" + + set "__ManagedBinaryDir=%__RootBinDir%\%__BuildType%\bin" + set "__ManagedBinaryDir=!__ManagedBinaryDir:\=/!" + set __ExtraCmakeArgs="-DCLR_MANAGED_BINARY_DIR=!__ManagedBinaryDir!" "-DCLR_CROSS_COMPONENTS_BUILD=1" "-DCLR_CMAKE_TARGET_ARCH=%__BuildArch%" "-DCMAKE_SYSTEM_VERSION=10.0" + + pushd "%__CrossCompIntermediatesDir%" + call "%__ProjectDir%\eng\gen-buildsys-win.bat" "%__ProjectDir%" %__VSVersion% %__CrossArch% !__ExtraCmakeArgs! + @if defined _echo @echo on + popd + +:SkipConfigureCrossBuild + if not exist "%__CrossCompIntermediatesDir%\install.vcxproj" ( + echo %__MsgPrefix%Error: failed to generate cross-arch components build project! + exit /b 1 + ) + + if defined __ConfigureOnly goto SkipCrossCompBuild + + set __BuildLogRootName=Native.Cross + set __BuildLog="%__LogDir%\!__BuildLogRootName!.log" + set __BuildWrn="%__LogDir%\!__BuildLogRootName!.wrn" + set __BuildErr="%__LogDir%\!__BuildLogRootName!.err" + set __MsbuildLog=/flp:Verbosity=normal;LogFile=!__BuildLog! + set __MsbuildWrn=/flp1:WarningsOnly;LogFile=!__BuildWrn! + set __MsbuildErr=/flp2:ErrorsOnly;LogFile=!__BuildErr! + + msbuild.exe %__CrossCompIntermediatesDir%\install.vcxproj /v:m !__MsbuildLog! !__MsbuildWrn! !__MsbuildErr! /p:Configuration=%__BuildType% /p:Platform=%__CrossArch% %__UnprocessedBuildArgs% + + if not !errorlevel! == 0 ( + echo %__MsgPrefix%Error: cross-arch components build failed. Refer to the build log files for details: + echo !__BuildLog! + echo !__BuildWrn! + echo !__BuildErr! + exit /b 1 + ) + +:SkipCrossCompBuild + REM } Scope environment changes end + endlocal +) + +REM ========================================================================================= +REM === +REM === All builds complete! +REM === +REM ========================================================================================= + +echo %__MsgPrefix%Repo successfully built. Finished at %TIME% +echo %__MsgPrefix%Product binaries are available at !__BinDir! +exit /b 0 + +REM ========================================================================================= +REM === +REM === Helper routines +REM === +REM ========================================================================================= + +:Usage +echo. +echo Build the Diagnostics repo. +echo. +echo Usage: +echo build-native.cmd [option1] [option2] +echo. +echo All arguments are optional. The options are: +echo. +echo.-? -h -help --help: view this message. +echo -build - build native components +echo -test - test native components +echo -architechure +echo -configuration +exit /b 1 + +:PrivateToolSet + +echo %__MsgPrefix%Setting up the usage of __ToolsetDir:%__ToolsetDir% + +if /i "%__ToolsetDir%" == "" ( + echo %__MsgPrefix%Error: A toolset directory is required for the Arm64 Windows build. Use the toolset_dir argument. + exit /b 1 +) + +if not exist "%__ToolsetDir%"\buildenv_arm64.cmd goto :Not_EWDK +call "%__ToolsetDir%"\buildenv_arm64.cmd +exit /b 0 + +:Not_EWDK +set PATH=%__ToolsetDir%\VC_sdk\bin;%PATH% +set LIB=%__ToolsetDir%\VC_sdk\lib\arm64;%__ToolsetDir%\sdpublic\sdk\lib\arm64 +set INCLUDE=^ +%__ToolsetDir%\VC_sdk\inc;^ +%__ToolsetDir%\sdpublic\sdk\inc;^ +%__ToolsetDir%\sdpublic\shared\inc;^ +%__ToolsetDir%\sdpublic\shared\inc\minwin;^ +%__ToolsetDir%\sdpublic\sdk\inc\ucrt;^ +%__ToolsetDir%\sdpublic\sdk\inc\minwin;^ +%__ToolsetDir%\sdpublic\sdk\inc\mincore;^ +%__ToolsetDir%\sdpublic\sdk\inc\abi;^ +%__ToolsetDir%\sdpublic\sdk\inc\clientcore;^ +%__ToolsetDir%\diasdk\include +exit /b 0 diff --git a/eng/Build.cmd b/eng/Build.cmd new file mode 100644 index 000000000..741751960 --- /dev/null +++ b/eng/Build.cmd @@ -0,0 +1,9 @@ +@echo off + +rem build/test managed components +powershell -ExecutionPolicy ByPass -command "& """%~dp0common\Build.ps1""" %*" +if NOT '%ERRORLEVEL%' == '0' exit /b %ERRORLEVEL% + +rem build/test native componments +call %~dp0build-native.cmd %* +exit /b %ERRORLEVEL% diff --git a/eng/CIBuild.cmd b/eng/CIBuild.cmd new file mode 100644 index 000000000..94fc7744b --- /dev/null +++ b/eng/CIBuild.cmd @@ -0,0 +1,3 @@ +@echo off +call %~dp0Build.cmd -restore -build -test -sign -ci %* +exit /b %ErrorLevel% diff --git a/eng/Versions.props b/eng/Versions.props index 18779ae02..59732b8e6 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -11,7 +11,7 @@ false - 1.6.0-preview2-26406-04 + 1.5.0 diff --git a/eng/build-native.sh b/eng/build-native.sh index d72343434..6d36189c6 100755 --- a/eng/build-native.sh +++ b/eng/build-native.sh @@ -30,6 +30,25 @@ __UnprocessedBuildArgs= __Build=0 __Test=0 +# resolve python-version to use +if [ "$PYTHON" == "" ] ; then + if ! PYTHON=$(command -v python2.7 || command -v python2 || command -v python) + then + echo "Unable to locate build-dependency python2.x!" 1>&2 + exit 1 + fi +fi + +# validate python-dependency +# useful in case of explicitly set option. +if ! command -v $PYTHON > /dev/null +then + echo "Unable to locate build-dependency python2.x ($PYTHON)!" 1>&2 + exit 1 +fi + +echo $PYTHON + usage() { echo "Usage: $0 [options]" @@ -37,7 +56,8 @@ usage() echo "--test - test native components" echo "--architechure " echo "--configuration " - echo "--clangx.y - optional argument to build using clang version x.y." + echo "--clangx.y - optional argument to build using clang version x.y" + echo "--help - this help message" exit 1 } @@ -197,6 +217,7 @@ done __RootBinDir=$__ProjectRoot/artifacts __IntermediatesDir="$__RootBinDir/obj/$__BuildOS.$__BuildArch.$__BuildType" __LogFileDir="$__RootBinDir/log/$__BuildOS.$__BuildArch.$__BuildType" +__ExtraCmakeArgs="-DCLR_MANAGED_BINARY_DIR=$__RootBinDir/$__BuildType/bin" # Specify path to be set for CMAKE_INSTALL_PREFIX. # This is where all built native libraries will copied to. @@ -213,6 +234,11 @@ if [[ $__ClangMajorVersion == 0 && $__ClangMinorVersion == 0 ]]; then fi fi +if [[ "$__BuildArch" == "armel" ]]; then + # Armel cross build is Tizen specific and does not support Portable RID build + __PortableBuild=0 +fi + mkdir -p "$__IntermediatesDir" mkdir -p "$__LogFileDir" mkdir -p "$__CMakeBinDir" @@ -260,7 +286,14 @@ initHostDistroRid() if [ "$__HostOS" == "Linux" ]; then if [ -e /etc/os-release ]; then source /etc/os-release + if [[ $ID == "rhel" ]]; then + # remove the last version digit + VERSION_ID=${VERSION_ID%.*} + fi __HostDistroRid="$ID.$VERSION_ID-$__HostArch" + if [[ $ID == "alpine" ]]; then + __HostDistroRid="linux-musl-$__HostArch" + fi elif [ -e /etc/redhat-release ]; then local redhatRelease=$(&2; + ExitWithExitCode 1 + fi; +} + +# install .NET Core +ReadJson "$scriptroot/../global.json" "version" + +# setting DOTNET_INSTALL_DIR prevents build.sh from installing it export DOTNET_INSTALL_DIR=$scriptroot/../.dotnet -"$scriptroot/install-dotnet.sh" $DOTNET_INSTALL_DIR 2.1.300-rc1-008673 +"$scriptroot/install-dotnet.sh" $DOTNET_INSTALL_DIR $readjsonvalue +if [[ $? != 0 ]]; then + exit 1 +fi # build/test managed components "$scriptroot/common/build.sh" $@ +if [[ $? != 0 ]]; then + exit 1 +fi # build/test native components "$scriptroot/build-native.sh" $@ diff --git a/eng/cibuild.sh b/eng/cibuild.sh index da5b4e51d..39fbbbba8 100755 --- a/eng/cibuild.sh +++ b/eng/cibuild.sh @@ -15,4 +15,4 @@ while [[ -h $source ]]; do done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" -"$scriptroot/build.sh" --restore --build --test --pack --ci $@ +"$scriptroot/build.sh" --restore --build --test --sign --ci $@ diff --git a/eng/gen-buildsys-win.bat b/eng/gen-buildsys-win.bat new file mode 100644 index 000000000..70da41aa8 --- /dev/null +++ b/eng/gen-buildsys-win.bat @@ -0,0 +1,55 @@ +@if not defined __echo @echo off +rem +rem This file invokes cmake and generates the build system for windows. + +set argC=0 +for %%x in (%*) do Set /A argC+=1 + +if %argC% lss 3 GOTO :USAGE +if %1=="/?" GOTO :USAGE + +setlocal +set basePath=%~dp0 +:: remove quotes +set "basePath=%basePath:"=%" +:: remove trailing slash +if %basePath:~-1%==\ set "basePath=%basePath:~0,-1%" + +set __SourceDir=%1 +set __VSVersion=%2 +set __Arch=%3 +set __CmakeGenerator=Visual Studio +if /i "%__VSVersion%" == "vs2017" (set __CmakeGenerator=%__CmakeGenerator% 15 2017) +if /i "%__VSVersion%" == "vs2015" (set __CmakeGenerator=%__CmakeGenerator% 14 2015) +if /i "%__Arch%" == "x64" (set __CmakeGenerator=%__CmakeGenerator% Win64) +if /i "%__Arch%" == "arm64" (set __CmakeGenerator=%__CmakeGenerator% Win64) +if /i "%__Arch%" == "arm" (set __CmakeGenerator=%__CmakeGenerator% ARM) + +if /i "%__NMakeMakefiles%" == "1" (set __CmakeGenerator=NMake Makefiles) + +:loop +if [%4] == [] goto end_loop +set __ExtraCmakeParams=%__ExtraCmakeParams% %4 +shift +goto loop +:end_loop + +if defined CMakePath goto DoGen + +:: Eval the output from probe-win1.ps1 +for /f "delims=" %%a in ('powershell -NoProfile -ExecutionPolicy ByPass "& "%basePath%\probe-win.ps1""') do %%a + +:DoGen +"%CMakePath%" "-DCMAKE_USER_MAKE_RULES_OVERRIDE=%basePath%\windows-compiler-override.txt" "-DCMAKE_INSTALL_PREFIX:PATH=$ENV{__CMakeBinDir}" "-DCLR_CMAKE_HOST_ARCH=%__Arch%" %__ExtraCmakeParams% -G "%__CmakeGenerator%" %__SourceDir% +endlocal +GOTO :DONE + +:USAGE + echo "Usage..." + echo "gen-buildsys-win.bat " + echo "Specify the path to the top level CMake file - /src/NDP" + echo "Specify the VSVersion to be used - VS2015 or VS2017" + EXIT /B 1 + +:DONE + EXIT /B 0 diff --git a/eng/generateexportedsymbols.awk b/eng/generateexportedsymbols.awk new file mode 100644 index 000000000..1d9b200d2 --- /dev/null +++ b/eng/generateexportedsymbols.awk @@ -0,0 +1,11 @@ +{ + # Remove the CR character in case the sources are mapped from + # a Windows share and contain CRLF line endings + gsub(/\r/,"", $0); + + # Skip empty lines and comment lines starting with semicolon + if (NF && !match($0, /^[:space:]*;/)) + { + print "_" $0; + } +} diff --git a/eng/generateversionscript.awk b/eng/generateversionscript.awk new file mode 100644 index 000000000..226c6a545 --- /dev/null +++ b/eng/generateversionscript.awk @@ -0,0 +1,19 @@ +BEGIN { + print "V1.0 {"; + print " global:"; +} +{ + # Remove the CR character in case the sources are mapped from + # a Windows share and contain CRLF line endings + gsub(/\r/,"", $0); + + # Skip empty lines and comment lines starting with semicolon + if (NF && !match($0, /^[ \t]*;/)) + { + print " " $0 ";"; + } +} +END { + print " local: *;" + print "};"; +} diff --git a/eng/install-dotnet.sh b/eng/install-dotnet.sh index 8dd36a283..2f385d06f 100755 --- a/eng/install-dotnet.sh +++ b/eng/install-dotnet.sh @@ -86,3 +86,6 @@ if [ ! -e "$dotnet_root" ]; then tar -xf "$dotnet_root/dotnet.tar" fi +echo "Adding to current process PATH: \`$dotnet_root\`. Note: This change will be visible only when sourcing script." +export PATH="$dotnet_root":"$PATH" + diff --git a/eng/probe-win.ps1 b/eng/probe-win.ps1 new file mode 100644 index 000000000..fa30d9cb4 --- /dev/null +++ b/eng/probe-win.ps1 @@ -0,0 +1,70 @@ +# This file probes for the prerequisites for the build system, and outputs commands for eval'ing +# from the cmd scripts to set variables (and exit on error) + +function GetCMakeVersions +{ + $items = @() + $items += @(Get-ChildItem hklm:\SOFTWARE\Wow6432Node\Kitware -ErrorAction SilentlyContinue) + $items += @(Get-ChildItem hklm:\SOFTWARE\Kitware -ErrorAction SilentlyContinue) + return $items | where { $_.PSChildName.StartsWith("CMake") } +} + +function GetCMakeInfo($regKey) +{ + try { + $version = [System.Version] $regKey.PSChildName.Split(' ')[1] + } + catch { + return $null + } + $itemProperty = Get-ItemProperty $regKey.PSPath; + if (Get-Member -inputobject $itemProperty -name "InstallDir" -Membertype Properties) { + $cmakeDir = $itemProperty.InstallDir + } + else { + $cmakeDir = $itemProperty.'(default)' + } + $cmakePath = [System.IO.Path]::Combine($cmakeDir, "bin\cmake.exe") + if (![System.IO.File]::Exists($cmakePath)) { + return $null + } + return @{'version' = $version; 'path' = $cmakePath} +} + +function LocateCMake +{ + $errorMsg = "CMake is a pre-requisite to build this repository but it was not found on the path. Please install CMake from http://www.cmake.org/download/ and ensure it is on your path." + $inPathPath = (get-command cmake.exe -ErrorAction SilentlyContinue) + if ($inPathPath -ne $null) { + # Resolve the first version of CMake if multiple commands are found + if ($inPathPath.Length -gt 1) { + return $inPathPath[0].Path + } + return $inPathPath.Path + } + # Let us hope that CMake keep using their current version scheme + $validVersions = @() + foreach ($regKey in GetCMakeVersions) { + $info = GetCMakeInfo($regKey) + if ($info -ne $null) { + $validVersions += @($info) + } + } + $newestCMakePath = ($validVersions | + Sort-Object -property @{Expression={$_.version}; Ascending=$false} | + select -first 1).path + if ($newestCMakePath -eq $null) { + Throw $errorMsg + } + return $newestCMakePath +} + +try { + $cmakePath = LocateCMake + [System.Console]::WriteLine("set CMakePath=" + $cmakePath) + +} +catch { + [System.Console]::Error.WriteLine($_.Exception.Message) + [System.Console]::WriteLine("exit /b 1") +} diff --git a/eng/setup_vs_tools.cmd b/eng/setup_vs_tools.cmd new file mode 100644 index 000000000..9af06e5bd --- /dev/null +++ b/eng/setup_vs_tools.cmd @@ -0,0 +1,48 @@ +@if not defined _echo @echo off + +REM This script is responsible for setting up either the vs2015 or vs2017 env +REM All passed arguments are ignored +REM Script will return with 0 if pass, 1 if there is a failure to find either +REM vs2015 or vs2017 + +:: Default to highest Visual Studio version available +:: +:: For VS2015 (and prior), only a single instance is allowed to be installed on a box +:: and VS140COMNTOOLS is set as a global environment variable by the installer. This +:: allows users to locate where the instance of VS2015 is installed. +:: +:: For VS2017, multiple instances can be installed on the same box SxS and VS150COMNTOOLS +:: is no longer set as a global environment variable and is instead only set if the user +:: has launched the VS2017 Developer Command Prompt. +:: +:: Following this logic, we will default to the VS2017 toolset if VS150COMNTOOLS tools is +:: set, as this indicates the user is running from the VS2017 Developer Command Prompt and +:: is already configured to use that toolset. Otherwise, we will fallback to using the VS2015 +:: toolset if it is installed. Finally, we will fail the script if no supported VS instance +:: can be found. + +if defined VisualStudioVersion ( + if not defined __VSVersion echo %__MsgPrefix%Detected Visual Studio %VisualStudioVersion% developer command ^prompt environment + goto skip_setup +) + +echo %__MsgPrefix%Searching ^for Visual Studio 2017 or 2015 installation +set _VSWHERE="%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" +if exist %_VSWHERE% ( + for /f "usebackq tokens=*" %%i in (`%_VSWHERE% -latest -prerelease -property installationPath`) do set _VSCOMNTOOLS=%%i\Common7\Tools + goto call_vs +) +if not exist "%_VSCOMNTOOLS%" set _VSCOMNTOOLS=%VS140COMNTOOLS% +echo VS2017 not found, using VS2015 +:call_vs +if not exist "%_VSCOMNTOOLS%" ( + echo %__MsgPrefix%Error: Visual Studio 2015 or 2017 required. + echo Please see https://github.com/dotnet/coreclr/blob/master/Documentation/building/windows-instructions.md for build instructions. + exit /b 1 +) +echo %__MsgPrefix%"%_VSCOMNTOOLS%\VsDevCmd.bat" +call "%_VSCOMNTOOLS%\VsDevCmd.bat" + +:skip_setup + +exit /b 0 \ No newline at end of file diff --git a/eng/windows-compiler-override.txt b/eng/windows-compiler-override.txt new file mode 100644 index 000000000..f6cb16856 --- /dev/null +++ b/eng/windows-compiler-override.txt @@ -0,0 +1,16 @@ +SET (CMAKE_C_FLAGS_INIT "/Wall /FC") +SET (CMAKE_C_FLAGS_DEBUG_INIT "/Od /Zi") +SET (CLR_C_FLAGS_CHECKED_INIT "/O1 /Zi") +SET (CMAKE_C_FLAGS_RELEASE_INIT "/Ox /Zi") +SET (CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "/O2 /Zi") + +SET (CMAKE_CXX_FLAGS_INIT "/Wall /FC") +SET (CMAKE_CXX_FLAGS_DEBUG_INIT "/Od /Zi") +SET (CLR_CXX_FLAGS_CHECKED_INIT "/O1 /Zi") +SET (CMAKE_CXX_FLAGS_RELEASE_INIT "/Ox /Zi") +SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "/O2 /Zi") + +SET (CLR_DEFINES_DEBUG_INIT DEBUG _DEBUG _DBG URTBLDENV_FRIENDLY=Checked BUILDENV_CHECKED=1) +SET (CLR_DEFINES_CHECKED_INIT DEBUG _DEBUG _DBG URTBLDENV_FRIENDLY=Checked BUILDENV_CHECKED=1) +SET (CLR_DEFINES_RELEASE_INIT NDEBUG URTBLDENV_FRIENDLY=Retail) +SET (CLR_DEFINES_RELWITHDEBINFO_INIT NDEBUG URTBLDENV_FRIENDLY=Retail) \ No newline at end of file diff --git a/functions.cmake b/functions.cmake index 913e71a65..afdd19006 100644 --- a/functions.cmake +++ b/functions.cmake @@ -11,6 +11,85 @@ function(clr_unknown_arch) endif() endfunction() +# Build a list of compiler definitions by putting -D in front of each define. +function(get_compile_definitions DefinitionName) + # Get the current list of definitions + get_directory_property(COMPILE_DEFINITIONS_LIST COMPILE_DEFINITIONS) + + foreach(DEFINITION IN LISTS COMPILE_DEFINITIONS_LIST) + if (${DEFINITION} MATCHES "^\\$<\\$]+)>:([^>]+)>$") + # The entries that contain generator expressions must have the -D inside of the + # expression. So we transform e.g. $<$:_DEBUG> to $<$:-D_DEBUG> + set(DEFINITION "$<$:-D${CMAKE_MATCH_2}>") + else() + set(DEFINITION -D${DEFINITION}) + endif() + list(APPEND DEFINITIONS ${DEFINITION}) + endforeach() + set(${DefinitionName} ${DEFINITIONS} PARENT_SCOPE) +endfunction(get_compile_definitions) + +# Build a list of include directories +function(get_include_directories IncludeDirectories) + get_directory_property(dirs INCLUDE_DIRECTORIES) + foreach(dir IN LISTS dirs) + + if (CLR_CMAKE_PLATFORM_ARCH_ARM AND WIN32) + list(APPEND INC_DIRECTORIES /I${dir}) + else() + list(APPEND INC_DIRECTORIES -I${dir}) + endif(CLR_CMAKE_PLATFORM_ARCH_ARM AND WIN32) + + endforeach() + set(${IncludeDirectories} ${INC_DIRECTORIES} PARENT_SCOPE) +endfunction(get_include_directories) + +# Set the passed in RetSources variable to the list of sources with added current source directory +# to form absolute paths. +# The parameters after the RetSources are the input files. +function(convert_to_absolute_path RetSources) + set(Sources ${ARGN}) + foreach(Source IN LISTS Sources) + list(APPEND AbsolutePathSources ${CMAKE_CURRENT_SOURCE_DIR}/${Source}) + endforeach() + set(${RetSources} ${AbsolutePathSources} PARENT_SCOPE) +endfunction(convert_to_absolute_path) + +#Preprocess exports definition file +function(preprocess_def_file inputFilename outputFilename) + get_compile_definitions(PREPROCESS_DEFINITIONS) + get_include_directories(ASM_INCLUDE_DIRECTORIES) + add_custom_command( + OUTPUT ${outputFilename} + COMMAND ${CMAKE_CXX_COMPILER} ${ASM_INCLUDE_DIRECTORIES} /P /EP /TC ${PREPROCESS_DEFINITIONS} /Fi${outputFilename} ${inputFilename} + DEPENDS ${inputFilename} + COMMENT "Preprocessing ${inputFilename} - ${CMAKE_CXX_COMPILER} ${ASM_INCLUDE_DIRECTORIES} /P /EP /TC ${PREPROCESS_DEFINITIONS} /Fi${outputFilename} ${inputFilename}" + ) + + set_source_files_properties(${outputFilename} + PROPERTIES GENERATED TRUE) +endfunction() +function(generate_exports_file) + set(INPUT_LIST ${ARGN}) + list(GET INPUT_LIST -1 outputFilename) + list(REMOVE_AT INPUT_LIST -1) + + if(CMAKE_SYSTEM_NAME STREQUAL Darwin) + set(AWK_SCRIPT eng/generateexportedsymbols.awk) + else() + set(AWK_SCRIPT eng/generateversionscript.awk) + endif(CMAKE_SYSTEM_NAME STREQUAL Darwin) + + add_custom_command( + OUTPUT ${outputFilename} + COMMAND ${AWK} -f ${CMAKE_SOURCE_DIR}/${AWK_SCRIPT} ${INPUT_LIST} >${outputFilename} + DEPENDS ${INPUT_LIST} ${CMAKE_SOURCE_DIR}/${AWK_SCRIPT} + COMMENT "Generating exports file ${outputFilename}" + ) + set_source_files_properties(${outputFilename} + PROPERTIES GENERATED TRUE) +endfunction() + function(strip_symbols targetName outputFilename) if (CLR_CMAKE_PLATFORM_UNIX) if (STRIP_SYMBOLS) @@ -69,18 +148,25 @@ function(install_clr targetName) install(PROGRAMS ${install_source_file} DESTINATION .) if(WIN32) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/$/${targetName}.pdb DESTINATION PDB) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/$/${targetName}.pdb DESTINATION .) else() install(FILES ${strip_destination_file} DESTINATION .) endif() - if(CLR_CMAKE_PGO_INSTRUMENT) - if(WIN32) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/$/${targetName}.pgd DESTINATION PGD OPTIONAL) - endif() - endif() endif() endfunction() +function(_add_executable) + if(NOT WIN32) + add_executable(${ARGV} ${VERSION_FILE_PATH}) + else() + add_executable(${ARGV}) + endif(NOT WIN32) + list(FIND CLR_CROSS_COMPONENTS_LIST ${ARGV0} INDEX) + if (DEFINED CLR_CROSS_COMPONENTS_LIST AND ${INDEX} EQUAL -1) + set_target_properties(${ARGV0} PROPERTIES EXCLUDE_FROM_ALL 1) + endif() +endfunction() + function(_add_library) if(NOT WIN32) add_library(${ARGV} ${VERSION_FILE_PATH}) @@ -96,3 +182,14 @@ endfunction() function(add_library_clr) _add_library(${ARGV}) endfunction() + +function(add_executable_clr) + _add_executable(${ARGV}) +endfunction() + +function(_install) + if(NOT DEFINED CLR_CROSS_COMPONENTS_BUILD) + install(${ARGV}) + endif() +endfunction() + diff --git a/pipeline.groovy b/pipeline.groovy index e1a440b50..0ca197ee8 100644 --- a/pipeline.groovy +++ b/pipeline.groovy @@ -41,7 +41,7 @@ buildConfigurations.each { config -> stage ('Build/Test') { if (os == "Windows_NT") { - bat ".\\eng\\common\\CIBuild.cmd -configuration ${config.Configuration} -prepareMachine" + bat ".\\eng\\CIBuild.cmd -configuration ${config.Configuration} -prepareMachine" } else { sh "./eng/cibuild.sh --configuration ${config.Configuration} --architechure ${config.Architechure} --prepareMachine" } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 61a472d0f..9015d3d4d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1 +1,11 @@ +include_directories(${ROOT_DIR}/src/pal/prebuilt/inc) + +include_directories(inc) + +if (CLR_CMAKE_PLATFORM_UNIX) + add_subdirectory(palrt) + add_subdirectory(pal) +endif(CLR_CMAKE_PLATFORM_UNIX) + +add_subdirectory(inc) add_subdirectory(SOS) diff --git a/src/SOS/CMakeLists.txt b/src/SOS/CMakeLists.txt index cd30034b9..5837e0330 100644 --- a/src/SOS/CMakeLists.txt +++ b/src/SOS/CMakeLists.txt @@ -1,3 +1,30 @@ if(CLR_CMAKE_PLATFORM_UNIX) + include_directories(${ROOT_DIR}/src/pal/inc) + include_directories(${ROOT_DIR}/src/pal/inc/rt) + add_subdirectory(lldbplugin) endif(CLR_CMAKE_PLATFORM_UNIX) + +# lldbplugin doesn't build with these options +if(WIN32) + add_compile_options(/FIWarningControl.h) # force include of WarningControl.h + add_compile_options(/Zl) # omit default library name in .OBJ +endif(WIN32) + +add_definitions(-D_SECURE_SCL=0) + +add_subdirectory(NETCore) +add_subdirectory(Strike) + +if(CLR_CMAKE_PLATFORM_UNIX) + add_compile_options(-fPIC) + + # Include the dummy c++ include files + include_directories(${ROOT_DIR}/src/pal/inc/rt/cpp) + + # This prevents inclusion of standard C compiler headers + add_compile_options(-nostdinc) +endif(CLR_CMAKE_PLATFORM_UNIX) + +add_subdirectory(debugshim) +add_subdirectory(dbgutil) diff --git a/src/SOS/NETCore/CMakeLists.txt b/src/SOS/NETCore/CMakeLists.txt new file mode 100644 index 000000000..28ff7ea70 --- /dev/null +++ b/src/SOS/NETCore/CMakeLists.txt @@ -0,0 +1,8 @@ +project(SOS.NETCore) + +if(NOT ${CLR_MANAGED_BINARY_DIR} STREQUAL "") + set(MANAGED_BINDIR ${CLR_MANAGED_BINARY_DIR}/SOS.NETCore/netcoreapp1.0) + + install(FILES ${MANAGED_BINDIR}/SOS.NETCore.dll DESTINATION .) + install(FILES ${MANAGED_BINDIR}/SOS.NETCore.pdb DESTINATION .) +endif() diff --git a/src/SOS/Strike/CMakeLists.txt b/src/SOS/Strike/CMakeLists.txt new file mode 100644 index 000000000..1230d169b --- /dev/null +++ b/src/SOS/Strike/CMakeLists.txt @@ -0,0 +1,219 @@ +project(sos) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# 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(-D_TARGET_WIN64_=1) + add_definitions(-DSOS_TARGET_AMD64=1) + if(WIN32) + add_definitions(-DSOS_TARGET_ARM64=1) + endif(WIN32) +elseif(CLR_CMAKE_PLATFORM_ARCH_I386) + add_definitions(-DSOS_TARGET_X86=1) + if(WIN32) + add_definitions(-DSOS_TARGET_ARM=1) + endif(WIN32) +elseif(CLR_CMAKE_PLATFORM_ARCH_ARM) + add_definitions(-DSOS_TARGET_ARM=1) +elseif(CLR_CMAKE_PLATFORM_ARCH_ARM64) + add_definitions(-D_TARGET_WIN64_=1) + add_definitions(-DSOS_TARGET_ARM64=1) +endif() + +add_definitions(-DSTRIKE) + +include_directories(${ROOT_DIR}/src/SOS/gcdump) +include_directories(${ROOT_DIR}/src/SOS/debugshim) + +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(-DFEATURE_ENABLE_HARDWARE_EXCEPTIONS) + add_definitions(-DPAL_STDCPP_COMPAT=1) + add_compile_options(-Wno-null-arithmetic) + 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(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}) + + if(CMAKE_SYSTEM_NAME STREQUAL Linux OR CMAKE_SYSTEM_NAME STREQUAL FreeBSD OR CMAKE_SYSTEM_NAME STREQUAL NetBSD) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Xlinker -Bsymbolic -Xlinker -Bsymbolic-functions") + + # These options are used to force every object to be included even if it's unused. + set(START_WHOLE_ARCHIVE -Wl,--whole-archive) + set(END_WHOLE_ARCHIVE -Wl,--no-whole-archive) + + set(EXPORTS_LINKER_OPTION -Wl,--version-script=${EXPORTS_FILE}) + endif(CMAKE_SYSTEM_NAME STREQUAL Linux OR CMAKE_SYSTEM_NAME STREQUAL FreeBSD OR CMAKE_SYSTEM_NAME STREQUAL NetBSD) + + if(CMAKE_SYSTEM_NAME STREQUAL Darwin) + # These options are used to force every object to be included even if it's unused. + set(START_WHOLE_ARCHIVE -force_load) + set(END_WHOLE_ARCHIVE ) + + set(EXPORTS_LINKER_OPTION -Wl,-exported_symbols_list,${EXPORTS_FILE}) + endif(CMAKE_SYSTEM_NAME STREQUAL Darwin) + + set(SOS_LIBRARY + corguids + debugshim + dbgutil + ${START_WHOLE_ARCHIVE} # force all PAL objects to be included so all exports are available + coreclrpal + ${END_WHOLE_ARCHIVE} + palrt + ) + +endif(WIN32) + +if(CLR_CMAKE_PLATFORM_ARCH_AMD64) +# if(CLR_CMAKE_PLATFORM_LINUX) + #list(APPEND + # SOS_LIBRARY + # createdump_lib + #) + #add_definitions(-DCREATE_DUMP_SUPPORTED) +# endif(CLR_CMAKE_PLATFORM_LINUX) + 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}) + +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_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}) +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/SOS/Strike/EventCallbacks.cpp b/src/SOS/Strike/EventCallbacks.cpp new file mode 100644 index 000000000..0066dfa1e --- /dev/null +++ b/src/SOS/Strike/EventCallbacks.cpp @@ -0,0 +1,160 @@ +// 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/SOS/Strike/EventCallbacks.h b/src/SOS/Strike/EventCallbacks.h new file mode 100644 index 000000000..acd5e412d --- /dev/null +++ b/src/SOS/Strike/EventCallbacks.h @@ -0,0 +1,68 @@ +// 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/SOS/Strike/ExpressionNode.cpp b/src/SOS/Strike/ExpressionNode.cpp new file mode 100644 index 000000000..920afbaed --- /dev/null +++ b/src/SOS/Strike/ExpressionNode.cpp @@ -0,0 +1,2178 @@ +// 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 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 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]; + WCHAR typeName[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]; + WCHAR typeName[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 +// 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 +// +// +// Valid combinations of state comming 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]; + WCHAR typeName[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/SOS/Strike/ExpressionNode.h b/src/SOS/Strike/ExpressionNode.h new file mode 100644 index 000000000..48cc03672 --- /dev/null +++ b/src/SOS/Strike/ExpressionNode.h @@ -0,0 +1,307 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + +#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/SOS/Strike/Native.rc b/src/SOS/Strike/Native.rc new file mode 100644 index 000000000..179ddfd24 --- /dev/null +++ b/src/SOS/Strike/Native.rc @@ -0,0 +1,10 @@ +// 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/SOS/Strike/UtilCode.h b/src/SOS/Strike/UtilCode.h new file mode 100644 index 000000000..a002edc89 --- /dev/null +++ b/src/SOS/Strike/UtilCode.h @@ -0,0 +1,11 @@ +// 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/SOS/Strike/WatchCmd.cpp b/src/SOS/Strike/WatchCmd.cpp new file mode 100644 index 000000000..443f1dd6e --- /dev/null +++ b/src/SOS/Strike/WatchCmd.cpp @@ -0,0 +1,331 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#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/SOS/Strike/WatchCmd.h b/src/SOS/Strike/WatchCmd.h new file mode 100644 index 000000000..a34e391b7 --- /dev/null +++ b/src/SOS/Strike/WatchCmd.h @@ -0,0 +1,110 @@ +// 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/SOS/Strike/data.h b/src/SOS/Strike/data.h new file mode 100644 index 000000000..998903865 --- /dev/null +++ b/src/SOS/Strike/data.h @@ -0,0 +1,51 @@ +// 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/SOS/Strike/datatarget.cpp b/src/SOS/Strike/datatarget.cpp new file mode 100644 index 000000000..fe90f0e82 --- /dev/null +++ b/src/SOS/Strike/datatarget.cpp @@ -0,0 +1,215 @@ +// 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/SOS/Strike/datatarget.h b/src/SOS/Strike/datatarget.h new file mode 100644 index 000000000..0293bc668 --- /dev/null +++ b/src/SOS/Strike/datatarget.h @@ -0,0 +1,90 @@ +// 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/SOS/Strike/dbgutil.cpp b/src/SOS/Strike/dbgutil.cpp new file mode 100644 index 000000000..a16849868 --- /dev/null +++ b/src/SOS/Strike/dbgutil.cpp @@ -0,0 +1,426 @@ +// 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. +//***************************************************************************** +// dbgutil.cpp +// + +// +//***************************************************************************** + +// +// Various common helpers for PE resource reading used by multiple debug components. +// + +#include +#include "corerror.h" +#include +#include + +// Returns the RVA of the resource section for the module specified by the given data target and module base. +// Returns failure if the module doesn't have a resource section. +// +// Arguments +// pDataTarget - dataTarget for the process we are inspecting +// moduleBaseAddress - base address of a module we should inspect +// pwImageFileMachine - updated with the Machine from the IMAGE_FILE_HEADER +// pdwResourceSectionRVA - updated with the resultant RVA on success +HRESULT GetMachineAndResourceSectionRVA(ICorDebugDataTarget* pDataTarget, + ULONG64 moduleBaseAddress, + WORD* pwImageFileMachine, + DWORD* pdwResourceSectionRVA) +{ + // Fun code ahead... below is a hand written PE decoder with some of the file offsets hardcoded. + // It supports no more than what we absolutely have to to get to the resources we need. Any of the + // magic numbers used below can be determined by using the public documentation on the web. + // + // Yes utilcode has a PE decoder, no it does not support reading its data through a datatarget + // It was easier to inspect the small portion that I needed than to shove an abstraction layer under + // our utilcode and then make sure everything still worked. + + // SECURITY WARNING: all data provided by the data target should be considered untrusted. + // Do not allow malicious data to cause large reads, memory allocations, buffer overflow, + // or any other undesirable behavior. + + HRESULT hr = S_OK; + + // at offset 3c in the image is a 4 byte file pointer that indicates where the PE signature is + IMAGE_DOS_HEADER dosHeader; + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress, (BYTE*)&dosHeader, sizeof(dosHeader)); + + // verify there is a 4 byte PE signature there + DWORD peSigFilePointer = 0; + if (SUCCEEDED(hr)) + { + peSigFilePointer = dosHeader.e_lfanew; + DWORD peSig = 0; + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peSigFilePointer, (BYTE*)&peSig, 4); + if (SUCCEEDED(hr) && peSig != IMAGE_NT_SIGNATURE) + { + hr = E_FAIL; // PE signature not present + } + } + + // after the signature is a 20 byte image file header + // we need to parse this to figure out the target architecture + IMAGE_FILE_HEADER imageFileHeader; + if (SUCCEEDED(hr)) + { + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peSigFilePointer + 4, (BYTE*)&imageFileHeader, IMAGE_SIZEOF_FILE_HEADER); + } + + + + WORD optHeaderMagic = 0; + DWORD peOptImageHeaderFilePointer = 0; + if (SUCCEEDED(hr)) + { + if(pwImageFileMachine != NULL) + { + *pwImageFileMachine = imageFileHeader.Machine; + } + + // 4 bytes after the signature is the 20 byte image file header + // 24 bytes after the signature is the image-only header + // at the beginning of the image-only header is a 2 byte magic number indicating its format + peOptImageHeaderFilePointer = peSigFilePointer + IMAGE_SIZEOF_FILE_HEADER + sizeof(DWORD); + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peOptImageHeaderFilePointer, (BYTE*)&optHeaderMagic, 2); + } + + // Either 112 or 128 bytes after the beginning of the image-only header is an 8 byte resource table + // depending on whether the image is PE32 or PE32+ + DWORD resourceSectionRVA = 0; + if (SUCCEEDED(hr)) + { + if (optHeaderMagic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) // PE32 + { + IMAGE_OPTIONAL_HEADER32 header32; + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peOptImageHeaderFilePointer, + (BYTE*)&header32, sizeof(header32)); + if (SUCCEEDED(hr)) + { + resourceSectionRVA = header32.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress; + } + } + else if (optHeaderMagic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) //PE32+ + { + IMAGE_OPTIONAL_HEADER64 header64; + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peOptImageHeaderFilePointer, + (BYTE*)&header64, sizeof(header64)); + if (SUCCEEDED(hr)) + { + resourceSectionRVA = header64.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress; + } + } + else + { + hr = E_FAIL; // Invalid PE + } + } + + *pdwResourceSectionRVA = resourceSectionRVA; + return S_OK; +} + +HRESULT GetResourceRvaFromResourceSectionRva(ICorDebugDataTarget* pDataTarget, + ULONG64 moduleBaseAddress, + DWORD resourceSectionRva, + DWORD type, + DWORD name, + DWORD language, + DWORD* pResourceRva, + DWORD* pResourceSize) +{ + HRESULT hr = S_OK; + DWORD nameTableRva = 0; + DWORD langTableRva = 0; + DWORD resourceDataEntryRva = 0; + *pResourceRva = 0; + *pResourceSize = 0; + + // The resource section begins with a resource directory that indexes all the resources by type. + // Each entry it points to is another resource directory that indexes all the same type + // resources by name. And each entry in that table points to another resource directory that indexes + // all the same type/name resources by language. Entries in the final table give the RVA of the actual + // resource. + // Note all RVAs in this section are relative to the beginning of the resource section, + // not the beginning of the image. + + hr = GetNextLevelResourceEntryRVA(pDataTarget, type, moduleBaseAddress, resourceSectionRva, &nameTableRva); + + + if (SUCCEEDED(hr)) + { + nameTableRva += resourceSectionRva; + hr = GetNextLevelResourceEntryRVA(pDataTarget, name, moduleBaseAddress, nameTableRva, &langTableRva); + + } + if (SUCCEEDED(hr)) + { + langTableRva += resourceSectionRva; + hr = GetNextLevelResourceEntryRVA(pDataTarget, language, moduleBaseAddress, langTableRva, &resourceDataEntryRva); + } + + // The resource data entry has the first 4 bytes indicating the RVA of the resource + // The next 4 bytes indicate the size of the resource + if (SUCCEEDED(hr)) + { + resourceDataEntryRva += resourceSectionRva; + IMAGE_RESOURCE_DATA_ENTRY dataEntry; + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDataEntryRva, + (BYTE*)&dataEntry, sizeof(dataEntry)); + *pResourceRva = dataEntry.OffsetToData; + *pResourceSize = dataEntry.Size; + } + + return hr; +} + +HRESULT GetResourceRvaFromResourceSectionRvaByName(ICorDebugDataTarget* pDataTarget, + ULONG64 moduleBaseAddress, + DWORD resourceSectionRva, + DWORD type, + LPCWSTR pwszName, + DWORD language, + DWORD* pResourceRva, + DWORD* pResourceSize) +{ + HRESULT hr = S_OK; + DWORD nameTableRva = 0; + DWORD langTableRva = 0; + DWORD resourceDataEntryRva = 0; + *pResourceRva = 0; + *pResourceSize = 0; + + // The resource section begins with a resource directory that indexes all the resources by type. + // Each entry it points to is another resource directory that indexes all the same type + // resources by name. And each entry in that table points to another resource directory that indexes + // all the same type/name resources by language. Entries in the final table give the RVA of the actual + // resource. + // Note all RVAs in this section are relative to the beginning of the resource section, + // not the beginning of the image. + hr = GetNextLevelResourceEntryRVA(pDataTarget, type, moduleBaseAddress, resourceSectionRva, &nameTableRva); + + + if (SUCCEEDED(hr)) + { + nameTableRva += resourceSectionRva; + hr = GetNextLevelResourceEntryRVAByName(pDataTarget, pwszName, moduleBaseAddress, nameTableRva, resourceSectionRva, &langTableRva); + } + if (SUCCEEDED(hr)) + { + langTableRva += resourceSectionRva; + hr = GetNextLevelResourceEntryRVA(pDataTarget, language, moduleBaseAddress, langTableRva, &resourceDataEntryRva); + } + + // The resource data entry has the first 4 bytes indicating the RVA of the resource + // The next 4 bytes indicate the size of the resource + if (SUCCEEDED(hr)) + { + resourceDataEntryRva += resourceSectionRva; + IMAGE_RESOURCE_DATA_ENTRY dataEntry; + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDataEntryRva, + (BYTE*)&dataEntry, sizeof(dataEntry)); + *pResourceRva = dataEntry.OffsetToData; + *pResourceSize = dataEntry.Size; + } + + return hr; +} + +// Traverses down one level in the PE resource tree structure +// +// Arguments: +// pDataTarget - the data target for inspecting this process +// id - the id of the next node in the resource tree you want +// moduleBaseAddress - the base address of the module being inspected +// resourceDirectoryRVA - the base address of the beginning of the resource directory for this +// level of the tree +// pNextLevelRVA - out - The RVA for the next level tree directory or the RVA of the resource entry +// +// Returns: +// S_OK if succesful or an appropriate failing HRESULT +HRESULT GetNextLevelResourceEntryRVA(ICorDebugDataTarget* pDataTarget, + DWORD id, + ULONG64 moduleBaseAddress, + DWORD resourceDirectoryRVA, + DWORD* pNextLevelRVA) +{ + *pNextLevelRVA = 0; + HRESULT hr = S_OK; + + // A resource directory which consists of + // a header followed by a number of entries. In the header at offset 12 is + // the number entries identified by name, followed by the number of entries + // identified by ID at offset 14. Both are 2 bytes. + // This method only supports locating entries by ID, not by name + IMAGE_RESOURCE_DIRECTORY resourceDirectory; + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDirectoryRVA, (BYTE*)&resourceDirectory, sizeof(resourceDirectory)); + + + + // The ith resource directory entry is at offset 16 + 8i from the beginning of the resource + // directory table + WORD numNameEntries; + WORD numIDEntries; + if (SUCCEEDED(hr)) + { + numNameEntries = resourceDirectory.NumberOfNamedEntries; + numIDEntries = resourceDirectory.NumberOfIdEntries; + + for (WORD i = numNameEntries; i < numNameEntries + numIDEntries; i++) + { + IMAGE_RESOURCE_DIRECTORY_ENTRY entry; + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDirectoryRVA + sizeof(resourceDirectory) + sizeof(entry)*i, + (BYTE*)&entry, sizeof(entry)); + if (FAILED(hr)) + { + break; + } + if (entry.Id == id) + { + *pNextLevelRVA = entry.OffsetToDirectory; + break; + } + } + } + + // If we didn't find the entry + if (SUCCEEDED(hr) && *pNextLevelRVA == 0) + { + hr = E_FAIL; + } + + return hr; // resource not found +} + +// Traverses down one level in the PE resource tree structure +// +// Arguments: +// pDataTarget - the data target for inspecting this process +// name - the name of the next node in the resource tree you want +// moduleBaseAddress - the base address of the module being inspected +// resourceDirectoryRVA - the base address of the beginning of the resource directory for this +// level of the tree +// resourceSectionRVA - the rva of the beginning of the resource section of the PE file +// pNextLevelRVA - out - The RVA for the next level tree directory or the RVA of the resource entry +// +// Returns: +// S_OK if succesful or an appropriate failing HRESULT +HRESULT GetNextLevelResourceEntryRVAByName(ICorDebugDataTarget* pDataTarget, + LPCWSTR pwzName, + ULONG64 moduleBaseAddress, + DWORD resourceDirectoryRva, + DWORD resourceSectionRva, + DWORD* pNextLevelRva) +{ + HRESULT hr = S_OK; + DWORD nameLength = (DWORD)wcslen(pwzName); + WCHAR entryName[50]; + assert(nameLength < 50); // this implementation won't support matching a name longer + // than 50 characters. We only look up the hard coded name + // of the debug resource in clr.dll though, so it shouldn't be + // an issue. Increase this count if we ever want to look up + // larger names + if (nameLength >= 50) + { + hr = E_FAIL; // invalid name length + } + + // A resource directory which consists of + // a header followed by a number of entries. In the header at offset 12 is + // the number entries identified by name, followed by the number of entries + // identified by ID at offset 14. Both are 2 bytes. + // This method only supports locating entries by ID, not by name + IMAGE_RESOURCE_DIRECTORY resourceDirectory = { 0 }; + if (SUCCEEDED(hr)) + { + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDirectoryRva, (BYTE*)&resourceDirectory, sizeof(resourceDirectory)); + } + + // The ith resource directory entry is at offset 16 + 8i from the beginning of the resource + // directory table + if (SUCCEEDED(hr)) + { + WORD numNameEntries = resourceDirectory.NumberOfNamedEntries; + for (WORD i = 0; i < numNameEntries; i++) + { + IMAGE_RESOURCE_DIRECTORY_ENTRY entry; + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDirectoryRva + sizeof(resourceDirectory) + sizeof(entry)*i, + (BYTE*)&entry, sizeof(entry)); + if (FAILED(hr)) + { + break; + } + + // the NameRVAOrID field points to a UTF16 string with a 2 byte length in front of it + // read the 2 byte length first. The doc of course doesn't mention this, but the RVA is + // relative to the base of the resource section and needs the leading bit stripped. + WORD entryNameLength = 0; + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceSectionRva + + entry.NameOffset, (BYTE*)&entryNameLength, sizeof(entryNameLength)); + if (FAILED(hr)) + { + break; + } + if (entryNameLength != nameLength) + { + continue; // names aren't the same length, not a match + } + + // read the rest of the string data and check for a match + hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceSectionRva + + entry.NameOffset + 2, (BYTE*)entryName, entryNameLength*sizeof(WCHAR)); + if (FAILED(hr)) + { + break; + } + if (memcmp(entryName, pwzName, entryNameLength*sizeof(WCHAR)) == 0) + { + *pNextLevelRva = entry.OffsetToDirectory; + break; + } + } + } + + if (SUCCEEDED(hr) && *pNextLevelRva == 0) + { + hr = E_FAIL; // resource not found + } + + return hr; +} + +// A small wrapper that reads from the data target and throws on error +HRESULT ReadFromDataTarget(ICorDebugDataTarget* pDataTarget, + ULONG64 addr, + BYTE* pBuffer, + ULONG32 bytesToRead) +{ + //PRECONDITION(CheckPointer(pDataTarget)); + //PRECONDITION(CheckPointer(pBuffer)); + + HRESULT hr = S_OK; + ULONG32 bytesReadTotal = 0; + ULONG32 bytesRead = 0; + do + { + if (FAILED(pDataTarget->ReadVirtual((CORDB_ADDRESS)(addr + bytesReadTotal), + pBuffer, + bytesToRead - bytesReadTotal, + &bytesRead))) + { + hr = CORDBG_E_READVIRTUAL_FAILURE; + break; + } + bytesReadTotal += bytesRead; + } while (bytesRead != 0 && (bytesReadTotal < bytesToRead)); + + // If we can't read all the expected memory, then fail + if (SUCCEEDED(hr) && (bytesReadTotal != bytesToRead)) + { + hr = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY); + } + + return hr; +} diff --git a/src/SOS/Strike/disasm.cpp b/src/SOS/Strike/disasm.cpp new file mode 100644 index 000000000..613703141 --- /dev/null +++ b/src/SOS/Strike/disasm.cpp @@ -0,0 +1,1142 @@ +// 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 (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) +{ + char Symbol[1024]; + char filename[MAX_PATH_FNAME+1]; + 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/SOS/Strike/disasm.h b/src/SOS/Strike/disasm.h new file mode 100644 index 000000000..2a2d9f777 --- /dev/null +++ b/src/SOS/Strike/disasm.h @@ -0,0 +1,450 @@ +// 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/SOS/Strike/disasmARM.cpp b/src/SOS/Strike/disasmARM.cpp new file mode 100644 index 000000000..cc80d7759 --- /dev/null +++ b/src/SOS/Strike/disasmARM.cpp @@ -0,0 +1,627 @@ +// 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 defintely 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